From b795f03faff894d62b02dc03dd37e4136f055db1 Mon Sep 17 00:00:00 2001 From: Harald Welte Date: Thu, 14 May 2020 11:42:53 +0200 Subject: Implement ITU-T I.460 multiplex / demultiplex This implements a multiplexer and de-multiplexer for the ITU-T I.460 standard. The latter covers the transmission of sub-slots of 32/16/8k inside 64k timeslots. Change-Id: Id522f06e73b77332b437b7a27e4966872da70eda --- include/Makefile.am | 1 + include/osmocom/gsm/i460_mux.h | 104 +++++++++++ src/gsm/Makefile.am | 2 +- src/gsm/i460_mux.c | 363 ++++++++++++++++++++++++++++++++++++ src/gsm/libosmogsm.map | 7 + tests/Makefile.am | 5 + tests/i460_mux/i460_mux_test.c | 397 ++++++++++++++++++++++++++++++++++++++++ tests/i460_mux/i460_mux_test.ok | 115 ++++++++++++ tests/testsuite.at | 6 + 9 files changed, 999 insertions(+), 1 deletion(-) create mode 100644 include/osmocom/gsm/i460_mux.h create mode 100644 src/gsm/i460_mux.c create mode 100644 tests/i460_mux/i460_mux_test.c create mode 100644 tests/i460_mux/i460_mux_test.ok diff --git a/include/Makefile.am b/include/Makefile.am index 572c880f..456b8ef0 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -103,6 +103,7 @@ nobase_include_HEADERS = \ osmocom/gsm/gsm_utils.h \ osmocom/gsm/gsup.h \ osmocom/gsm/gsup_sms.h \ + osmocom/gsm/i460_mux.h \ osmocom/gsm/ipa.h \ osmocom/gsm/lapd_core.h \ osmocom/gsm/lapdm.h \ diff --git a/include/osmocom/gsm/i460_mux.h b/include/osmocom/gsm/i460_mux.h new file mode 100644 index 00000000..2e33b37e --- /dev/null +++ b/include/osmocom/gsm/i460_mux.h @@ -0,0 +1,104 @@ +/*! \file i460_mux.h + * ITU-T I.460 sub-channel multiplexer + demultiplexer */ +/* + * (C) 2020 by Harald Welte + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#pragma once +#include +#include +#include +#include + +/* I.460 sub-slot rate */ +enum osmo_i460_rate { + OSMO_I460_RATE_NONE, /* disabled */ + OSMO_I460_RATE_64k, + OSMO_I460_RATE_32k, + OSMO_I460_RATE_16k, + OSMO_I460_RATE_8k, +}; + +typedef void (*out_cb_bits_t)(void *user_data, const ubit_t *bits, unsigned int num_bits); +typedef void (*out_cb_bytes_t)(void *user_data, const uint8_t *bytes, unsigned int num_bytes); + +struct osmo_i460_subchan_demux { + /*! bit-buffer for output bits */ + uint8_t *out_bitbuf; + /*! size of out_bitbuf in bytes */ + unsigned int out_bitbuf_size; + /*! offset of next bit to be written in out_bitbuf */ + unsigned int out_idx; + /*! callback to be called once we have received out_bitbuf_size bits */ + out_cb_bits_t out_cb_bits; + out_cb_bytes_t out_cb_bytes; + void *user_data; +}; + +struct osmo_i460_subchan_mux { + /*! list of to-be-transmitted message buffers */ + struct llist_head tx_queue; +}; + +struct osmo_i460_subchan { + enum osmo_i460_rate rate; /* 8/16/32/64k */ + uint8_t bit_offset; /* bit offset inside each byte of the B-channel */ + struct osmo_i460_subchan_demux demux; + struct osmo_i460_subchan_mux mux; +}; + +struct osmo_i460_timeslot { + struct osmo_i460_subchan schan[8]; +}; + +/*! description of a sub-channel; passed by caller */ +struct osmo_i460_schan_desc { + enum osmo_i460_rate rate; + uint8_t bit_offset; + struct { + /* size (in bits) of the internal buffer; determines granularity */ + size_t num_bits; + /*! call-back function called whenever we received num_bits */ + out_cb_bits_t out_cb_bits; + /*! out_cb_bytes call-back function called whenever we received num_bits. + * The user is usually expected to provide either out_cb_bits or out_cb_bytes. If only + * out_cb_bits is provided, output data will always be provided as unpacked bits; if only + * out_cb_bytes is provided, output data will always be provided as packet bits (bytes). If + * both are provided, it is up to the I.460 multiplex to decide if it calls either of the two, + * depending on what can be provided without extra conversion. */ + out_cb_bytes_t out_cb_bytes; + /* opaque user data pointer to pass to out_cb */ + void *user_data; + } demux; +}; + +void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len); + +void osmo_i460_mux_enqueue(struct osmo_i460_subchan *schan, struct msgb *msg); +int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len); + +void osmo_i460_ts_init(struct osmo_i460_timeslot *ts); + +struct osmo_i460_subchan * +osmo_i460_subchan_add(void *ctx, struct osmo_i460_timeslot *ts, const struct osmo_i460_schan_desc *chd); + +void osmo_i460_subchan_del(struct osmo_i460_subchan *schan); + +/*! @} */ diff --git a/src/gsm/Makefile.am b/src/gsm/Makefile.am index 6935eabd..eeb11648 100644 --- a/src/gsm/Makefile.am +++ b/src/gsm/Makefile.am @@ -32,7 +32,7 @@ libgsmint_la_SOURCES = a5.c rxlev_stat.c tlv_parser.c comp128.c comp128v23.c \ milenage/milenage.c gan.c ipa.c gsm0341.c apn.c \ gsup.c gsup_sms.c gprs_gea.c gsm0503_conv.c oap.c gsm0808_utils.c \ gsm23003.c mncc.c bts_features.c oap_client.c \ - gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c + gsm29118.c gsm48_rest_octets.c cbsp.c gsm48049.c i460_mux.c libgsmint_la_LDFLAGS = -no-undefined libgsmint_la_LIBADD = $(top_builddir)/src/libosmocore.la diff --git a/src/gsm/i460_mux.c b/src/gsm/i460_mux.c new file mode 100644 index 00000000..3fb63ec0 --- /dev/null +++ b/src/gsm/i460_mux.c @@ -0,0 +1,363 @@ +/*! \file i460_mux.c + * ITU-T I.460 sub-channel multiplexer + demultiplexer */ +/* + * (C) 2020 by Harald Welte + * + * SPDX-License-Identifier: GPL-2.0+ + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301, USA. + */ + +#include + +#include +#include +#include +#include + +/* count the number of sub-channels in this I460 slot */ +static int osmo_i460_subchan_count(struct osmo_i460_timeslot *ts) +{ + int i, num_used = 0; + + for (i = 0; i < ARRAY_SIZE(ts->schan); i++) { + if (ts->schan[i].rate != OSMO_I460_RATE_NONE) + num_used++; + } + + return num_used; +} + +/* does this channel have no sub-streams (single 64k subchannel)? */ +static bool osmo_i460_has_single_64k_schan(struct osmo_i460_timeslot *ts) +{ + if (osmo_i460_subchan_count(ts) != 1) + return false; + + if (ts->schan[0].rate != OSMO_I460_RATE_64k) + return false; + + return true; +} + +/*********************************************************************** + * Demultiplexer + ***********************************************************************/ + +/* append a single bit to a sub-channel */ +static void demux_subchan_append_bit(struct osmo_i460_subchan *schan, uint8_t bit) +{ + struct osmo_i460_subchan_demux *demux = &schan->demux; + + OSMO_ASSERT(demux->out_bitbuf); + OSMO_ASSERT(demux->out_idx < demux->out_bitbuf_size); + + demux->out_bitbuf[demux->out_idx++] = bit ? 1 : 0; + + if (demux->out_idx >= demux->out_bitbuf_size) { + if (demux->out_cb_bits) + demux->out_cb_bits(demux->user_data, demux->out_bitbuf, demux->out_idx); + else { + /* pack bits into bytes */ + OSMO_ASSERT((demux->out_idx % 8) == 0); + unsigned int num_bytes = demux->out_idx / 8; + uint8_t bytes[num_bytes]; + osmo_ubit2pbit(bytes, demux->out_bitbuf, demux->out_idx); + demux->out_cb_bytes(demux->user_data, bytes, num_bytes); + } + demux->out_idx = 0; + } +} + +/* extract those bits relevant to this schan of each byte in 'data' */ +static void demux_subchan_extract_bits(struct osmo_i460_subchan *schan, const uint8_t *data, size_t data_len) +{ + int i; + + for (i = 0; i < data_len; i++) { + uint8_t inbyte = data[i]; + uint8_t inbits = inbyte >> schan->bit_offset; + + /* extract the bits relevant to the given schan */ + switch (schan->rate) { + case OSMO_I460_RATE_8k: + demux_subchan_append_bit(schan, inbits & 0x01); + break; + case OSMO_I460_RATE_16k: + demux_subchan_append_bit(schan, inbits & 0x01); + demux_subchan_append_bit(schan, inbits & 0x02); + break; + case OSMO_I460_RATE_32k: + demux_subchan_append_bit(schan, inbits & 0x01); + demux_subchan_append_bit(schan, inbits & 0x02); + demux_subchan_append_bit(schan, inbits & 0x04); + demux_subchan_append_bit(schan, inbits & 0x08); + break; + case OSMO_I460_RATE_64k: + demux_subchan_append_bit(schan, inbits & 0x01); + demux_subchan_append_bit(schan, inbits & 0x02); + demux_subchan_append_bit(schan, inbits & 0x04); + demux_subchan_append_bit(schan, inbits & 0x08); + demux_subchan_append_bit(schan, inbits & 0x10); + demux_subchan_append_bit(schan, inbits & 0x20); + demux_subchan_append_bit(schan, inbits & 0x40); + demux_subchan_append_bit(schan, inbits & 0x80); + break; + default: + OSMO_ASSERT(0); + } + } +} + +/*! Data from E1 timeslot into de-multiplexer + * \param[in] ts timeslot state + * \param[in] data input data bytes as received from E1/T1 + * \param[in] data_len length of data in bytes */ +void osmo_i460_demux_in(struct osmo_i460_timeslot *ts, const uint8_t *data, size_t data_len) +{ + struct osmo_i460_subchan *schan; + struct osmo_i460_subchan_demux *demux; + int i; + + /* fast path if entire 64k slot is used */ + if (osmo_i460_has_single_64k_schan(ts)) { + schan = &ts->schan[0]; + demux = &schan->demux; + if (demux->out_cb_bytes) + demux->out_cb_bytes(demux->user_data, data, data_len); + else { + ubit_t bits[data_len*8]; + osmo_pbit2ubit(bits, data, data_len*8); + demux->out_cb_bits(demux->user_data, bits, data_len*8); + } + return; + } + + /* Slow path iterating over all lchans */ + for (i = 0; i < ARRAY_SIZE(ts->schan); i++) { + schan = &ts->schan[i]; + if (schan->rate == OSMO_I460_RATE_NONE) + continue; + demux_subchan_extract_bits(schan, data, data_len); + } +} + + +/*********************************************************************** + * Multiplexer + ***********************************************************************/ + +/*! enqueue a to-be-transmitted message buffer containing unpacked bits */ +void osmo_i460_mux_enqueue(struct osmo_i460_subchan *schan, struct msgb *msg) +{ + OSMO_ASSERT(msgb_length(msg) > 0); + msgb_enqueue(&schan->mux.tx_queue, msg); +} + +/* mux: pull the next bit out of the given sub-channel */ +static ubit_t mux_schan_provide_bit(struct osmo_i460_subchan *schan) +{ + struct osmo_i460_subchan_mux *mux = &schan->mux; + struct msgb *msg; + ubit_t bit; + + /* if we don't have anything to transmit, return '1' bits */ + if (llist_empty(&mux->tx_queue)) + return 0x01; + + msg = llist_entry(mux->tx_queue.next, struct msgb, list); + bit = msgb_pull_u8(msg); + + /* free msgb if we have pulled the last bit */ + if (msgb_length(msg) <= 0) { + llist_del(&msg->list); + talloc_free(msg); + } + + return bit; +} + +/*! provide one byte with the subchan-specific bits of given sub-channel. + * \param[in] schan sub-channel that is to provide bits + * \parma[out] mask bitmask of those bits filled in + * \returns bits of given sub-channel */ +static uint8_t mux_subchan_provide_bits(struct osmo_i460_subchan *schan, uint8_t *mask) +{ + uint8_t outbits = 0; + uint8_t outmask; + + switch (schan->rate) { + case OSMO_I460_RATE_8k: + outbits = mux_schan_provide_bit(schan); + outmask = 0x01; + break; + case OSMO_I460_RATE_16k: + outbits |= mux_schan_provide_bit(schan) << 1; + outbits |= mux_schan_provide_bit(schan) << 0; + outmask = 0x03; + break; + case OSMO_I460_RATE_32k: + outbits |= mux_schan_provide_bit(schan) << 3; + outbits |= mux_schan_provide_bit(schan) << 2; + outbits |= mux_schan_provide_bit(schan) << 1; + outbits |= mux_schan_provide_bit(schan) << 0; + outmask = 0x0F; + break; + case OSMO_I460_RATE_64k: + outbits |= mux_schan_provide_bit(schan) << 7; + outbits |= mux_schan_provide_bit(schan) << 6; + outbits |= mux_schan_provide_bit(schan) << 5; + outbits |= mux_schan_provide_bit(schan) << 4; + outbits |= mux_schan_provide_bit(schan) << 3; + outbits |= mux_schan_provide_bit(schan) << 2; + outbits |= mux_schan_provide_bit(schan) << 1; + outbits |= mux_schan_provide_bit(schan) << 0; + outmask = 0xFF; + break; + default: + OSMO_ASSERT(0); + } + *mask = outmask << schan->bit_offset; + return outbits << schan->bit_offset; +} + +/* provide one byte of multiplexed I.460 bits */ +static uint8_t mux_timeslot_provide_bits(struct osmo_i460_timeslot *ts) +{ + int i, count = 0; + uint8_t ret = 0xff; /* unused bits must be '1' as per I.460 */ + + for (i = 0; i < ARRAY_SIZE(ts->schan); i++) { + struct osmo_i460_subchan *schan = &ts->schan[i]; + uint8_t bits, mask; + + if (schan->rate == OSMO_I460_RATE_NONE) + continue; + count++; + bits = mux_subchan_provide_bits(schan, &mask); + ret &= ~mask; + ret |= bits; + } + + return ret; +} + + +/*! Data from E1 timeslot into de-multiplexer + * \param[in] ts timeslot state + * \param[out] out caller-provided buffer where to store generated output bytes + * \param[in] out_len number of bytes to be stored at out + */ +int osmo_i460_mux_out(struct osmo_i460_timeslot *ts, uint8_t *out, size_t out_len) +{ + int i; + + /* fast path if entire 64k slot is used */ + //if (osmo_i460_has_single_64k_schan(ts)) { } + + for (i = 0; i < out_len; i++) + out[i] = mux_timeslot_provide_bits(ts); + + return out_len; +} + + +/*********************************************************************** + * Initialization / Control + ***********************************************************************/ + + +static int alloc_bitbuf(void *ctx, struct osmo_i460_subchan *schan, size_t num_bits) +{ + struct osmo_i460_subchan_demux *demux = &schan->demux; + + talloc_free(demux->out_bitbuf); + demux->out_bitbuf = talloc_zero_size(ctx, num_bits); + if (!demux->out_bitbuf) + return -ENOMEM; + demux->out_bitbuf_size = num_bits; + + return 0; +} + + +static int find_unused_subchan_idx(const struct osmo_i460_timeslot *ts) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ts->schan); i++) { + const struct osmo_i460_subchan *schan = &ts->schan[i]; + if (schan->rate == OSMO_I460_RATE_NONE) + return i; + } + return -1; +} + +/*! initialize an I.460 timeslot */ +void osmo_i460_ts_init(struct osmo_i460_timeslot *ts) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(ts->schan); i++) { + struct osmo_i460_subchan *schan = &ts->schan[i]; + + memset(schan, 0, sizeof(*schan)); + schan->rate = OSMO_I460_RATE_NONE; + INIT_LLIST_HEAD(&schan->mux.tx_queue); + } +} + +/*! add a new sub-channel to the given timeslot + * \param[in] ctx talloc context from where to allocate the internal buffer + * \param[in] ts timeslot to which to add a sub-channel + * \param[in] chd description of the sub-channel to be added + * \return pointer to sub-channel on success, NULL on error */ +struct osmo_i460_subchan * +osmo_i460_subchan_add(void *ctx, struct osmo_i460_timeslot *ts, const struct osmo_i460_schan_desc *chd) +{ + struct osmo_i460_subchan *schan; + int idx, rc; + + idx = find_unused_subchan_idx(ts); + if (idx < 0) + return NULL; + + schan = &ts->schan[idx]; + + schan->rate = chd->rate; + schan->bit_offset = chd->bit_offset; + + schan->demux.out_cb_bits = chd->demux.out_cb_bits; + schan->demux.out_cb_bytes = chd->demux.out_cb_bytes; + schan->demux.user_data = chd->demux.user_data; + rc = alloc_bitbuf(ctx, schan, chd->demux.num_bits); + if (rc < 0) { + memset(schan, 0, sizeof(*schan)); + return NULL; + } + + /* return number of schan in use */ + return schan; +} + +/* remove a su-channel from the multiplex */ +void osmo_i460_subchan_del(struct osmo_i460_subchan *schan) +{ + talloc_free(schan->demux.out_bitbuf); + memset(schan, 0, sizeof(*schan)); +} + +/*! @} */ diff --git a/src/gsm/libosmogsm.map b/src/gsm/libosmogsm.map index 70b39163..2000e6c0 100644 --- a/src/gsm/libosmogsm.map +++ b/src/gsm/libosmogsm.map @@ -667,5 +667,12 @@ osmo_cbsp_decode; osmo_cbsp_recv_buffered; osmo_cbsp_errstr; +osmo_i460_demux_in; +osmo_i460_mux_enqueue; +osmo_i460_mux_out; +osmo_i460_subchan_add; +osmo_i460_subchan_del; +osmo_i460_ts_init; + local: *; }; diff --git a/tests/Makefile.am b/tests/Makefile.am index 0d0327a3..5e810e6e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -35,6 +35,7 @@ check_PROGRAMS = timer/timer_test sms/sms_test ussd/ussd_test \ context/context_test \ gsm0502/gsm0502_test \ dtx/dtx_gsm0503_test \ + i460_mux/i460_mux_test \ $(NULL) if ENABLE_MSGFILE @@ -269,6 +270,9 @@ context_context_test_LDADD = $(LDADD) exec_exec_test_SOURCES = exec/exec_test.c exec_exec_test_LDADD = $(LDADD) +i460_mux_i460_mux_test_SOURCES = i460_mux/i460_mux_test.c +i460_mux_i460_mux_test_LDADD = $(LDADD) $(top_builddir)/src/gsm/libosmogsm.la + # The `:;' works around a Bash 3.2 bug when the output is not writeable. $(srcdir)/package.m4: $(top_srcdir)/configure.ac :;{ \ @@ -346,6 +350,7 @@ EXTRA_DIST = testsuite.at $(srcdir)/package.m4 $(TESTSUITE) \ gsm0502/gsm0502_test.ok \ dtx/dtx_gsm0503_test.ok \ exec/exec_test.ok exec/exec_test.err \ + i460_mux/i460_mux_test.ok \ $(NULL) DISTCLEANFILES = atconfig atlocal conv/gsm0503_test_vectors.c diff --git a/tests/i460_mux/i460_mux_test.c b/tests/i460_mux/i460_mux_test.c new file mode 100644 index 00000000..53144fde --- /dev/null +++ b/tests/i460_mux/i460_mux_test.c @@ -0,0 +1,397 @@ + +#include + +#include + +static void bits_cb(void *user_data, const ubit_t *bits, unsigned int num_bits) +{ + char *str = user_data; + printf("demux_bits_cb '%s': %s\n", str, osmo_ubit_dump(bits, num_bits)); +} + + +const struct osmo_i460_schan_desc scd64 = { + .rate = OSMO_I460_RATE_64k, + .bit_offset = 0, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "64k", + }, +}; + +const struct osmo_i460_schan_desc scd32_0 = { + .rate = OSMO_I460_RATE_32k, + .bit_offset = 0, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "32k_0", + }, +}; +const struct osmo_i460_schan_desc scd32_4 = { + .rate = OSMO_I460_RATE_32k, + .bit_offset = 4, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "32k_4", + }, +}; + +const struct osmo_i460_schan_desc scd16_0 = { + .rate = OSMO_I460_RATE_16k, + .bit_offset = 0, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "16k_0", + }, +}; +const struct osmo_i460_schan_desc scd16_2 = { + .rate = OSMO_I460_RATE_16k, + .bit_offset = 2, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "16k_2", + }, +}; +const struct osmo_i460_schan_desc scd16_4 = { + .rate = OSMO_I460_RATE_16k, + .bit_offset = 4, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "16k_4", + }, +}; +const struct osmo_i460_schan_desc scd16_6 = { + .rate = OSMO_I460_RATE_16k, + .bit_offset = 6, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "16k_6", + }, +}; + +const struct osmo_i460_schan_desc scd8_0 = { + .rate = OSMO_I460_RATE_8k, + .bit_offset = 0, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "8k_0", + }, +}; +const struct osmo_i460_schan_desc scd8_1 = { + .rate = OSMO_I460_RATE_8k, + .bit_offset = 1, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "8k_1", + }, +}; +const struct osmo_i460_schan_desc scd8_2 = { + .rate = OSMO_I460_RATE_8k, + .bit_offset = 2, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "8k_2", + }, +}; +const struct osmo_i460_schan_desc scd8_3 = { + .rate = OSMO_I460_RATE_8k, + .bit_offset = 3, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "8k_3", + }, +}; +const struct osmo_i460_schan_desc scd8_4 = { + .rate = OSMO_I460_RATE_8k, + .bit_offset = 4, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "8k_4", + }, +}; +const struct osmo_i460_schan_desc scd8_5 = { + .rate = OSMO_I460_RATE_8k, + .bit_offset = 5, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "8k_5", + }, +}; +const struct osmo_i460_schan_desc scd8_6 = { + .rate = OSMO_I460_RATE_8k, + .bit_offset = 6, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "8k_6", + }, +}; +const struct osmo_i460_schan_desc scd8_7 = { + .rate = OSMO_I460_RATE_8k, + .bit_offset = 7, + .demux = { + .num_bits = 40, + .out_cb_bits = bits_cb, + .out_cb_bytes = NULL, + .user_data = "8k_7", + }, +}; + +static void test_no_subchan(void) +{ + struct osmo_i460_timeslot _ts, *ts = &_ts; + + /* Initialization */ + printf("\n==> %s\n", __func__); + osmo_i460_ts_init(ts); + + /* feed in some data; expect nothing to happen */ + const uint8_t nothing[128] = { 0, }; + osmo_i460_demux_in(ts, nothing, sizeof(nothing)); + + /* pull bytes out of mux (should be all 0xff) */ + uint8_t buf[128]; + osmo_i460_mux_out(ts, buf, sizeof(buf)); + printf("out: %s\n", osmo_hexdump(buf, sizeof(buf))); +} + +static struct msgb *gen_alternating_bitmsg(unsigned int num_bits) +{ + struct msgb *msg = msgb_alloc(num_bits, "mux-in"); + int i; + for (i = 0; i < num_bits; i++) + msgb_put_u8(msg, i & 1); + return msg; +} + +static void test_64k_subchan(void) +{ + struct osmo_i460_timeslot _ts, *ts = &_ts; + + /* Initialization */ + printf("\n==> %s\n", __func__); + osmo_i460_ts_init(ts); + osmo_i460_subchan_add(NULL, ts, &scd64); + + /* demux */ + uint8_t sequence[128]; + int i; + for (i = 0; i < sizeof(sequence); i++) + sequence[i] = i; + osmo_i460_demux_in(ts, sequence, sizeof(sequence)); + + /* mux */ + struct msgb *msg = gen_alternating_bitmsg(128); + osmo_i460_mux_enqueue(&ts->schan[0], msg); + + uint8_t buf[16]; + osmo_i460_mux_out(ts, buf, sizeof(buf)); + printf("mux_out: %s\n", osmo_hexdump(buf, sizeof(buf))); + + osmo_i460_subchan_del(&ts->schan[0]); +} + +static void test_32k_subchan(void) +{ + struct osmo_i460_timeslot _ts, *ts = &_ts; + + /* Initialization */ + printf("\n==> %s\n", __func__); + osmo_i460_ts_init(ts); + osmo_i460_subchan_add(NULL, ts, &scd32_0); + osmo_i460_subchan_add(NULL, ts, &scd32_4); + + /* demux */ + uint8_t sequence[10]; + int i; + for (i = 0; i < sizeof(sequence); i++) + sequence[i] = 0; + sequence[0] = 0x0f; + sequence[1] = 0xf0; + sequence[2] = 0xff; + osmo_i460_demux_in(ts, sequence, sizeof(sequence)); + + /* mux */ + + /* test with only a single channel active */ + for (i = 0; i < 2; i++) { + struct msgb *msg = gen_alternating_bitmsg(128); + osmo_i460_mux_enqueue(&ts->schan[i], msg); + printf("%s-single-%u\n", __func__, i); + + uint8_t buf[16]; + int j; + for (j = 0; j < 3; j++) { + osmo_i460_mux_out(ts, buf, sizeof(buf)); + printf("mux_out: %s\n", osmo_hexdump(buf, sizeof(buf))); + } + } + + for (i = 0; i < 4; i++) + osmo_i460_subchan_del(&ts->schan[i]); +} + + + +static void test_16k_subchan(void) +{ + struct osmo_i460_timeslot _ts, *ts = &_ts; + + /* Initialization */ + printf("\n==> %s\n", __func__); + osmo_i460_ts_init(ts); + osmo_i460_subchan_add(NULL, ts, &scd16_0); + osmo_i460_subchan_add(NULL, ts, &scd16_2); + osmo_i460_subchan_add(NULL, ts, &scd16_4); + osmo_i460_subchan_add(NULL, ts, &scd16_6); + + /* demux */ + uint8_t sequence[20]; + int i; + for (i = 0; i < sizeof(sequence); i++) + sequence[i] = 0; + sequence[0] = 0x03; + sequence[1] = 0x0c; + sequence[2] = 0x30; + sequence[3] = 0xc0; + sequence[4] = 0xff; + osmo_i460_demux_in(ts, sequence, sizeof(sequence)); + + /* mux */ + + /* test with only a single channel active */ + for (i = 0; i < 4; i++) { + struct msgb *msg = gen_alternating_bitmsg(128); + osmo_i460_mux_enqueue(&ts->schan[i], msg); + printf("%s-single-%u\n", __func__, i); + + uint8_t buf[16]; + int j; + for (j = 0; j < 5; j++) { + osmo_i460_mux_out(ts, buf, sizeof(buf)); + printf("mux_out: %s\n", osmo_hexdump(buf, sizeof(buf))); + } + } + + for (i = 0; i < 4; i++) + osmo_i460_subchan_del(&ts->schan[i]); +} + + +static void test_8k_subchan(void) +{ + struct osmo_i460_timeslot _ts, *ts = &_ts; + + /* Initialization */ + printf("\n==> %s\n", __func__); + osmo_i460_ts_init(ts); + osmo_i460_subchan_add(NULL, ts, &scd8_0); + osmo_i460_subchan_add(NULL, ts, &scd8_1); + osmo_i460_subchan_add(NULL, ts, &scd8_2); + osmo_i460_subchan_add(NULL, ts, &scd8_3); + osmo_i460_subchan_add(NULL, ts, &scd8_4); + osmo_i460_subchan_add(NULL, ts, &scd8_5); + osmo_i460_subchan_add(NULL, ts, &scd8_6); + osmo_i460_subchan_add(NULL, ts, &scd8_7); + + /* demux */ + uint8_t sequence[40]; + int i; + for (i = 0; i < sizeof(sequence); i++) + sequence[i] = 0; + i = 0; + sequence[i++] = 0x01; + sequence[i++] = 0x02; + sequence[i++] = 0x04; + sequence[i++] = 0x08; + sequence[i++] = 0x0f; + sequence[i++] = 0x10; + sequence[i++] = 0x20; + sequence[i++] = 0x40; + sequence[i++] = 0x80; + sequence[i++] = 0xf0; + sequence[i++] = 0xff; + osmo_i460_demux_in(ts, sequence, sizeof(sequence)); + + /* mux */ + + /* test with only a single channel active */ + for (i = 0; i < 8; i++) { + struct msgb *msg = gen_alternating_bitmsg(64); + osmo_i460_mux_enqueue(&ts->schan[i], msg); + printf("%s-single-%u\n", __func__, i); + + uint8_t buf[16]; + int j; + for (j = 0; j < 5; j++) { + osmo_i460_mux_out(ts, buf, sizeof(buf)); + printf("mux_out: %s\n", osmo_hexdump(buf, sizeof(buf))); + } + } + + for (i = 0; i < 8; i++) + osmo_i460_subchan_del(&ts->schan[i]); +} + +/* activate only one sub-channel; expect unused bits to be '1' */ +static void test_unused_subchan(void) +{ + struct osmo_i460_timeslot _ts, *ts = &_ts; + + /* Initialization */ + printf("\n==> %s\n", __func__); + osmo_i460_ts_init(ts); + osmo_i460_subchan_add(NULL, ts, &scd16_0); + + /* mux */ + struct msgb *msg = gen_alternating_bitmsg(128); + memset(msgb_data(msg), 0, msgb_length(msg)); + osmo_i460_mux_enqueue(&ts->schan[0], msg); + printf("%s-single\n", __func__); + + uint8_t buf[16]; + int j; + for (j = 0; j < 5; j++) { + osmo_i460_mux_out(ts, buf, sizeof(buf)); + printf("mux_out: %s\n", osmo_hexdump(buf, sizeof(buf))); + } + + osmo_i460_subchan_del(&ts->schan[0]); +} + +int main(int argc, char **argv) +{ + test_no_subchan(); + test_64k_subchan(); + test_32k_subchan(); + test_16k_subchan(); + test_8k_subchan(); + test_unused_subchan(); +} diff --git a/tests/i460_mux/i460_mux_test.ok b/tests/i460_mux/i460_mux_test.ok new file mode 100644 index 00000000..b94fb7b9 --- /dev/null +++ b/tests/i460_mux/i460_mux_test.ok @@ -0,0 +1,115 @@ + +==> test_no_subchan +out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +==> test_64k_subchan +demux_bits_cb '64k': 0000000000000001000000100000001100000100000001010000011000000111000010000000100100001010000010110000110000001101000011100000111100010000000100010001001000010011000101000001010100010110000101110001100000011001000110100001101100011100000111010001111000011111001000000010000100100010001000110010010000100101001001100010011100101000001010010010101000101011001011000010110100101110001011110011000000110001001100100011001100110100001101010011011000110111001110000011100100111010001110110011110000111101001111100011111101000000010000010100001001000011010001000100010101000110010001110100100001001001010010100100101101001100010011010100111001001111010100000101000101010010010100110101010001010101010101100101011101011000010110010101101001011011010111000101110101011110010111110110000001100001011000100110001101100100011001010110011001100111011010000110100101101010011010110110110001101101011011100110111101110000011100010111001001110011011101000111010101110110011101110111100001111001011110100111101101111100011111010111111001111111 +mux_out: 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 55 + +==> test_32k_subchan +demux_bits_cb '32k_0': 1111000011110000000000000000000000000000 +demux_bits_cb '32k_4': 0000111111110000000000000000000000000000 +test_32k_subchan-single-0 +mux_out: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 +mux_out: f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 f5 +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_32k_subchan-single-1 +mux_out: 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f +mux_out: 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f 5f +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +==> test_16k_subchan +demux_bits_cb '16k_0': 1100000011000000000000000000000000000000 +demux_bits_cb '16k_2': 0011000011000000000000000000000000000000 +demux_bits_cb '16k_4': 0000110011000000000000000000000000000000 +demux_bits_cb '16k_6': 0000001111000000000000000000000000000000 +test_16k_subchan-single-0 +mux_out: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd +mux_out: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd +mux_out: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd +mux_out: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_16k_subchan-single-1 +mux_out: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 +mux_out: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 +mux_out: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 +mux_out: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_16k_subchan-single-2 +mux_out: df df df df df df df df df df df df df df df df +mux_out: df df df df df df df df df df df df df df df df +mux_out: df df df df df df df df df df df df df df df df +mux_out: df df df df df df df df df df df df df df df df +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_16k_subchan-single-3 +mux_out: 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f +mux_out: 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f +mux_out: 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f +mux_out: 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f 7f +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +==> test_8k_subchan +demux_bits_cb '8k_0': 1000100000100000000000000000000000000000 +demux_bits_cb '8k_1': 0100100000100000000000000000000000000000 +demux_bits_cb '8k_2': 0010100000100000000000000000000000000000 +demux_bits_cb '8k_3': 0001100000100000000000000000000000000000 +demux_bits_cb '8k_4': 0000010001100000000000000000000000000000 +demux_bits_cb '8k_5': 0000001001100000000000000000000000000000 +demux_bits_cb '8k_6': 0000000101100000000000000000000000000000 +demux_bits_cb '8k_7': 0000000011100000000000000000000000000000 +test_8k_subchan-single-0 +mux_out: fe ff fe ff fe ff fe ff fe ff fe ff fe ff fe ff +mux_out: fe ff fe ff fe ff fe ff fe ff fe ff fe ff fe ff +mux_out: fe ff fe ff fe ff fe ff fe ff fe ff fe ff fe ff +mux_out: fe ff fe ff fe ff fe ff fe ff fe ff fe ff fe ff +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_8k_subchan-single-1 +mux_out: fd ff fd ff fd ff fd ff fd ff fd ff fd ff fd ff +mux_out: fd ff fd ff fd ff fd ff fd ff fd ff fd ff fd ff +mux_out: fd ff fd ff fd ff fd ff fd ff fd ff fd ff fd ff +mux_out: fd ff fd ff fd ff fd ff fd ff fd ff fd ff fd ff +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_8k_subchan-single-2 +mux_out: fb ff fb ff fb ff fb ff fb ff fb ff fb ff fb ff +mux_out: fb ff fb ff fb ff fb ff fb ff fb ff fb ff fb ff +mux_out: fb ff fb ff fb ff fb ff fb ff fb ff fb ff fb ff +mux_out: fb ff fb ff fb ff fb ff fb ff fb ff fb ff fb ff +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_8k_subchan-single-3 +mux_out: f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff +mux_out: f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff +mux_out: f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff +mux_out: f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff f7 ff +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_8k_subchan-single-4 +mux_out: ef ff ef ff ef ff ef ff ef ff ef ff ef ff ef ff +mux_out: ef ff ef ff ef ff ef ff ef ff ef ff ef ff ef ff +mux_out: ef ff ef ff ef ff ef ff ef ff ef ff ef ff ef ff +mux_out: ef ff ef ff ef ff ef ff ef ff ef ff ef ff ef ff +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_8k_subchan-single-5 +mux_out: df ff df ff df ff df ff df ff df ff df ff df ff +mux_out: df ff df ff df ff df ff df ff df ff df ff df ff +mux_out: df ff df ff df ff df ff df ff df ff df ff df ff +mux_out: df ff df ff df ff df ff df ff df ff df ff df ff +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_8k_subchan-single-6 +mux_out: bf ff bf ff bf ff bf ff bf ff bf ff bf ff bf ff +mux_out: bf ff bf ff bf ff bf ff bf ff bf ff bf ff bf ff +mux_out: bf ff bf ff bf ff bf ff bf ff bf ff bf ff bf ff +mux_out: bf ff bf ff bf ff bf ff bf ff bf ff bf ff bf ff +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff +test_8k_subchan-single-7 +mux_out: 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff +mux_out: 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff +mux_out: 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff +mux_out: 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff 7f ff +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff + +==> test_unused_subchan +test_unused_subchan-single +mux_out: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc +mux_out: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc +mux_out: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc +mux_out: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc +mux_out: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff diff --git a/tests/testsuite.at b/tests/testsuite.at index bab57309..4ff6671a 100644 --- a/tests/testsuite.at +++ b/tests/testsuite.at @@ -375,3 +375,9 @@ cat $abs_srcdir/exec/exec_test.ok > expout cat $abs_srcdir/exec/exec_test.err > experr AT_CHECK([$abs_top_builddir/tests/exec/exec_test], [0], [expout], [experr]) AT_CLEANUP + +AT_SETUP([i460_mux]) +AT_KEYWORDS([i460_mux]) +cat $abs_srcdir/i460_mux/i460_mux_test.ok > expout +AT_CHECK([$abs_top_builddir/tests/i460_mux/i460_mux_test], [0], [expout], [ignore]) +AT_CLEANUP -- cgit v1.2.3