blob: 281716e951b65047c2c1aa9a664e4b89e025ed49 [file] [log] [blame]
Ericb7253c62022-11-28 19:21:08 +01001/*
2 * (C) 2022 by sysmocom s.f.m.c. GmbH <info@sysmocom.de>
3 * All Rights Reserved
4 *
5 * Author: Eric Wild <ewild@sysmocom.de>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU Affero General Public License as published by
9 * the Free Software Foundation; either version 3 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU Affero General Public License for more details.
16 *
17 * You should have received a copy of the GNU Affero General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 *
20 */
21
22#include "sigProcLib.h"
23#include "ms.h"
24#include <signalVector.h>
25#include <radioVector.h>
26#include <radioInterface.h>
27#include <grgsm_vitac/grgsm_vitac.h>
28
29extern "C" {
30#include <stdio.h>
31#include <stdlib.h>
32#include <stdint.h>
33#include <string.h>
34#include <getopt.h>
35#include <unistd.h>
36#include <signal.h>
37#include <errno.h>
38#include <time.h>
39#include <fenv.h>
40
41#include "sch.h"
42#include "convolve.h"
43#include "convert.h"
44
45#ifdef LSANDEBUG
46void __lsan_do_recoverable_leak_check();
47#endif
48}
49
50#include "ms_upper.h"
51
52namespace trxcon
53{
54extern "C" {
55#include <osmocom/core/fsm.h>
56#include <osmocom/core/msgb.h>
57#include <osmocom/core/talloc.h>
58#include <osmocom/core/signal.h>
59#include <osmocom/core/select.h>
60#include <osmocom/gsm/gsm_utils.h>
61
62#include <osmocom/core/logging.h>
63#include <osmocom/bb/trxcon/logging.h>
64
65#include <osmocom/bb/trxcon/trxcon.h>
66#include <osmocom/bb/trxcon/trxcon_fsm.h>
67#include <osmocom/bb/trxcon/phyif.h>
68#include <osmocom/bb/trxcon/l1ctl_server.h>
69}
70struct trxcon_inst *g_trxcon;
71// trx_instance *trxcon_instance; // local handle
72struct internal_q_tx_buf {
73 trxcon_phyif_burst_req r;
74 uint8_t buf[148];
75};
76using tx_queue_t = spsc_cond<8 * 1, internal_q_tx_buf, true, false>;
77using cmd_queue_t = spsc_cond<8 * 1, trxcon_phyif_cmd, true, false>;
78using cmdr_queue_t = spsc_cond<8 * 1, trxcon_phyif_rsp, false, false>;
79static tx_queue_t txq;
80static cmd_queue_t cmdq_to_phy;
81static cmdr_queue_t cmdq_from_phy;
82
83extern bool trxc_l1ctl_init(void *tallctx);
84
85} // namespace trxcon
86extern "C" void trxc_log_init(void *tallctx);
87
88#ifdef LOG
89#undef LOG
90#define LOG(...) upper_trx::dummy_log()
91#endif
92
93#define DBGLG(...) upper_trx::dummy_log()
94
95std::atomic<bool> g_exit_flag;
96
97void upper_trx::start_threads()
98{
99 thr_control = std::thread([this] {
100 set_name_aff_sched("upper_ctrl", 1, SCHED_RR, sched_get_priority_max(SCHED_RR));
101 while (!g_exit_flag) {
102 driveControl();
103 }
104 });
105 msleep(1);
106 thr_tx = std::thread([this] {
107 set_name_aff_sched("upper_tx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 1);
108 while (!g_exit_flag) {
109 driveTx();
110 }
111 });
112
113 // atomic ensures data is not written to q until loop reads
114 start_lower_ms();
115
116 set_name_aff_sched("upper_rx", 1, SCHED_FIFO, sched_get_priority_max(SCHED_RR) - 5);
117 while (!g_exit_flag) {
118 // set_upper_ready(true);
119 driveReceiveFIFO();
120 trxcon::osmo_select_main(1);
121
122 trxcon::trxcon_phyif_rsp r;
123 if (trxcon::cmdq_from_phy.spsc_pop(&r)) {
124 DBGLG() << "HAVE RESP:" << r.type << std::endl;
125 trxcon_phyif_handle_rsp(trxcon::g_trxcon, &r);
126 }
127 }
128
129#ifdef LSANDEBUG
130 std::thread([this] {
131 set_name_aff_sched("leakcheck", 1, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 10);
132
133 while (1) {
134 std::this_thread::sleep_for(std::chrono::seconds{ 5 });
135 __lsan_do_recoverable_leak_check();
136 }
137 }).detach();
138#endif
139}
140
141void upper_trx::start_lower_ms()
142{
143 ms_trx::start();
144}
145
146// signalvector is owning despite claiming not to, but we can pretend, too..
147static void static_free(void *wData){};
148static void *static_alloc(size_t newSize)
149{
150 return 0;
151};
152
153bool upper_trx::pullRadioVector(GSM::Time &wTime, int &RSSI, int &timingOffset)
154{
155 float pow, avg = 1.0;
156 const auto zero_pad_len = 40; // give the VA some runway for misaligned bursts
157 const auto workbuf_size = zero_pad_len + ONE_TS_BURST_LEN + zero_pad_len;
158 static complex workbuf[workbuf_size];
159
160 static signalVector sv(workbuf, zero_pad_len, ONE_TS_BURST_LEN, static_alloc, static_free);
161 one_burst e;
162 auto ss = reinterpret_cast<std::complex<float> *>(&workbuf[zero_pad_len]);
163 std::fill(workbuf, workbuf + workbuf_size, 0);
164 // assert(sv.begin() == &workbuf[40]);
165
166 while (!rxqueue.spsc_pop(&e)) {
167 rxqueue.spsc_prep_pop();
168 }
169
170 wTime = e.gsmts;
171
172 const auto is_sch = gsm_sch_check_ts(wTime.TN(), wTime.FN());
173 const auto is_fcch = gsm_fcch_check_ts(wTime.TN(), wTime.FN());
174
175 trxcon::trxcon_phyif_rtr_ind i = { static_cast<uint32_t>(wTime.FN()), static_cast<uint8_t>(wTime.TN()) };
176 trxcon::trxcon_phyif_rtr_rsp r = {};
177 trxcon_phyif_handle_rtr_ind(trxcon::g_trxcon, &i, &r);
178 if (!(r.flags & TRXCON_PHYIF_RTR_F_ACTIVE))
179 return false;
180
181 if (is_fcch) {
182 // return trash
183 return true;
184 }
185
186 if (is_sch) {
187 for (int i = 0; i < 148; i++)
188 (demodded_softbits)[i] = (e.sch_bits[i]);
189 RSSI = 10;
190 timingOffset = 0;
191 return true;
192 }
193
194 convert_and_scale<float, int16_t>(ss, e.burst, ONE_TS_BURST_LEN * 2, 1.f / float(rxFullScale));
195
196 pow = energyDetect(sv, 20 * 4 /*sps*/);
197 if (pow < -1) {
198 LOG(ALERT) << "Received empty burst";
199 return false;
200 }
201
202 avg = sqrt(pow);
203 {
204 float ncmax;
205 std::complex<float> chan_imp_resp[CHAN_IMP_RESP_LENGTH * d_OSR];
206 auto normal_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp[0], &ncmax, mTSC);
207#ifdef DBGXX
208 float dcmax;
209 std::complex<float> chan_imp_resp2[CHAN_IMP_RESP_LENGTH * d_OSR];
210 auto dummy_burst_start = get_norm_chan_imp_resp(ss, &chan_imp_resp2[0], &dcmax, TS_DUMMY);
211 auto is_nb = ncmax > dcmax;
212 // DBGLG() << " U " << (is_nb ? "NB" : "DB") << "@ o nb: " << normal_burst_start
213 // << " o db: " << dummy_burst_start << std::endl;
214#endif
215 normal_burst_start = normal_burst_start < 39 ? normal_burst_start : 39;
216 normal_burst_start = normal_burst_start > -39 ? normal_burst_start : -39;
217#ifdef DBGXX
218 // fprintf(stderr, "%s %d\n", (is_nb ? "N":"D"), burst_time.FN());
219 // if (is_nb)
220#endif
221 detect_burst(ss, &chan_imp_resp[0], normal_burst_start, demodded_softbits);
222#ifdef DBGXX
223 // else
224 // detect_burst(ss, &chan_imp_resp2[0], dummy_burst_start, outbin);
225#endif
226 }
227 RSSI = (int)floor(20.0 * log10(rxFullScale / avg));
228 // FIXME: properly handle offset, sch/nb alignment diff? handled by lower anyway...
229 timingOffset = (int)round(0);
230
231 return true;
232}
233
234void upper_trx::driveReceiveFIFO()
235{
236 int RSSI;
237 int TOA; // in 1/256 of a symbol
238 GSM::Time burstTime;
239
240 if (!mOn)
241 return;
242
243 if (pullRadioVector(burstTime, RSSI, TOA)) {
244 trxcon::trxcon_phyif_burst_ind bi;
245 bi.fn = burstTime.FN();
246 bi.tn = burstTime.TN();
247 bi.rssi = RSSI;
248 bi.toa256 = TOA;
249 bi.burst = (sbit_t *)demodded_softbits;
250 bi.burst_len = sizeof(demodded_softbits);
251 trxcon_phyif_handle_burst_ind(trxcon::g_trxcon, &bi);
252 }
253
254 struct trxcon::trxcon_phyif_rts_ind rts {
255 static_cast<uint32_t>(burstTime.FN()), static_cast<uint8_t>(burstTime.TN())
256 };
257 trxcon_phyif_handle_rts_ind(trxcon::g_trxcon, &rts);
258}
259
260void upper_trx::driveTx()
261{
262 trxcon::internal_q_tx_buf e;
263 static BitVector newBurst(sizeof(e.buf));
264 while (!trxcon::txq.spsc_pop(&e)) {
265 trxcon::txq.spsc_prep_pop();
266 }
267
268 // ensure our tx cb is tickled and can exit
269 if (g_exit_flag) {
270 blade_sample_type dummy[10] = {};
271 submit_burst_ts(dummy, 10, 1);
272 return;
273 }
274
275 trxcon::internal_q_tx_buf *burst = &e;
276
277#ifdef TXDEBUG
278 DBGLG() << "got burst!" << burst->r.fn << ":" << burst->ts << " current: " << timekeeper.gsmtime().FN()
279 << " dff: " << (int64_t)((int64_t)timekeeper.gsmtime().FN() - (int64_t)burst->r.fn) << std::endl;
280#endif
281
282 auto currTime = GSM::Time(burst->r.fn, burst->r.tn);
283 int RSSI = (int)burst->r.pwr;
284
285 BitVector::iterator itr = newBurst.begin();
286 auto *bufferItr = burst->buf;
287 while (itr < newBurst.end())
288 *itr++ = *bufferItr++;
289
290 auto txburst = modulateBurst(newBurst, 8 + (currTime.TN() % 4 == 0), 4);
291 scaleVector(*txburst, txFullScale * pow(10, -RSSI / 10));
292
293 // float -> int16
294 blade_sample_type burst_buf[txburst->size()];
295 convert_and_scale<int16_t, float>(burst_buf, txburst->begin(), txburst->size() * 2, 1);
296#ifdef TXDEBUG
297 auto check = signalVector(txburst->size(), 40);
298 convert_and_scale<float, int16_t, 1>(check.begin(), burst_buf, txburst->size() * 2);
299 estim_burst_params ebp;
300 auto d = detectAnyBurst(check, 2, 4, 4, CorrType::RACH, 40, &ebp);
301 if (d)
302 DBGLG() << "RACH D! " << ebp.toa << std::endl;
303 else
304 DBGLG() << "RACH NOOOOOOOOOO D! " << ebp.toa << std::endl;
305
306 // memory read --binary --outfile /tmp/mem.bin &burst_buf[0] --count 2500 --force
307#endif
308 submit_burst(burst_buf, txburst->size(), currTime);
309 delete txburst;
310}
311
312#ifdef TXDEBUG
313static const char *cmd2str(trxcon::trxcon_phyif_cmd_type c)
314{
315 switch (c) {
316 case trxcon::TRXCON_PHYIF_CMDT_RESET:
317 return "TRXCON_PHYIF_CMDT_RESET";
318 case trxcon::TRXCON_PHYIF_CMDT_POWERON:
319 return "TRXCON_PHYIF_CMDT_POWERON";
320 case trxcon::TRXCON_PHYIF_CMDT_POWEROFF:
321 return "TRXCON_PHYIF_CMDT_POWEROFF";
322 case trxcon::TRXCON_PHYIF_CMDT_MEASURE:
323 return "TRXCON_PHYIF_CMDT_MEASURE";
324 case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0:
325 return "TRXCON_PHYIF_CMDT_SETFREQ_H0";
326 case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1:
327 return "TRXCON_PHYIF_CMDT_SETFREQ_H1";
328 case trxcon::TRXCON_PHYIF_CMDT_SETSLOT:
329 return "TRXCON_PHYIF_CMDT_SETSLOT";
330 case trxcon::TRXCON_PHYIF_CMDT_SETTA:
331 return "TRXCON_PHYIF_CMDT_SETTA";
332 default:
333 return "UNKNOWN COMMAND!";
334 }
335}
336
337static void print_cmd(trxcon::trxcon_phyif_cmd_type c)
338{
339 DBGLG() << cmd2str(c) << std::endl;
340}
341#endif
342
343bool upper_trx::driveControl()
344{
345 trxcon::trxcon_phyif_rsp r;
346 trxcon::trxcon_phyif_cmd cmd;
347 while (!trxcon::cmdq_to_phy.spsc_pop(&cmd)) {
348 trxcon::cmdq_to_phy.spsc_prep_pop();
349 }
350
351 if (g_exit_flag)
352 return false;
353
354#ifdef TXDEBUG
355 print_cmd(cmd.type);
356#endif
357
358 switch (cmd.type) {
359 case trxcon::TRXCON_PHYIF_CMDT_RESET:
360 set_ta(0);
361 break;
362 case trxcon::TRXCON_PHYIF_CMDT_POWERON:
363
364 if (!mOn) {
365 set_upper_ready(true);
366 mOn = true;
367 }
368 break;
369 case trxcon::TRXCON_PHYIF_CMDT_POWEROFF:
370 break;
371 case trxcon::TRXCON_PHYIF_CMDT_MEASURE:
372 r.type = trxcon::trxcon_phyif_cmd_type::TRXCON_PHYIF_CMDT_MEASURE;
373 r.param.measure.band_arfcn = cmd.param.measure.band_arfcn;
374 // FIXME: do we want to measure anything, considering the transceiver just syncs by.. syncing?
375 r.param.measure.dbm = -80;
376 tuneRx(trxcon::gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 0) * 1000 * 100);
377 tuneTx(trxcon::gsm_arfcn2freq10(cmd.param.measure.band_arfcn, 1) * 1000 * 100);
378 trxcon::cmdq_from_phy.spsc_push(&r);
379 break;
380 case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H0:
381 tuneRx(trxcon::gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 0) * 1000 * 100);
382 tuneTx(trxcon::gsm_arfcn2freq10(cmd.param.setfreq_h0.band_arfcn, 1) * 1000 * 100);
383 break;
384 case trxcon::TRXCON_PHYIF_CMDT_SETFREQ_H1:
385 break;
386 case trxcon::TRXCON_PHYIF_CMDT_SETSLOT:
387 break;
388 case trxcon::TRXCON_PHYIF_CMDT_SETTA:
389 set_ta(cmd.param.setta.ta);
390 break;
391 }
392 return false;
393}
394
395// trxcon C call(back) if
396extern "C" {
397int trxcon_phyif_handle_burst_req(void *phyif, const struct trxcon::trxcon_phyif_burst_req *br)
398{
399 if (br->burst_len == 0) // dummy/nope
400 return 0;
401 OSMO_ASSERT(br->burst != 0);
402
403 trxcon::internal_q_tx_buf b;
404 b.r = *br;
405 memcpy(b.buf, (void *)br->burst, br->burst_len);
406
407 if (!g_exit_flag)
408 trxcon::txq.spsc_push(&b);
409 return 0;
410}
411
412int trxcon_phyif_handle_cmd(void *phyif, const struct trxcon::trxcon_phyif_cmd *cmd)
413{
414#ifdef TXDEBUG
415 DBGLG() << "TOP C: " << cmd2str(cmd->type) << std::endl;
416#endif
417 if (!g_exit_flag)
418 trxcon::cmdq_to_phy.spsc_push(cmd);
419 // q for resp polling happens in main loop
420 return 0;
421}
422
423void trxcon_phyif_close(void *phyif)
424{
425}
426
427void trxcon_l1ctl_close(struct trxcon::trxcon_inst *trxcon)
428{
429 /* Avoid use-after-free: both *fi and *trxcon are children of
430 * the L2IF (L1CTL connection), so we need to re-parent *fi
431 * to NULL before calling l1ctl_client_conn_close(). */
432 talloc_steal(NULL, trxcon->fi);
433 trxcon::l1ctl_client_conn_close((struct trxcon::l1ctl_client *)trxcon->l2if);
434}
435
436int trxcon_l1ctl_send(struct trxcon::trxcon_inst *trxcon, struct trxcon::msgb *msg)
437{
438 struct trxcon::l1ctl_client *l1c = (struct trxcon::l1ctl_client *)trxcon->l2if;
439
440 return trxcon::l1ctl_client_send(l1c, msg);
441}
442}
443
444void sighandler(int sigset)
445{
446 // we might get a sigpipe in case the l1ctl ud socket disconnects because mobile quits
447 if (sigset == SIGPIPE) {
448 g_exit_flag = true;
449
450 // we know the flag is atomic and it prevents the trxcon cb handlers from writing
451 // to the queues, so submit some trash to unblock the threads & exit
Ericd0c10552022-12-28 16:52:54 +0100452 trxcon::trxcon_phyif_cmd cmd = {};
453 trxcon::internal_q_tx_buf b = {};
Ericb7253c62022-11-28 19:21:08 +0100454 trxcon::txq.spsc_push(&b);
455 trxcon::cmdq_to_phy.spsc_push(&cmd);
456
457 return;
458 }
459}
460
461int main(int argc, char *argv[])
462{
463 auto tall_trxcon_ctx = talloc_init("trxcon context");
464 signal(SIGPIPE, sighandler);
465 fesetround(FE_TOWARDZERO);
466
467 trxcon::msgb_talloc_ctx_init(tall_trxcon_ctx, 0);
468 trxc_log_init(tall_trxcon_ctx);
469
470 trxcon::g_trxcon = trxcon::trxcon_inst_alloc(tall_trxcon_ctx, 0, 0);
471 trxcon::g_trxcon->gsmtap = nullptr;
472 trxcon::g_trxcon->phyif = nullptr;
473 trxcon::g_trxcon->phy_quirks.fbsb_extend_fns = 866; // 4 seconds, known to work.
474
475 convolve_init();
476 convert_init();
477 sigProcLibSetup();
478 initvita();
479
480 int status = 0;
481 auto trx = new upper_trx();
482 trx->do_auto_gain = true;
483
484 status = trx->init_dev_and_streams();
485 trx->set_name_aff_sched("main", 3, SCHED_FIFO, sched_get_priority_max(SCHED_FIFO) - 5);
486
487 if (!trxcon::trxc_l1ctl_init(tall_trxcon_ctx)) {
488 std::cerr << "Error initializing l1ctl, quitting.." << std::endl;
489 return -1;
490 }
491
492 trx->start_threads();
493 trx->stop_threads();
494
495 return status;
496}