aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHarald Welte <laforge@osmocom.org>2020-05-14 11:42:53 +0200
committerlaforge <laforge@osmocom.org>2020-05-28 13:08:52 +0000
commitb795f03faff894d62b02dc03dd37e4136f055db1 (patch)
tree00a76b0e32dd2b0da826c413b646f5bb3fba029e
parent6d67032d5092bdcc8fcd1082a6fb136b4372691e (diff)
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
-rw-r--r--include/Makefile.am1
-rw-r--r--include/osmocom/gsm/i460_mux.h104
-rw-r--r--src/gsm/Makefile.am2
-rw-r--r--src/gsm/i460_mux.c363
-rw-r--r--src/gsm/libosmogsm.map7
-rw-r--r--tests/Makefile.am5
-rw-r--r--tests/i460_mux/i460_mux_test.c397
-rw-r--r--tests/i460_mux/i460_mux_test.ok115
-rw-r--r--tests/testsuite.at6
9 files changed, 999 insertions, 1 deletions
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 <laforge@gnumonks.org>
+ *
+ * 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 <stdint.h>
+#include <osmocom/core/bits.h>
+#include <osmocom/core/linuxlist.h>
+#include <osmocom/core/msgb.h>
+
+/* 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 <laforge@gnumonks.org>
+ *
+ * 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 <errno.h>
+
+#include <osmocom/core/bits.h>
+#include <osmocom/core/utils.h>
+#include <osmocom/core/msgb.h>
+#include <osmocom/gsm/i460_mux.h>
+
+/* 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 <osmocom/core/utils.h>
+
+#include <osmocom/gsm/i460_mux.h>
+
+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