34 #include <libconfig.h++>
45 #include "spdlog/async.h"
46 #include "spdlog/spdlog.h"
47 #include "spdlog/sinks/syslog_sink.h"
48 #include "srsran/srsran.h"
49 #include "srsran/upper/pdcp.h"
50 #include "srsran/rlc/rlc.h"
53 using libconfig::Config;
54 using libconfig::FileIOException;
55 using libconfig::ParseException;
57 using std::placeholders::_1;
58 using std::placeholders::_2;
59 using std::placeholders::_3;
61 static void print_version(FILE *stream,
struct argp_state *state);
64 static char doc[] =
"5G-MAG-RT MBMS Modem Process";
66 static struct argp_option
options[] = {
67 {
"config",
'c',
"FILE", 0,
"Configuration file (default: /etc/5gmag-rt.conf)", 0},
68 {
"log-level",
'l',
"LEVEL", 0,
69 "Log verbosity: 0 = trace, 1 = debug, 2 = info, 3 = warn, 4 = error, 5 = "
70 "critical, 6 = none. Default: 2.",
72 {
"srsran-log-level",
's',
"LEVEL", 0,
73 "Log verbosity for srsran: 0 = debug, 1 = info, 2 = warn, 3 = error, 4 = "
76 {
"sample-file",
'f',
"FILE", 0,
77 "Sample file in 4 byte float interleaved format to read I/Q data from. If "
78 "present, the data from this file will be decoded instead of live SDR "
79 "data. The channel bandwith must be specified with the --file-bandwidth "
80 "flag, and the sample rate of the file must be suitable for this "
83 {
"write-sample-file",
'w',
"FILE", 0,
84 "Create a sample file in 4 byte float interleaved format containing the "
85 "raw received I/Q data.",
87 {
"file-bandwidth",
'b',
"BANDWIDTH (MHz)", 0,
88 "If decoding data from a file, specify the channel bandwidth of the "
89 "recorded data in MHz here (e.g. 5)",
91 {
"override_nof_prb",
'p',
"# PRB", 0,
92 "Override the number of PRB received in the MIB", 0},
93 {
"sdr_devices",
'd',
nullptr, 0,
94 "Prints a list of all available SDR devices", 0},
95 {
"repeat",
'r',
nullptr, 0,
96 "Replay the sample file endlessly (default: false)", 0},
98 {
nullptr, 0,
nullptr, 0,
nullptr, 0}};
119 static auto parse_opt(
int key,
char *arg,
struct argp_state *state) -> error_t {
130 static_cast<unsigned>(strtoul(arg,
nullptr, 10));
143 static_cast<int8_t
>(strtol(arg,
nullptr, 10));
155 return ARGP_ERR_UNKNOWN;
161 nullptr,
nullptr,
nullptr};
167 fprintf(stream,
"%s.%s.%s\n", std::to_string(VERSION_MAJOR).c_str(),
168 std::to_string(VERSION_MINOR).c_str(),
169 std::to_string(VERSION_PATCH).c_str());
205 void set_params(
const std::string& ant,
unsigned fc,
double g,
unsigned sr,
unsigned bw) {
211 spdlog::info(
"RESTful API requesting new parameters: fc {}, bw {}, rate {}, gain {}, antenna {}",
224 auto main(
int argc,
char **argv) ->
int {
235 }
catch(
const FileIOException &fioex) {
238 }
catch(
const ParseException &pex) {
239 spdlog::error(
"Config parse error at {}:{} - {}. Exiting.",
240 pex.getFile(), pex.getLine(), pex.getError());
245 std::string ident =
"modem";
246 auto syslog_logger = spdlog::syslog_logger_mt(
"syslog", ident, LOG_PID | LOG_PERROR | LOG_CONS );
250 spdlog::set_pattern(
"[%H:%M:%S.%f %z] [%^%l%$] [thr %t] %v");
252 spdlog::set_default_logger(syslog_logger);
253 spdlog::info(
"5g-mag-rt modem v{}.{}.{} starting up", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
256 auto rx_channels = 1;
257 cfg.lookupValue(
"modem.sdr.rx_channels", rx_channels);
258 spdlog::info(
"Initialising SDR with {} RX channel(s)", rx_channels);
265 std::string sdr_dev =
"driver=lime";
266 cfg.lookupValue(
"modem.sdr.device_args", sdr_dev);
268 spdlog::error(
"Failed to initialize I/Q data source.");
275 unsigned long long center_frequency =
frequency;
276 if (!
cfg.lookupValue(
"modem.sdr.center_frequency_hz", center_frequency)) {
277 spdlog::error(
"Unable to parse center_frequency_hz - values must have a ‘L’ character appended");
282 if (center_frequency <= UINT_MAX) {
283 frequency =
static_cast<unsigned>(center_frequency);
285 spdlog::error(
"Configured center_frequency_hz is {}, maximal value supported is {}.",
286 center_frequency, UINT_MAX);
291 cfg.lookupValue(
"modem.sdr.normalized_gain",
gain);
292 cfg.lookupValue(
"modem.sdr.antenna",
antenna);
293 cfg.lookupValue(
"modem.sdr.use_agc",
use_agc);
296 spdlog::error(
"Failed to set initial center frequency. Exiting.");
300 set_srsran_verbose_level(
arguments.
log_level <= 1 ? SRSRAN_VERBOSE_DEBUG : SRSRAN_VERBOSE_NONE);
301 srsran_use_standard_symbol_size(
true);
304 unsigned thread_cnt = 4;
305 cfg.lookupValue(
"modem.phy.threads", thread_cnt);
307 cfg.lookupValue(
"modem.phy.thread_priority_rt", phy_prio);
311 struct sched_param thread_param = {};
312 thread_param.sched_priority = 20;
313 cfg.lookupValue(
"modem.phy.main_thread_priority_rt", thread_param.sched_priority);
315 spdlog::info(
"Raising main thread to realtime scheduling priority {}", thread_param.sched_priority);
317 int error = pthread_setschedparam(pthread_self(), SCHED_RR, &thread_param);
319 spdlog::error(
"Cannot set main thread priority to realtime: {}. Thread will run at default priority.", strerror(error));
322 bool enable_measurement_file =
false;
323 cfg.lookupValue(
"modem.measurement_file.enabled", enable_measurement_file);
336 srsran::pdcp pdcp(
nullptr,
"PDCP");
337 srsran::rlc rlc(
"RLC");
338 srsran::timer_handler timers;
344 rlc.init(&pdcp, &rrc, &timers, 0 );
345 pdcp.init(&rlc, &rrc, &gw);
347 auto srs_level = srslog::basic_levels::none;
349 case 0: srs_level = srslog::basic_levels::debug;
break;
350 case 1: srs_level = srslog::basic_levels::info;
break;
351 case 2: srs_level = srslog::basic_levels::warning;
break;
352 case 3: srs_level = srslog::basic_levels::error;
break;
353 case 4: srs_level = srslog::basic_levels::none;
break;
357 auto& mac_log = srslog::fetch_basic_logger(
"MAC",
false);
358 mac_log.set_level(srs_level);
359 auto& phy_log = srslog::fetch_basic_logger(
"PHY",
false);
360 phy_log.set_level(srs_level);
361 auto& rlc_log = srslog::fetch_basic_logger(
"RLC",
false);
362 rlc_log.set_level(srs_level);
363 auto& asn1_log = srslog::fetch_basic_logger(
"ASN1",
false);
364 asn1_log.set_level(srs_level);
370 std::string uri =
"http://0.0.0.0:3010/modem-api/";
371 cfg.lookupValue(
"modem.restful_api.uri", uri);
372 spdlog::info(
"Starting RESTful API handler at {}", uri);
377 if (!cas_processor.
init()) {
378 spdlog::error(
"Failed to create CAS processor. Exiting.");
385 std::vector<MbsfnFrameProcessor*> mbsfn_processors;
386 for (
int i = 0; i < thread_cnt; i++) {
389 spdlog::error(
"Failed to create MBSFN processor. Exiting.");
392 mbsfn_processors.push_back(p);
395 rest_handler.
start();
400 uint32_t mch_bler_global = 0;
401 uint32_t mcch_bler_global = 0;
402 uint32_t pdsch_bler_global = 0;
403 uint32_t mch_total_global = 0;
404 uint32_t mcch_total_global = 0;
405 uint32_t pdsch_total_global = 0;
408 uint32_t sync_losses = 0;
409 uint32_t lost_subframes = 0;
410 uint32_t measurements = 0.0f;
414 float measurement_interval_f = 5;
415 cfg.lookupValue(
"modem.measurement_file.interval_secs", measurement_interval_f);
416 uint32_t measurement_interval = measurement_interval_f * 1000;
427 tti = (tti + 1) % 10240;
432 spdlog::debug(
"sending tti {} to regular processor", tti);
433 pool.push([ObjectPtr = &cas_processor, tti, &rest_handler] {
434 if (ObjectPtr->process(tti)) {
436 rest_handler.add_cinr_value(ObjectPtr->cinr_db());
450 spdlog::info(
"Setting sample rate {} Mhz for MBSFN with {} PRB / {} Mhz channel width", new_srate/1000000.0,
mbsfn_nof_prb,
463 spdlog::info(
"Synchronizing subframe after PRB extension");
468 spdlog::warn(
"Synchronization lost while processing. Going back to searching state.");
474 spdlog::debug(
"sending tti {} to mbsfn proc {}", tti, mb_idx);
478 if (!
restart && phy.
get_next_frame(mbsfn_processors[mb_idx]->get_rx_buffer_and_lock(), mbsfn_processors[mb_idx]->rx_buffer_size())) {
481 if (!mbsfn_processors[mb_idx]->mbsfn_configured()) {
482 srsran_scs_t scs = SRSRAN_SCS_15KHZ;
488 auto cell = phy.
cell();
489 cell.nof_prb = cell.mbsfn_prb;
490 mbsfn_processors[mb_idx]->set_cell(cell);
491 mbsfn_processors[mb_idx]->configure_mbsfn(phy.
mbsfn_area_id(), scs);
493 pool.push([ObjectPtr = mbsfn_processors[mb_idx], tti] {
494 ObjectPtr->process(tti);
499 mbsfn_processors[mb_idx]->unlock();
503 spdlog::warn(
"Synchronization lost while processing. Going back to searching state.");
507 mb_idx =
static_cast<int>((mb_idx + 1) % thread_cnt);
541 unsigned new_srate = srsran_sampling_freq_hz(
cas_nof_prb);
542 spdlog::info(
"Setting sample rate {} Mhz for {} PRB / {} Mhz channel width", new_srate/1000000.0, phy.
nr_prb(),
552 spdlog::debug(
"Synchronizing subframe");
569 bool sfn_sync =
false;
574 lost_subframes += (((phy.
tti() < tti) * 10240 + phy.
tti())-tti) * cas_processor.
is_started() ;
575 spdlog::info(
"Decoded MIB at target sample rate, TTI is {}. Subframe synchronized, sync lost in TTI {}, {} subframes lost, {} total subframe lost, sync losses {}.", phy.
tti(), tti, (((phy.
tti() < tti) * 10240 + phy.
tti())-tti) * cas_processor.
is_started(), lost_subframes, sync_losses);
580 for (
int i = 0; i < thread_cnt; i++) {
581 mbsfn_processors[i]->unlock();
604 if (tick%measurement_interval == 0) {
607 std::vector<std::string> cols;
611 spdlog::info(
"CINR {:.2f} dB", rest_handler.
cinr_db() );
612 cols.push_back(std::to_string((
float)rest_handler.
cinr_db()));
615 cas_processor.
lock();
617 spdlog::info(
"PDSCH: MCS {}, BLER {}",
621 cols.push_back(std::to_string(rest_handler.
_pdsch.
mcs));
632 for (
int i = 0; i < thread_cnt; i++) {
633 mbsfn_processors[i]->lock();
635 spdlog::info(
"MCCH: MCS {}, BLER {}",
639 cols.push_back(std::to_string(rest_handler.
_mcch.
mcs));
640 cols.push_back(std::to_string(((rest_handler.
_mcch.
errors * 1.0) / (rest_handler.
_mcch.
total * 1.0))));
642 cols.push_back(std::to_string(tti));
643 cols.emplace_back(std::string(
""));
644 cols.push_back(std::to_string(lost_subframes));
648 std::for_each(std::begin(mch_info), std::end(mch_info), [&cols, &mch_idx, &rest_handler, &mch_bler_global, &mch_total_global](
Phy::mch_info_t const& mch) {
650 spdlog::info(
"MCH {}: MCS {}, BLER {}",
653 ((rest_handler.
_mch[mch_idx].errors > 0 && rest_handler.
_mch[mch_idx].total > 0) ? (rest_handler.
_mch[mch_idx].errors * 1.0) / (rest_handler.
_mch[mch_idx].total * 1.0) : 0));
655 cols.push_back(std::to_string(mch_idx));
656 cols.push_back(std::to_string(mch.
mcs));
657 cols.push_back(std::to_string((rest_handler.
_mch[mch_idx].errors * 1.0) / (rest_handler.
_mch[mch_idx].total * 1.0)));
660 std::for_each(std::begin(mch.
mtchs), std::end(mch.
mtchs), [&mtch_idx, &mch_bler_global, &mch_total_global](
Phy::mtch_info_t const& mtch) {
661 spdlog::info(
" MTCH {}: LCID {}, TMGI 0x{}, {}",
669 mch_bler_global += rest_handler.
_mch[mch_idx].errors;
670 mch_total_global += rest_handler.
_mch[mch_idx].total;
671 rest_handler.
_mch[mch_idx].errors = 0;
672 rest_handler.
_mch[mch_idx].total = 0;
677 mcch_total_global += rest_handler.
_mcch.
total;
681 for (
int i = 0; i < thread_cnt; i++) {
682 mbsfn_processors[i]->unlock();
685 cols.emplace_back(std::string(
"NOT SYNC - SYNCING..."));
686 cols.emplace_back(std::string(
"nan"));
687 cols.emplace_back(std::string(
"nan"));
688 cols.emplace_back(std::string(
"nan"));
689 cols.emplace_back(std::string(
"nan"));
690 cols.emplace_back(std::string(
"nan"));
691 cols.push_back(std::to_string(sync_losses));
692 cols.push_back(std::to_string(lost_subframes));
693 cols.emplace_back(std::string(
"nan"));
694 cols.emplace_back(std::string(
"nan"));
695 cols.emplace_back(std::string(
"nan"));
698 cols.emplace_back(std::string(
"SEARCHING FOR A CELL..."));
699 cols.emplace_back(std::string(
"nan"));
700 cols.emplace_back(std::string(
"nan"));
701 cols.emplace_back(std::string(
"nan"));
702 cols.emplace_back(std::string(
"nan"));
703 cols.emplace_back(std::string(
"nan"));
704 cols.emplace_back(std::string(
""));
705 cols.push_back(std::to_string(lost_subframes));
706 cols.emplace_back(std::string(
"nan"));
707 cols.emplace_back(std::string(
"nan"));
708 cols.emplace_back(std::string(
"nan"));
711 if (enable_measurement_file) {
716 spdlog::info(
"------ Global statistics ------ \n\t\tMCH BLER {}, \n\t\tMCH TOTAL ERRORS {}, \n\t\tMCCH BLER: {}, \n\t\tPDSCH BLER: {}, \n\t\tSYNC LOSSES: {}, \n\t\tSF PROCESSED: {}",
717 ((mch_bler_global > 0 && mch_total_global > 0) ? (mch_bler_global * 1.0) / (mch_total_global * 1.0) : 0),
719 ((mcch_bler_global > 0 && mcch_total_global > 0) ? (mcch_bler_global * 1.0) / (mcch_total_global * 1.0) : 0),
720 ((pdsch_bler_global > 0 && pdsch_total_global > 0) ? (pdsch_bler_global * 1.0) / (pdsch_total_global * 1.0) : 0),
724 spdlog::info(
"Total subframe lost {}, sync losses {}.", lost_subframes, sync_losses);
730 for (
int i = 0; i < thread_cnt; i++) {
731 delete( mbsfn_processors[i] );
Frame processor for CAS subframes.
cf_t ** get_rx_buffer_and_lock()
Get a handle of the signal buffer to store samples for processing in.
bool is_started()
Returns if the CasFrameProcessor is started or not.
bool init()
Initialize signal- and softbuffers, init all underlying components.
void set_cell(srsran_cell_t cell)
Set the parameters for the cell (Nof PRB, etc).
uint32_t rx_buffer_size()
Size of the signal buffer.
void unlock()
Unlock the processor.
Network gateway component.
void init()
Creates the TUN interface according to params from Cfg.
Frame processor for MBSFN subframes.
Writes measurement data / current reception parameters to a file.
void WriteLogValues(const std::vector< std::string > &values)
Write a line containing the passed values.
bool mcch_configured()
Return true if MCCH has been configured.
SubcarrierSpacing mbsfn_subcarrier_spacing()
unsigned nr_prb()
Get the current number of PRB.
uint8_t mbsfn_area_id()
Returns the current MBSFN area ID.
const std::vector< mch_info_t > & mch_info()
bool get_next_frame(cf_t **buffer, uint32_t size)
Get the sample data for the next subframe.
srsran_cell_t cell()
Get the current cell (with params adjusted for MBSFN)
bool synchronize_subframe()
Synchronizes PSS/SSS and tries to deocode the MIB.
uint8_t nof_mbsfn_prb()
Get number of PRB in MBSFN/PMCH.
bool is_cas_subframe(unsigned tti)
uint32_t tti()
Get the current subframe TTI.
bool init()
Initialize the underlying components.
bool cell_search()
Search for a cell.
void set_nof_mbsfn_prb(uint8_t prb)
Override number of PRB in MBSFN/PMCH.
bool is_mbsfn_subframe(unsigned tti)
void set_cas_processor(CasFrameProcessor *cas_processor)
Save the pointer to the CasFrameProcessor.
float cinr_db()
Current instantaneous CINR value.
void start()
Start function for the listener.
ChannelInfo _mcch
RX info for MCCH.
std::map< uint32_t, ChannelInfo > _mch
RX info for MCHs.
ChannelInfo _pdsch
RX info for PDSCH.
Simple RRC component between PHY and RLC.
Interface to the SDR stick.
void stop()
Stop reading samples from the SDR.
void start()
Start reading samples from the SDR.
bool init(const std::string &device_args, const char *sample_file, const char *write_sample_file, bool repeat_sample_file)
Initializes the SDR interface and creates a ring buffer according to the params from Cfg.
void enumerateDevices()
Prints a list of all available SDR devices.
int get_samples(cf_t *data[SRSRAN_MAX_CHANNELS], uint32_t nsamples, srsran_timestamp_t *rx_time)
Store nsamples count samples into the buffer at data.
void clear_buffer()
Clear all samples from the rx buffers.
bool tune(uint32_t frequency, uint32_t sample_rate, uint32_t bandwidth, double gain, const std::string &antenna, bool use_agc)
Tune the SDR to the desired frequency, and set gain, filter and antenna parameters.
void enableSampleFileWriting()
If sample file creation is enabled, writing samples starts after this call.
static Config cfg
Global configuration object.
static std::string antenna
Antenna input to be used.
static unsigned mbsfn_nof_prb
static unsigned sample_rate
Sample rate of the SDR.
static void print_version(FILE *stream, struct argp_state *state)
Print the program version in MAJOR.MINOR.PATCH format.
auto main(int argc, char **argv) -> int
Main entry point for the program.
static bool restart
Restart flag.
static unsigned cas_nof_prb
static uint32_t bandwidth
Low pass filter bandwidth for the SDR.
static unsigned search_sample_rate
Sample rate of the SDR.
const char * argp_program_bug_address
void set_params(const std::string &ant, unsigned fc, double g, unsigned sr, unsigned bw)
Set new SDR parameters and initialize resynchronisation.
static unsigned frequency
Center freqeuncy the SDR is tuned to.
static struct argp_option options[]
static double gain
Overall system gain for the SDR.
void(* argp_program_version_hook)(FILE *, struct argp_state *)
static auto parse_opt(int key, char *arg, struct argp_state *state) -> error_t
Parses the command line options into the arguments struct.
std::vector< mtch_info_t > mtchs
Holds all options passed on the command line.
uint8_t file_bw
bandwidth of the sample file
const char * config_file
file path of the config file.
const char * write_sample_file
file path of the created sample file.
unsigned srs_log_level
srsLTE log level
int8_t override_nof_prb
ovride PRB number
unsigned log_level
log level
const char * sample_file
file path of the sample file.