/* * Copyright 2013-2019 Software Radio Systems Limited * * This file is part of srsLTE. * * srsLTE 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. * * srsLTE 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 "srsue/hdr/phy/async_scell_recv.h" #include "srsue/hdr/phy/phy_common.h" #include #include #include #include #include #define LOG_PREABLE "[scell_recv] " #define LOG_ALL_CONSOLE 0 #if LOG_ALL_CONSOLE #define Error(fmt, ...) log_h->console(LOG_PREABLE fmt, ##__VA_ARGS__) #define Warning(fmt, ...) log_h->console(LOG_PREABLE fmt, ##__VA_ARGS__) #define Info(fmt, ...) log_h->console(LOG_PREABLE fmt, ##__VA_ARGS__) #define Debug(fmt, ...) log_h->console(LOG_PREABLE fmt, ##__VA_ARGS__) #else #define Error(fmt, ...) log_h->error(LOG_PREABLE fmt, ##__VA_ARGS__) #define Warning(fmt, ...) log_h->warning(LOG_PREABLE fmt, ##__VA_ARGS__) #define Info(fmt, ...) log_h->info(LOG_PREABLE fmt, ##__VA_ARGS__) #define Debug(fmt, ...) log_h->debug(LOG_PREABLE fmt, ##__VA_ARGS__) #endif namespace srsue { async_scell_recv::async_scell_recv() { initiated = false; buffer_write_idx = 0; buffer_read_idx = 0; dl_freq = -1; ul_freq = -1; bzero(&cell, sizeof(srslte_cell_t)); bzero(sf_buffer, sizeof(sf_buffer)); running = false; } async_scell_recv::~async_scell_recv() { if (initiated) { srslte_ue_sync_free(&ue_sync); } for (int i = 0; i < SRSLTE_MAX_PORTS; i++) { if (sf_buffer[i]) { free(sf_buffer[i]); } } } static int radio_recv_callback(void* obj, cf_t* data[SRSLTE_MAX_PORTS], uint32_t nsamples, srslte_timestamp_t* rx_time) { return ((async_scell_recv*)obj)->radio_recv_fnc(data, nsamples, rx_time); } static double callback_set_rx_gain(void* h, double gain) { return ((async_scell_recv*)h)->set_rx_gain(gain); } void async_scell_recv::init(radio_interface_phy* _radio_handler, phy_common* _worker_com, srslte::log* _log_h) { // Get handlers radio_h = _radio_handler; worker_com = _worker_com; log_h = _log_h; // Calculate number of RF channels uint32_t nof_rf_channels = worker_com->args->nof_rf_channels * worker_com->args->nof_rx_ant; // Initialise buffers for (uint32_t s = 0; s < ASYNC_NOF_BUFFERS; s++) { buffers[s].init(nof_rf_channels); } for (uint32_t i = 0; i < nof_rf_channels; i++) { sf_buffer[i] = (cf_t*)srslte_vec_malloc(sizeof(cf_t) * SRSLTE_SF_LEN_MAX * 5); if (!sf_buffer[i]) { fprintf(stderr, "Error allocating buffer\n"); return; } } if (srslte_ue_sync_init_multi(&ue_sync, SRSLTE_MAX_PRB, false, radio_recv_callback, nof_rf_channels, this)) { fprintf(stderr, "SYNC: Initiating ue_sync\n"); return; } if (srslte_ue_mib_init(&ue_mib, sf_buffer, SRSLTE_MAX_PRB)) { fprintf(stderr, "Error initaiting UE MIB decoder\n"); return; } if (pthread_cond_init(&cvar_buffer, NULL)) { fprintf(stderr, "Initiating condition var\n"); return; } reset(); running = false; initiated = true; } void async_scell_recv::stop() { running = false; wait_thread_finish(); pthread_mutex_destroy(&mutex_buffer); pthread_mutex_destroy(&mutex_uesync); pthread_cond_destroy(&cvar_buffer); } void async_scell_recv::in_sync() { in_sync_cnt++; // Send RRC in-sync signal after 100 ms consecutive subframes if (in_sync_cnt == NOF_IN_SYNC_SF) { in_sync_cnt = 0; out_of_sync_cnt = 0; } } void async_scell_recv::out_of_sync() { // Send RRC out-of-sync signal after 200 ms consecutive subframes Info("Out-of-sync %d/%d\n", out_of_sync_cnt, NOF_OUT_OF_SYNC_SF); out_of_sync_cnt++; if (out_of_sync_cnt == NOF_OUT_OF_SYNC_SF) { Info("Sending to RRC\n"); out_of_sync_cnt = 0; in_sync_cnt = 0; } } void async_scell_recv::set_cfo(float cfo) { srslte_ue_sync_set_cfo_ref(&ue_sync, cfo); } void async_scell_recv::set_agc_enable(bool enable) { do_agc = enable; if (do_agc) { if (radio_h) { srslte_rf_info_t* rf_info = radio_h->get_info(); srslte_ue_sync_start_agc( &ue_sync, callback_set_rx_gain, rf_info->min_rx_gain, rf_info->max_rx_gain, radio_h->get_rx_gain()); } else { fprintf(stderr, "Error setting Secondary cell AGC: PHY not initiated\n"); } } else { fprintf(stderr, "Error stopping AGC: not implemented\n"); } } double async_scell_recv::set_rx_gain(double gain) { return radio_h->set_rx_gain_th((float)gain); } int async_scell_recv::radio_recv_fnc(cf_t* data[SRSLTE_MAX_PORTS], uint32_t nsamples, srslte_timestamp_t* rx_time) { int ret = 0; if (running) { if (radio_h->rx_now(data, nsamples, rx_time)) { log_h->debug("SYNC: received %d samples from radio\n", nsamples); ret = nsamples; } else { ret = SRSLTE_ERROR; } } return ret; } void async_scell_recv::reset() { in_sync_cnt = 0; out_of_sync_cnt = 0; for (int i = 0; i < SRSLTE_MAX_PORTS; i++) { current_earfcn[i] = UINT32_MAX; } } void async_scell_recv::radio_error() { log_h->error("SYNC: Receiving from radio.\n"); // Need to find a method to effectively reset radio, reloading the driver does not work radio_h->reset(); } void async_scell_recv::set_ue_sync_opts(srslte_ue_sync_t* q, float cfo) { if (worker_com->args->cfo_integer_enabled) { srslte_ue_sync_set_cfo_i_enable(q, true); } srslte_ue_sync_set_cfo_ema(q, worker_com->args->cfo_pss_ema); srslte_ue_sync_set_cfo_tol(q, worker_com->args->cfo_correct_tol_hz); srslte_ue_sync_set_cfo_loop_bw(q, worker_com->args->cfo_loop_bw_pss, worker_com->args->cfo_loop_bw_ref, worker_com->args->cfo_loop_pss_tol, worker_com->args->cfo_loop_ref_min, worker_com->args->cfo_loop_pss_tol, worker_com->args->cfo_loop_pss_conv); q->strack.pss.chest_on_filter = worker_com->args->sic_pss_enabled; // Disable CP based CFO estimation during find if (cfo != 0) { q->cfo_current_value = cfo / 15000; q->cfo_is_copied = true; q->cfo_correct_enable_find = true; srslte_sync_set_cfo_cp_enable(&q->sfind, false, 0); } // Set SFO ema and correct period srslte_ue_sync_set_sfo_correct_period(q, worker_com->args->sfo_correct_period); srslte_ue_sync_set_sfo_ema(q, worker_com->args->sfo_ema); srslte_sync_set_sss_algorithm(&q->strack, SSS_FULL); srslte_sync_set_sss_algorithm(&q->sfind, SSS_FULL); } bool async_scell_recv::set_scell_cell(uint32_t carrier_idx, srslte_cell_t* _cell, uint32_t dl_earfcn) { bool ret = true; bool reset_ue_sync = false; Info("Set cell:{nof_prb=%d; cp=%s; id=%d} dl_earfcn=%d\n", _cell->nof_prb, srslte_cp_string(_cell->cp), _cell->id, dl_earfcn); // Lock mutex pthread_mutex_lock(&mutex_uesync); // Get transceiver mapping carrier_map_t* m = &worker_com->args->carrier_map[carrier_idx]; uint32_t channel_idx = m->channel_idx; // Set radio frequency if frequency changed if (current_earfcn[channel_idx] != dl_earfcn && ret) { dl_freq = srslte_band_fd(dl_earfcn) * 1e6f; ul_freq = srslte_band_fu(srslte_band_ul_earfcn(dl_earfcn)) * 1e6f; radio_h->set_rx_freq(channel_idx, dl_freq); radio_h->set_tx_freq(channel_idx, ul_freq); Info("Setting DL: %.1f MHz; UL %.1fMHz; Radio/Chan: %d/%d\n", dl_freq / 1e6, ul_freq / 1e6, m->radio_idx, m->channel_idx); ul_dl_factor = ul_freq / dl_freq; current_earfcn[channel_idx] = dl_earfcn; reset_ue_sync = true; } // Detect change in cell configuration if (memcmp(&cell, _cell, sizeof(srslte_cell_t)) != 0) { // Set sampling rate, if number of PRB changed if (cell.nof_prb != _cell->nof_prb && ret) { double srate = srslte_sampling_freq_hz(_cell->nof_prb); if (srate < 10e6) { radio_h->set_master_clock_rate(4 * srate); } else { radio_h->set_master_clock_rate(srate); } radio_h->set_rx_srate(srate); radio_h->set_tx_srate(srate); Info("Setting SRate to %.2f MHz\n", srate / 1e6); } // Copy cell cell = *_cell; reset_ue_sync = true; // Set cell in ue sync if (srslte_ue_sync_set_cell(&ue_sync, cell)) { Error("SYNC: Setting cell: initiating ue_sync\n"); ret = false; } // Set cell in MIB decoder if (srslte_ue_mib_set_cell(&ue_mib, cell)) { fprintf(stderr, "Error setting cell in UE MIB decoder\n"); ret = false; } srslte_ue_mib_reset(&ue_mib); } // Reset ue_sync and set CFO/gain from search procedure if (reset_ue_sync) { srslte_ue_sync_reset(&ue_sync); } // Reset thread state state = DECODE_MIB; // If not running start! if (!running) { // Start main thread start(1); running = true; } pthread_mutex_unlock(&mutex_uesync); return ret; } void async_scell_recv::state_decode_mib() { int sfn_offset = 0; uint8_t bch_payload[SRSLTE_BCH_PAYLOAD_LEN]; uint32_t sfidx = srslte_ue_sync_get_sfidx(&ue_sync); if (sfidx == 0) { // Run only for sub-frame index 0 int n = srslte_ue_mib_decode(&ue_mib, bch_payload, NULL, &sfn_offset); if (n < SRSLTE_SUCCESS) { // Error decoding MIB, log error Error("Error decoding UE MIB (%d)\n", n); } else if (n == SRSLTE_UE_MIB_FOUND) { // MIB Found uint32_t sfn = 0; srslte_pbch_mib_unpack(bch_payload, &cell, &sfn); Info("SCell MIB synchronised (SNR=%.2fdB)\n", ue_mib.chest_res.snr_db); // Set sub-frame index tti = ((sfn + sfn_offset) % 1024) * 10; // Change state, reset ring buffer and go to Synchronized but idle buffer_write_idx = 0; buffer_read_idx = 0; state = SYNCH_IDLE; } else { // MIB Not found // Do nothing. Keep going. } } else { // Do nothing. Keep going. } } void async_scell_recv::state_write_buffer() { if (tti % SRSLTE_NOF_SF_X_FRAME != srslte_ue_sync_get_sfidx(&ue_sync) || ue_sync.state != SF_TRACK) { // Real-time failure, go to decode MIB Info("Detected Real-Time failure; Going to search MIB (from WRITE)\n"); state = DECODE_MIB; } else { // Normal operation, try to write buffer phch_scell_recv_buffer* buffer = &buffers[buffer_write_idx]; srslte_timestamp_t rx_time = {}; // Lock mutex pthread_mutex_lock(&mutex_buffer); // Copy last timestamp srslte_ue_sync_get_last_timestamp(&ue_sync, &rx_time); // Extract essential information buffer->set_sf(tti, &rx_time); // Increment write index buffer_write_idx = (buffer_write_idx + 1) % ASYNC_NOF_BUFFERS; // Detect overflow if (buffer_write_idx == buffer_read_idx) { // Reset buffer and goto synchronized IDLE Info("Detected overflow; reseting ring buffer and going to IDLE...\n"); buffer_write_idx = 0; buffer_read_idx = 0; state = SYNCH_IDLE; } // Unlock mutex and inform that data was received pthread_cond_broadcast(&cvar_buffer); pthread_mutex_unlock(&mutex_buffer); } } void async_scell_recv::state_synch_idle() { if (tti % SRSLTE_NOF_SF_X_FRAME != srslte_ue_sync_get_sfidx(&ue_sync)) { // Real-time failure, go to decode MIB Debug("Detected Real-Time failure; Going to search MIB (from IDLE)\n"); state = DECODE_MIB; } else { // Do nothing } } void async_scell_recv::run_thread() { Info("Starting asynchronous scell reception...\n"); while (running) { phch_scell_recv_buffer* buffer = &buffers[buffer_write_idx]; // Lock ue_sync pthread_mutex_lock(&mutex_uesync); // Get RF base-band int ret = srslte_ue_sync_zerocopy(&ue_sync, (state == DECODE_MIB) ? sf_buffer : buffer->get_buffer_ptr()); if (ret < 0) { fprintf(stderr, "Error calling srslte_ue_sync_work()\n"); } // Unlock ue_sync pthread_mutex_unlock(&mutex_uesync); if (ret == 1) { // Synchronized switch (state) { case DECODE_MIB: state_decode_mib(); break; case WRITE_BUFFER: state_write_buffer(); break; case SYNCH_IDLE: state_synch_idle(); break; } // Increment tti tti = (tti + 1) % 10240; } else if (ret == 0) { // Error in synchronization // Warning("SYNC: Out-of-sync detected in PSS/SSS\n"); // out_of_sync(); } if (ret < 0) { // Radio error radio_error(); } } } bool async_scell_recv::tti_align(uint32_t tti) { bool ret = false; if (state == SYNCH_IDLE) { // Enable Writing in buffer Debug("Start writing in buffer\n"); state = WRITE_BUFFER; } else if (state == DECODE_MIB) { // Debug("SCell not ready for reading\n"); return false; } pthread_mutex_lock(&mutex_buffer); // Stage 1: Flush buffers if the tti is not available // While data is available and no tti match, discard while ((buffer_write_idx != buffer_read_idx) && (buffers[buffer_read_idx].get_tti() != tti)) { // Discard buffer Error("Expected TTI %d. Discarding tti %d.\n", tti, buffers[buffer_read_idx].get_tti()); buffer_read_idx = (buffer_read_idx + 1) % ASYNC_NOF_BUFFERS; } if ((buffers[buffer_read_idx].get_tti() == tti)) { // tti match ret = true; } // Stage 2: If the tti is not found and the latest tti was -1; wait // Get time and set timeout time if (!ret) { bool timedout = false; while (!ret && !timedout && buffer_write_idx == buffer_read_idx && running) { struct timespec timeToWait; struct timeval now; gettimeofday(&now, NULL); timeToWait.tv_sec = now.tv_sec; timeToWait.tv_nsec = (now.tv_usec + 1000UL) * 1000UL; int rt = pthread_cond_timedwait(&cvar_buffer, &mutex_buffer, &timeToWait); switch (rt) { case ETIMEDOUT: case EPERM: // Consider all errors timed out, exit loop timedout = true; Error("Expected TTI %04d. timeout (%d).\n", tti, rt); tti_align_timeout_counter++; if (tti_align_timeout_counter > max_tti_align_timeout_counter) { Error("Maximum number of timeouts reached (%d). Going back to decode MIB.\n", max_tti_align_timeout_counter); state = DECODE_MIB; } break; default: if ((buffers[buffer_read_idx].get_tti() == tti)) { // tti match ret = true; } break; } } } pthread_mutex_unlock(&mutex_buffer); return ret; } void async_scell_recv::read_sf(cf_t** dst, srslte_timestamp_t* timestamp) { pthread_mutex_lock(&mutex_buffer); // Block until data is filled while (buffer_write_idx == buffer_read_idx && running) { pthread_cond_wait(&cvar_buffer, &mutex_buffer); } // Exit condition detected if (!running) { pthread_mutex_unlock(&mutex_buffer); return; } // Get reading buffer phch_scell_recv_buffer* buffer = &buffers[buffer_read_idx]; if (dst) { // Get data pointer cf_t** buff = buffer->get_buffer_ptr(); uint32_t nof_rf_channels = worker_com->args->nof_rf_channels * worker_com->args->nof_rx_ant; // Copy data for (uint32_t i = 0; i < nof_rf_channels; i++) { if (dst[i]) { // Check pointer is allocated memcpy(dst[i], buff[i], sizeof(cf_t) * SRSLTE_SF_LEN_PRB(cell.nof_prb)); } } } buffer->get_timestamp(timestamp); // Increment read index buffer_read_idx = (buffer_read_idx + 1) % ASYNC_NOF_BUFFERS; pthread_mutex_unlock(&mutex_buffer); } } // namespace srsue