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 {
nullptr, 0,
nullptr, 0,
nullptr, 0}};
115 static auto parse_opt(
int key,
char *arg,
struct argp_state *state) -> error_t {
126 static_cast<unsigned>(strtoul(arg,
nullptr, 10));
139 static_cast<int8_t
>(strtol(arg,
nullptr, 10));
148 return ARGP_ERR_UNKNOWN;
154 nullptr,
nullptr,
nullptr};
160 fprintf(stream,
"%s.%s.%s\n", std::to_string(VERSION_MAJOR).c_str(),
161 std::to_string(VERSION_MINOR).c_str(),
162 std::to_string(VERSION_PATCH).c_str());
198 void set_params(
const std::string& ant,
unsigned fc,
double g,
unsigned sr,
unsigned bw) {
204 spdlog::info(
"RESTful API requesting new parameters: fc {}, bw {}, rate {}, gain {}, antenna {}",
217 auto main(
int argc,
char **argv) ->
int {
228 }
catch(
const FileIOException &fioex) {
231 }
catch(
const ParseException &pex) {
232 spdlog::error(
"Config parse error at {}:{} - {}. Exiting.",
233 pex.getFile(), pex.getLine(), pex.getError());
238 std::string ident =
"modem";
239 auto syslog_logger = spdlog::syslog_logger_mt(
"syslog", ident, LOG_PID | LOG_PERROR | LOG_CONS );
243 spdlog::set_pattern(
"[%H:%M:%S.%f %z] [%^%l%$] [thr %t] %v");
245 spdlog::set_default_logger(syslog_logger);
246 spdlog::info(
"5g-mag-rt modem v{}.{}.{} starting up", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH);
249 auto rx_channels = 1;
250 cfg.lookupValue(
"modem.sdr.rx_channels", rx_channels);
251 spdlog::info(
"Initialising SDR with {} RX channel(s)", rx_channels);
258 std::string sdr_dev =
"driver=lime";
259 cfg.lookupValue(
"modem.sdr.device_args", sdr_dev);
261 spdlog::error(
"Failed to initialize I/Q data source.");
268 unsigned long long center_frequency =
frequency;
269 if (!
cfg.lookupValue(
"modem.sdr.center_frequency_hz", center_frequency)) {
270 spdlog::error(
"Unable to parse center_frequency_hz - values must have a ‘L’ character appended");
275 if (center_frequency <= UINT_MAX) {
276 frequency =
static_cast<unsigned>(center_frequency);
278 spdlog::error(
"Configured center_frequency_hz is {}, maximal value supported is {}.",
279 center_frequency, UINT_MAX);
284 cfg.lookupValue(
"modem.sdr.normalized_gain",
gain);
285 cfg.lookupValue(
"modem.sdr.antenna",
antenna);
286 cfg.lookupValue(
"modem.sdr.use_agc",
use_agc);
289 spdlog::error(
"Failed to set initial center frequency. Exiting.");
293 set_srsran_verbose_level(
arguments.
log_level <= 1 ? SRSRAN_VERBOSE_DEBUG : SRSRAN_VERBOSE_NONE);
294 srsran_use_standard_symbol_size(
true);
297 unsigned thread_cnt = 4;
298 cfg.lookupValue(
"modem.phy.threads", thread_cnt);
300 cfg.lookupValue(
"modem.phy.thread_priority_rt", phy_prio);
304 struct sched_param thread_param = {};
305 thread_param.sched_priority = 20;
306 cfg.lookupValue(
"modem.phy.main_thread_priority_rt", thread_param.sched_priority);
308 spdlog::info(
"Raising main thread to realtime scheduling priority {}", thread_param.sched_priority);
310 int error = pthread_setschedparam(pthread_self(), SCHED_RR, &thread_param);
312 spdlog::error(
"Cannot set main thread priority to realtime: {}. Thread will run at default priority.", strerror(error));
315 bool enable_measurement_file =
false;
316 cfg.lookupValue(
"modem.measurement_file.enabled", enable_measurement_file);
329 srsran::pdcp pdcp(
nullptr,
"PDCP");
330 srsran::rlc rlc(
"RLC");
331 srsran::timer_handler timers;
337 rlc.init(&pdcp, &rrc, &timers, 0 );
338 pdcp.init(&rlc, &rrc, &gw);
340 auto srs_level = srslog::basic_levels::none;
342 case 0: srs_level = srslog::basic_levels::debug;
break;
343 case 1: srs_level = srslog::basic_levels::info;
break;
344 case 2: srs_level = srslog::basic_levels::warning;
break;
345 case 3: srs_level = srslog::basic_levels::error;
break;
346 case 4: srs_level = srslog::basic_levels::none;
break;
350 auto& mac_log = srslog::fetch_basic_logger(
"MAC",
false);
351 mac_log.set_level(srs_level);
352 auto& phy_log = srslog::fetch_basic_logger(
"PHY",
false);
353 phy_log.set_level(srs_level);
354 auto& rlc_log = srslog::fetch_basic_logger(
"RLC",
false);
355 rlc_log.set_level(srs_level);
356 auto& asn1_log = srslog::fetch_basic_logger(
"ASN1",
false);
357 asn1_log.set_level(srs_level);
363 std::string uri =
"http://0.0.0.0:3010/modem-api/";
364 cfg.lookupValue(
"modem.restful_api.uri", uri);
365 spdlog::info(
"Starting RESTful API handler at {}", uri);
370 if (!cas_processor.
init()) {
371 spdlog::error(
"Failed to create CAS processor. Exiting.");
375 std::vector<MbsfnFrameProcessor*> mbsfn_processors;
376 for (
int i = 0; i < thread_cnt; i++) {
379 spdlog::error(
"Failed to create MBSFN processor. Exiting.");
382 mbsfn_processors.push_back(p);
390 uint32_t measurement_interval = 5;
391 cfg.lookupValue(
"modem.measurement_file.interval_secs", measurement_interval);
392 measurement_interval *= 1000;
429 unsigned new_srate = srsran_sampling_freq_hz(
cas_nof_prb);
430 spdlog::info(
"Setting sample rate {} Mhz for {} PRB / {} Mhz channel width", new_srate/1000000.0, phy.
nr_prb(),
440 spdlog::debug(
"Synchronizing subframe");
450 unsigned max_frames = 200;
451 bool sfn_sync =
false;
452 while (!sfn_sync && max_frames-- > 0) {
456 if (max_frames == 0 && !sfn_sync) {
458 spdlog::warn(
"Synchronization failed. Going back to search state.");
465 spdlog::info(
"Decoded MIB at target sample rate, TTI is {}. Subframe synchronized.", phy.
tti());
470 for (
int i = 0; i < thread_cnt; i++) {
471 mbsfn_processors[i]->unlock();
488 tti = (tti + 1) % 10240;
489 unsigned sfn = tti / 10;
494 spdlog::debug(
"sending tti {} to regular processor", tti);
495 pool.push([ObjectPtr = &cas_processor, tti, &rest_handler] {
496 if (ObjectPtr->process(tti)) {
498 rest_handler.add_cinr_value(ObjectPtr->cinr_db());
512 spdlog::info(
"Setting sample rate {} Mhz for MBSFN with {} PRB / {} Mhz channel width", new_srate/1000000.0,
mbsfn_nof_prb,
525 spdlog::info(
"Synchronizing subframe after PRB extension");
542 spdlog::debug(
"sending tti {} to mbsfn proc {}", tti, mb_idx);
546 if (!
restart && phy.
get_next_frame(mbsfn_processors[mb_idx]->get_rx_buffer_and_lock(), mbsfn_processors[mb_idx]->rx_buffer_size())) {
549 if (!mbsfn_processors[mb_idx]->mbsfn_configured()) {
550 srsran_scs_t scs = SRSRAN_SCS_15KHZ;
556 auto cell = phy.
cell();
557 cell.nof_prb = cell.mbsfn_prb;
558 mbsfn_processors[mb_idx]->set_cell(cell);
559 mbsfn_processors[mb_idx]->configure_mbsfn(phy.
mbsfn_area_id(), scs);
561 pool.push([ObjectPtr = mbsfn_processors[mb_idx], tti] {
562 ObjectPtr->process(tti);
567 mbsfn_processors[mb_idx]->unlock();
571 spdlog::warn(
"Synchronization lost while processing. Going back to searching state.");
582 mb_idx =
static_cast<int>((mb_idx + 1) % thread_cnt);
586 if (tick%measurement_interval == 0) {
589 std::vector<std::string> cols;
591 spdlog::info(
"CINR {:.2f} dB", rest_handler.
cinr_db() );
592 cols.push_back(std::to_string(rest_handler.
cinr_db()));
594 spdlog::info(
"PDSCH: MCS {}, BLER {}, BER {}",
598 cols.push_back(std::to_string(rest_handler.
_pdsch.
mcs));
600 cols.push_back(std::to_string(rest_handler.
_pdsch.
ber));
602 spdlog::info(
"MCCH: MCS {}, BLER {}, BER {}",
607 cols.push_back(std::to_string(rest_handler.
_mcch.
mcs));
608 cols.push_back(std::to_string(((rest_handler.
_mcch.
errors * 1.0) / (rest_handler.
_mcch.
total * 1.0))));
609 cols.push_back(std::to_string(rest_handler.
_mcch.
ber));
613 std::for_each(std::begin(mch_info), std::end(mch_info), [&cols, &mch_idx, &rest_handler](
Phy::mch_info_t const& mch) {
614 spdlog::info(
"MCH {}: MCS {}, BLER {}, BER {}",
617 (rest_handler.
_mch[mch_idx].errors * 1.0) / (rest_handler.
_mch[mch_idx].total * 1.0),
618 rest_handler.
_mch[mch_idx].ber);
619 cols.push_back(std::to_string(mch_idx));
620 cols.push_back(std::to_string(mch.
mcs));
621 cols.push_back(std::to_string((rest_handler.
_mch[mch_idx].errors * 1.0) / (rest_handler.
_mch[mch_idx].total * 1.0)));
622 cols.push_back(std::to_string(rest_handler.
_mch[mch_idx].ber));
626 spdlog::info(
" MTCH {}: LCID {}, TMGI 0x{}, {}",
635 spdlog::info(
"-----");
636 if (enable_measurement_file) {
645 for (
int i = 0; i < thread_cnt; i++) {
646 delete( mbsfn_processors[i] );
Frame processor for CAS subframes.
cf_t ** rx_buffer()
Get a handle of the signal buffer to store samples for processing in.
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.
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.
void reset()
Clear configuration values.
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)
float cinr_db()
Current CINR value.
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)
Initializes the SDR interface and creates a ring buffer according to the params from Cfg.
void disableSampleFileWriting()
If sample file creation is enabled, writing samples stops after this call.
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.