/** * * \section COPYRIGHT * * Copyright 2013-2015 Software Radio Systems Limited * * \section LICENSE * * This file is part of the srsUE library. * * srsUE is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * srsUE is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * A copy of the GNU Affero General Public License can be found in * the LICENSE file in the top-level directory of this distribution * and at http://www.gnu.org/licenses/. * */ #include #include "srslte/srslte.h" #include "srslte/common/log.h" #include "phy/phch_worker.h" #include "phy/phch_common.h" #include "phy/phch_recv.h" #define Error(fmt, ...) if (SRSLTE_DEBUG_ENABLED) log_h->error_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Warning(fmt, ...) if (SRSLTE_DEBUG_ENABLED) log_h->warning_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Info(fmt, ...) if (SRSLTE_DEBUG_ENABLED) log_h->info_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) #define Debug(fmt, ...) if (SRSLTE_DEBUG_ENABLED) log_h->debug_line(__FILE__, __LINE__, fmt, ##__VA_ARGS__) namespace srsue { phch_recv::phch_recv() { running = false; } void phch_recv::init(srslte::radio_multi* _radio_handler, mac_interface_phy *_mac, rrc_interface_phy *_rrc, prach* _prach_buffer, srslte::thread_pool* _workers_pool, phch_common* _worker_com, srslte::log* _log_h, uint32_t nof_rx_antennas_, uint32_t prio, int sync_cpu_affinity) { radio_h = _radio_handler; log_h = _log_h; mac = _mac; rrc = _rrc; workers_pool = _workers_pool; worker_com = _worker_com; prach_buffer = _prach_buffer; nof_rx_antennas = nof_rx_antennas_; tx_mutex_cnt = 0; running = true; phy_state = IDLE; time_adv_sec = 0; cell_is_set = false; sync_sfn_cnt = 0; for (uint32_t i=0;iget_nof_workers(); worker_com->set_nof_mutex(nof_tx_mutex); if(sync_cpu_affinity < 0){ start(prio); } else { start_cpu(prio, sync_cpu_affinity); } } void phch_recv::stop() { running = false; wait_thread_finish(); for (uint32_t i=0;irx_now(data, nsamples, rx_time)) { int offset = nsamples-radio_h->get_tti_len(); if (abs(offset)<10 && offset != 0) { radio_h->tx_offset(offset); } else if (nsamples<10) { radio_h->tx_offset(nsamples); } return nsamples; } else { return -1; } } double callback_set_rx_gain(void *h, double gain) { srslte::radio_multi *radio_handler = (srslte::radio_multi*) h; return radio_handler->set_rx_gain_th(gain); } void phch_recv::set_time_adv_sec(float _time_adv_sec) { time_adv_sec = _time_adv_sec; } void phch_recv::set_ue_sync_opts(srslte_ue_sync_t *q) { if (worker_com->args->cfo_integer_enabled) { srslte_ue_sync_cfo_i_detec_en(q, true); } float cfo_tol = worker_com->args->cfo_correct_tol_hz; srslte_cfo_set_tol(&q->strack.cfocorr, cfo_tol/(15000*q->fft_size)); srslte_cfo_set_tol(&q->sfind.cfocorr, cfo_tol/(15000*q->fft_size)); int time_correct_period = worker_com->args->time_correct_period; if (time_correct_period > 0) { srslte_ue_sync_set_sample_offset_correct_period(q, time_correct_period); } sss_alg_t sss_alg = SSS_FULL; if (!worker_com->args->sss_algorithm.compare("diff")) { sss_alg = SSS_DIFF; } else if (!worker_com->args->sss_algorithm.compare("partial")) { sss_alg = SSS_PARTIAL_3; } else if (!worker_com->args->sss_algorithm.compare("full")){ sss_alg = SSS_FULL; } else { Warning("Invalid SSS algorithm %s. Using 'full'\n", worker_com->args->sss_algorithm.c_str()); } srslte_sync_set_sss_algorithm(&q->strack, (sss_alg_t) sss_alg); srslte_sync_set_sss_algorithm(&q->sfind, (sss_alg_t) sss_alg); } bool phch_recv::init_cell() { cell_is_set = false; if (!srslte_ue_mib_init(&ue_mib, cell)) { if (!srslte_ue_sync_init_multi(&ue_sync, cell, radio_recv_wrapper_cs, nof_rx_antennas, radio_h)) { // Set options defined in expert section set_ue_sync_opts(&ue_sync); for (uint32_t i=0;iget_nof_workers();i++) { if (!((phch_worker*) workers_pool->get_worker(i))->init_cell(cell)) { Error("Error setting cell: initiating PHCH worker\n"); return false; } } radio_h->set_tti_len(SRSLTE_SF_LEN_PRB(cell.nof_prb)); if (do_agc) { srslte_ue_sync_start_agc(&ue_sync, callback_set_rx_gain, last_gain); } srslte_ue_sync_set_cfo(&ue_sync, cellsearch_cfo); cell_is_set = true; } else { Error("Error setting cell: initiating ue_sync"); } } else { Error("Error setting cell: initiating ue_mib\n"); } return cell_is_set; } void phch_recv::free_cell() { if (cell_is_set) { for (uint32_t i=0;iget_nof_workers();i++) { ((phch_worker*) workers_pool->get_worker(i))->free_cell(); } prach_buffer->free_cell(); } } bool phch_recv::cell_search(int force_N_id_2) { uint8_t bch_payload[SRSLTE_BCH_PAYLOAD_LEN]; uint8_t bch_payload_bits[SRSLTE_BCH_PAYLOAD_LEN/8]; srslte_ue_cellsearch_result_t found_cells[3]; srslte_ue_cellsearch_t cs; bzero(found_cells, 3*sizeof(srslte_ue_cellsearch_result_t)); log_h->console("Searching for cell...\n"); if (srslte_ue_cellsearch_init_multi(&cs, SRSLTE_DEFAULT_MAX_FRAMES_PSS, radio_recv_wrapper_cs, nof_rx_antennas, radio_h)) { Error("Initiating UE cell search\n"); return false; } srslte_ue_cellsearch_set_nof_valid_frames(&cs, SRSLTE_DEFAULT_NOF_VALID_PSS_FRAMES); // Set options defined in expert section set_ue_sync_opts(&cs.ue_sync); if (do_agc) { srslte_ue_sync_start_agc(&cs.ue_sync, callback_set_rx_gain, last_gain); } radio_h->set_rx_srate(1.92e6); radio_h->start_rx(); /* Find a cell in the given N_id_2 or go through the 3 of them to find the strongest */ uint32_t max_peak_cell = 0; int ret = SRSLTE_ERROR; if (force_N_id_2 >= 0 && force_N_id_2 < 3) { ret = srslte_ue_cellsearch_scan_N_id_2(&cs, force_N_id_2, &found_cells[force_N_id_2]); max_peak_cell = force_N_id_2; } else { ret = srslte_ue_cellsearch_scan(&cs, found_cells, &max_peak_cell); } last_gain = srslte_agc_get_gain(&cs.ue_sync.agc); radio_h->stop_rx(); srslte_ue_cellsearch_free(&cs); if (ret < 0) { Error("Error decoding MIB: Error searching PSS\n"); return false; } else if (ret == 0) { Error("Error decoding MIB: Could not find any PSS in this frequency\n"); return false; } // Save result cell.id = found_cells[max_peak_cell].cell_id; cell.cp = found_cells[max_peak_cell].cp; cellsearch_cfo = found_cells[max_peak_cell].cfo; log_h->console("Found CELL ID: %d CP: %s, CFO: %.1f KHz.\nTrying to decode MIB...\n", cell.id, srslte_cp_string(cell.cp), cellsearch_cfo/1000); srslte_ue_mib_sync_t ue_mib_sync; if (srslte_ue_mib_sync_init_multi(&ue_mib_sync, cell.id, cell.cp, radio_recv_wrapper_cs, nof_rx_antennas, radio_h)) { Error("Initiating UE MIB synchronization\n"); return false; } // Set options defined in expert section set_ue_sync_opts(&ue_mib_sync.ue_sync); if (do_agc) { srslte_ue_sync_start_agc(&ue_mib_sync.ue_sync, callback_set_rx_gain, last_gain); } srslte_ue_sync_set_cfo(&ue_mib_sync.ue_sync, cellsearch_cfo); /* Find and decode MIB */ uint32_t sfn; int sfn_offset; radio_h->start_rx(); ret = srslte_ue_mib_sync_decode(&ue_mib_sync, SRSLTE_DEFAULT_MAX_FRAMES_PBCH, bch_payload, &cell.nof_ports, &sfn_offset); radio_h->stop_rx(); last_gain = srslte_agc_get_gain(&ue_mib_sync.ue_sync.agc); cellsearch_cfo = srslte_ue_sync_get_cfo(&ue_mib_sync.ue_sync); srslte_ue_mib_sync_free(&ue_mib_sync); if (ret == 1) { srslte_pbch_mib_unpack(bch_payload, &cell, NULL); worker_com->set_cell(cell); srslte_cell_fprint(stdout, &cell, 0); srslte_bit_pack_vector(bch_payload, bch_payload_bits, SRSLTE_BCH_PAYLOAD_LEN); mac->bch_decoded_ok(bch_payload_bits, SRSLTE_BCH_PAYLOAD_LEN/8); return true; } else { Warning("Error decoding MIB: Error decoding PBCH\n"); return false; } } int phch_recv::sync_sfn(void) { int ret = SRSLTE_ERROR; uint8_t bch_payload[SRSLTE_BCH_PAYLOAD_LEN]; srslte_ue_sync_decode_sss_on_track(&ue_sync, true); ret = srslte_ue_sync_zerocopy_multi(&ue_sync, sf_buffer_sfn); if (ret < 0) { Error("Error calling ue_sync_get_buffer"); return -1; } if (ret == 1) { if (srslte_ue_sync_get_sfidx(&ue_sync) == 0) { int sfn_offset=0; Info("SYNC: Decoding MIB...\n"); int n = srslte_ue_mib_decode(&ue_mib, sf_buffer_sfn[0], bch_payload, NULL, &sfn_offset); if (n < 0) { Error("Error decoding MIB while synchronising SFN"); return -1; } else if (n == SRSLTE_UE_MIB_FOUND) { uint32_t sfn; srslte_pbch_mib_unpack(bch_payload, &cell, &sfn); sfn = (sfn + sfn_offset)%1024; tti = sfn*10; srslte_ue_sync_decode_sss_on_track(&ue_sync, true); Info("SYNC: DONE, TTI=%d, sfn_offset=%d\n", tti, sfn_offset); srslte_ue_mib_reset(&ue_mib); return 1; } } } else { Debug("SYNC: PSS/SSS not found...\n"); } return 0; } void phch_recv::resync_sfn() { sync_sfn_cnt = 0; phy_state = SYNCING; } void phch_recv::run_thread() { int sync_res; phch_worker *worker = NULL; cf_t *buffer[SRSLTE_MAX_PORTS]; while(running) { switch(phy_state) { case CELL_SEARCH: if (cell_search()) { log_h->console("Initializating cell configuration...\n"); init_cell(); float srate = (float) srslte_sampling_freq_hz(cell.nof_prb); if (30720%((int) srate/1000) == 0) { radio_h->set_master_clock_rate(30.72e6); } else { radio_h->set_master_clock_rate(23.04e6); } log_h->console("Setting Sampling frequency %.2f MHz\n", (float) srate/1000000); radio_h->set_rx_srate(srate); radio_h->set_tx_srate(srate); ul_dl_factor = radio_h->get_tx_freq()/radio_h->get_rx_freq(); Info("SYNC: Cell found. Synchronizing...\n"); phy_state = SYNCING; sync_sfn_cnt = 0; srslte_ue_mib_reset(&ue_mib); } break; case SYNCING: srslte_ue_sync_decode_sss_on_track(&ue_sync, true); if (!radio_is_streaming) { // Start streaming radio_h->start_rx(); radio_is_streaming = true; } switch(sync_sfn()) { default: log_h->console("Going IDLE\n"); phy_state = IDLE; break; case 1: srslte_ue_sync_set_agc_period(&ue_sync, 20); phy_state = SYNC_DONE; break; case 0: break; } sync_sfn_cnt++; if (sync_sfn_cnt >= SYNC_SFN_TIMEOUT) { sync_sfn_cnt = 0; radio_h->stop_rx(); radio_is_streaming = false; log_h->console("Timeout while synchronizing SFN\n"); log_h->warning("Timeout while synchronizing SFN\n"); } break; case SYNC_DONE: tti = (tti+1)%10240; worker = (phch_worker*) workers_pool->wait_worker(tti); sync_res = 0; if (worker) { for (uint32_t i=0;iget_buffer(i); } sync_res = srslte_ue_sync_zerocopy_multi(&ue_sync, buffer); if (sync_res == 1) { log_h->step(tti); Debug("Worker %d synchronized\n", worker->get_id()); metrics.sfo = srslte_ue_sync_get_sfo(&ue_sync); metrics.cfo = srslte_ue_sync_get_cfo(&ue_sync); worker->set_cfo(ul_dl_factor*metrics.cfo/15000); worker_com->set_sync_metrics(metrics); float sample_offset = (float) srslte_ue_sync_get_sfo(&ue_sync)/1000; worker->set_sample_offset(sample_offset); /* Compute TX time: Any transmission happens in TTI+4 thus advance 4 ms the reception time */ srslte_timestamp_t rx_time, tx_time, tx_time_prach; srslte_ue_sync_get_last_timestamp(&ue_sync, &rx_time); srslte_timestamp_copy(&tx_time, &rx_time); srslte_timestamp_add(&tx_time, 0, 4e-3 - time_adv_sec); worker->set_tx_time(tx_time); Debug("Settting TTI=%d, tx_mutex=%d to worker %d\n", tti, tx_mutex_cnt, worker->get_id()); worker->set_tti(tti, tx_mutex_cnt); tx_mutex_cnt = (tx_mutex_cnt+1)%nof_tx_mutex; // Check if we need to TX a PRACH if (prach_buffer->is_ready_to_send(tti)) { srslte_timestamp_copy(&tx_time_prach, &rx_time); srslte_timestamp_add(&tx_time_prach, 0, prach::tx_advance_sf*1e-3); prach_buffer->send(radio_h, ul_dl_factor*metrics.cfo/15000, worker_com->pathloss, tx_time_prach); radio_h->tx_end(); worker_com->p0_preamble = prach_buffer->get_p0_preamble(); worker_com->cur_radio_power = SRSLTE_MIN(SRSLTE_PC_MAX, worker_com->pathloss + worker_com->p0_preamble); } workers_pool->start_worker(worker); // Notify RRC in-sync every 1 frame if ((tti%10) == 0) { rrc->in_sync(); log_h->debug("Sending in-sync to RRC\n"); } } else { log_h->console("Sync error.\n"); log_h->error("Sync error. Sending out-of-sync to RRC\n"); // Notify RRC of out-of-sync frame rrc->out_of_sync(); worker->release(); worker_com->reset_ul(); phy_state = SYNCING; } } else { // wait_worker() only returns NULL if it's being closed. Quit now to avoid unnecessary loops here running = false; } break; case IDLE: usleep(1000); break; } } } uint32_t phch_recv::get_current_tti() { return tti; } bool phch_recv::status_is_sync() { return phy_state == SYNC_DONE; } void phch_recv::get_current_cell(srslte_cell_t* cell_) { if (cell_) { memcpy(cell_, &cell, sizeof(srslte_cell_t)); } } void phch_recv::sync_start() { radio_h->set_master_clock_rate(30.72e6); phy_state = CELL_SEARCH; } void phch_recv::sync_stop() { free_cell(); radio_h->stop_rx(); radio_is_streaming = false; phy_state = IDLE; } }