Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 1 | /* Dummy TRX for sening PRBS test sequences into osmo-bts-trx to test |
| 2 | * the decoder/receiver processing in osmo-bts-trx as well as any |
| 3 | * additional PRBS testing code. |
| 4 | * |
| 5 | * The purpose of this program is to use it as a mock dummy MS-side |
| 6 | * transmitter of GSM bursts that contain encoded PRBS sequences, |
| 7 | * similar to what one would normally do with an arbitrary |
| 8 | * function/waveform generator or BERT tester in hardware. |
| 9 | * |
| 10 | * (C) 2017 by Harald Welte <laforge@gnumonks.org> |
| 11 | * All Rights Reserved |
| 12 | * |
| 13 | * Licensed under terms of the GNU Generral Public License, Version 2, |
| 14 | * or (at your option) any later version. |
| 15 | */ |
| 16 | |
| 17 | #include <stdint.h> |
| 18 | #include <string.h> |
| 19 | #include <stdio.h> |
| 20 | #include <errno.h> |
| 21 | #include <unistd.h> |
| 22 | |
| 23 | #include <netinet/in.h> |
| 24 | |
| 25 | #include <osmocom/core/bits.h> |
| 26 | #include <osmocom/core/utils.h> |
| 27 | #include <osmocom/core/prbs.h> |
| 28 | #include <osmocom/core/socket.h> |
| 29 | #include <osmocom/gsm/gsm_utils.h> |
| 30 | #include <osmocom/coding/gsm0503_coding.h> |
| 31 | |
| 32 | /*********************************************************************** |
| 33 | * GSM Constants |
| 34 | ***********************************************************************/ |
| 35 | |
| 36 | #define GSM_FR_BYTES 33 |
| 37 | #define GSM_BURST_BITS 116 |
| 38 | #define GSM_4BURST_BITS (GSM_BURST_BITS*4) |
| 39 | #define GSM_BURST_LEN 148 |
| 40 | |
| 41 | |
| 42 | /*********************************************************************** |
| 43 | * TRX Interface / Protocol |
| 44 | ***********************************************************************/ |
| 45 | |
| 46 | #define TRX_BASE_PORT 5700 |
| 47 | /* DATA port on the TRX side */ |
| 48 | #define TRX_PORT_CTRL_TRX(C) (TRX_BASE_PORT+(2*(C))+1) |
| 49 | #define TRX_PORT_DATA_TRX(C) (TRX_BASE_PORT+(2*(C))+2) |
| 50 | #define TRX_PORT_CTRL_BTS(C) (TRX_PORT_CTRL_TRX(C)+100) |
| 51 | #define TRX_PORT_DATA_BTS(C) (TRX_PORT_DATA_TRX(C)+100) |
| 52 | |
| 53 | struct trx_ul_msg { |
| 54 | uint8_t ts; |
| 55 | uint32_t fn; |
| 56 | uint8_t rssi; |
| 57 | uint16_t t_offs; |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 58 | uint8_t bits[148]; /* 0..255, *NOT* sbit_t */ |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 59 | } __attribute__((packed)); |
| 60 | |
| 61 | struct trx_dl_msg { |
| 62 | uint8_t ts; |
| 63 | uint32_t fn; |
| 64 | uint8_t att_db; |
| 65 | ubit_t bits[148]; |
| 66 | } __attribute__((packed)); |
| 67 | |
| 68 | |
| 69 | /*********************************************************************** |
| 70 | * Helper Functions |
| 71 | ***********************************************************************/ |
| 72 | |
| 73 | static int ubits2trxbits(uint8_t *sbits, const ubit_t *ubits, unsigned int count) |
| 74 | { |
| 75 | unsigned int i; |
| 76 | |
| 77 | for (i = 0; i < count; i++) { |
| 78 | if ((*ubits++) & 1) { |
| 79 | *sbits++ = 255; |
| 80 | } else { |
| 81 | *sbits++ = 0; |
| 82 | } |
| 83 | } |
| 84 | |
| 85 | return count; |
| 86 | } |
| 87 | |
| 88 | static int __attribute__((__unused__)) dec(const ubit_t *bursts_u) |
| 89 | { |
| 90 | sbit_t bursts_s[GSM_4BURST_BITS*2]; |
| 91 | uint8_t dec_tch_data[GSM_FR_BYTES]; |
| 92 | int n_errors, n_bits_total; |
| 93 | int rc; |
| 94 | |
| 95 | /* convert from u_bit (tx) to s_bit (rx) */ |
| 96 | osmo_ubit2sbit(bursts_s, bursts_u, sizeof(bursts_s)); |
| 97 | |
| 98 | rc = gsm0503_tch_fr_decode(dec_tch_data, bursts_s, 1, 0, &n_errors, &n_bits_total); |
| 99 | printf("rc=%d, n_errors=%d, n_bits_total=%d: %s\n", rc, n_errors, n_bits_total, |
| 100 | osmo_hexdump(dec_tch_data, sizeof(dec_tch_data))); |
| 101 | |
| 102 | return rc; |
| 103 | } |
| 104 | |
| 105 | /*! \brief Training Sequences (TS 05.02 Chapter 5.2.3) */ |
| 106 | static const ubit_t _sched_tsc[8][26] = { |
| 107 | { 0,0,1,0,0,1,0,1,1,1,0,0,0,0,1,0,0,0,1,0,0,1,0,1,1,1, }, |
| 108 | { 0,0,1,0,1,1,0,1,1,1,0,1,1,1,1,0,0,0,1,0,1,1,0,1,1,1, }, |
| 109 | { 0,1,0,0,0,0,1,1,1,0,1,1,1,0,1,0,0,1,0,0,0,0,1,1,1,0, }, |
| 110 | { 0,1,0,0,0,1,1,1,1,0,1,1,0,1,0,0,0,1,0,0,0,1,1,1,1,0, }, |
| 111 | { 0,0,0,1,1,0,1,0,1,1,1,0,0,1,0,0,0,0,0,1,1,0,1,0,1,1, }, |
| 112 | { 0,1,0,0,1,1,1,0,1,0,1,1,0,0,0,0,0,1,0,0,1,1,1,0,1,0, }, |
| 113 | { 1,0,1,0,0,1,1,1,1,1,0,1,1,0,0,0,1,0,1,0,0,1,1,1,1,1, }, |
| 114 | { 1,1,1,0,1,1,1,1,0,0,0,1,0,0,1,0,1,1,1,0,1,1,1,1,0,0, }, |
| 115 | }; |
| 116 | |
| 117 | /*********************************************************************** |
| 118 | * state + processing functions |
| 119 | ***********************************************************************/ |
| 120 | |
| 121 | /* state we have to keep for one physical channel */ |
| 122 | struct pchan_data { |
| 123 | /* PRBS state */ |
| 124 | struct osmo_prbs_state st; |
| 125 | /* unpacked PRBS bits, generated from PRBS */ |
| 126 | ubit_t prbs_u[4+260]; |
| 127 | /* packed frame (to be sent) */ |
| 128 | uint8_t tch_data[GSM_FR_BYTES]; |
| 129 | /* burst bits (ubit) to be transmitted */ |
| 130 | ubit_t bursts[GSM_4BURST_BITS*2]; /* 116 * 8 */ |
| 131 | /* burst bits (sbit) 'as if received' */ |
| 132 | sbit_t bursts_s[GSM_4BURST_BITS*2]; |
| 133 | /* next to-be transmitted burst number */ |
| 134 | unsigned int burst_nr; |
| 135 | /* training sequence code */ |
| 136 | unsigned int tsc; |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 137 | |
| 138 | /* loose 'count' bursts every 'nth_mframe' on TRX-BTS interface */ |
| 139 | struct { |
| 140 | unsigned int count; |
| 141 | unsigned int nth_mframe; |
| 142 | } sim_lost_bursts; |
| 143 | |
| 144 | /* zero 'count' bursts every 'nth_mframe' on TRX-BTS interface */ |
| 145 | struct { |
| 146 | unsigned int count; |
| 147 | unsigned int nth_mframe; |
| 148 | } sim_zero_bursts; |
| 149 | |
| 150 | /* flip every 'nth_bit' of the PRNG oudput before encoding */ |
| 151 | struct { |
| 152 | unsigned int nth_bit; |
| 153 | unsigned int i; |
| 154 | } sim_flip_codec_bits; |
| 155 | |
| 156 | unsigned int i; |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 157 | }; |
| 158 | |
| 159 | struct ts_data { |
| 160 | struct pchan_data pchan[2]; |
| 161 | }; |
| 162 | |
| 163 | struct trx_data { |
| 164 | struct ts_data ts[8]; |
| 165 | }; |
| 166 | |
| 167 | static struct trx_data g_trx_data; |
| 168 | |
| 169 | /* initialize the state for one TRX */ |
| 170 | static void trx_data_init(struct trx_data *trx) |
| 171 | { |
| 172 | int i; |
| 173 | |
| 174 | for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { |
| 175 | struct ts_data *ts = &trx->ts[i]; |
| 176 | int j; |
| 177 | for (j = 0; j < ARRAY_SIZE(ts->pchan); j++) { |
| 178 | struct pchan_data *pchan = &ts->pchan[j]; |
| 179 | |
| 180 | memset(pchan, 0, sizeof(*pchan)); |
| 181 | osmo_prbs_state_init(&pchan->st, &osmo_prbs9); |
| 182 | pchan->tsc = 7; |
| 183 | } |
| 184 | } |
| 185 | } |
| 186 | |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 187 | /* apply any intentional errors to the output of the PRBS sequence */ |
| 188 | static void apply_errors_prbs(struct pchan_data *pchan) |
| 189 | { |
Harald Welte | cf35c37 | 2020-05-07 23:55:56 +0200 | [diff] [blame^] | 190 | int i; |
| 191 | |
| 192 | for (i = 0; i < sizeof(pchan->prbs_u)-4; i++) { |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 193 | pchan->sim_flip_codec_bits.i++; |
| 194 | if (pchan->sim_flip_codec_bits.i == pchan->sim_flip_codec_bits.nth_bit) { |
| 195 | pchan->sim_flip_codec_bits.i = 0; |
| 196 | pchan->prbs_u[4+i] ^= 0x01; |
| 197 | } |
| 198 | } |
| 199 | } |
| 200 | |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 201 | /*! obtain the next to-be-transmitted burst for the given pchan |
| 202 | * \param pchan physical channel on which we operate |
| 203 | * \param[in] fn frame number |
| 204 | * \param[out] burst_out caller-provided buffer for 148 unpacked output bits |
| 205 | * \retruns number of bits stored in \a burst_out */ |
| 206 | static int pchan_get_next_burst(struct pchan_data *pchan, uint32_t fn, ubit_t *burst_out) |
| 207 | { |
| 208 | uint32_t fn26 = fn % 26; |
| 209 | int rc; |
| 210 | |
| 211 | if (fn26 == 0 || fn26 == 4 || fn26 == 8 || fn26 == 13 || fn26 == 17 || fn26 == 21) |
| 212 | pchan->burst_nr = 0; |
| 213 | |
| 214 | if (fn26 == 12 || fn26 == 25) { |
| 215 | memset(burst_out, 0, GSM_BURST_LEN); |
| 216 | return GSM_BURST_LEN; |
| 217 | } |
| 218 | |
| 219 | if (pchan->burst_nr == 0) { |
| 220 | /* generate PRBS output in ubit format, skipping first nibble for 260-264 padding */ |
| 221 | const uint8_t prefix[] = { 0xd0 }; |
| 222 | osmo_pbit2ubit(pchan->prbs_u, prefix, 4); |
| 223 | rc = osmo_prbs_get_ubits(pchan->prbs_u+4, sizeof(pchan->prbs_u)-4, &pchan->st); |
| 224 | OSMO_ASSERT(rc == sizeof(pchan->prbs_u)-4); |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 225 | |
| 226 | apply_errors_prbs(pchan); |
| 227 | |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 228 | /* pack to PBIT format */ |
| 229 | rc = osmo_ubit2pbit(pchan->tch_data, pchan->prbs_u, sizeof(pchan->prbs_u)); |
| 230 | //memset(pchan->tch_data, 0xff, sizeof(pchan->tch_data)); |
| 231 | |
| 232 | printf("%s\n", osmo_hexdump(pchan->tch_data, GSM_FR_BYTES)); |
| 233 | |
| 234 | /* shift buffer by 4 bursts for interleaving */ |
| 235 | memcpy(pchan->bursts, pchan->bursts + GSM_4BURST_BITS, GSM_4BURST_BITS); |
| 236 | memset(pchan->bursts + GSM_4BURST_BITS, 0, GSM_4BURST_BITS); |
| 237 | |
| 238 | /* encode block (codec frame) into four bursts */ |
| 239 | rc = gsm0503_tch_fr_encode(pchan->bursts, pchan->tch_data, GSM_FR_BYTES, 1); |
| 240 | OSMO_ASSERT(rc == 0); |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 241 | #if 0 |
Harald Welte | cf35c37 | 2020-05-07 23:55:56 +0200 | [diff] [blame^] | 242 | int i; |
| 243 | for (i = 0; i < sizeof(pchan->bursts); i += GSM_BURST_BITS) |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 244 | printf("\t%s\n", osmo_ubit_dump(pchan->bursts + i, GSM_BURST_BITS)); |
| 245 | |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 246 | dec(pchan->bursts); |
| 247 | #endif |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 248 | } |
| 249 | |
| 250 | /* for all bursts: format 148 symbols from 116 input bits */ |
| 251 | ubit_t *burst = pchan->bursts + pchan->burst_nr * GSM_BURST_BITS; |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 252 | // printf("TX(%u): %s\n", pchan->burst_nr, osmo_ubit_dump(burst, GSM_BURST_BITS)); |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 253 | memset(burst_out, 0, 3); /* guard bits */ |
| 254 | memcpy(burst_out+3, burst, 58); /* firrst half */ |
| 255 | memcpy(burst_out+61, _sched_tsc[pchan->tsc], 26); /* midamble */ |
| 256 | memcpy(burst_out+87, burst+58, 58); /* second half */ |
| 257 | memset(burst_out+145, 0, 3); /* guard bits */ |
| 258 | |
| 259 | /* increment burst number for next call */ |
| 260 | pchan->burst_nr += 1; |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 261 | |
| 262 | return GSM_BURST_LEN; |
| 263 | } |
| 264 | |
| 265 | static int pchan_process_ts_fn(struct pchan_data *pchan, uint32_t fn, uint8_t *burst_t) |
| 266 | { |
| 267 | ubit_t burst_u[GSM_BURST_LEN]; |
| 268 | int rc; |
| 269 | |
| 270 | rc = pchan_get_next_burst(pchan, fn, burst_u); |
| 271 | OSMO_ASSERT(rc == sizeof(burst_u)); |
| 272 | |
| 273 | /* convert from u_bit (tx) to s_bit (rx) */ |
| 274 | ubits2trxbits(burst_t, burst_u, GSM_BURST_LEN); |
| 275 | |
| 276 | return GSM_BURST_LEN; |
| 277 | } |
| 278 | |
| 279 | /* read TRX DL data from BTS, write TRX UL data to BTS */ |
| 280 | static int read_and_process(int fd) |
| 281 | { |
| 282 | /* receive (downlink) buffer */ |
| 283 | uint8_t rx_dl_buf[1024]; |
| 284 | struct trx_dl_msg *dl_msg = (struct trx_dl_msg *) rx_dl_buf; |
| 285 | /* transmit (uplink) buffer */ |
| 286 | uint8_t tx_ul_buf[1024]; |
| 287 | struct trx_ul_msg *ul_msg = (struct trx_ul_msg *) tx_ul_buf; |
| 288 | /* other variables */ |
| 289 | struct pchan_data *pchan; |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 290 | uint32_t fn; |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 291 | uint8_t rc; |
| 292 | |
| 293 | /* do a blocking read on the socket and receive DL from BTS */ |
| 294 | rc = read(fd, rx_dl_buf, sizeof(rx_dl_buf)); |
| 295 | if (rc < sizeof(*dl_msg)) |
| 296 | return rc; |
| 297 | |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 298 | fn = ntohl(dl_msg->fn); |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 299 | |
| 300 | if (dl_msg->ts >= ARRAY_SIZE(g_trx_data.ts)) |
| 301 | return -ENODEV; |
| 302 | |
| 303 | if (dl_msg->ts != 2) |
| 304 | return 0; |
| 305 | |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 306 | printf("FN=%s TS=%u\n", gsm_fn_as_gsmtime_str(fn), dl_msg->ts); |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 307 | |
| 308 | /* FIXME: second pchan for TCH/H */ |
| 309 | pchan = &g_trx_data.ts[dl_msg->ts].pchan[0]; |
| 310 | |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 311 | rc = pchan_process_ts_fn(pchan, fn, (uint8_t *) ul_msg->bits); |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 312 | OSMO_ASSERT(rc == sizeof(ul_msg->bits)); |
| 313 | |
| 314 | /* copy over timeslot and frame number */ |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 315 | ul_msg->fn = htonl(fn); |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 316 | ul_msg->ts = dl_msg->ts; |
| 317 | |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 318 | /* simulate lost frames on TRX <-> BTS interface */ |
| 319 | if (pchan->sim_lost_bursts.count) { |
| 320 | /* count number of 26-multiframes */ |
| 321 | static int count = 0; |
| 322 | if (fn % 26 == 0) |
| 323 | count++; |
| 324 | |
| 325 | /* every 10th multiframe, drop two entire block of 8 bursts */ |
| 326 | if (count % pchan->sim_lost_bursts.nth_mframe == 0 && |
| 327 | (fn % 26) <= pchan->sim_lost_bursts.count) { |
| 328 | printf("===> SKIPPING BURST\n"); |
| 329 | return 0; |
| 330 | } |
| 331 | } |
| 332 | |
| 333 | /* simulate zero-ed frames on TRX <-> BTS interface */ |
| 334 | if (pchan->sim_zero_bursts.count) { |
| 335 | /* count number of 26-multiframes */ |
| 336 | static int count = 0; |
| 337 | if (fn % 26 == 0) |
| 338 | count++; |
| 339 | |
| 340 | /* every 10th multiframe, drop two entire block of 8 bursts */ |
| 341 | if (count % pchan->sim_zero_bursts.nth_mframe == 0 && |
| 342 | (fn % 26) <= pchan->sim_zero_bursts.count) { |
| 343 | memset(ul_msg->bits, 0, sizeof(ul_msg->bits)); |
| 344 | printf("===> ZEROING BURST\n"); |
| 345 | } |
| 346 | } |
| 347 | |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 348 | /* write uplink message towards BTS */ |
| 349 | rc = write(fd, tx_ul_buf, sizeof(*ul_msg)); |
| 350 | if (rc < sizeof(*ul_msg)) |
| 351 | return -EIO; |
| 352 | |
| 353 | return 0; |
| 354 | } |
| 355 | |
| 356 | static int open_trx_data_sock(unsigned int trx_nr, const char *bts_host) |
| 357 | { |
| 358 | int rc; |
| 359 | |
| 360 | rc = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, TRX_PORT_DATA_TRX(trx_nr), |
| 361 | bts_host, TRX_PORT_DATA_BTS(trx_nr), |
| 362 | OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_BIND); |
| 363 | return rc; |
| 364 | } |
| 365 | |
| 366 | |
| 367 | int main(int argc, char **argv) |
| 368 | { |
| 369 | int fd; |
| 370 | |
| 371 | trx_data_init(&g_trx_data); |
| 372 | |
Harald Welte | 546516d | 2017-12-02 23:01:29 +0100 | [diff] [blame] | 373 | //g_trx_data.ts[2].pchan[0].sim_zero_bursts.count = 8; |
| 374 | //g_trx_data.ts[2].pchan[0].sim_zero_bursts.nth_mframe = 10; |
| 375 | g_trx_data.ts[2].pchan[0].sim_flip_codec_bits.nth_bit = 260*4; |
| 376 | |
Harald Welte | 6879bb0 | 2020-05-02 22:27:14 +0200 | [diff] [blame] | 377 | fd = open_trx_data_sock(0, "127.0.0.1"); |
| 378 | if (fd < 0) |
| 379 | exit(1); |
| 380 | |
| 381 | while (1) { |
| 382 | read_and_process(fd); |
| 383 | } |
| 384 | |
| 385 | return 0; |
| 386 | } |