aboutsummaryrefslogtreecommitdiffstats
path: root/src/gsm/i460_mux.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/gsm/i460_mux.c')
-rw-r--r--src/gsm/i460_mux.c363
1 files changed, 363 insertions, 0 deletions
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));
+}
+
+/*! @} */