5G-MAG Reference Tools - MBMS Modem
Phy.cpp
Go to the documentation of this file.
1 // 5G-MAG Reference Tools
2 // MBMS Modem Process
3 //
4 // Copyright (C) 2021 Klaus Kühnhammer (Österreichische Rundfunksender GmbH & Co KG)
5 //
6 // This program is free software: you can redistribute it and/or modify
7 // it under the terms of the GNU Affero General Public License as published by
8 // the Free Software Foundation, either version 3 of the License, or
9 // (at your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 // GNU Affero General Public License for more details.
15 //
16 // You should have received a copy of the GNU Affero General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19 
20 #include "Phy.h"
21 
22 #include <utility>
23 #include <iomanip>
24 
25 #include "srsran/interfaces/rrc_interface_types.h"
26 #include "srsran/asn1/rrc_utils.h"
27 #include "spdlog/spdlog.h"
28 
29 static auto receive_callback(void* obj, cf_t* data[SRSRAN_MAX_CHANNELS], // NOLINT
30  uint32_t nsamples, srsran_timestamp_t* rx_time)
31  -> int {
32  return (static_cast<Phy*>(obj))->_sample_cb(data, nsamples, rx_time); // NOLINT
33 }
34 
35 const uint32_t kMaxBufferSamples = 2 * 15360;
36 const uint32_t kMaxSfn = 1024;
37 const uint32_t kSfnOffset = 4;
38 const uint32_t kSubframesPerFrame = 10;
39 
40 const uint32_t kMaxCellsToDiscover = 3;
41 
42 const uint32_t kMaxScannedFrames = 10;
43 const uint32_t kMaxValidFrames = 4;
44 
45 const uint32_t kMaxFramesTimeout = 80;
46 
47 Phy::Phy(const libconfig::Config& cfg, get_samples_t cb, uint8_t cs_nof_prb,
48  int8_t override_nof_prb, uint8_t rx_channels)
49  : _cfg(cfg),
50  _sample_cb(std::move(std::move(cb))),
51  _cs_nof_prb(cs_nof_prb),
52  _override_nof_prb(override_nof_prb),
53  _rx_channels(rx_channels) {
55  _mib_buffer[0] = static_cast<cf_t*>(malloc(_buffer_max_samples * sizeof(cf_t))); // NOLINT
56  _mib_buffer[1] = static_cast<cf_t*>(malloc(_buffer_max_samples * sizeof(cf_t))); // NOLINT
57 }
58 
60  srsran_ue_sync_free(&_ue_sync);
61  free(_mib_buffer[0]); // NOLINT
62 }
63 
64 auto Phy::synchronize_subframe() -> bool {
65 
66  int ret = srsran_ue_sync_zerocopy(&_ue_sync, _mib_buffer, _buffer_max_samples); // NOLINT
67  if (ret < 0) {
68  spdlog::error("SYNC: Error calling ue_sync_get_buffer.\n");
69  return false;
70  }
71 
72  if (ret == 1) {
73  std::array<uint8_t, SRSRAN_BCH_PAYLOAD_LEN> bch_payload = {};
74  if (srsran_ue_sync_get_sfidx(&_ue_sync) == 0) {
75  int sfn_offset = 0;
76  int n =
77  srsran_ue_mib_decode(&_mib, bch_payload.data(), nullptr, &sfn_offset);
78  if (n == 1) {
79  uint32_t sfn = 0;
80  if (_cell.mbms_dedicated) {
81  srsran_pbch_mib_mbms_unpack(bch_payload.data(), &_cell, &sfn, nullptr,
82  _override_nof_prb);
83  sfn = (sfn + sfn_offset * kSfnOffset) % kMaxSfn;
84  } else {
85  srsran_pbch_mib_unpack(bch_payload.data(), &_cell, &sfn);
86  sfn = (sfn + sfn_offset) % kMaxSfn;
87  }
88  _tti = sfn * kSubframesPerFrame;
89  return true;
90  }
91  }
92  }
93  return false;
94 }
95 
96 auto Phy::cell_search() -> bool {
97  std::array<srsran_ue_cellsearch_result_t, kMaxCellsToDiscover> found_cells = {0};
98 
99  uint32_t max_peak_cell = 0;
100  int ret = srsran_ue_cellsearch_scan(&_cell_search, found_cells.data(), &max_peak_cell);
101  if (ret < 0) {
102  spdlog::error("Phy: Error decoding MIB: Error searching PSS\n");
103  return false;
104  }
105  if (ret == 0) {
106  spdlog::error("Phy: Could not find any cell in this frequency\n");
107  return false;
108  }
109 
110  srsran_cell_t new_cell = {};
111  new_cell.id = found_cells.at(max_peak_cell).cell_id;
112  new_cell.cp = found_cells.at(max_peak_cell).cp;
113  new_cell.frame_type = found_cells.at(max_peak_cell).frame_type;
114  float cfo = found_cells.at(max_peak_cell).cfo;
115 
116  spdlog::info("Phy: PSS/SSS detected: Mode {}, PCI {}, CFO {} KHz, CP {}",
117  new_cell.frame_type != 0U ? "TDD" : "FDD", new_cell.id,
118  cfo / 1000, srsran_cp_string(new_cell.cp));
119 
120 
121 
122  std::array<uint8_t, SRSRAN_BCH_PAYLOAD_LEN> bch_payload = {};
123  /* Find and decode MIB */
124  int sfn_offset = 0;
125 
126  // Try to decode MIB-MBMS
127  new_cell.mbms_dedicated = true;
128  if (srsran_ue_mib_sync_set_cell_prb(&_mib_sync, new_cell, _cs_nof_prb) != 0) {
129  spdlog::error("Phy: Error setting UE MIB sync cell");
130  return false;
131  }
132  srsran_ue_sync_reset(&_mib_sync.ue_sync);
133  ret = srsran_ue_mib_sync_decode_prb(&_mib_sync, kMaxFramesTimeout, bch_payload.data(), &new_cell.nof_ports, &sfn_offset, _cs_nof_prb);
134 
135  if (!ret) { // MIB-MBMS failed, try to decode regular MIB
136  init();
137  new_cell.mbms_dedicated = false;
138  if (srsran_ue_mib_sync_set_cell_prb(&_mib_sync, new_cell, _cs_nof_prb) != 0) {
139  spdlog::error("Phy: Error setting UE MIB sync cell");
140  return false;
141  }
142  srsran_ue_sync_reset(&_mib_sync.ue_sync);
143  ret = srsran_ue_mib_sync_decode_prb(&_mib_sync, kMaxFramesTimeout, bch_payload.data(), &new_cell.nof_ports, &sfn_offset, _cs_nof_prb);
144  }
145 
146  if (ret == 1) {
147  uint32_t sfn = 0;
148 
149  if (new_cell.mbms_dedicated) {
150  srsran_pbch_mib_mbms_unpack(bch_payload.data(), &new_cell, &sfn, nullptr,
151  _override_nof_prb);
152  } else {
153  srsran_pbch_mib_unpack(bch_payload.data(), &new_cell, &sfn);
154  }
155  sfn = (sfn + sfn_offset) % 1024;
156 
157  spdlog::info(
158  "Phy: MIB Decoded. {} cell, Mode {}, PCI {}, PRB {}, Ports {}, CFO {} KHz, SFN "
159  "{}, sfn_offset {}\n",
160  new_cell.mbms_dedicated ? "MBMS dedicated" : "MBMS/Unicast mixed",
161  new_cell.frame_type != 0u ? "TDD" : "FDD", new_cell.id,
162  new_cell.nof_prb, new_cell.nof_ports, cfo / 1000, sfn, sfn_offset);
163 
164  if (!srsran_cell_isvalid(&new_cell)) {
165  spdlog::error("SYNC: Detected invalid cell.\n");
166  return false;
167  }
168 
169  _cell = new_cell;
170  _cell.mbsfn_prb = _cell.nof_prb;
171 
172  if (srsran_ue_sync_set_cell(&_ue_sync, cell()) != 0) {
173  spdlog::error("Phy: failed to set cell.\n");
174  return false;
175  }
176  if (srsran_ue_mib_set_cell(&_mib, cell()) != 0) {
177  spdlog::error("Phy: Error setting UE MIB cell");
178  return false;
179  }
180 
181  return true;
182  }
183 
184  spdlog::error("Phy: failed to receive MIB\n");
185  return false;
186 }
187 
188 auto Phy::set_cell() -> void {
189  if (srsran_ue_sync_set_cell(&_ue_sync, cell()) != 0) {
190  spdlog::error("Phy: failed to set cell.\n");
191  }
192  if (srsran_ue_mib_set_cell(&_mib, cell()) != 0) {
193  spdlog::error("Phy: Error setting UE MIB cell");
194  }
195 }
196 
197 auto Phy::init() -> bool {
198  if (srsran_ue_cellsearch_init_multi_prb_cp(&_cell_search, kMaxScannedFrames, receive_callback, _rx_channels,
199  this, _cs_nof_prb, _search_extended_cp) != 0) {
200  spdlog::error("Phy: error while initiating UE cell search\n");
201  return false;
202  }
203  srsran_ue_cellsearch_set_nof_valid_frames(&_cell_search, kMaxValidFrames);
204 
205  if (srsran_ue_sync_init_multi(&_ue_sync, MAX_PRB, false, receive_callback, _rx_channels,
206  this) != 0) {
207  spdlog::error("Cannot init ue_sync");
208  return false;
209  }
210 
211  if (srsran_ue_mib_sync_init_multi_prb(&_mib_sync, receive_callback, _rx_channels, this,
212  _cs_nof_prb) != 0) {
213  spdlog::error("Cannot init ue_mib_sync");
214  return false;
215  }
216 
217  if (srsran_ue_mib_init(&_mib, _mib_buffer[0], MAX_PRB) != 0) {
218  spdlog::error("Cannot init ue_mib");
219  return false;
220  }
221 
222  return true;
223 }
224 
225 auto Phy::get_next_frame(cf_t** buffer, uint32_t size) -> bool {
226  return 1 == srsran_ue_sync_zerocopy(&_ue_sync, buffer, size);
227 }
228 
229 void Phy::set_mch_scheduling_info(const srsran::sib13_t& sib13) {
230  if (sib13.nof_mbsfn_area_info > 1) {
231  spdlog::warn("SIB13 has {} MBSFN area info elements - only 1 supported", sib13.nof_mbsfn_area_info);
232  }
233 
234  if (sib13.mbsfn_area_info_list[0].pmch_bandwidth != 0) {
235  _cell.mbsfn_prb = sib13.mbsfn_area_info_list[0].pmch_bandwidth;
236  }
237 
238  if (sib13.nof_mbsfn_area_info > 0) {
239  _sib13 = sib13;
240 
241  bzero(&_mcch_table[0], sizeof(uint8_t) * 10);
242  if (sib13.mbsfn_area_info_list[0].mcch_cfg.sf_alloc_info_is_r16) {
243  generate_mcch_table_r16(
244  &_mcch_table[0],
245  static_cast<uint32_t>(
246  sib13.mbsfn_area_info_list[0].mcch_cfg.sf_alloc_info));
247  } else {
248  generate_mcch_table(
249  &_mcch_table[0],
250  static_cast<uint32_t>(
251  sib13.mbsfn_area_info_list[0].mcch_cfg.sf_alloc_info));
252  }
253 
254  std::stringstream ss;
255  ss << "|";
256  for (unsigned char j : _mcch_table) {
257  ss << static_cast<int>(j) << "|";
258  }
259  spdlog::debug("MCCH table: {}", ss.str());
260 
261  _mcch_configured = true;
262  }
263 }
264 
265 void Phy::set_mbsfn_config(const srsran::mcch_msg_t& mcch) {
266  _mcch = mcch;
267  _mch_configured = true;
268 
269  _mch_info.clear();
270  for (uint32_t i = 0; i < _mcch.nof_pmch_info; i++) {
272  mch_info.mcs = _mcch.pmch_info_list[i].data_mcs;
273 
274  for (uint32_t j = 0; j < _mcch.pmch_info_list[i].nof_mbms_session_info; j++) {
275  mtch_info_t mtch_info;
276  mtch_info.lcid = _mcch.pmch_info_list[i].mbms_session_info_list[j].lc_ch_id;
277  char tmgi[20]; // NOLINT
278  /* acc to TS24.008 10.5.6.13:
279  * MCC 1,2,3: 901 -> 9, 0, 1
280  * MNC 3,1,2: 56 -> (F), 5, 6
281  * HEX 0x09F165
282  *
283  * -------------+-------------+---------
284  * MCC digit 2 | MCC digit 1 | Octet 6*
285  * -------------+-------------+---------
286  * MNC digit 3 | MCC digit 3 | Octet 7*
287  * -------------+-------------+---------
288  * MNC digit 2 | MNC digit 1 | Octet 8*
289  * -------------+-------------+---------
290  */
291  sprintf (tmgi, "%06x%02x%02x%02x",
292  _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.serviced_id[2] |
293  _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.serviced_id[1] << 8 |
294  _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.serviced_id[0] << 16 ,
295  _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.plmn_id.explicit_value.mcc[1] << 4 | mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.plmn_id.explicit_value.mcc[0],
296  ( _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.plmn_id.explicit_value.nof_mnc_digits == 2 ? 0xF : _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.plmn_id.explicit_value.mnc[2] ) << 4 | _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.plmn_id.explicit_value.mcc[2] ,
297  _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.plmn_id.explicit_value.mnc[1] << 4 | _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.plmn_id.explicit_value.mnc[0]
298  );
299  mtch_info.tmgi = tmgi;
300  mtch_info.dest = _dests[i][mtch_info.lcid];
301  mch_info.mtchs.push_back(mtch_info);
302  }
303 
304  _mch_info.push_back(mch_info);
305  }
306 }
307 
308 auto Phy::is_cas_subframe(unsigned tti) -> bool
309 {
310  if (_cell.mbms_dedicated) {
311  // This is subframe 0 in a radio frame divisible by 4, and hence a CAS frame.
312  return tti%40 == 0;
313  } else {
314  unsigned sfn = tti / 10;
315  return (tti%10 == 0 || tti%10 == 5);
316  }
317 }
318 
319 auto Phy::is_mbsfn_subframe(unsigned tti) -> bool
320 {
321  if (_cell.mbms_dedicated) {
322  // This is subframe 0 in a radio frame divisible by 4, and hence a CAS frame.
323  return !is_cas_subframe(tti);
324  } else {
325  return !is_cas_subframe(tti) &&
326  (tti%10 == 1 || tti%10 == 2 || tti%10 == 3 || tti%10 == 6 || tti%10 == 7 || tti%10 == 8);
327  }
328 }
329 auto Phy::mbsfn_config_for_tti(uint32_t tti, unsigned& area)
330  -> srsran_mbsfn_cfg_t {
331  srsran_mbsfn_cfg_t cfg;
332  cfg.enable = false;
333  cfg.is_mcch = false;
334 
335  if (!_mcch_configured) {
336  {
337  return cfg;
338  }
339  }
340 
341  uint32_t sfn = tti / 10;
342  uint8_t sf = tti % 10;
343 
344  srsran::mbsfn_area_info_t& area_info = _sib13.mbsfn_area_info_list[0];
345 
346  cfg.mbsfn_area_id = area_info.mbsfn_area_id;
347  cfg.non_mbsfn_region_length = enum_to_number(area_info.non_mbsfn_region_len);
348 
349  if (sfn % enum_to_number(area_info.mcch_cfg.mcch_repeat_period) == area_info.mcch_cfg.mcch_offset &&
350  _mcch_table[sf] == 1) {
351  // MCCH
352  if (_decode_mcch) {
353  cfg.mbsfn_mcs = enum_to_number(area_info.mcch_cfg.sig_mcs);
354  cfg.enable = true;
355  cfg.is_mcch = true;
356  }
357  } else if (sfn % enum_to_number(area_info.mcch_cfg.mcch_repeat_period) == area_info.mcch_cfg.mcch_offset &&
358  sf == 1) {
359  cfg.mbsfn_mcs = enum_to_number(area_info.mcch_cfg.sig_mcs);
360  cfg.enable = true;
361  cfg.is_mcch = false;
362  } else {
363  if (_mch_configured) {
364  cfg.mbsfn_area_id = area_info.mbsfn_area_id;
365 
366  for (uint32_t i = 0; i < _mcch.nof_pmch_info; i++) {
367  unsigned fn_in_scheduling_period = sfn % enum_to_number(_mcch.pmch_info_list[i].mch_sched_period);
368  unsigned sf_idx = fn_in_scheduling_period * 10 + sf
369  - (fn_in_scheduling_period / 4) // minus 1 CAS SF per 4 SFNs
370  - 1; // minus 1 MCCH SF per scheduling period;
371 
372  spdlog::debug("i {}, tti {}, fn_in_ {}, sf_idx {}", i, tti, fn_in_scheduling_period, sf_idx);
373 
374  if (sf_idx <= _mcch.pmch_info_list[i].sf_alloc_end) {
375  area = i;
376  if ((i == 0 && fn_in_scheduling_period == 0 && sf == 1) ||
377  (i > 0 && _mcch.pmch_info_list[i-1].sf_alloc_end + 1 == sf_idx)) {
378  spdlog::debug("assigning sig_mcs {}, mch_idx is {}", area_info.mcch_cfg.sig_mcs, area);
379  cfg.mbsfn_mcs = enum_to_number(area_info.mcch_cfg.sig_mcs);
380  } else {
381  spdlog::debug("assigning pmch_mcs {}, mch_idx is {}", _mcch.pmch_info_list[i].data_mcs, area);
382  cfg.mbsfn_mcs = _mcch.pmch_info_list[i].data_mcs;
383  }
384  cfg.enable = true;
385  break;
386  }
387  }
388  }
389  }
390  return cfg;
391 }
const uint32_t kMaxBufferSamples
Definition: Phy.cpp:35
const uint32_t kMaxCellsToDiscover
Definition: Phy.cpp:40
const uint32_t kMaxSfn
Definition: Phy.cpp:36
const uint32_t kSfnOffset
Definition: Phy.cpp:37
const uint32_t kMaxScannedFrames
Definition: Phy.cpp:42
const uint32_t kMaxValidFrames
Definition: Phy.cpp:43
const uint32_t kSubframesPerFrame
Definition: Phy.cpp:38
const uint32_t kMaxFramesTimeout
Definition: Phy.cpp:45
static auto receive_callback(void *obj, cf_t *data[SRSRAN_MAX_CHANNELS], uint32_t nsamples, srsran_timestamp_t *rx_time) -> int
Definition: Phy.cpp:29
constexpr unsigned int MAX_PRB
Definition: Phy.h:35
The PHY component.
Definition: Phy.h:42
srsran_mbsfn_cfg_t mbsfn_config_for_tti(uint32_t tti, unsigned &area)
Returns the MBSFN configuration (MCS, etc) for the subframe with the passed TTI.
Definition: Phy.cpp:329
std::function< int(cf_t *data[SRSRAN_MAX_CHANNELS], uint32_t nsamples, srsran_timestamp_t *rx_time)> get_samples_t
Definition of the callback function used to fetch samples from the SDR.
Definition: Phy.h:47
std::map< uint32_t, std::map< int, std::string > > _dests
Definition: Phy.h:317
const std::vector< mch_info_t > & mch_info()
Definition: Phy.h:253
void set_mch_scheduling_info(const srsran::sib13_t &sib13)
Set the values received in SIB13.
Definition: Phy.cpp:229
std::vector< mch_info_t > _mch_info
Definition: Phy.h:315
cf_t * _mib_buffer[SRSRAN_MAX_CHANNELS]
Definition: Phy.h:302
bool get_next_frame(cf_t **buffer, uint32_t size)
Get the sample data for the next subframe.
Definition: Phy.cpp:225
bool synchronize_subframe()
Synchronizes PSS/SSS and tries to deocode the MIB.
Definition: Phy.cpp:64
bool _mcch_configured
Definition: Phy.h:307
uint8_t _mcch_table[10]
Definition: Phy.h:306
void set_cell()
Definition: Phy.cpp:188
bool is_cas_subframe(unsigned tti)
Definition: Phy.cpp:308
srsran::mcch_msg_t _mcch
Definition: Phy.h:309
srsran_ue_sync_t _ue_sync
Definition: Phy.h:294
srsran_cell_t _cell
Definition: Phy.h:298
bool _mch_configured
Definition: Phy.h:311
bool init()
Initialize the underlying components.
Definition: Phy.cpp:197
bool cell_search()
Search for a cell.
Definition: Phy.cpp:96
virtual ~Phy()
Default destructor.
Definition: Phy.cpp:59
uint32_t _buffer_max_samples
Definition: Phy.h:303
srsran::sib13_t _sib13
Definition: Phy.h:308
srsran::mcch_msg_t & mcch()
Definition: Phy.h:287
bool is_mbsfn_subframe(unsigned tti)
Definition: Phy.cpp:319
void set_mbsfn_config(const srsran::mcch_msg_t &mcch)
Set MBSFN configuration values.
Definition: Phy.cpp:265
Phy(const libconfig::Config &cfg, get_samples_t cb, uint8_t cs_nof_prb, int8_t override_nof_prb, uint8_t rx_channels)
Default constructor.
Definition: Phy.cpp:47
static Config cfg
Global configuration object.
Definition: main.cpp:172
std::string dest
Definition: Phy.h:245
std::string tmgi
Definition: Phy.h:244