OBECA Receive Process
Phy.cpp
1 // OBECA - Open Broadcast Edge Cache Appliance
2 // Receive 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 
24 #include "spdlog/spdlog.h"
25 #include "srslte/asn1/rrc_asn1_utils.h"
26 
27 static auto receive_callback(void* obj, cf_t* data[SRSLTE_MAX_CHANNELS], // NOLINT
28  uint32_t nsamples, srslte_timestamp_t* rx_time)
29  -> int {
30  return (static_cast<Phy*>(obj))->_sample_cb(data[0], nsamples, rx_time); // NOLINT
31 }
32 
33 const uint32_t kMaxBufferSamples = 2 * 15360;
34 const uint32_t kMaxSfn = 1024;
35 const uint32_t kSfnOffset = 4;
36 const uint32_t kSubframesPerFrame = 10;
37 
38 const uint32_t kMaxCellsToDiscover = 3;
39 
40 Phy::Phy(const libconfig::Config& cfg, get_samples_t cb, uint8_t cs_nof_prb,
41  int8_t override_nof_prb)
42  : _cfg(cfg),
43  _sample_cb(std::move(std::move(cb))),
44  _cs_nof_prb(cs_nof_prb),
45  _override_nof_prb(override_nof_prb) {
46  _buffer_max_samples = kMaxBufferSamples;
47  _mib_buffer[0] = static_cast<cf_t*>(malloc(_buffer_max_samples * sizeof(cf_t))); // NOLINT
48 }
49 
51  srslte_ue_sync_free(&_ue_sync);
52  free(_mib_buffer[0]); // NOLINT
53 }
54 
55 auto Phy::synchronize_subframe() -> bool {
56 
57  int ret = srslte_ue_sync_zerocopy(&_ue_sync, _mib_buffer, _buffer_max_samples); // NOLINT
58  if (ret < 0) {
59  spdlog::error("SYNC: Error calling ue_sync_get_buffer.\n");
60  return false;
61  }
62 
63  if (ret == 1) {
64  std::array<uint8_t, SRSLTE_BCH_PAYLOAD_LEN> bch_payload = {};
65  if (srslte_ue_sync_get_sfidx(&_ue_sync) == 0) {
66  int sfn_offset = 0;
67  int n =
68  srslte_ue_mib_decode(&_mib, bch_payload.data(), nullptr, &sfn_offset);
69  if (n == 1) {
70  uint32_t sfn = 0;
71  srslte_pbch_mib_mbms_unpack(bch_payload.data(), &_cell, &sfn, nullptr,
72  _override_nof_prb);
73  sfn = (sfn + sfn_offset * kSfnOffset) % kMaxSfn;
74  _tti = sfn * kSubframesPerFrame;
75  return true;
76  }
77  }
78  }
79  return false;
80 }
81 
82 auto Phy::cell_search() -> bool {
83  std::array<srslte_ue_cellsearch_result_t, kMaxCellsToDiscover> found_cells = {0};
84 
85  uint32_t max_peak_cell = 0;
86  int ret = srslte_ue_cellsearch_scan(&_cell_search, found_cells.data(), &max_peak_cell);
87  if (ret < 0) {
88  spdlog::error("Phy: Error decoding MIB: Error searching PSS\n");
89  return false;
90  }
91  if (ret == 0) {
92  spdlog::error("Phy: Could not find any cell in this frequency\n");
93  return false;
94  }
95 
96  srslte_cell_t new_cell = {};
97  new_cell.id = found_cells.at(max_peak_cell).cell_id;
98  new_cell.cp = found_cells.at(max_peak_cell).cp;
99  new_cell.frame_type = found_cells.at(max_peak_cell).frame_type;
100  new_cell.mbms_dedicated = true;
101  float cfo = found_cells.at(max_peak_cell).cfo;
102 
103  spdlog::info("Phy: PSS/SSS detected: Mode {}, PCI {}, CFO {} KHz, CP {}",
104  new_cell.frame_type != 0U ? "TDD" : "FDD", new_cell.id,
105  cfo / 1000, srslte_cp_string(new_cell.cp));
106 
107  if (srslte_ue_mib_sync_set_cell_prb(&_mib_sync, new_cell, _cs_nof_prb) != 0) {
108  spdlog::error("Phy: Error setting UE MIB sync cell");
109  return false;
110  }
111 
112  srslte_ue_sync_reset(&_mib_sync.ue_sync);
113 
114  std::array<uint8_t, SRSLTE_BCH_PAYLOAD_LEN> bch_payload = {};
115  /* Find and decode MIB */
116  int sfn_offset = 0;
117  ret = srslte_ue_mib_sync_decode_prb(&_mib_sync, 40, bch_payload.data(), &new_cell.nof_ports, &sfn_offset, _cs_nof_prb);
118  if (ret == 1) {
119  uint32_t sfn = 0;
120  srslte_pbch_mib_mbms_unpack(bch_payload.data(), &new_cell, &sfn, nullptr,
121  _override_nof_prb);
122 
123  spdlog::info(
124  "Phy: MIB Decoded. Mode {}, PCI {}, PRB {}, Ports {}, CFO {} KHz, SFN "
125  "{}\n",
126  new_cell.frame_type != 0u ? "TDD" : "FDD", new_cell.id,
127  new_cell.nof_prb, new_cell.nof_ports, cfo / 1000, sfn);
128 
129  if (!srslte_cell_isvalid(&new_cell)) {
130  spdlog::error("SYNC: Detected invalid cell.\n");
131  return false;
132  }
133 
134  _cell = new_cell;
135  _cell.mbsfn_prb = _cell.nof_prb;
136 
137  if (srslte_ue_sync_set_cell(&_ue_sync, cell()) != 0) {
138  spdlog::error("Phy: failed to set cell.\n");
139  return false;
140  }
141  if (srslte_ue_mib_set_cell(&_mib, cell()) != 0) {
142  spdlog::error("Phy: Error setting UE MIB cell");
143  return false;
144  }
145 
146  return true;
147  }
148 
149  spdlog::error("Phy: failed to receive MIB\n");
150  return false;
151 }
152 
153 auto Phy::set_cell() -> void {
154  if (srslte_ue_sync_set_cell(&_ue_sync, cell()) != 0) {
155  spdlog::error("Phy: failed to set cell.\n");
156  }
157  if (srslte_ue_mib_set_cell(&_mib, cell()) != 0) {
158  spdlog::error("Phy: Error setting UE MIB cell");
159  }
160 }
161 
162 auto Phy::init() -> bool {
163  if (srslte_ue_cellsearch_init_multi_prb_cp(&_cell_search, 8, receive_callback, 1,
164  this, _cs_nof_prb, true) != 0) {
165  spdlog::error("Phy: error while initiating UE cell search\n");
166  return false;
167  }
168  srslte_ue_cellsearch_set_nof_valid_frames(&_cell_search, 4);
169 
170  if (srslte_ue_sync_init_multi(&_ue_sync, MAX_PRB, false, receive_callback, 1,
171  this) != 0) {
172  spdlog::error("Cannot init ue_sync");
173  return false;
174  }
175 
176  if (srslte_ue_mib_sync_init_multi_prb(&_mib_sync, receive_callback, 1, this,
177  _cs_nof_prb) != 0) {
178  spdlog::error("Cannot init ue_mib_sync");
179  return false;
180  }
181 
182  if (srslte_ue_mib_init(&_mib, _mib_buffer[0], 50) != 0) {
183  spdlog::error("Cannot init ue_mib");
184  return false;
185  }
186 
187  return true;
188 }
189 
190 auto Phy::get_next_frame(cf_t** buffer, uint32_t size) -> bool {
191  return 1 == srslte_ue_sync_zerocopy(&_ue_sync, buffer, size);
192 }
193 
194 void Phy::set_mch_scheduling_info(const srslte::sib13_t& sib13) {
195  if (sib13.nof_mbsfn_area_info > 1) {
196  spdlog::warn("SIB13 has {} MBSFN area info elements - only 1 supported", sib13.nof_mbsfn_area_info);
197  }
198 
199  if (sib13.mbsfn_area_info_list[0].pmch_bandwidth != 0) {
200  _cell.mbsfn_prb = sib13.mbsfn_area_info_list[0].pmch_bandwidth;
201  }
202 
203  if (sib13.nof_mbsfn_area_info > 0) {
204  _sib13 = sib13;
205 
206  bzero(&_mcch_table[0], sizeof(uint8_t) * 10);
207  if (sib13.mbsfn_area_info_list[0].mcch_cfg.sf_alloc_info_is_r16) {
208  generate_mcch_table_r16(
209  &_mcch_table[0],
210  static_cast<uint32_t>(
211  sib13.mbsfn_area_info_list[0].mcch_cfg.sf_alloc_info));
212  } else {
213  generate_mcch_table(
214  &_mcch_table[0],
215  static_cast<uint32_t>(
216  sib13.mbsfn_area_info_list[0].mcch_cfg.sf_alloc_info));
217  }
218 
219  std::stringstream ss;
220  ss << "|";
221  for (unsigned char j : _mcch_table) {
222  ss << static_cast<int>(j) << "|";
223  }
224  spdlog::debug("MCCH table: {}", ss.str());
225 
226  _mcch_configured = true;
227  }
228 }
229 
230 void Phy::set_mbsfn_config(const srslte::mcch_msg_t& mcch) {
231  _mcch = mcch;
232  _mch_configured = true;
233 
234  _mch_info.clear();
235  for (uint32_t i = 0; i < _mcch.nof_pmch_info; i++) {
236  mch_info_t mch_info;
237  mch_info.mcs = _mcch.pmch_info_list[i].data_mcs;
238 
239  for (uint32_t j = 0; j < _mcch.pmch_info_list[i].nof_mbms_session_info; j++) {
240  mtch_info_t mtch_info;
241  mtch_info.lcid = _mcch.pmch_info_list[i].mbms_session_info_list[j].lc_ch_id;
242  std::string tmgi;
243  for (int k = 1; k < 3; k++) {
244  tmgi += std::to_string(_mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.serviced_id[k]);
245  }
246  tmgi += _mcch.pmch_info_list[i].mbms_session_info_list[j].tmgi.plmn_id.explicit_value.to_string();
247  mtch_info.tmgi = tmgi;
248  mtch_info.dest = _dests[mch_info.mcs][mtch_info.lcid];
249  mch_info.mtchs.push_back(mtch_info);
250  }
251 
252  _mch_info.push_back(mch_info);
253  }
254 }
255 
256 auto Phy::mbsfn_config_for_tti(uint32_t tti, unsigned& area)
257  -> srslte_mbsfn_cfg_t {
258  srslte_mbsfn_cfg_t cfg;
259  cfg.enable = false;
260  cfg.is_mcch = false;
261 
262  if (!_mcch_configured) {
263  {
264  return cfg;
265  }
266  }
267 
268  uint32_t sfn = tti / 10;
269  uint8_t sf = tti % 10;
270 
271  srslte::mbsfn_area_info_t& area_info = _sib13.mbsfn_area_info_list[0];
272 
273  cfg.mbsfn_area_id = area_info.mbsfn_area_id;
274  cfg.non_mbsfn_region_length = enum_to_number(area_info.non_mbsfn_region_len);
275 
276  if (sfn % enum_to_number(area_info.mcch_cfg.mcch_repeat_period) == area_info.mcch_cfg.mcch_offset &&
277  _mcch_table[sf] == 1) {
278  // MCCH
279  if (_decode_mcch) {
280  cfg.mbsfn_mcs = enum_to_number(area_info.mcch_cfg.sig_mcs);
281  cfg.enable = true;
282  cfg.is_mcch = true;
283  }
284  } else {
285  if (_mch_configured) {
286  cfg.mbsfn_area_id = area_info.mbsfn_area_id;
287 
288  for (uint32_t i = 0; i < _mcch.nof_pmch_info; i++) {
289  unsigned fn_in_scheduling_period = sfn % enum_to_number(_mcch.pmch_info_list[i].mch_sched_period);
290  unsigned sf_idx = fn_in_scheduling_period * 10 + sf - (fn_in_scheduling_period / 4) - 1;
291 
292  spdlog::debug("tti {}, fn_in_ {}, sf_idx {}", tti, fn_in_scheduling_period, sf_idx);
293 
294  if (sf_idx <= _mcch.pmch_info_list[i].sf_alloc_end) {
295  area = i;
296  if ((i == 0 && fn_in_scheduling_period == 0 && sf == 1) ||
297  (i > 0 && _mcch.pmch_info_list[i-1].sf_alloc_end + 1 == sf_idx)) {
298  spdlog::debug("assigning sig_mcs {}", area_info.mcch_cfg.sig_mcs);
299  cfg.mbsfn_mcs = enum_to_number(area_info.mcch_cfg.sig_mcs);
300  } else {
301  spdlog::debug("assigning pmch_mcs {}", area_info.mcch_cfg.sig_mcs);
302  cfg.mbsfn_mcs = _mcch.pmch_info_list[i].data_mcs;
303  }
304  cfg.enable = true;
305  break;
306  }
307  }
308  }
309  }
310  return cfg;
311 }
Phy::set_mch_scheduling_info
void set_mch_scheduling_info(const srslte::sib13_t &sib13)
Set the values received in SIB13.
Definition: Phy.cpp:194
Phy::~Phy
virtual ~Phy()
Default destructor.
Definition: Phy.cpp:50
Phy::mbsfn_config_for_tti
srslte_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:256
Phy
The PHY component.
Definition: Phy.h:43
Phy::mtch_info_t
Definition: Phy.h:158
Phy::get_samples_t
std::function< int(cf_t *data, uint32_t nsamples, srslte_timestamp_t *rx_time)> get_samples_t
Definition of the callback function used to fetch samples from the SDR.
Definition: Phy.h:48
Phy::get_next_frame
bool get_next_frame(cf_t **buffer, uint32_t size)
Get the sample data for the next subframe.
Definition: Phy.cpp:190
Phy::init
bool init()
Initialize the underlying components.
Definition: Phy.cpp:162
Phy::set_mbsfn_config
void set_mbsfn_config(const srslte::mcch_msg_t &mcch)
Set MBSFN configuration values.
Definition: Phy.cpp:230
Phy::synchronize_subframe
bool synchronize_subframe()
Synchronizes PSS/SSS and tries to deocode the MIB.
Definition: Phy.cpp:55
Phy::cell_search
bool cell_search()
Search for a cell.
Definition: Phy.cpp:82
Phy::mch_info_t
Definition: Phy.h:163
Phy::Phy
Phy(const libconfig::Config &cfg, get_samples_t cb, uint8_t cs_nof_prb, int8_t override_nof_prb)
Default constructor.
Definition: Phy.cpp:40