diff --git a/Makefile.am b/Makefile.am index 8b55e79c..9462fa8c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -33,7 +33,8 @@ SUBDIRS = \ GSM \ Transceiver52M \ contrib \ - tests + tests \ + utils EXTRA_DIST = \ LEGAL \ diff --git a/configure.ac b/configure.ac index b7b0d00a..d0cfe440 100644 --- a/configure.ac +++ b/configure.ac @@ -92,6 +92,7 @@ CPPFLAGS=$save_CPPFLAGS PKG_CHECK_MODULES(LIBOSMOCORE, libosmocore >= 1.3.0) PKG_CHECK_MODULES(LIBOSMOVTY, libosmovty >= 1.3.0) PKG_CHECK_MODULES(LIBOSMOCTRL, libosmoctrl >= 1.3.0) +PKG_CHECK_MODULES(LIBOSMOCODING, libosmocoding >= 1.3.0) AC_ARG_ENABLE(sanitize, [AS_HELP_STRING( @@ -327,6 +328,7 @@ AC_CONFIG_FILES([\ tests/Makefile \ tests/CommonLibs/Makefile \ tests/Transceiver52M/Makefile \ + utils/Makefile \ doc/Makefile \ doc/examples/Makefile \ contrib/Makefile \ diff --git a/utils/Makefile.am b/utils/Makefile.am new file mode 100644 index 00000000..0b8bda04 --- /dev/null +++ b/utils/Makefile.am @@ -0,0 +1,7 @@ +AM_CPPFLAGS = $(LIBOSMOCODING_CFLAGS) +AM_CFLAGS = -Wall + +noinst_PROGRAMS = osmo-prbs-tool + +osmo_prbs_tool_SOURCES = prbs-tool.c +osmo_prbs_tool_LDADD = $(LIBOSMOCODING_LIBS) diff --git a/utils/prbs-tool.c b/utils/prbs-tool.c new file mode 100644 index 00000000..de68f0b2 --- /dev/null +++ b/utils/prbs-tool.c @@ -0,0 +1,316 @@ +/* Dummy TRX for sening PRBS test sequences into osmo-bts-trx to test + * the decoder/receiver processing in osmo-bts-trx as well as any + * additional PRBS testing code. + * + * The purpose of this program is to use it as a mock dummy MS-side + * transmitter of GSM bursts that contain encoded PRBS sequences, + * similar to what one would normally do with an arbitrary + * function/waveform generator or BERT tester in hardware. + * + * (C) 2017 by Harald Welte + * All Rights Reserved + * + * Licensed under terms of the GNU Generral Public License, Version 2, + * or (at your option) any later version. + */ + +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +/*********************************************************************** + * GSM Constants + ***********************************************************************/ + +#define GSM_FR_BYTES 33 +#define GSM_BURST_BITS 116 +#define GSM_4BURST_BITS (GSM_BURST_BITS*4) +#define GSM_BURST_LEN 148 + + +/*********************************************************************** + * TRX Interface / Protocol + ***********************************************************************/ + +#define TRX_BASE_PORT 5700 +/* DATA port on the TRX side */ +#define TRX_PORT_CTRL_TRX(C) (TRX_BASE_PORT+(2*(C))+1) +#define TRX_PORT_DATA_TRX(C) (TRX_BASE_PORT+(2*(C))+2) +#define TRX_PORT_CTRL_BTS(C) (TRX_PORT_CTRL_TRX(C)+100) +#define TRX_PORT_DATA_BTS(C) (TRX_PORT_DATA_TRX(C)+100) + +struct trx_ul_msg { + uint8_t ts; + uint32_t fn; + uint8_t rssi; + uint16_t t_offs; + sbit_t bits[148]; +} __attribute__((packed)); + +struct trx_dl_msg { + uint8_t ts; + uint32_t fn; + uint8_t att_db; + ubit_t bits[148]; +} __attribute__((packed)); + + +/*********************************************************************** + * Helper Functions + ***********************************************************************/ + +static int ubits2trxbits(uint8_t *sbits, const ubit_t *ubits, unsigned int count) +{ + unsigned int i; + + for (i = 0; i < count; i++) { + if ((*ubits++) & 1) { + *sbits++ = 255; + } else { + *sbits++ = 0; + } + } + + return count; +} + +static int __attribute__((__unused__)) dec(const ubit_t *bursts_u) +{ + sbit_t bursts_s[GSM_4BURST_BITS*2]; + uint8_t dec_tch_data[GSM_FR_BYTES]; + int n_errors, n_bits_total; + int rc; + + /* convert from u_bit (tx) to s_bit (rx) */ + osmo_ubit2sbit(bursts_s, bursts_u, sizeof(bursts_s)); + + rc = gsm0503_tch_fr_decode(dec_tch_data, bursts_s, 1, 0, &n_errors, &n_bits_total); + printf("rc=%d, n_errors=%d, n_bits_total=%d: %s\n", rc, n_errors, n_bits_total, + osmo_hexdump(dec_tch_data, sizeof(dec_tch_data))); + + return rc; +} + +/*! \brief Training Sequences (TS 05.02 Chapter 5.2.3) */ +static const ubit_t _sched_tsc[8][26] = { + { 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, }, + { 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, }, + { 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, }, + { 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, }, + { 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, }, + { 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, }, + { 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, }, + { 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, }, +}; + +/*********************************************************************** + * state + processing functions + ***********************************************************************/ + +/* state we have to keep for one physical channel */ +struct pchan_data { + /* PRBS state */ + struct osmo_prbs_state st; + /* unpacked PRBS bits, generated from PRBS */ + ubit_t prbs_u[4+260]; + /* packed frame (to be sent) */ + uint8_t tch_data[GSM_FR_BYTES]; + /* burst bits (ubit) to be transmitted */ + ubit_t bursts[GSM_4BURST_BITS*2]; /* 116 * 8 */ + /* burst bits (sbit) 'as if received' */ + sbit_t bursts_s[GSM_4BURST_BITS*2]; + /* next to-be transmitted burst number */ + unsigned int burst_nr; + /* training sequence code */ + unsigned int tsc; +}; + +struct ts_data { + struct pchan_data pchan[2]; +}; + +struct trx_data { + struct ts_data ts[8]; +}; + +static struct trx_data g_trx_data; + +/* initialize the state for one TRX */ +static void trx_data_init(struct trx_data *trx) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(trx->ts); i++) { + struct ts_data *ts = &trx->ts[i]; + int j; + for (j = 0; j < ARRAY_SIZE(ts->pchan); j++) { + struct pchan_data *pchan = &ts->pchan[j]; + + memset(pchan, 0, sizeof(*pchan)); + osmo_prbs_state_init(&pchan->st, &osmo_prbs9); + pchan->tsc = 7; + } + } +} + +/*! obtain the next to-be-transmitted burst for the given pchan + * \param pchan physical channel on which we operate + * \param[in] fn frame number + * \param[out] burst_out caller-provided buffer for 148 unpacked output bits + * \retruns number of bits stored in \a burst_out */ +static int pchan_get_next_burst(struct pchan_data *pchan, uint32_t fn, ubit_t *burst_out) +{ + uint32_t fn26 = fn % 26; + int rc; + + if (fn26 == 0 || fn26 == 4 || fn26 == 8 || fn26 == 13 || fn26 == 17 || fn26 == 21) + pchan->burst_nr = 0; + + if (fn26 == 12 || fn26 == 25) { + memset(burst_out, 0, GSM_BURST_LEN); + return GSM_BURST_LEN; + } + + if (pchan->burst_nr == 0) { + /* generate PRBS output in ubit format, skipping first nibble for 260-264 padding */ + const uint8_t prefix[] = { 0xd0 }; + osmo_pbit2ubit(pchan->prbs_u, prefix, 4); + rc = osmo_prbs_get_ubits(pchan->prbs_u+4, sizeof(pchan->prbs_u)-4, &pchan->st); + OSMO_ASSERT(rc == sizeof(pchan->prbs_u)-4); + /* pack to PBIT format */ + rc = osmo_ubit2pbit(pchan->tch_data, pchan->prbs_u, sizeof(pchan->prbs_u)); + //memset(pchan->tch_data, 0xff, sizeof(pchan->tch_data)); + + printf("%s\n", osmo_hexdump(pchan->tch_data, GSM_FR_BYTES)); + + /* shift buffer by 4 bursts for interleaving */ + memcpy(pchan->bursts, pchan->bursts + GSM_4BURST_BITS, GSM_4BURST_BITS); + memset(pchan->bursts + GSM_4BURST_BITS, 0, GSM_4BURST_BITS); + + /* encode block (codec frame) into four bursts */ + rc = gsm0503_tch_fr_encode(pchan->bursts, pchan->tch_data, GSM_FR_BYTES, 1); + OSMO_ASSERT(rc == 0); + + for (int i = 0; i < sizeof(pchan->bursts); i += GSM_BURST_BITS) + printf("\t%s\n", osmo_ubit_dump(pchan->bursts + i, GSM_BURST_BITS)); + +// dec(pchan->bursts); + } + + /* for all bursts: format 148 symbols from 116 input bits */ + ubit_t *burst = pchan->bursts + pchan->burst_nr * GSM_BURST_BITS; + printf("TX(%u): %s\n", pchan->burst_nr, osmo_ubit_dump(burst, GSM_BURST_BITS)); + memset(burst_out, 0, 3); /* guard bits */ + memcpy(burst_out+3, burst, 58); /* firrst half */ + memcpy(burst_out+61, _sched_tsc[pchan->tsc], 26); /* midamble */ + memcpy(burst_out+87, burst+58, 58); /* second half */ + memset(burst_out+145, 0, 3); /* guard bits */ + + /* increment burst number for next call */ + pchan->burst_nr += 1; +#if 0 + if (pchan->burst_nr == 4) + pchan->burst_nr = 0; +#endif + + return GSM_BURST_LEN; +} + +static int pchan_process_ts_fn(struct pchan_data *pchan, uint32_t fn, uint8_t *burst_t) +{ + ubit_t burst_u[GSM_BURST_LEN]; + int rc; + + rc = pchan_get_next_burst(pchan, fn, burst_u); + OSMO_ASSERT(rc == sizeof(burst_u)); + + /* convert from u_bit (tx) to s_bit (rx) */ + ubits2trxbits(burst_t, burst_u, GSM_BURST_LEN); + + return GSM_BURST_LEN; +} + +/* read TRX DL data from BTS, write TRX UL data to BTS */ +static int read_and_process(int fd) +{ + /* receive (downlink) buffer */ + uint8_t rx_dl_buf[1024]; + struct trx_dl_msg *dl_msg = (struct trx_dl_msg *) rx_dl_buf; + /* transmit (uplink) buffer */ + uint8_t tx_ul_buf[1024]; + struct trx_ul_msg *ul_msg = (struct trx_ul_msg *) tx_ul_buf; + /* other variables */ + struct pchan_data *pchan; + uint8_t rc; + + /* do a blocking read on the socket and receive DL from BTS */ + rc = read(fd, rx_dl_buf, sizeof(rx_dl_buf)); + if (rc < sizeof(*dl_msg)) + return rc; + + dl_msg->fn = ntohl(dl_msg->fn); + + if (dl_msg->ts >= ARRAY_SIZE(g_trx_data.ts)) + return -ENODEV; + + if (dl_msg->ts != 2) + return 0; + + printf("FN=%s TS=%u\n", gsm_fn_as_gsmtime_str(dl_msg->fn), dl_msg->ts); + + /* FIXME: second pchan for TCH/H */ + pchan = &g_trx_data.ts[dl_msg->ts].pchan[0]; + + rc = pchan_process_ts_fn(pchan, dl_msg->fn, (uint8_t *) ul_msg->bits); + OSMO_ASSERT(rc == sizeof(ul_msg->bits)); + + /* copy over timeslot and frame number */ + ul_msg->fn = htonl(dl_msg->fn); + ul_msg->ts = dl_msg->ts; + + /* write uplink message towards BTS */ + rc = write(fd, tx_ul_buf, sizeof(*ul_msg)); + if (rc < sizeof(*ul_msg)) + return -EIO; + + return 0; +} + +static int open_trx_data_sock(unsigned int trx_nr, const char *bts_host) +{ + int rc; + + rc = osmo_sock_init2(AF_INET, SOCK_DGRAM, IPPROTO_UDP, NULL, TRX_PORT_DATA_TRX(trx_nr), + bts_host, TRX_PORT_DATA_BTS(trx_nr), + OSMO_SOCK_F_CONNECT | OSMO_SOCK_F_BIND); + return rc; +} + + +int main(int argc, char **argv) +{ + int fd; + + trx_data_init(&g_trx_data); + + fd = open_trx_data_sock(0, "127.0.0.1"); + if (fd < 0) + exit(1); + + while (1) { + read_and_process(fd); + } + + return 0; +}