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