diff options
Diffstat (limited to 'src/isdn')
-rw-r--r-- | src/isdn/Makefile.am | 26 | ||||
-rw-r--r-- | src/isdn/i460_mux.c | 387 | ||||
-rw-r--r-- | src/isdn/lapd_core.c | 2351 | ||||
-rw-r--r-- | src/isdn/libosmoisdn.map | 52 | ||||
-rw-r--r-- | src/isdn/v110.c | 590 | ||||
-rw-r--r-- | src/isdn/v110_ta.c | 881 |
6 files changed, 4287 insertions, 0 deletions
diff --git a/src/isdn/Makefile.am b/src/isdn/Makefile.am new file mode 100644 index 00000000..51455452 --- /dev/null +++ b/src/isdn/Makefile.am @@ -0,0 +1,26 @@ +# This is _NOT_ the library release version, it's an API version. +# Please read chapter "Library interface versions" of the libtool documentation +# before making any modifications: https://www.gnu.org/software/libtool/manual/html_node/Versioning.html +LIBVERSION=2:0:2 + +AM_CPPFLAGS = -I$(top_srcdir)/include -I$(top_builddir)/include +AM_CFLAGS = -Wall $(TALLOC_CFLAGS) + +if ENABLE_PSEUDOTALLOC +AM_CPPFLAGS += -I$(top_srcdir)/src/pseudotalloc +endif + +noinst_LTLIBRARIES = libisdnint.la +lib_LTLIBRARIES = libosmoisdn.la + +libisdnint_la_SOURCES = i460_mux.c lapd_core.c v110.c v110_ta.c + +libisdnint_la_LDFLAGS = -no-undefined +libisdnint_la_LIBADD = $(top_builddir)/src/core/libosmocore.la + +libosmoisdn_la_SOURCES = +libosmoisdn_la_LDFLAGS = $(LTLDFLAGS_OSMOISDN) -version-info $(LIBVERSION) -no-undefined +libosmoisdn_la_LIBADD = libisdnint.la $(TALLOC_LIBS) + +EXTRA_DIST = libosmoisdn.map +EXTRA_libosmoisdn_la_DEPENDENCIES = libosmoisdn.map diff --git a/src/isdn/i460_mux.c b/src/isdn/i460_mux.c new file mode 100644 index 00000000..b070bbdc --- /dev/null +++ b/src/isdn/i460_mux.c @@ -0,0 +1,387 @@ +/*! \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. + */ + +#include <errno.h> + +#include <osmocom/core/bits.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/msgb.h> +#include <osmocom/isdn/i460_mux.h> + +/*! count the number of sub-channels in this I.460 slot. + * \param[in] ts timeslot that holds the I.460 subchannels. + * \return number of subchannels. */ +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(schan, 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(schan, 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]; + /* I.460 defines sub-channel 0 is using bit positions 1+2 (the two + * most significant bits, hence we extract msb-first */ + 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 & 0x80); + break; + case OSMO_I460_RATE_16k: + demux_subchan_append_bit(schan, inbits & 0x80); + demux_subchan_append_bit(schan, inbits & 0x40); + break; + case OSMO_I460_RATE_32k: + demux_subchan_append_bit(schan, inbits & 0x80); + demux_subchan_append_bit(schan, inbits & 0x40); + demux_subchan_append_bit(schan, inbits & 0x20); + demux_subchan_append_bit(schan, inbits & 0x10); + break; + case OSMO_I460_RATE_64k: + demux_subchan_append_bit(schan, inbits & 0x80); + demux_subchan_append_bit(schan, inbits & 0x40); + demux_subchan_append_bit(schan, inbits & 0x20); + demux_subchan_append_bit(schan, inbits & 0x10); + demux_subchan_append_bit(schan, inbits & 0x08); + demux_subchan_append_bit(schan, inbits & 0x04); + demux_subchan_append_bit(schan, inbits & 0x02); + demux_subchan_append_bit(schan, inbits & 0x01); + break; + default: + OSMO_ASSERT(0); + } + } +} + +/*! Feed multiplexed data (from an 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(schan, demux->user_data, data, data_len); + else { + ubit_t bits[data_len*8]; + osmo_pbit2ubit(bits, data, data_len*8); + demux->out_cb_bits(schan, 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)) { + /* User code now has a last chance to put something into the queue. */ + if (mux->in_cb_queue_empty) + mux->in_cb_queue_empty(schan, mux->user_data); + + /* If the queue is still empty, return idle 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 + * \param[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; + + /* I.460 defines sub-channel 0 is using bit positions 1+2 (the two + * most significant bits, hence we provide msb-first */ + + switch (schan->rate) { + case OSMO_I460_RATE_8k: + outbits = mux_schan_provide_bit(schan) << 7; + outmask = 0x80; + break; + case OSMO_I460_RATE_16k: + outbits |= mux_schan_provide_bit(schan) << 7; + outbits |= mux_schan_provide_bit(schan) << 6; + outmask = 0xC0; + break; + case OSMO_I460_RATE_32k: + 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; + outmask = 0xF0; + 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) +{ + uint8_t ret = 0xff; /* unused bits must be '1' as per I.460 */ + + for (int 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; + bits = mux_subchan_provide_bits(schan, &mask); + ret &= ~mask; + ret |= bits; + } + + return ret; +} + + +/*! Get multiplexed data from de-multiplexer (for feeding it into an E1 timeslot). + * \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; +} + +/* reset subchannel struct into a defined state */ +static void subchan_reset(struct osmo_i460_subchan *schan, bool first_time) +{ + /* Before we zero out the subchannel struct, we must be sure that the + * tx_queue is cleared and all dynamically allocated memory is freed. + * However, on an uninitalized subchannel struct we can not be sure + * that the pointers are valid. If the subchannel is reset the first + * time the caller must set first_time to true. */ + if (!first_time) { + if (schan->demux.out_bitbuf) + talloc_free(schan->demux.out_bitbuf); + msgb_queue_free(&schan->mux.tx_queue); + } + + /* Reset subchannel to a defined state */ + memset(schan, 0, sizeof(*schan)); + schan->rate = OSMO_I460_RATE_NONE; + INIT_LLIST_HEAD(&schan->mux.tx_queue); +} + +/*! 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]; + schan->ts = ts; + subchan_reset(schan, true); + } +} + +/*! 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; + schan->mux.in_cb_queue_empty = chd->mux.in_cb_queue_empty; + schan->mux.user_data = chd->mux.user_data; + rc = alloc_bitbuf(ctx, schan, chd->demux.num_bits); + if (rc < 0) { + subchan_reset(schan, false); + 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) +{ + subchan_reset(schan, false); +} + +/*! @} */ diff --git a/src/isdn/lapd_core.c b/src/isdn/lapd_core.c new file mode 100644 index 00000000..b32ed263 --- /dev/null +++ b/src/isdn/lapd_core.c @@ -0,0 +1,2351 @@ +/*! \file lapd_core.c + * LAPD core implementation */ +/* + * (C) 2010-2020 by Harald Welte <laforge@gnumonks.org> + * (C) 2010-2011 by Andreas Eversberg <jolly@eversberg.eu> + * + * All Rights Reserved + * + * 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. + * + */ + +/*! \addtogroup lapd + * @{ + * + * Osmocom LAPD core, used for Q.921, LAPDm and others. + * + * Notes on Buffering: rcv_buffer, tx_queue, tx_hist, send_buffer, send_queue + * + * RX data is stored in the rcv_buffer (pointer). If the message is complete, it + * is removed from rcv_buffer pointer and forwarded to L3. If the RX data is + * received while there is an incomplete rcv_buffer, it is appended to it. + * + * TX data is stored in the send_queue first. When transmitting a frame, + * the first message in the send_queue is moved to the send_buffer. There it + * resides until all fragments are acknowledged. Fragments to be sent by I + * frames are stored in the tx_hist buffer for resend, if required. Also the + * current fragment is copied into the tx_queue. There it resides until it is + * forwarded to layer 1. + * + * In case we have SAPI 0, we only have a window size of 1, so the unack- + * nowledged message resides always in the send_buffer. In case of a suspend, + * it can be written back to the first position of the send_queue. + * + * The layer 1 normally sends a PH-READY-TO-SEND. But because we use + * asynchronous transfer between layer 1 and layer 2 (serial link), we must + * send a frame before layer 1 reaches the right timeslot to send it. So we + * move the tx_queue to layer 1 when there is not already a pending frame, and + * wait until acknowledge after the frame has been sent. If we receive an + * acknowledge, we can send the next frame from the buffer, if any. + * + * The moving of tx_queue to layer 1 may also trigger T200, if desired. Also it + * will trigger next I frame, if possible. + * + * T203 is optional. It will be stated when entering MF EST state. It will also + * be started when I or S frame is received in that state . It will be + * restarted in the lapd_acknowledge() function, in case outstanding frames + * will not trigger T200. It will be stoped, when T200 is started in MF EST + * state. It will also be stoped when leaving MF EST state. + * + * \file lapd_core.c + */ + +/* Enable this to test content resolution on network side: + * - The first SABM is received, UA is dropped. + * - The phone repeats SABM, but it's content is wrong, so it is ignored + * - The phone repeats SABM again, content is right, so UA is sent. + */ +//#define TEST_CONTENT_RESOLUTION_NETWORK + +#include <stdio.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> +#include <errno.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/timer.h> +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/talloc.h> +#include <osmocom/isdn/lapd_core.h> +#include <osmocom/gsm/rsl.h> + +/* TS 04.06 Table 4 / Section 3.8.1 */ +#define LAPD_U_SABM 0x7 +#define LAPD_U_SABME 0xf +#define LAPD_U_DM 0x3 +#define LAPD_U_UI 0x0 +#define LAPD_U_DISC 0x8 +#define LAPD_U_UA 0xC +#define LAPD_U_FRMR 0x11 + +#define LAPD_S_RR 0x0 +#define LAPD_S_RNR 0x1 +#define LAPD_S_REJ 0x2 + +#define CR_USER2NET_CMD 0 +#define CR_USER2NET_RESP 1 +#define CR_NET2USER_CMD 1 +#define CR_NET2USER_RESP 0 + +#define LAPD_HEADROOM 56 +#define LAPD_TAILROOM 16 + +#define SBIT(a) (1 << a) +#define ALL_STATES 0xffffffff + +static void lapd_t200_cb(void *data); +static void lapd_t203_cb(void *data); +static int lapd_send_i(struct lapd_datalink *dl, int line, bool rts); +static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx); + +/* UTILITY FUNCTIONS */ + +struct msgb *lapd_msgb_alloc(int length, const char *name) +{ + /* adding space for padding, FIXME: add as an option */ + if (length < 21) + length = 21; + return msgb_alloc_headroom(length + LAPD_HEADROOM + LAPD_TAILROOM, LAPD_HEADROOM, name); +} + +static inline uint8_t do_mod(uint8_t x, uint8_t m) +{ + return x & (m - 1); +} + +static inline uint8_t inc_mod(uint8_t x, uint8_t m) +{ + return (x + 1) & (m - 1); +} + +static inline uint8_t add_mod(uint8_t x, uint8_t y, uint8_t m) +{ + return (x + y) & (m - 1); +} + +static inline uint8_t sub_mod(uint8_t x, uint8_t y, uint8_t m) +{ + return (x - y) & (m - 1); /* handle negative results correctly */ +} + +static void lapd_dl_flush_send(struct lapd_datalink *dl) +{ + struct msgb *msg; + + /* Flush send-queue */ + while ((msg = msgb_dequeue(&dl->send_queue))) + msgb_free(msg); + + /* Clear send-buffer */ + msgb_free(dl->send_buffer); + dl->send_buffer = NULL; +} + +static void lapd_dl_flush_hist(struct lapd_datalink *dl) +{ + unsigned int i; + + if (!dl->range_hist || !dl->tx_hist) + return; + + for (i = 0; i < dl->range_hist; i++) { + if (dl->tx_hist[i].msg) { + msgb_free(dl->tx_hist[i].msg); + dl->tx_hist[i].msg = NULL; + } + } +} + +static void lapd_dl_flush_tx_queue(struct lapd_datalink *dl) +{ + struct msgb *msg; + + while ((msg = msgb_dequeue(&dl->tx_queue))) + msgb_free(msg); +} + +static void lapd_dl_flush_tx(struct lapd_datalink *dl) +{ + lapd_dl_flush_tx_queue(dl); + lapd_dl_flush_hist(dl); +} + +/* Figure B.2/Q.921 */ +const struct value_string lapd_state_names[] = { + OSMO_VALUE_STRING(LAPD_STATE_NULL), + OSMO_VALUE_STRING(LAPD_STATE_TEI_UNASS), + OSMO_VALUE_STRING(LAPD_STATE_ASS_TEI_WAIT), + OSMO_VALUE_STRING(LAPD_STATE_EST_TEI_WAIT), + OSMO_VALUE_STRING(LAPD_STATE_IDLE), + OSMO_VALUE_STRING(LAPD_STATE_SABM_SENT), + OSMO_VALUE_STRING(LAPD_STATE_DISC_SENT), + OSMO_VALUE_STRING(LAPD_STATE_MF_EST), + OSMO_VALUE_STRING(LAPD_STATE_TIMER_RECOV), + { 0, NULL } +}; + +static inline const char *lapd_state_name(enum lapd_state state) +{ + return get_value_string(lapd_state_names, state); +} + +static void lapd_start_t200(struct lapd_datalink *dl) +{ + if ((dl->lapd_flags & LAPD_F_RTS)) { + if (dl->t200_rts != LAPD_T200_RTS_OFF) + return; + LOGDL(dl, LOGL_INFO, "Start T200. (pending until triggered by RTS)\n"); + dl->t200_rts = LAPD_T200_RTS_PENDING; + } else { + if (osmo_timer_pending(&dl->t200)) + return; + LOGDL(dl, LOGL_INFO, "Start T200 (timeout=%d.%06ds).\n", dl->t200_sec, dl->t200_usec); + osmo_timer_schedule(&dl->t200, dl->t200_sec, dl->t200_usec); + } +} + +/*! Handle timeout condition of T200 in RTS mode. + * The caller (LAPDm code) implements the T200 timer and must detect timeout condition. + * The function gets called by LAPDm code when it detects a timeout of T200. + * \param[in] dl caller-allocated datalink structure */ +int lapd_t200_timeout(struct lapd_datalink *dl) +{ + OSMO_ASSERT((dl->lapd_flags & LAPD_F_RTS)); + + if (dl->t200_rts != LAPD_T200_RTS_RUNNING) + return -EINVAL; + + dl->t200_rts = LAPD_T200_RTS_OFF; + + lapd_t200_cb(dl); + + return 0; +} + +static void lapd_start_t203(struct lapd_datalink *dl) +{ + if (osmo_timer_pending(&dl->t203)) + return; + LOGDL(dl, LOGL_INFO, "start T203\n"); + osmo_timer_schedule(&dl->t203, dl->t203_sec, dl->t203_usec); +} + +static void lapd_stop_t200(struct lapd_datalink *dl) +{ + if ((dl->lapd_flags & LAPD_F_RTS)) { + if (dl->t200_rts == LAPD_T200_RTS_OFF) + return; + dl->t200_rts = LAPD_T200_RTS_OFF; + } else { + if (!osmo_timer_pending(&dl->t200)) + return; + osmo_timer_del(&dl->t200); + } + LOGDL(dl, LOGL_INFO, "stop T200\n"); +} + +static bool lapd_is_t200_started(struct lapd_datalink *dl) +{ + if ((dl->lapd_flags & LAPD_F_RTS)) + return (dl->t200_rts != LAPD_T200_RTS_OFF); + else + return osmo_timer_pending(&dl->t200); +} + +static void lapd_stop_t203(struct lapd_datalink *dl) +{ + if (!osmo_timer_pending(&dl->t203)) + return; + LOGDL(dl, LOGL_INFO, "stop T203\n"); + osmo_timer_del(&dl->t203); +} + +static void lapd_dl_newstate(struct lapd_datalink *dl, uint32_t state) +{ + LOGDL(dl, LOGL_INFO, "new state %s -> %s\n", + lapd_state_name(dl->state), lapd_state_name(state)); + + if (state != LAPD_STATE_MF_EST && dl->state == LAPD_STATE_MF_EST) { + /* stop T203 on leaving MF EST state, if running */ + lapd_stop_t203(dl); + /* remove content res. (network side) on leaving MF EST state */ + msgb_free(dl->cont_res); + dl->cont_res = NULL; + } + + /* start T203 on entering MF EST state, if enabled */ + if ((dl->t203_sec || dl->t203_usec) + && state == LAPD_STATE_MF_EST && dl->state != LAPD_STATE_MF_EST) + lapd_start_t203(dl); + + dl->state = state; +} + +void *tall_lapd_ctx = NULL; + +/*! Initialize LAPD datalink instance and allocate history + * \param[in] dl caller-allocated datalink structure + * \param[in] k maximum number of unacknowledged frames + * \param[in] v_range range of sequence numbers + * \param[in] maxf maximum frame size (after defragmentation) + * \param[in] name human-readable name for this LAPD datalink */ +void lapd_dl_init2(struct lapd_datalink *dl, uint8_t k, uint8_t v_range, int maxf, + const char *name) +{ + int m; + + memset(dl, 0, sizeof(*dl)); + INIT_LLIST_HEAD(&dl->send_queue); + INIT_LLIST_HEAD(&dl->tx_queue); + dl->reestablish = 1; + dl->n200_est_rel = 3; + dl->n200 = 3; + dl->t200_sec = 1; + dl->t200_usec = 0; + osmo_timer_setup(&dl->t200, lapd_t200_cb, dl); + dl->t203_sec = 10; + dl->t203_usec = 0; + osmo_timer_setup(&dl->t203, lapd_t203_cb, dl); + dl->maxf = maxf; + if (k > v_range - 1) + k = v_range - 1; + dl->k = k; + dl->v_range = v_range; + + /* Calculate modulo for history array: + * - The history range must be at least k+1. + * - The history range must be 2^x, where x is as low as possible. + */ + k++; + for (m = 0x80; m; m >>= 1) { + if ((m & k)) { + if (k > m) + m <<= 1; + dl->range_hist = m; + break; + } + } + + if (!tall_lapd_ctx) { + tall_lapd_ctx = talloc_named_const(NULL, 1, "lapd context"); + OSMO_ASSERT(tall_lapd_ctx); + } + + talloc_free(dl->name); + if (name) + dl->name = talloc_strdup(tall_lapd_ctx, name); + else + dl->name = talloc_asprintf(tall_lapd_ctx, "dl=%p", dl); + + LOGDL(dl, LOGL_INFO, "Init DL layer: sequence range = %d, k = %d, " + "history range = %d\n", dl->v_range, dl->k, dl->range_hist); + + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + + dl->tx_hist = talloc_zero_array(tall_lapd_ctx, + struct lapd_history, dl->range_hist); +} + +/*! Initialize LAPD datalink instance and allocate history + * \param[in] dl caller-allocated datalink structure + * \param[in] k maximum number of unacknowledged frames + * \param[in] v_range range of sequence numbers + * \param[in] maxf maximum frame size (after defragmentation) */ +void lapd_dl_init(struct lapd_datalink *dl, uint8_t k, uint8_t v_range, int maxf) +{ + lapd_dl_init2(dl, k, v_range, maxf, NULL); +} + +void lapd_dl_set_name(struct lapd_datalink *dl, const char *name) +{ + if (!name) + return; + osmo_talloc_replace_string(tall_lapd_ctx, &dl->name, name); +} + +/* reset to IDLE state */ +void lapd_dl_reset(struct lapd_datalink *dl) +{ + LOGDL(dl, LOGL_INFO, "Resetting LAPD instance\n"); + /* enter idle state (and remove eventual cont_res) */ + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + /* flush buffer */ + lapd_dl_flush_tx(dl); + lapd_dl_flush_send(dl); + /* Discard partly received L3 message */ + msgb_free(dl->rcv_buffer); + dl->rcv_buffer = NULL; + /* stop Timers */ + lapd_stop_t200(dl); + lapd_stop_t203(dl); + if (dl->state == LAPD_STATE_IDLE) + return; + /* enter idle state (and remove eventual cont_res) */ + lapd_dl_newstate(dl, LAPD_STATE_IDLE); +} + +/*! Set lapd_flags to change behaviour + * \param[in] dl \ref lapd_datalink instance + * \param[in] flags \ref lapd_flags */ +int lapd_dl_set_flags(struct lapd_datalink *dl, unsigned int flags) +{ + if (lapd_is_t200_started(dl) && (flags & LAPD_F_RTS) != (dl->lapd_flags & LAPD_F_RTS)) { + LOGDL(dl, LOGL_ERROR, "Changing RTS flag not allowed while T200 is running.\n"); + return -EINVAL; + } + + dl->lapd_flags = flags; + + return 0; +} + +/* reset and de-allocate history buffer */ +void lapd_dl_exit(struct lapd_datalink *dl) +{ + /* free all ressources except history buffer */ + lapd_dl_reset(dl); + + /* enter null state */ + lapd_dl_newstate(dl, LAPD_STATE_NULL); + + /* free history buffer list */ + talloc_free(dl->tx_hist); + dl->tx_hist = NULL; + talloc_free(dl->name); + dl->name = NULL; +} + +/*! Set the \ref lapdm_mode of a LAPDm entity */ +int lapd_set_mode(struct lapd_datalink *dl, enum lapd_mode mode) +{ + switch (mode) { + case LAPD_MODE_USER: + dl->cr.loc2rem.cmd = CR_USER2NET_CMD; + dl->cr.loc2rem.resp = CR_USER2NET_RESP; + dl->cr.rem2loc.cmd = CR_NET2USER_CMD; + dl->cr.rem2loc.resp = CR_NET2USER_RESP; + break; + case LAPD_MODE_NETWORK: + dl->cr.loc2rem.cmd = CR_NET2USER_CMD; + dl->cr.loc2rem.resp = CR_NET2USER_RESP; + dl->cr.rem2loc.cmd = CR_USER2NET_CMD; + dl->cr.rem2loc.resp = CR_USER2NET_RESP; + break; + default: + return -EINVAL; + } + dl->mode = mode; + + return 0; +} + +/* send DL message with optional msgb */ +static int send_dl_l3(uint8_t prim, uint8_t op, struct lapd_msg_ctx *lctx, + struct msgb *msg) +{ + struct lapd_datalink *dl = lctx->dl; + struct osmo_dlsap_prim dp; + + osmo_prim_init(&dp.oph, 0, prim, op, msg); + return dl->send_dlsap(&dp, lctx); +} + +/* send simple DL message */ +static inline int send_dl_simple(uint8_t prim, uint8_t op, + struct lapd_msg_ctx *lctx) +{ + return send_dl_l3(prim, op, lctx, NULL); +} + +/* send MDL-ERROR INDICATION */ +static int mdl_error(uint8_t cause, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct osmo_dlsap_prim dp; + + LOGDL(dl, LOGL_NOTICE, + "sending MDL-ERROR-IND cause %d from state %s\n", + cause, lapd_state_name(dl->state)); + osmo_prim_init(&dp.oph, 0, PRIM_MDL_ERROR, PRIM_OP_INDICATION, NULL); + dp.u.error_ind.cause = cause; + return dl->send_dlsap(&dp, lctx); +} + +/* send UA response */ +static int lapd_send_ua(struct lapd_msg_ctx *lctx, uint8_t len, uint8_t *data) +{ + struct msgb *msg = lapd_msgb_alloc(len, "LAPD UA"); + struct lapd_msg_ctx nctx; + struct lapd_datalink *dl = lctx->dl; + + memcpy(&nctx, lctx, sizeof(nctx)); + msg->l3h = msgb_put(msg, len); + if (len) + memcpy(msg->l3h, data, len); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.resp; + nctx.format = LAPD_FORM_U; + nctx.s_u = LAPD_U_UA; + /* keep nctx.p_f */ + nctx.length = len; + nctx.more = 0; + + return dl->send_ph_data_req(&nctx, msg); +} + +/* send DM response */ +static int lapd_send_dm(struct lapd_msg_ctx *lctx) +{ + struct msgb *msg = lapd_msgb_alloc(0, "LAPD DM"); + struct lapd_msg_ctx nctx; + struct lapd_datalink *dl = lctx->dl; + + memcpy(&nctx, lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.resp; + nctx.format = LAPD_FORM_U; + nctx.s_u = LAPD_U_DM; + /* keep nctx.p_f */ + nctx.length = 0; + nctx.more = 0; + + return dl->send_ph_data_req(&nctx, msg); +} + +/* send RR response / command */ +static int lapd_send_rr(struct lapd_msg_ctx *lctx, uint8_t f_bit, uint8_t cmd) +{ + struct msgb *msg = lapd_msgb_alloc(0, "LAPD RR"); + struct lapd_msg_ctx nctx; + struct lapd_datalink *dl = lctx->dl; + + memcpy(&nctx, lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = (cmd) ? dl->cr.loc2rem.cmd : dl->cr.loc2rem.resp; + nctx.format = LAPD_FORM_S; + nctx.s_u = LAPD_S_RR; + nctx.p_f = f_bit; + nctx.n_recv = dl->v_recv; + nctx.length = 0; + nctx.more = 0; + + return dl->send_ph_data_req(&nctx, msg); +} + +/* send RNR response / command */ +static int lapd_send_rnr(struct lapd_msg_ctx *lctx, uint8_t f_bit, uint8_t cmd) +{ + struct msgb *msg = lapd_msgb_alloc(0, "LAPD RNR"); + struct lapd_msg_ctx nctx; + struct lapd_datalink *dl = lctx->dl; + + memcpy(&nctx, lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = (cmd) ? dl->cr.loc2rem.cmd : dl->cr.loc2rem.resp; + nctx.format = LAPD_FORM_S; + nctx.s_u = LAPD_S_RNR; + nctx.p_f = f_bit; + nctx.n_recv = dl->v_recv; + nctx.length = 0; + nctx.more = 0; + + return dl->send_ph_data_req(&nctx, msg); +} + +/* send REJ response */ +static int lapd_send_rej(struct lapd_msg_ctx *lctx, uint8_t f_bit) +{ + struct msgb *msg = lapd_msgb_alloc(0, "LAPD REJ"); + struct lapd_msg_ctx nctx; + struct lapd_datalink *dl = lctx->dl; + + memcpy(&nctx, lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.resp; + nctx.format = LAPD_FORM_S; + nctx.s_u = LAPD_S_REJ; + nctx.p_f = f_bit; + nctx.n_recv = dl->v_recv; + nctx.length = 0; + nctx.more = 0; + + return dl->send_ph_data_req(&nctx, msg); +} + +/* resend SABM or DISC message */ +static int lapd_send_resend(struct lapd_datalink *dl) +{ + struct msgb *msg; + uint8_t h = do_mod(dl->v_send, dl->range_hist); + int length = dl->tx_hist[h].msg->len; + struct lapd_msg_ctx nctx; + + /* assemble message */ + memcpy(&nctx, &dl->lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.cmd; + nctx.format = LAPD_FORM_U; + if (dl->state == LAPD_STATE_SABM_SENT) + nctx.s_u = (dl->use_sabme) ? LAPD_U_SABME : LAPD_U_SABM; + else + nctx.s_u = LAPD_U_DISC; + nctx.p_f = 1; + nctx.length = length; + nctx.more = 0; + + /* Resend SABM/DISC from tx_hist */ + msg = lapd_msgb_alloc(length, "LAPD resend"); + msg->l3h = msgb_put(msg, length); + if (length) + memcpy(msg->l3h, dl->tx_hist[h].msg->data, length); + + return dl->send_ph_data_req(&nctx, msg); +} + +/* reestablish link */ +static int lapd_reestablish(struct lapd_datalink *dl) +{ + struct osmo_dlsap_prim dp; + struct msgb *msg; + + LOGDL(dl, LOGL_DEBUG, "LAPD reestablish\n"); + + msg = lapd_msgb_alloc(0, "DUMMY"); + osmo_prim_init(&dp.oph, 0, PRIM_DL_EST, PRIM_OP_REQUEST, msg); + + return lapd_est_req(&dp, &dl->lctx); +} + +/* Timer callback on T200 expiry */ +static void lapd_t200_cb(void *data) +{ + struct lapd_datalink *dl = data; + + LOGDL(dl, LOGL_INFO, "Timeout T200 state=%s\n", lapd_state_name(dl->state)); + + switch (dl->state) { + case LAPD_STATE_SABM_SENT: + /* 5.4.1.3 */ + /* increment re-transmission counter */ + dl->retrans_ctr++; + if (dl->retrans_ctr >= dl->n200_est_rel + 1) { + /* flush tx and send buffers */ + lapd_dl_flush_tx(dl); + lapd_dl_flush_send(dl); + /* go back to idle state */ + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + /* NOTE: we must not change any other states or buffers + * and queues, since we may reconnect after handover + * failure. the buffered messages is replaced there */ + /* send MDL ERROR INIDCATION to L3 */ + mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx); + /* send RELEASE INDICATION to L3 */ + send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, + &dl->lctx); + break; + } + /* retransmit SABM command */ + lapd_send_resend(dl); + /* restart T200 (PH-READY-TO-SEND) */ + lapd_start_t200(dl); + break; + case LAPD_STATE_DISC_SENT: + /* 5.4.4.3 */ + /* increment re-transmission counter */ + dl->retrans_ctr++; + if (dl->retrans_ctr >= dl->n200_est_rel + 1) { + /* send MDL ERROR INIDCATION to L3 */ + mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx); + /* flush tx and send buffers */ + lapd_dl_flush_tx(dl); + lapd_dl_flush_send(dl); + /* go back to idle state */ + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + /* NOTE: we must not change any other states or buffers + * and queues, since we may reconnect after handover + * failure. the buffered messages is replaced there */ + /* send RELEASE INDICATION to L3 */ + send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx); + break; + } + /* retransmit DISC command */ + lapd_send_resend(dl); + /* restart T200 (PH-READY-TO-SEND) */ + lapd_start_t200(dl); + break; + case LAPD_STATE_MF_EST: + /* 5.5.7 */ + dl->retrans_ctr = 0; + lapd_dl_newstate(dl, LAPD_STATE_TIMER_RECOV); + /* fall through */ + case LAPD_STATE_TIMER_RECOV: + dl->retrans_ctr++; + if (dl->retrans_ctr <= dl->n200) { + uint8_t vs = sub_mod(dl->v_send, 1, dl->v_range); + uint8_t h = do_mod(vs, dl->range_hist); + /* retransmit I frame (V_s-1) with P=1, if any */ + if (dl->tx_hist[h].msg) { + struct msgb *msg; + int length = dl->tx_hist[h].msg->len; + struct lapd_msg_ctx nctx; + + LOGDL(dl, LOGL_INFO, "retransmit last frame V(S)=%d\n", vs); + /* Create I frame (segment) from tx_hist */ + memcpy(&nctx, &dl->lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.cmd; + nctx.format = LAPD_FORM_I; + nctx.p_f = 1; + nctx.n_send = vs; + nctx.n_recv = dl->v_recv; + nctx.length = length; + nctx.more = dl->tx_hist[h].more; + msg = lapd_msgb_alloc(length, "LAPD I resend"); + msg->l3h = msgb_put(msg, length); + memcpy(msg->l3h, dl->tx_hist[h].msg->data, + length); + dl->send_ph_data_req(&nctx, msg); + } else { + /* OR send appropriate supervision frame with P=1 */ + if (!dl->own_busy && !dl->seq_err_cond) { + lapd_send_rr(&dl->lctx, 1, 1); + /* NOTE: In case of sequence error + * condition, the REJ frame has been + * transmitted when entering the + * condition, so it has not be done + * here + */ + } else if (dl->own_busy) { + lapd_send_rnr(&dl->lctx, 1, 1); + } else { + LOGDL(dl, LOGL_INFO, "unhandled, pls. fix\n"); + } + } + /* restart T200 (PH-READY-TO-SEND) */ + lapd_start_t200(dl); + } else { + /* send MDL ERROR INIDCATION to L3 */ + mdl_error(MDL_CAUSE_T200_EXPIRED, &dl->lctx); + /* reestablish */ + if (!dl->reestablish) + break; + LOGDL(dl, LOGL_NOTICE, "N200+1 reached, performingreestablishment\n"); + lapd_reestablish(dl); + } + break; + default: + LOGDL(dl, LOGL_INFO, "T200 expired in unexpected dl->state %s)\n", + lapd_state_name(dl->state)); + } +} + +/* Timer callback on T203 expiry */ +static void lapd_t203_cb(void *data) +{ + struct lapd_datalink *dl = data; + + LOGDL(dl, LOGL_INFO, "Timeout T203 state=%s\n", lapd_state_name(dl->state)); + + if (dl->state != LAPD_STATE_MF_EST) { + LOGDL(dl, LOGL_ERROR, "T203 fired outside MF EST state, please fix!\n"); + return; + } + + /* set retransmission counter to 0 */ + dl->retrans_ctr = 0; + /* enter timer recovery state */ + lapd_dl_newstate(dl, LAPD_STATE_TIMER_RECOV); + /* transmit a supervisory command with P bit set to 1 as follows: */ + if (!dl->own_busy) { + LOGDL(dl, LOGL_INFO, "transmit an RR poll command\n"); + /* Send RR with P=1 */ + lapd_send_rr(&dl->lctx, 1, 1); + } else { + LOGDL(dl, LOGL_INFO, "transmit an RNR poll command\n"); + /* Send RNR with P=1 */ + lapd_send_rnr(&dl->lctx, 1, 1); + } + /* start T200 */ + lapd_start_t200(dl); +} + +/* 5.5.3.1: Common function to acknowlege frames up to the given N(R) value + * In case of a sequence error, the cause is returned with negative sign. */ +static int lapd_acknowledge(struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + uint8_t nr = lctx->n_recv; + int s = 0, rej = 0; + bool t200_reset = false; + int i, h; + + /* supervisory frame ? */ + if (lctx->format == LAPD_FORM_S) + s = 1; + /* REJ frame ? */ + if (s && lctx->s_u == LAPD_S_REJ) + rej = 1; + + /* Flush all transmit buffers of acknowledged frames */ + for (i = dl->v_ack; i != nr; i = inc_mod(i, dl->v_range)) { + h = do_mod(i, dl->range_hist); + if (dl->tx_hist[h].msg) { + msgb_free(dl->tx_hist[h].msg); + dl->tx_hist[h].msg = NULL; + LOGDL(dl, LOGL_INFO, "ack frame %d\n", i); + } + } + + if (dl->state != LAPD_STATE_TIMER_RECOV) { + /* When not in the timer recovery condition, the data + * link layer entity shall reset the timer T200 on + * receipt of a valid I frame with N(R) higher than V(A), + * or an REJ with an N(R) equal to V(A). */ + if ((!rej && nr != dl->v_ack) || (rej && nr == dl->v_ack)) { + t200_reset = true; + lapd_stop_t200(dl); + /* 5.5.3.1 Note 1 + 2 imply timer recovery cond. */ + } + /* 5.7.4: N(R) sequence error + * N(R) is called valid, if and only if + * (N(R)-V(A)) mod 8 <= (V(S)-V(A)) mod 8. + */ + if (sub_mod(nr, dl->v_ack, dl->v_range) > sub_mod(dl->v_send, dl->v_ack, dl->v_range)) { + LOGDL(dl, LOGL_NOTICE, "N(R) sequence error\n"); + return -MDL_CAUSE_SEQ_ERR; + } + } + + /* V(A) shall be set to the value of N(R) */ + dl->v_ack = nr; + + /* If T200 has been stopped by the receipt of an I, RR or RNR frame, + * and if there are outstanding I frames, restart T200 */ + if (t200_reset && !rej) { + if (dl->tx_hist[sub_mod(dl->v_send, 1, dl->range_hist)].msg) { + LOGDL(dl, LOGL_INFO, "start T200, due to unacked I frame(s)\n"); + lapd_start_t200(dl); + } + } + + /* This also does a restart, when I or S frame is received */ + + /* Stop T203, if running */ + lapd_stop_t203(dl); + /* Start T203, if T200 is not running in MF EST state, if enabled */ + if (!lapd_is_t200_started(dl) && (dl->t203_sec || dl->t203_usec) && (dl->state == LAPD_STATE_MF_EST)) + lapd_start_t203(dl); + + return 0; +} + +/* L1 -> L2 */ + +/* Receive a LAPD U SABM(E) message from L1 */ +static int lapd_rx_u_sabm(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + int length = lctx->length; + int rc = 0; + uint8_t prim, op; + + prim = PRIM_DL_EST; + op = PRIM_OP_INDICATION; + + LOGDL(dl, LOGL_INFO, "SABM(E) received in state %s\n", lapd_state_name(dl->state)); + /* 5.7.1 */ + dl->seq_err_cond = 0; + /* G.2.2 Wrong value of the C/R bit */ + if (lctx->cr == dl->cr.rem2loc.resp) { + LOGDL(dl, LOGL_ERROR, "SABM response error\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx); + return -EINVAL; + } + + /* G.4.5 If SABM is received with L>N201 or with M bit + * set, AN MDL-ERROR-INDICATION is sent to MM. + */ + if (lctx->more || length > lctx->n201) { + LOGDL(dl, LOGL_ERROR, "SABM too large error\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx); + return -EIO; + } + + switch (dl->state) { + case LAPD_STATE_IDLE: + break; + case LAPD_STATE_TIMER_RECOV: + LOGDL(dl, LOGL_INFO, "SABM command, timer recovery state\n"); + /* If link is lost on the remote side, we start over + * and send DL-ESTABLISH indication again. */ + /* 3GPP TS 44.006 8.6.3 "Procedures for re-establishment" */ + if (length) { + /* check for contention resoultion */ + LOGDL(dl, LOGL_ERROR, "SABM L>0 not expected in timer " + "recovery state\n"); + mdl_error(MDL_CAUSE_SABM_INFO_NOTALL, lctx); + lapd_send_dm(lctx); + msgb_free(msg); + return 0; + } + /* re-establishment, continue below */ + lapd_stop_t200(dl); + break; + case LAPD_STATE_MF_EST: + LOGDL(dl, LOGL_INFO, "SABM command, multiple frame established state\n"); + /* If link is lost on the remote side, we start over + * and send DL-ESTABLISH indication again. */ + /* Additionally, continue in case of content resoltion + * (GSM network). This happens, if the mobile has not + * yet received UA or another mobile (collision) tries + * to establish connection. The mobile must receive + * UA again. */ + /* 5.4.2.1 */ + if (!length) { + /* If no content resolution, this is a + * re-establishment. */ + LOGDL(dl, LOGL_INFO, "Remote reestablish\n"); + break; + } + if (!dl->cont_res) { + LOGDL(dl, LOGL_INFO, "SABM command not allowed in state %s\n", + lapd_state_name(dl->state)); + mdl_error(MDL_CAUSE_SABM_MF, lctx); + msgb_free(msg); + return 0; + } + /* Ignore SABM if content differs from first SABM. */ + if (dl->mode == LAPD_MODE_NETWORK && length) { +#ifdef TEST_CONTENT_RESOLUTION_NETWORK + dl->cont_res->data[0] ^= 0x01; +#endif + if (memcmp(dl->cont_res->data, msg->data, + length)) { + LOGDL(dl, LOGL_INFO, "Another SABM with different content - " + "ignoring!\n"); + msgb_free(msg); + return 0; + } + } + /* send UA again */ + lapd_send_ua(lctx, length, msg->l3h); + msgb_free(msg); + return 0; + case LAPD_STATE_DISC_SENT: + /* 5.4.6.2 send DM with F=P */ + lapd_send_dm(lctx); + /* stop Timer T200 */ + lapd_stop_t200(dl); + msgb_free(msg); + return send_dl_simple(prim, op, lctx); + default: + /* collision: Send UA, but still wait for rx UA, then + * change to MF_EST state. + */ + /* check for contention resoultion */ + if (dl->tx_hist[0].msg && dl->tx_hist[0].msg->len) { + LOGDL(dl, LOGL_NOTICE, "SABM not allowed during contention " + "resolution (state=%s)\n", lapd_state_name(dl->state)); + mdl_error(MDL_CAUSE_SABM_INFO_NOTALL, lctx); + } + lapd_send_ua(lctx, length, msg->l3h); + msgb_free(msg); + return 0; + } + /* save message context for further use */ + memcpy(&dl->lctx, lctx, sizeof(dl->lctx)); +#ifndef TEST_CONTENT_RESOLUTION_NETWORK + /* send UA response */ + lapd_send_ua(lctx, length, msg->l3h); +#endif + /* set Vs, Vr and Va to 0 */ + dl->v_send = dl->v_recv = dl->v_ack = 0; + /* clear tx_hist */ + lapd_dl_flush_hist(dl); + /* enter multiple-frame-established state */ + lapd_dl_newstate(dl, LAPD_STATE_MF_EST); + /* store content resolution data on network side + * Note: cont_res will be removed when changing state again, + * so it must be allocated AFTER lapd_dl_newstate(). */ + if (dl->mode == LAPD_MODE_NETWORK && length) { + dl->cont_res = lapd_msgb_alloc(length, "CONT RES"); + memcpy(msgb_put(dl->cont_res, length), msg->l3h, + length); + LOGDL(dl, LOGL_INFO, "Store content res.\n"); + } + /* send notification to L3 */ + if (length == 0) { + /* 5.4.1.2 Normal establishment procedures */ + rc = send_dl_simple(prim, op, lctx); + msgb_free(msg); + } else { + /* 5.4.1.4 Contention resolution establishment */ + msgb_trim(msg, length); + rc = send_dl_l3(prim, op, lctx, msg); + } + return rc; +} + +/* Receive a LAPD U DM message from L1 */ +static int lapd_rx_u_dm(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + int rc = 0; + + LOGDL(dl, LOGL_INFO, "DM received in state %s\n", lapd_state_name(dl->state)); + /* G.2.2 Wrong value of the C/R bit */ + if (lctx->cr == dl->cr.rem2loc.cmd) { + LOGDL(dl, LOGL_ERROR, "DM command error\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx); + return -EINVAL; + } + if (!lctx->p_f) { + /* 5.4.1.2 DM responses with the F bit set to "0" + * shall be ignored. + */ + msgb_free(msg); + return 0; + } + switch (dl->state) { + case LAPD_STATE_SABM_SENT: + break; + case LAPD_STATE_MF_EST: + if (lctx->p_f) { + LOGDL(dl, LOGL_INFO, "unsolicited DM response\n"); + mdl_error(MDL_CAUSE_UNSOL_DM_RESP, lctx); + } else { + LOGDL(dl, LOGL_INFO, "unsolicited DM response, " + "multiple frame established state\n"); + mdl_error(MDL_CAUSE_UNSOL_DM_RESP_MF, lctx); + /* reestablish */ + if (!dl->reestablish) { + msgb_free(msg); + return 0; + } + LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n"); + lapd_reestablish(dl); + } + msgb_free(msg); + return 0; + case LAPD_STATE_TIMER_RECOV: + /* FP = 0 (DM is normal in case PF = 1) */ + if (!lctx->p_f) { + LOGDL(dl, LOGL_INFO, "unsolicited DM response, multiple frame " + "established state\n"); + mdl_error(MDL_CAUSE_UNSOL_DM_RESP_MF, lctx); + msgb_free(msg); + /* reestablish */ + if (!dl->reestablish) + return 0; + LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n"); + return lapd_reestablish(dl); + } + break; + case LAPD_STATE_DISC_SENT: + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* go to idle state */ + lapd_dl_flush_tx(dl); + lapd_dl_flush_send(dl); + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, lctx); + msgb_free(msg); + return 0; + case LAPD_STATE_IDLE: + /* 5.4.5 all other frame types shall be discarded */ + default: + LOGDL(dl, LOGL_INFO, "unsolicited DM response! (discarding)\n"); + msgb_free(msg); + return 0; + } + /* stop timer T200 */ + lapd_stop_t200(dl); + /* go to idle state */ + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, lctx); + msgb_free(msg); + return rc; +} + +/* Receive a LAPD U UI message from L1 */ +static int lapd_rx_u_ui(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + int length = lctx->length; + + LOGDL(dl, LOGL_INFO, "UI received\n"); + /* G.2.2 Wrong value of the C/R bit */ + if (lctx->cr == dl->cr.rem2loc.resp) { + LOGDL(dl, LOGL_ERROR, "UI indicates response error\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx); + return -EINVAL; + } + + /* G.4.5 If UI is received with L>N201 or with M bit + * set, AN MDL-ERROR-INDICATION is sent to MM. + */ + if (length > lctx->n201 || lctx->more) { + LOGDL(dl, LOGL_ERROR, "UI too large error (%d > N201(%d) or M=%d)\n", + length, lctx->n201, lctx->more); + msgb_free(msg); + mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx); + return -EIO; + } + + /* do some length checks */ + if (length == 0) { + /* 5.3.3 UI frames received with the length indicator + * set to "0" shall be ignored + */ + LOGDL(dl, LOGL_INFO, "length=0 (discarding)\n"); + msgb_free(msg); + return 0; + } + msgb_trim(msg, length); + return send_dl_l3(PRIM_DL_UNIT_DATA, PRIM_OP_INDICATION, lctx, msg); +} + +/* Receive a LAPD U DISC message from L1 */ +static int lapd_rx_u_disc(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + int length = lctx->length; + int rc = 0; + uint8_t prim, op; + + prim = PRIM_DL_REL; + op = PRIM_OP_INDICATION; + + LOGDL(dl, LOGL_INFO, "DISC received in state %s\n", lapd_state_name(dl->state)); + /* flush tx and send buffers */ + lapd_dl_flush_tx(dl); + lapd_dl_flush_send(dl); + /* 5.7.1 */ + dl->seq_err_cond = 0; + /* G.2.2 Wrong value of the C/R bit */ + if (lctx->cr == dl->cr.rem2loc.resp) { + LOGDL(dl, LOGL_ERROR, "DISC response error\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx); + return -EINVAL; + } + if (length > 0 || lctx->more) { + /* G.4.4 If a DISC or DM frame is received with L>0 or + * with the M bit set to "1", an MDL-ERROR-INDICATION + * primitive with cause "U frame with incorrect + * parameters" is sent to the mobile management entity. + */ + LOGDL(dl, LOGL_ERROR, "U frame iwth incorrect parameters\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx); + return -EIO; + } + switch (dl->state) { + case LAPD_STATE_IDLE: + LOGDL(dl, LOGL_INFO, "DISC in idle state\n"); + /* send DM with F=P */ + msgb_free(msg); + return lapd_send_dm(lctx); + case LAPD_STATE_SABM_SENT: + LOGDL(dl, LOGL_INFO, "DISC in SABM state\n"); + /* 5.4.6.2 send DM with F=P */ + lapd_send_dm(lctx); + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* go to idle state */ + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + msgb_free(msg); + return send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, + lctx); + case LAPD_STATE_MF_EST: + case LAPD_STATE_TIMER_RECOV: + LOGDL(dl, LOGL_INFO, "DISC in est state\n"); + break; + case LAPD_STATE_DISC_SENT: + LOGDL(dl, LOGL_INFO, "DISC in disc state\n"); + prim = PRIM_DL_REL; + op = PRIM_OP_CONFIRM; + break; + default: + lapd_send_ua(lctx, length, msg->l3h); + msgb_free(msg); + return 0; + } + /* send UA response */ + lapd_send_ua(lctx, length, msg->l3h); + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* enter idle state, keep tx-buffer with UA response */ + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + /* send notification to L3 */ + rc = send_dl_simple(prim, op, lctx); + msgb_free(msg); + return rc; +} + +/* Receive a LAPD U UA message from L1 */ +static int lapd_rx_u_ua(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + int length = lctx->length; + int rc = 0; + + LOGDL(dl, LOGL_INFO, "UA received in state %s\n", lapd_state_name(dl->state)); + /* G.2.2 Wrong value of the C/R bit */ + if (lctx->cr == dl->cr.rem2loc.cmd) { + LOGDL(dl, LOGL_ERROR, "UA indicates command error\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx); + return -EINVAL; + } + + /* G.4.5 If UA is received with L>N201 or with M bit + * set, AN MDL-ERROR-INDICATION is sent to MM. + */ + if (lctx->more || length > lctx->n201) { + LOGDL(dl, LOGL_ERROR, "UA too large error\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_UFRM_INC_PARAM, lctx); + return -EIO; + } + + if (!lctx->p_f) { + /* 5.4.1.2 A UA response with the F bit set to "0" + * shall be ignored. + */ + LOGDL(dl, LOGL_INFO, "F=0 (discarding)\n"); + msgb_free(msg); + return 0; + } + switch (dl->state) { + case LAPD_STATE_SABM_SENT: + break; + case LAPD_STATE_MF_EST: + case LAPD_STATE_TIMER_RECOV: + LOGDL(dl, LOGL_INFO, "unsolicited UA response! (discarding)\n"); + mdl_error(MDL_CAUSE_UNSOL_UA_RESP, lctx); + msgb_free(msg); + return 0; + case LAPD_STATE_DISC_SENT: + LOGDL(dl, LOGL_INFO, "UA in disconnect state\n"); + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* go to idle state */ + lapd_dl_flush_tx(dl); + lapd_dl_flush_send(dl); + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, lctx); + msgb_free(msg); + return 0; + case LAPD_STATE_IDLE: + /* 5.4.5 all other frame types shall be discarded */ + default: + LOGDL(dl, LOGL_INFO, "unsolicited UA response! (discarding)\n"); + msgb_free(msg); + return 0; + } + LOGDL(dl, LOGL_INFO, "UA in SABM state\n"); + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* compare UA with SABME if contention resolution is applied */ + if (dl->tx_hist[0].msg->len) { + if (length != (dl->tx_hist[0].msg->len) + || !!memcmp(dl->tx_hist[0].msg->data, msg->l3h, + length)) { + LOGDL(dl, LOGL_INFO, "**** UA response mismatches ****\n"); + /* go to idle state */ + lapd_dl_flush_tx(dl); + lapd_dl_flush_send(dl); + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + rc = send_dl_simple(PRIM_DL_REL, PRIM_OP_INDICATION, lctx); + msgb_free(msg); + return 0; + } + } + /* set Vs, Vr and Va to 0 */ + dl->v_send = dl->v_recv = dl->v_ack = 0; + /* clear tx_hist */ + lapd_dl_flush_hist(dl); + /* enter multiple-frame-established state */ + lapd_dl_newstate(dl, LAPD_STATE_MF_EST); + /* send outstanding frames, if any (resume / reconnect) */ + lapd_send_i(dl, __LINE__, false); + /* send notification to L3 */ + rc = send_dl_simple(PRIM_DL_EST, PRIM_OP_CONFIRM, lctx); + msgb_free(msg); + return rc; +} + +/* Receive a LAPD U FRMR message from L1 */ +static int lapd_rx_u_frmr(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + + LOGDL(dl, LOGL_NOTICE, "Frame reject received\n"); + /* send MDL ERROR INIDCATION to L3 */ + mdl_error(MDL_CAUSE_FRMR, lctx); + msgb_free(msg); + /* reestablish */ + if (!dl->reestablish) + return 0; + LOGDL(dl, LOGL_NOTICE, "Performing reestablishment\n"); + return lapd_reestablish(dl); +} + +/* Receive a LAPD U (Unnumbered) message from L1 */ +static int lapd_rx_u(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + switch (lctx->s_u) { + case LAPD_U_SABM: + case LAPD_U_SABME: + return lapd_rx_u_sabm(msg, lctx); + case LAPD_U_DM: + return lapd_rx_u_dm(msg, lctx); + case LAPD_U_UI: + return lapd_rx_u_ui(msg, lctx); + case LAPD_U_DISC: + return lapd_rx_u_disc(msg, lctx); + case LAPD_U_UA: + return lapd_rx_u_ua(msg, lctx); + case LAPD_U_FRMR: + return lapd_rx_u_frmr(msg, lctx); + default: + /* G.3.1 */ + LOGDL(lctx->dl, LOGL_NOTICE, "Unnumbered frame not allowed\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx); + return -EINVAL; + } +} + +/* Receive a LAPD S (Supervisory) message from L1 */ +static int lapd_rx_s(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + int length = lctx->length; + + if (length > 0 || lctx->more) { + /* G.4.3 If a supervisory frame is received with L>0 or + * with the M bit set to "1", an MDL-ERROR-INDICATION + * primitive with cause "S frame with incorrect + * parameters" is sent to the mobile management entity. */ + LOGDL(dl, LOGL_ERROR, "S frame with incorrect parameters\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_SFRM_INC_PARAM, lctx); + return -EIO; + } + + if (lctx->cr == dl->cr.rem2loc.resp + && lctx->p_f + && dl->state != LAPD_STATE_TIMER_RECOV) { + /* 5.4.2.2: Inidcate error on supervisory reponse F=1 */ + LOGDL(dl, LOGL_NOTICE, "S frame response with F=1 error\n"); + mdl_error(MDL_CAUSE_UNSOL_SPRV_RESP, lctx); + } + + switch (dl->state) { + case LAPD_STATE_IDLE: + /* if P=1, respond DM with F=1 (5.2.2) */ + /* 5.4.5 all other frame types shall be discarded */ + if (lctx->p_f) + lapd_send_dm(lctx); /* F=P */ + /* fall though */ + case LAPD_STATE_SABM_SENT: + case LAPD_STATE_DISC_SENT: + LOGDL(dl, LOGL_NOTICE, "S frame ignored in this state\n"); + msgb_free(msg); + return 0; + } + switch (lctx->s_u) { + case LAPD_S_RR: + LOGDL(dl, LOGL_INFO, "RR received in state %s\n", lapd_state_name(dl->state)); + /* 5.5.3.1: Acknowlege all tx frames up the the N(R)-1 */ + lapd_acknowledge(lctx); + + /* 5.5.3.2 */ + if (lctx->cr == dl->cr.rem2loc.cmd + && lctx->p_f) { + if (!dl->own_busy && !dl->seq_err_cond) { + LOGDL(dl, LOGL_INFO, "RR frame command with polling bit set and " + "we are not busy, so we reply with RR frame response\n"); + lapd_send_rr(lctx, 1, 0); + /* NOTE: In case of sequence error condition, + * the REJ frame has been transmitted when + * entering the condition, so it has not be + * done here + */ + } else if (dl->own_busy) { + LOGDL(dl, LOGL_INFO, "RR frame command with polling bit set and " + "we are busy, so we reply with RR frame response\n"); + lapd_send_rnr(lctx, 1, 0); + } + } else if (lctx->cr == dl->cr.rem2loc.resp + && lctx->p_f + && dl->state == LAPD_STATE_TIMER_RECOV) { + LOGDL(dl, LOGL_INFO, "RR response with F==1, and we are in timer recovery " + "state, so we leave that state\n"); + /* V(S) to the N(R) in the RR frame */ + dl->v_send = lctx->n_recv; + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* 5.5.7 Clear timer recovery condition */ + lapd_dl_newstate(dl, LAPD_STATE_MF_EST); + } + /* Send message, if possible due to acknowledged data */ + lapd_send_i(dl, __LINE__, false); + + break; + case LAPD_S_RNR: + LOGDL(dl, LOGL_INFO, "RNR received in state %s\n", lapd_state_name(dl->state)); + /* 5.5.3.1: Acknowlege all tx frames up the the N(R)-1 */ + lapd_acknowledge(lctx); + + /* 5.5.5 */ + /* Set peer receiver busy condition */ + dl->peer_busy = 1; + /* Flush pending messages in TX queue. */ + lapd_dl_flush_tx_queue(dl); + /* stop Timer T200 */ + lapd_stop_t200(dl); + + if (lctx->p_f) { + if (lctx->cr == dl->cr.rem2loc.cmd) { + if (!dl->own_busy) { + LOGDL(dl, LOGL_INFO, "RNR poll command and we are not busy, " + "so we reply with RR final response\n"); + /* Send RR with F=1 */ + lapd_send_rr(lctx, 1, 0); + } else { + LOGDL(dl, LOGL_INFO, "RNR poll command and we are busy, so " + "we reply with RNR final response\n"); + /* Send RNR with F=1 */ + lapd_send_rnr(lctx, 1, 0); + } + } else if (dl->state == LAPD_STATE_TIMER_RECOV) { + LOGDL(dl, LOGL_INFO, "RNR poll response and we in timer recovery " + "state, so we leave that state\n"); + /* 5.5.7 Clear timer recovery condition */ + lapd_dl_newstate(dl, LAPD_STATE_MF_EST); + /* V(S) to the N(R) in the RNR frame */ + dl->v_send = lctx->n_recv; + } + } else + LOGDL(dl, LOGL_INFO, "RNR not polling/final state received\n"); + + /* Send message, if possible due to acknowledged data */ + lapd_send_i(dl, __LINE__, false); + + break; + case LAPD_S_REJ: + LOGDL(dl, LOGL_INFO, "REJ received in state %s\n", lapd_state_name(dl->state)); + /* 5.5.3.1: Acknowlege all tx frames up the the N(R)-1 */ + lapd_acknowledge(lctx); + + /* 5.5.4.1 */ + if (dl->state != LAPD_STATE_TIMER_RECOV) { + /* Clear an existing peer receiver busy condition */ + dl->peer_busy = 0; + /* V(S) and V(A) to the N(R) in the REJ frame */ + dl->v_send = dl->v_ack = lctx->n_recv; + /* Flush pending messages in TX queue. */ + lapd_dl_flush_tx_queue(dl); + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* 5.5.3.2 */ + if (lctx->cr == dl->cr.rem2loc.cmd && lctx->p_f) { + if (!dl->own_busy && !dl->seq_err_cond) { + LOGDL(dl, LOGL_INFO, "REJ poll command not in timer recovery " + "state and not in own busy condition received, so we " + "respond with RR final response\n"); + lapd_send_rr(lctx, 1, 0); + /* NOTE: In case of sequence error + * condition, the REJ frame has been + * transmitted when entering the + * condition, so it has not be done + * here + */ + } else if (dl->own_busy) { + LOGDL(dl, LOGL_INFO, "REJ poll command not in timer recovery " + "state and in own busy condition received, so we " + "respond with RNR final response\n"); + lapd_send_rnr(lctx, 1, 0); + } + } else + LOGDL(dl, LOGL_INFO, "REJ response or not polling command not " + "in timer recovery state received\n"); + /* send MDL ERROR INIDCATION to L3 */ + if (lctx->cr == dl->cr.rem2loc.resp && lctx->p_f) { + LOGDL(dl, LOGL_ERROR, "unsolicited supervisory response!\n"); + mdl_error(MDL_CAUSE_UNSOL_SPRV_RESP, lctx); + } + + } else if (lctx->cr == dl->cr.rem2loc.resp && lctx->p_f) { + LOGDL(dl, LOGL_INFO, "REJ poll response in timer recovery state received\n"); + /* Clear an existing peer receiver busy condition */ + dl->peer_busy = 0; + /* V(S) and V(A) to the N(R) in the REJ frame */ + dl->v_send = dl->v_ack = lctx->n_recv; + /* Flush pending messages in TX queue. */ + lapd_dl_flush_tx_queue(dl); + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* 5.5.7 Clear timer recovery condition */ + lapd_dl_newstate(dl, LAPD_STATE_MF_EST); + } else { + /* Clear an existing peer receiver busy condition */ + dl->peer_busy = 0; + /* V(S) and V(A) to the N(R) in the REJ frame */ + dl->v_send = dl->v_ack = lctx->n_recv; + /* Flush pending messages in TX queue. */ + lapd_dl_flush_tx_queue(dl); + /* 5.5.3.2 */ + if (lctx->cr == dl->cr.rem2loc.cmd && lctx->p_f) { + if (!dl->own_busy && !dl->seq_err_cond) { + LOGDL(dl, LOGL_INFO, "REJ poll command in timer recovery " + "state and not in own busy condition received, so we " + "respond with RR final response\n"); + lapd_send_rr(lctx, 1, 0); + /* NOTE: In case of sequence error + * condition, the REJ frame has been + * transmitted when entering the + * condition, so it has not be done + * here + */ + } else if (dl->own_busy) { + LOGDL(dl, LOGL_INFO, "REJ poll command in timer recovery " + "state and in own busy condition received, so we " + "respond with RNR final response\n"); + lapd_send_rnr(lctx, 1, 0); + } + } else + LOGDL(dl, LOGL_INFO, "REJ response or not polling command in " + "timer recovery state received\n"); + } + + /* FIXME: 5.5.4.2 2) */ + + /* Send message, if possible due to acknowledged data and new V(S) and V(A). */ + lapd_send_i(dl, __LINE__, false); + + break; + default: + /* G.3.1 */ + LOGDL(dl, LOGL_ERROR, "Supervisory frame not allowed\n"); + msgb_free(msg); + mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx); + return -EINVAL; + } + msgb_free(msg); + return 0; +} + +/* Receive a LAPD I (Information) message from L1 */ +static int lapd_rx_i(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + //uint8_t nr = lctx->n_recv; + uint8_t ns = lctx->n_send; + int length = lctx->length; + int rc; + bool i_frame_in_queue = false; + int mdl_cause = 0; + + LOGDL(dl, LOGL_INFO, "I received in state %s on SAPI(%u)\n", + lapd_state_name(dl->state), lctx->sapi); + + /* G.2.2 Wrong value of the C/R bit */ + if (lctx->cr == dl->cr.rem2loc.resp) { + LOGDL(dl, LOGL_ERROR, "I frame response not allowed (state %s)\n", + lapd_state_name(dl->state)); + msgb_free(msg); + mdl_error(MDL_CAUSE_FRM_UNIMPL, lctx); + return -EINVAL; + } + + if (length == 0 || length > lctx->n201) { + /* G.4.2 If the length indicator of an I frame is set + * to a numerical value L>N201 or L=0, an MDL-ERROR-INDICATION + * primitive with cause "I frame with incorrect length" + * is sent to the mobile management entity. */ + LOGDL(dl, LOGL_ERROR, "I frame length not allowed (state %s)\n", + lapd_state_name(dl->state)); + msgb_free(msg); + mdl_error(MDL_CAUSE_IFRM_INC_LEN, lctx); + return -EIO; + } + + /* G.4.2 If the numerical value of L is L<N201 and the M + * bit is set to "1", then an MDL-ERROR-INDICATION primitive with + * cause "I frame with incorrect use of M bit" is sent to the + * mobile management entity. */ + if (lctx->more && length < lctx->n201) { + LOGDL(dl, LOGL_ERROR, "I frame with M bit too short (state %s)\n", + lapd_state_name(dl->state)); + msgb_free(msg); + mdl_error(MDL_CAUSE_IFRM_INC_MBITS, lctx); + return -EIO; + } + + switch (dl->state) { + case LAPD_STATE_IDLE: + /* if P=1, respond DM with F=1 (5.2.2) */ + /* 5.4.5 all other frame types shall be discarded */ + if (lctx->p_f) + lapd_send_dm(lctx); /* F=P */ + /* fall though */ + case LAPD_STATE_SABM_SENT: + case LAPD_STATE_DISC_SENT: + LOGDL(dl, LOGL_NOTICE, "I frame ignored in state %s\n", lapd_state_name(dl->state)); + msgb_free(msg); + return 0; + } + + /* 5.7.1: N(s) sequence error */ + if (ns != dl->v_recv) { + LOGDL(dl, LOGL_NOTICE, "N(S) sequence error: N(S)=%u, V(R)=%u (state %s)\n", + ns, dl->v_recv, lapd_state_name(dl->state)); + /* discard data */ + msgb_free(msg); + /* Send reject, but suppress second reject if LAPD_F_DROP_2ND_REJ flag is set. */ + if (dl->seq_err_cond != 1 || !(dl->lapd_flags & LAPD_F_DROP_2ND_REJ)) { + dl->seq_err_cond = 1; + lapd_send_rej(lctx, lctx->p_f); + } else { + /* If there are two subsequent sequence errors received, + * ignore it. (Ignore every second subsequent error.) + * This happens if our reply with the REJ is too slow, + * so the remote gets a T200 timeout and sends another + * frame with a sequence error. + * Test showed that replying with two subsequent REJ + * messages could the remote L2 process to abort. + * Replying too slow shouldn't happen, but may happen + * over serial link between BB and LAPD. + */ + dl->seq_err_cond = 2; + } + /* Even if N(s) sequence error, acknowledge to N(R)-1 */ + /* 5.5.3.1: Acknowlege all transmitted frames up the N(R)-1 */ + mdl_cause = lapd_acknowledge(lctx); /* V(A) is also set here */ + if (mdl_cause < 0) + mdl_error(-mdl_cause, lctx); + + /* Send message, if possible due to acknowledged data */ + lapd_send_i(dl, __LINE__, false); + + return 0; + } + dl->seq_err_cond = 0; + + /* Increment receiver state */ + dl->v_recv = inc_mod(dl->v_recv, dl->v_range); + LOGDL(dl, LOGL_INFO, "incrementing V(R) to %u\n", dl->v_recv); + + /* Update all pending frames in the queue to the new V(R) state. */ + if (dl->update_pending_frames) { + rc = dl->update_pending_frames(lctx); + if (!rc) + i_frame_in_queue = true; + } + + /* 5.5.3.1: Acknowlege all transmitted frames up the the N(R)-1 */ + mdl_cause = lapd_acknowledge(lctx); /* V(A) is also set here */ + + /* Only if we are not in own receiver busy condition */ + if (!dl->own_busy) { + /* if the frame carries a complete segment */ + if (!lctx->more && !dl->rcv_buffer) { + LOGDL(dl, LOGL_INFO, "message in single I frame\n"); + /* send a DATA INDICATION to L3 */ + msgb_trim(msg, length); + rc = send_dl_l3(PRIM_DL_DATA, PRIM_OP_INDICATION, lctx, + msg); + } else { + /* create rcv_buffer */ + if (!dl->rcv_buffer) { + LOGDL(dl, LOGL_INFO, "message in multiple I frames (first message)\n"); + dl->rcv_buffer = lapd_msgb_alloc(dl->maxf, + "LAPD RX"); + dl->rcv_buffer->l3h = dl->rcv_buffer->data; + } + /* concat. rcv_buffer */ + if (msgb_l3len(dl->rcv_buffer) + length > dl->maxf) { + LOGDL(dl, LOGL_NOTICE, "Received frame overflow!\n"); + } else { + memcpy(msgb_put(dl->rcv_buffer, length), + msg->l3h, length); + } + /* if the last segment was received */ + if (!lctx->more) { + LOGDL(dl, LOGL_INFO, "message in multiple I frames (last message)\n"); + rc = send_dl_l3(PRIM_DL_DATA, + PRIM_OP_INDICATION, lctx, + dl->rcv_buffer); + dl->rcv_buffer = NULL; + } else + LOGDL(dl, LOGL_INFO, "message in multiple I frames (next message)\n"); + msgb_free(msg); + + } + /* the L3 or higher (called in-line above via send_dl_l3) might have destroyed the + * data link meanwhile. See OS#1761 */ + if (dl->state == LAPD_STATE_NULL) + return 0; + } else + LOGDL(dl, LOGL_INFO, "I frame ignored during own receiver busy condition\n"); + + /* Indicate sequence error, if exists. */ + if (mdl_cause < 0) + mdl_error(-mdl_cause, lctx); + + /* Check for P bit */ + if (lctx->p_f) { + /* 5.5.2.1 */ + /* check if we are not in own receiver busy */ + if (!dl->own_busy) { + LOGDL(dl, LOGL_INFO, "we are not busy, send RR\n"); + /* Send RR with F=1 */ + rc = lapd_send_rr(lctx, 1, 0); + } else { + LOGDL(dl, LOGL_INFO, "we are busy, send RNR\n"); + /* Send RNR with F=1 */ + rc = lapd_send_rnr(lctx, 1, 0); + } + } else { + /* 5.5.2.2 */ + /* check if we are not in own receiver busy */ + if (!dl->own_busy) { + /* NOTE: V(R) is already set above */ + rc = lapd_send_i(dl, __LINE__, false); + if (rc && !i_frame_in_queue) { + LOGDL(dl, LOGL_INFO, "we are not busy and have no pending data, " + "send RR\n"); + /* Send RR with F=0 */ + return lapd_send_rr(lctx, 0, 0); + } + /* all I or one RR is sent, we are done */ + return 0; + } else { + LOGDL(dl, LOGL_INFO, "we are busy, send RNR\n"); + /* Send RNR with F=0 */ + rc = lapd_send_rnr(lctx, 0, 0); + } + } + + /* Send message, if possible due to acknowledged data */ + lapd_send_i(dl, __LINE__, false); + + return rc; +} + +/* Receive a LAPD message from L1 */ +int lapd_ph_data_ind(struct msgb *msg, struct lapd_msg_ctx *lctx) +{ + int rc; + + switch (lctx->format) { + case LAPD_FORM_U: + rc = lapd_rx_u(msg, lctx); + break; + case LAPD_FORM_S: + rc = lapd_rx_s(msg, lctx); + break; + case LAPD_FORM_I: + rc = lapd_rx_i(msg, lctx); + break; + default: + LOGDL(lctx->dl, LOGL_NOTICE, "unknown LAPD format 0x%02x\n", lctx->format); + msgb_free(msg); + rc = -EINVAL; + } + return rc; +} + +/*! Enqueue next LAPD frame and run pending T200. (Must be called when frame is ready to send.) + * The caller (LAPDm code) calls this function before it sends the next frame. + * If there is no frame in the TX queue, LAPD will enqueue next I-frame, if possible. + * If the T200 is pending, it is changed to running state. + * \param[in] lctx LAPD context + * \param[out] rc set to 1, if timer T200 state changed to running, set to 0, if not. */ +int lapd_ph_rts_ind(struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + + /* If there is no pending frame, try to enqueue next I frame. */ + if (llist_empty(&dl->tx_queue) && (dl->state == LAPD_STATE_MF_EST || dl->state == LAPD_STATE_TIMER_RECOV)) { + /* Send an I frame, if there are pending outgoing messages. */ + lapd_send_i(dl, __LINE__, true); + } + + /* Run T200 at RTS, if pending. Tell caller that is has been started. (rc = 1) */ + if (dl->t200_rts == LAPD_T200_RTS_PENDING) { + dl->t200_rts = LAPD_T200_RTS_RUNNING; + return 1; + } + + return 0; +} + +/* L3 -> L2 */ + +/* send unit data */ +static int lapd_udata_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct msgb *msg = dp->oph.msg; + struct lapd_msg_ctx nctx; + + memcpy(&nctx, lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.cmd; + nctx.format = LAPD_FORM_U; + nctx.s_u = LAPD_U_UI; + /* keep nctx.p_f */ + nctx.length = msg->len; + nctx.more = 0; + + return dl->send_ph_data_req(&nctx, msg); +} + +static void msg_to_tx_hist(struct lapd_history *tx_hist, const struct msgb *msg, int length, int more) +{ + tx_hist->msg = lapd_msgb_alloc(msg->len, "HIST"); + tx_hist->more = more; + msgb_put(tx_hist->msg, msg->len); + if (length) + memcpy(tx_hist->msg->data, msg->l3h, msg->len); +} + +static void msg_to_tx_hist0(struct lapd_datalink *dl, const struct msgb *msg) +{ + return msg_to_tx_hist(&dl->tx_hist[0], msg, msg->len, 0); +} + +/* request link establishment */ +static int lapd_est_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct msgb *msg = dp->oph.msg; + struct lapd_msg_ctx nctx; + + if (msg->len) + LOGDL(dl, LOGL_INFO, "perform establishment with content (SABM)\n"); + else + LOGDL(dl, LOGL_INFO, "perform normal establishm. (SABM)\n"); + + /* Flush send-queue */ + /* Clear send-buffer */ + lapd_dl_flush_send(dl); + /* be sure that history is empty */ + lapd_dl_flush_hist(dl); + + /* save message context for further use */ + memcpy(&dl->lctx, lctx, sizeof(dl->lctx)); + + /* Discard partly received L3 message */ + msgb_free(dl->rcv_buffer); + dl->rcv_buffer = NULL; + + /* assemble message */ + memcpy(&nctx, &dl->lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.cmd; + nctx.format = LAPD_FORM_U; + nctx.s_u = (dl->use_sabme) ? LAPD_U_SABME : LAPD_U_SABM; + nctx.p_f = 1; + nctx.length = msg->len; + nctx.more = 0; + + /* Transmit-buffer carries exactly one segment */ + msg_to_tx_hist0(dl, msg); + + /* set Vs to 0, because it is used as index when resending SABM */ + dl->v_send = 0; + + /* Set states */ + dl->own_busy = dl->peer_busy = 0; + dl->retrans_ctr = 0; + lapd_dl_newstate(dl, LAPD_STATE_SABM_SENT); + + /* Tramsmit and start T200 */ + dl->send_ph_data_req(&nctx, msg); + lapd_start_t200(dl); + + return 0; +} + +/* send data */ +static int lapd_data_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct msgb *msg = dp->oph.msg; + + if (msgb_l3len(msg) == 0) { + LOGDL(dl, LOGL_ERROR, "writing an empty message is not possible\n"); + msgb_free(msg); + return -1; + } + + LOGDL(dl, LOGL_INFO, "writing message to send-queue: l3len: %d\n", msgb_l3len(msg)); + + /* Write data into the send queue */ + msgb_enqueue(&dl->send_queue, msg); + + /* Send message, if possible */ + lapd_send_i(dl, __LINE__, false); + + return 0; +} + +/* Send next I frame from queued/buffered data */ +static int lapd_send_i(struct lapd_datalink *dl, int line, bool rts) +{ + struct lapd_msg_ctx *lctx = &dl->lctx; + uint8_t k = dl->k; + uint8_t h; + struct msgb *msg; + int length, left; + int rc = - 1; /* we sent nothing */ + struct lapd_msg_ctx nctx; + + if (!rts) + LOGDL(dl, LOGL_INFO, "%s() called from line %d\n", __func__, line); + + if ((dl->lapd_flags & LAPD_F_RTS) && !llist_empty(&dl->tx_queue)) { + if (!rts) + LOGDL(dl, LOGL_INFO, "There is a frame in the TX queue, not checking for sending I frame.\n"); + return rc; + } + + next_frame: + + if (dl->peer_busy) { + if (!rts) + LOGDL(dl, LOGL_INFO, "Peer busy, not sending.\n"); + return rc; + } + + if (dl->state == LAPD_STATE_TIMER_RECOV) { + if (!rts) + LOGDL(dl, LOGL_INFO, "Timer recovery, not sending.\n"); + return rc; + } + + /* If the send state variable V(S) is equal to V(A) plus k + * (where k is the maximum number of outstanding I frames - see + * subclause 5.8.4), the data link layer entity shall not transmit any + * new I frames, but shall retransmit an I frame as a result + * of the error recovery procedures as described in subclauses 5.5.4 and + * 5.5.7. */ + if (dl->v_send == add_mod(dl->v_ack, k, dl->v_range)) { + if (!rts) + LOGDL(dl, LOGL_INFO, "k frames outstanding, not sending more. (k=%u V(S)=%u V(A)=%u)\n", + k, dl->v_send, dl->v_ack); + return rc; + } + + h = do_mod(dl->v_send, dl->range_hist); + + /* if we have no tx_hist yet, we create it */ + if (!dl->tx_hist[h].msg) { + /* Get next message into send-buffer, if any */ + if (!dl->send_buffer) { + next_message: + dl->send_out = 0; + dl->send_buffer = msgb_dequeue(&dl->send_queue); + /* No more data to be sent */ + if (!dl->send_buffer) + return rc; + LOGDL(dl, LOGL_INFO, "get message from send-queue\n"); + } + + /* How much is left in the send-buffer? */ + left = msgb_l3len(dl->send_buffer) - dl->send_out; + /* Segment, if data exceeds N201 */ + length = left; + if (length > lctx->n201) + length = lctx->n201; + LOGDL(dl, LOGL_INFO, "msg-len %d sent %d left %d N201 %d length %d " + "first byte %02x\n", msgb_l3len(dl->send_buffer), dl->send_out, left, + lctx->n201, length, dl->send_buffer->l3h[0]); + /* If message in send-buffer is completely sent */ + if (left == 0) { + msgb_free(dl->send_buffer); + dl->send_buffer = NULL; + goto next_message; + } + + LOGDL(dl, LOGL_INFO, "send I frame %sV(S)=%d\n", + (left > length) ? "segment " : "", dl->v_send); + + /* Create I frame (segment) and transmit-buffer content */ + msg = lapd_msgb_alloc(length, "LAPD I"); + msg->l3h = msgb_put(msg, length); + /* assemble message */ + memcpy(&nctx, lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.cmd; + nctx.format = LAPD_FORM_I; + nctx.p_f = 0; + nctx.n_send = dl->v_send; + nctx.n_recv = dl->v_recv; + nctx.length = length; + if (left > length) + nctx.more = 1; + else + nctx.more = 0; + if (length) + memcpy(msg->l3h, dl->send_buffer->l3h + dl->send_out, + length); + /* store in tx_hist */ + msg_to_tx_hist(&dl->tx_hist[h], msg, length, nctx.more); + + /* Add length to track how much is already in the tx buffer */ + dl->send_out += length; + } else { + LOGDL(dl, LOGL_INFO, "resend I frame from tx buffer V(S)=%d\n", dl->v_send); + + /* Create I frame (segment) from tx_hist */ + length = dl->tx_hist[h].msg->len; + msg = lapd_msgb_alloc(length, "LAPD I resend"); + msg->l3h = msgb_put(msg, length); + /* assemble message */ + memcpy(&nctx, lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.cmd; + nctx.format = LAPD_FORM_I; + nctx.p_f = 0; + nctx.n_send = dl->v_send; + nctx.n_recv = dl->v_recv; + nctx.length = length; + nctx.more = dl->tx_hist[h].more; + if (length) + memcpy(msg->l3h, dl->tx_hist[h].msg->data, length); + } + + /* The value of the send state variable V(S) shall be incremented by 1 + * at the end of the transmission of the I frame */ + dl->v_send = inc_mod(dl->v_send, dl->v_range); + + /* If timer T200 is not running at the time right before transmitting a + * frame, when the PH-READY-TO-SEND primitive is received from the + * physical layer., it shall be set. */ + if (!lapd_is_t200_started(dl)) { + /* stop Timer T203, if running */ + lapd_stop_t203(dl); + /* start Timer T200 */ + lapd_start_t200(dl); + } + + dl->send_ph_data_req(&nctx, msg); + + /* When using RTS, we send only one frame. */ + if ((dl->lapd_flags & LAPD_F_RTS)) + return 0; + + rc = 0; /* We sent an I frame, so sending RR frame is not required. */ + goto next_frame; +} + +/* request link suspension */ +static int lapd_susp_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct msgb *msg = dp->oph.msg; + + LOGDL(dl, LOGL_INFO, "perform suspension\n"); + + /* put back the send-buffer to the send-queue (first position) */ + if (dl->send_buffer) { + LOGDL(dl, LOGL_INFO, "put frame in sendbuffer back to queue\n"); + llist_add(&dl->send_buffer->list, &dl->send_queue); + dl->send_buffer = NULL; + } else + LOGDL(dl, LOGL_INFO, "no frame in sendbuffer\n"); + + /* Clear transmit buffer, but keep send buffer */ + lapd_dl_flush_tx(dl); + /* Stop timers (there is no state change, so we must stop all timers */ + lapd_stop_t200(dl); + lapd_stop_t203(dl); + + msgb_free(msg); + + return send_dl_simple(PRIM_DL_SUSP, PRIM_OP_CONFIRM, &dl->lctx); +} + +/* request, resume or reconnect of link */ +static int lapd_res_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct msgb *msg = dp->oph.msg; + struct lapd_msg_ctx nctx; + + LOGDL(dl, LOGL_INFO, "perform re-establishment (SABM) length=%d\n", msg->len); + + /* be sure that history is empty */ + lapd_dl_flush_hist(dl); + + /* save message context for further use */ + memcpy(&dl->lctx, lctx, sizeof(dl->lctx)); + + /* Replace message in the send-buffer (reconnect) */ + msgb_free(dl->send_buffer); + dl->send_buffer = NULL; + + dl->send_out = 0; + if (msg->len) { + /* Write data into the send buffer, to be sent first */ + dl->send_buffer = msg; + } else { + msgb_free(msg); + msg = NULL; + dl->send_buffer = NULL; + } + + /* Discard partly received L3 message */ + msgb_free(dl->rcv_buffer); + dl->rcv_buffer = NULL; + + /* Create new msgb (old one is now free) */ + msg = lapd_msgb_alloc(0, "LAPD SABM"); + msg->l3h = msg->data; + /* assemble message */ + memcpy(&nctx, &dl->lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.cmd; + nctx.format = LAPD_FORM_U; + nctx.s_u = (dl->use_sabme) ? LAPD_U_SABME : LAPD_U_SABM; + nctx.p_f = 1; + nctx.length = 0; + nctx.more = 0; + + msg_to_tx_hist0(dl, msg); + + /* set Vs to 0, because it is used as index when resending SABM */ + dl->v_send = 0; + + /* Set states */ + dl->own_busy = dl->peer_busy = 0; + dl->retrans_ctr = 0; + lapd_dl_newstate(dl, LAPD_STATE_SABM_SENT); + + /* Tramsmit and start T200 */ + dl->send_ph_data_req(&nctx, msg); + lapd_start_t200(dl); + + return 0; +} + +/* requesst release of link */ +static int lapd_rel_req(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct msgb *msg = dp->oph.msg; + struct lapd_msg_ctx nctx; + + /* local release */ + if (dp->u.rel_req.mode) { + LOGDL(dl, LOGL_INFO, "perform local release\n"); + msgb_free(msg); + /* stop Timer T200 */ + lapd_stop_t200(dl); + /* enter idle state, T203 is stopped here, if running */ + lapd_dl_newstate(dl, LAPD_STATE_IDLE); + /* flush buffers */ + lapd_dl_flush_tx(dl); + lapd_dl_flush_send(dl); + /* send notification to L3 */ + return send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx); + } + + /* in case we are already disconnecting */ + if (dl->state == LAPD_STATE_DISC_SENT) + return -EBUSY; + + /* flush tx_hist */ + lapd_dl_flush_hist(dl); + + LOGDL(dl, LOGL_INFO, "perform normal release (DISC)\n"); + + /* Push LAPD header on msgb */ + /* assemble message */ + memcpy(&nctx, &dl->lctx, sizeof(nctx)); + /* keep nctx.ldp */ + /* keep nctx.sapi */ + /* keep nctx.tei */ + nctx.cr = dl->cr.loc2rem.cmd; + nctx.format = LAPD_FORM_U; + nctx.s_u = LAPD_U_DISC; + nctx.p_f = 1; + nctx.length = 0; + nctx.more = 0; + + msg_to_tx_hist0(dl, msg); + + /* set Vs to 0, because it is used as index when resending DISC */ + dl->v_send = 0; + + /* Set states */ + dl->own_busy = dl->peer_busy = 0; + dl->retrans_ctr = 0; + lapd_dl_newstate(dl, LAPD_STATE_DISC_SENT); + + /* Tramsmit and start T200 */ + dl->send_ph_data_req(&nctx, msg); + lapd_start_t200(dl); + + return 0; +} + +/* request release of link in idle state */ +static int lapd_rel_req_idle(struct osmo_dlsap_prim *dp, + struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + struct msgb *msg = dp->oph.msg; + + msgb_free(msg); + + /* send notification to L3 */ + return send_dl_simple(PRIM_DL_REL, PRIM_OP_CONFIRM, &dl->lctx); +} + +/* statefull handling for DL SAP messages from L3 */ +static const struct l2downstate { + uint32_t states; + int prim, op; + const char *name; + int (*rout) (struct osmo_dlsap_prim *dp, + struct lapd_msg_ctx *lctx); +} l2downstatelist[] = { + /* create and send UI command */ + {ALL_STATES, + PRIM_DL_UNIT_DATA, PRIM_OP_REQUEST, + "DL-UNIT-DATA-REQUEST", lapd_udata_req}, + + /* create and send SABM command */ + {SBIT(LAPD_STATE_IDLE), + PRIM_DL_EST, PRIM_OP_REQUEST, + "DL-ESTABLISH-REQUEST", lapd_est_req}, + + /* create and send I command */ + {SBIT(LAPD_STATE_MF_EST) | + SBIT(LAPD_STATE_TIMER_RECOV), + PRIM_DL_DATA, PRIM_OP_REQUEST, + "DL-DATA-REQUEST", lapd_data_req}, + + /* suspend datalink */ + {SBIT(LAPD_STATE_MF_EST) | + SBIT(LAPD_STATE_TIMER_RECOV), + PRIM_DL_SUSP, PRIM_OP_REQUEST, + "DL-SUSPEND-REQUEST", lapd_susp_req}, + + /* create and send SABM command (resume) */ + {SBIT(LAPD_STATE_MF_EST) | + SBIT(LAPD_STATE_TIMER_RECOV), + PRIM_DL_RES, PRIM_OP_REQUEST, + "DL-RESUME-REQUEST", lapd_res_req}, + + /* create and send SABM command (reconnect) */ + {SBIT(LAPD_STATE_IDLE) | + SBIT(LAPD_STATE_MF_EST) | + SBIT(LAPD_STATE_TIMER_RECOV), + PRIM_DL_RECON, PRIM_OP_REQUEST, + "DL-RECONNECT-REQUEST", lapd_res_req}, + + /* create and send DISC command */ + {SBIT(LAPD_STATE_SABM_SENT) | + SBIT(LAPD_STATE_MF_EST) | + SBIT(LAPD_STATE_TIMER_RECOV) | + SBIT(LAPD_STATE_DISC_SENT), + PRIM_DL_REL, PRIM_OP_REQUEST, + "DL-RELEASE-REQUEST", lapd_rel_req}, + + /* release in idle state */ + {SBIT(LAPD_STATE_IDLE), + PRIM_DL_REL, PRIM_OP_REQUEST, + "DL-RELEASE-REQUEST", lapd_rel_req_idle}, +}; + +#define L2DOWNSLLEN \ + (sizeof(l2downstatelist) / sizeof(struct l2downstate)) + +int lapd_recv_dlsap(struct osmo_dlsap_prim *dp, struct lapd_msg_ctx *lctx) +{ + struct lapd_datalink *dl = lctx->dl; + int i, supported = 0; + struct msgb *msg = dp->oph.msg; + int rc; + + /* find function for current state and message */ + for (i = 0; i < L2DOWNSLLEN; i++) { + if (dp->oph.primitive == l2downstatelist[i].prim + && dp->oph.operation == l2downstatelist[i].op) { + supported = 1; + if ((SBIT(dl->state) & l2downstatelist[i].states)) + break; + } + } + if (!supported) { + LOGDL(dl, LOGL_NOTICE, "Message %u/%u unsupported\n", + dp->oph.primitive, dp->oph.operation); + msgb_free(msg); + return 0; + } + if (i == L2DOWNSLLEN) { + LOGDL(dl, LOGL_NOTICE, "Message %u/%u unhandled at this state %s\n", + dp->oph.primitive, dp->oph.operation, lapd_state_name(dl->state)); + msgb_free(msg); + return 0; + } + + LOGDL(dl, LOGL_INFO, "Message %s received in state %s\n", + l2downstatelist[i].name, lapd_state_name(dl->state)); + + rc = l2downstatelist[i].rout(dp, lctx); + + return rc; +} + +/*! @} */ diff --git a/src/isdn/libosmoisdn.map b/src/isdn/libosmoisdn.map new file mode 100644 index 00000000..8f34f0d8 --- /dev/null +++ b/src/isdn/libosmoisdn.map @@ -0,0 +1,52 @@ +LIBOSMOISDN_1.0 { +global: + +tall_lapd_ctx; +lapd_dl_exit; +lapd_dl_init; +lapd_dl_init2; +lapd_dl_set_name; +lapd_dl_reset; +lapd_dl_set_flags; +lapd_msgb_alloc; +lapd_ph_data_ind; +lapd_ph_rts_ind; +lapd_recv_dlsap; +lapd_set_mode; +lapd_state_names; +lapd_t200_timeout; + +osmo_i460_demux_in; +osmo_i460_mux_enqueue; +osmo_i460_mux_out; +osmo_i460_subchan_add; +osmo_i460_subchan_del; +osmo_i460_subchan_count; +osmo_i460_ts_init; + +osmo_v110_decode_frame; +osmo_v110_encode_frame; +osmo_v110_ubit_dump; +osmo_v110_e1e2e3; +osmo_v110_sync_ra1_get_user_data_chunk_bitlen; +osmo_v110_sync_ra1_get_user_data_rate; +osmo_v110_sync_ra1_get_intermediate_rate; +osmo_v110_sync_ra1_user_to_ir; +osmo_v110_sync_ra1_ir_to_user; + +osmo_v110_ta_alloc; +osmo_v110_ta_free; +osmo_v110_ta_set_timer_val_ms; +osmo_v110_ta_frame_in; +osmo_v110_ta_frame_out; +osmo_v110_ta_sync_ind; +osmo_v110_ta_desync_ind; +osmo_v110_ta_get_status; +osmo_v110_ta_get_circuit; +osmo_v110_ta_set_circuit; + +osmo_v110_ta_circuit_names; +osmo_v110_ta_circuit_descs; + +local: *; +}; diff --git a/src/isdn/v110.c b/src/isdn/v110.c new file mode 100644 index 00000000..b4bc4e30 --- /dev/null +++ b/src/isdn/v110.c @@ -0,0 +1,590 @@ +/* V.110 frames according to ITU-T V.110 + * + * This code implements the following functionality: + * - parsing/encoding of osmo_v110_decoded_frame from/to actual 80-bit V.110 frame + * - synchronous rate adapting of user bit rate to V.110 D-bits as per Table 6 + * + * It is (at least initially) a very "naive" implementation, as it first and foremost + * aims to be functional and correct, rather than efficient in any way. Hence it + * operates on unpacked bits (ubit_t, 1 bit per byte), and has various intermediate + * representations and indirect function calls. If needed, a more optimized variant + * can always be developed later on. + */ + +/* (C) 2022 by Harald Welte <laforge@osmocom.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. + */ + +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/bits.h> + +#include <osmocom/isdn/v110.h> + +/************************************************************************* + * V.110 frame decoding/encoding (ubits <-> struct with D/S/X/E bits) + *************************************************************************/ + +/*! Decode a 80-bit V.110 frame present as 80 ubits into a struct osmo_v110_decoded_frame. + * \param[out] fr caller-allocated output data structure, filled by this function + * \param[in] ra_bits One V.110 frame as 80 unpacked bits. + * \param[in] n_bits number of unpacked bits provided in ra_bits + * \returns 0 in case of success; negative on error. */ +int osmo_v110_decode_frame(struct osmo_v110_decoded_frame *fr, const ubit_t *ra_bits, size_t n_bits) +{ + if (n_bits < 80) + return -EINVAL; + + /* X1 .. X2 */ + fr->x_bits[0] = ra_bits[2 * 8 + 7]; + fr->x_bits[1] = ra_bits[7 * 8 + 7]; + + /* S1, S3, S4, S6, S8, S9 */ + fr->s_bits[0] = ra_bits[1 * 8 + 7]; + fr->s_bits[2] = ra_bits[3 * 8 + 7]; + fr->s_bits[3] = ra_bits[4 * 8 + 7]; + fr->s_bits[5] = ra_bits[6 * 8 + 7]; + fr->s_bits[7] = ra_bits[8 * 8 + 7]; + fr->s_bits[8] = ra_bits[9 * 8 + 7]; + + /* E1 .. E7 */ + memcpy(fr->e_bits, ra_bits + 5 * 8 + 1, 7); + + /* D-bits */ + memcpy(fr->d_bits + 0 * 6, ra_bits + 1 * 8 + 1, 6); + memcpy(fr->d_bits + 1 * 6, ra_bits + 2 * 8 + 1, 6); + memcpy(fr->d_bits + 2 * 6, ra_bits + 3 * 8 + 1, 6); + memcpy(fr->d_bits + 3 * 6, ra_bits + 4 * 8 + 1, 6); + + memcpy(fr->d_bits + 4 * 6, ra_bits + 6 * 8 + 1, 6); + memcpy(fr->d_bits + 5 * 6, ra_bits + 7 * 8 + 1, 6); + memcpy(fr->d_bits + 6 * 6, ra_bits + 8 * 8 + 1, 6); + memcpy(fr->d_bits + 7 * 6, ra_bits + 9 * 8 + 1, 6); + + return 0; +} + +/*! Encode a struct osmo_v110_decoded_frame into an 80-bit V.110 frame as ubits. + * \param[out] ra_bits caller-provided output buffer at leat 80 ubits large + * \param[in] n_bits length of ra_bits. Must be at least 80. + * \param[in] input data structure + * \returns number of bits written to ra_bits */ +int osmo_v110_encode_frame(ubit_t *ra_bits, size_t n_bits, const struct osmo_v110_decoded_frame *fr) +{ + if (n_bits < 80) + return -ENOSPC; + + /* alignment pattern */ + memset(ra_bits+0, 0, 8); + for (int i = 1; i < 10; i++) + ra_bits[i*8] = 1; + + /* X1 .. X2 */ + ra_bits[2 * 8 + 7] = fr->x_bits[0]; + ra_bits[7 * 8 + 7] = fr->x_bits[1]; + + /* S1, S3, S4, S6, S8, S9 */ + ra_bits[1 * 8 + 7] = fr->s_bits[0]; + ra_bits[3 * 8 + 7] = fr->s_bits[2]; + ra_bits[4 * 8 + 7] = fr->s_bits[3]; + ra_bits[6 * 8 + 7] = fr->s_bits[5]; + ra_bits[8 * 8 + 7] = fr->s_bits[7]; + ra_bits[9 * 8 + 7] = fr->s_bits[8]; + + /* E1 .. E7 */ + memcpy(ra_bits + 5 * 8 + 1, fr->e_bits, 7); + + /* D-bits */ + memcpy(ra_bits + 1 * 8 + 1, fr->d_bits + 0 * 6, 6); + memcpy(ra_bits + 2 * 8 + 1, fr->d_bits + 1 * 6, 6); + memcpy(ra_bits + 3 * 8 + 1, fr->d_bits + 2 * 6, 6); + memcpy(ra_bits + 4 * 8 + 1, fr->d_bits + 3 * 6, 6); + + memcpy(ra_bits + 6 * 8 + 1, fr->d_bits + 4 * 6, 6); + memcpy(ra_bits + 7 * 8 + 1, fr->d_bits + 5 * 6, 6); + memcpy(ra_bits + 8 * 8 + 1, fr->d_bits + 6 * 6, 6); + memcpy(ra_bits + 9 * 8 + 1, fr->d_bits + 7 * 6, 6); + + return 10 * 8; +} + +/*! Print a encoded V.110 frame in the same table-like structure as the spec. + * \param outf output FILE stream to which to dump + * \param[in] fr unpacked bits to dump + * \param[in] in_len length of unpacked bits available at fr. */ +void osmo_v110_ubit_dump(FILE *outf, const ubit_t *fr, size_t in_len) +{ + if (in_len < 80) + fprintf(outf, "short input data\n"); + + for (unsigned int octet = 0; octet < 10; octet++) { + fprintf(outf, "%d\t%d\t%d\t%d\t%d\t%d\t%d\t%d\n", + fr[octet * 8 + 0], fr[octet * 8 + 1], fr[octet * 8 + 2], fr[octet * 8 + 3], + fr[octet * 8 + 4], fr[octet * 8 + 5], fr[octet * 8 + 6], fr[octet * 8 + 7]); + } +} + +/************************************************************************* + * RA1 synchronous rate adaptation + *************************************************************************/ + +/* I actually couldn't find any reference as to the value of F(ill) bits */ +#define F 1 + +/*! E1/E2/E3 bit values as per Table 5/V.110 */ +const ubit_t osmo_v110_e1e2e3[_NUM_OSMO_V110_SYNC_RA1][3] = { + [OSMO_V110_SYNC_RA1_600] = { 1, 0, 0 }, + [OSMO_V110_SYNC_RA1_1200] = { 0, 1, 0 }, + [OSMO_V110_SYNC_RA1_2400] = { 1, 1, 0 }, + [OSMO_V110_SYNC_RA1_4800] = { 0, 1, 1 }, + [OSMO_V110_SYNC_RA1_7200] = { 1, 0, 1 }, + [OSMO_V110_SYNC_RA1_9600] = { 0, 1, 1 }, + [OSMO_V110_SYNC_RA1_12000] = { 0, 0, 1 }, + [OSMO_V110_SYNC_RA1_14400] = { 1, 0, 1 }, + [OSMO_V110_SYNC_RA1_19200] = { 0, 1, 1 }, + [OSMO_V110_SYNC_RA1_24000] = { 0, 0, 1 }, + [OSMO_V110_SYNC_RA1_28800] = { 1, 0, 1 }, + [OSMO_V110_SYNC_RA1_38400] = { 0, 1, 1 }, +}; + +/*! Adapt from 6 synchronous 600bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 6. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_600_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + if (in_len != 6) + return -EINVAL; + + /* Table 6a / V.110 */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_600); + for (int i = 0; i < 6; i++) + memset(fr->d_bits + i*8, d_in[i], 8); + + return 0; +} + +static int v110_adapt_IR8000_to_600(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + if (out_len < 6) + return -ENOSPC; + + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_600)) + return -EINVAL; + + for (int i = 0; i < 6; i++) { + /* we only use one of the bits, not some kind of consistency check or majority vote */ + d_out[i] = fr->d_bits[i*8]; + } + + return 6; +} + +/*! Adapt from 12 synchronous 1200bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 12. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_1200_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + if (in_len != 12) + return -EINVAL; + + /* Table 6b / V.110 */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_1200); + for (int i = 0; i < 12; i++) + memset(fr->d_bits + i*4, d_in[i], 4); + + return 0; +} + +static int v110_adapt_IR8000_to_1200(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + if (out_len < 12) + return -ENOSPC; + + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_1200)) + return -EINVAL; + + for (int i = 0; i < 12; i++) { + /* we only use one of the bits, not some kind of consistency check or majority vote */ + d_out[i] = fr->d_bits[i*4]; + } + + return 12; +} + +/*! Adapt from 24 synchronous 2400bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 24. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_2400_to_IR8000(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + if (in_len != 24) + return -EINVAL; + + /* Table 6c / V.110 */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_2400); + for (int i = 0; i < 24; i++) { + fr->d_bits[i*2 + 0] = d_in[i]; + fr->d_bits[i*2 + 1] = d_in[i]; + } + + return 0; +} + +static int v110_adapt_IR8000_to_2400(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + if (out_len < 24) + return -ENOSPC; + + /* Table 6c / V.110 */ + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_2400)) + return -EINVAL; + + for (int i = 0; i < 24; i++) { + /* we only use one of the bits, not some kind of consistency check or majority vote */ + d_out[i] = fr->d_bits[i*2]; + } + + return 24; +} + +/*! Adapt from 36 synchronous N x 3600bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 36. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_Nx3600_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + int d_idx = 0; + + if (in_len != 36) + return -EINVAL; + + /* Table 6d / V.110 (7200 is one of Nx3600) */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_7200); + + memcpy(fr->d_bits + d_idx, d_in + 0, 10); d_idx += 10; /* D1..D10 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 10, 2); d_idx += 2; /* D11..D12 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 12, 2); d_idx += 2; /* D13..D14 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 14, 14); d_idx += 14; /* D15..D28 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 28, 2); d_idx += 2; /* D29..D30 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 30, 2); d_idx += 2; /* D31..D32 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 32, 4); d_idx += 4; /* D33..D36 */ + + OSMO_ASSERT(d_idx == 48); + + return 0; +} + +static int v110_adapt_IR_to_Nx3600(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + int d_idx = 0; + + if (out_len < 36) + return -ENOSPC; + + /* Table 6d / V.110 (7200 is one of Nx3600) */ + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_7200)) + return -EINVAL; + + memcpy(d_out + 0, fr->d_bits + d_idx, 10); d_idx += 10; /* D1..D10 */ + d_idx += 2; + memcpy(d_out + 10, fr->d_bits + d_idx, 2); d_idx += 2; /* D11..D12 */ + d_idx += 2; + memcpy(d_out + 12, fr->d_bits + d_idx, 2); d_idx += 2; /* D13..D14 */ + d_idx += 2; + memcpy(d_out + 14, fr->d_bits + d_idx, 14); d_idx += 14;/* D15..D28 */ + d_idx += 2; + memcpy(d_out + 28, fr->d_bits + d_idx, 2); d_idx += 2; /* D29..D30 */ + d_idx += 2; + memcpy(d_out + 30, fr->d_bits + d_idx, 2); d_idx += 2; /* D31..D32 */ + d_idx += 2; + memcpy(d_out + 32, fr->d_bits + d_idx, 4); d_idx += 4; /* D33..D36 */ + + OSMO_ASSERT(d_idx == 48); + + return 36; +} + + +/*! Adapt from 48 synchronous N x 4800bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 48. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_Nx4800_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + if (in_len != 48) + return -EINVAL; + + /* Table 6e / V.110 (4800 is one of Nx4800) */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_4800); + + memcpy(fr->d_bits, d_in, 48); + + return 0; +} + +static int v110_adapt_IR_to_Nx4800(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + if (out_len < 48) + return -ENOSPC; + + /* Table 6e / V.110 (4800 is one of Nx4800) */ + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_4800)) + return -EINVAL; + + memcpy(d_out, fr->d_bits, 48); + + return 48; +} + +/*! Adapt from 30 synchronous N x 12000bit/s input bits to a decoded V.110 frame. + * \param[out] fr caller-allocated output frame to which E+D bits are stored + * \param[in] d_in input user bits + * \param[in] in_len number of bits in d_in. Must be 30. + * \returns 0 on success; negative in case of error. */ +static int v110_adapt_Nx12000_to_IR(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len) +{ + int d_idx = 0; + + if (in_len != 30) + return -EINVAL; + + /* Table 6f / V.110 (12000 is one of Nx12000) */ + osmo_v110_e1e2e3_set(fr->e_bits, OSMO_V110_SYNC_RA1_12000); + + memcpy(fr->d_bits + d_idx, d_in + 0, 10); d_idx += 10; /* D1..D10 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 10, 2); d_idx += 2; /* D11..D12 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 12, 2); d_idx += 2; /* D13..D14 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + fr->d_bits[d_idx++] = d_in[14]; /* D15 */ + memset(fr->d_bits + d_idx, F, 3); d_idx += 3; + memcpy(fr->d_bits + d_idx, d_in + 15, 10); d_idx += 10; /* D16..D25 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 25, 2); d_idx += 2; /* D26..D27 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + memcpy(fr->d_bits + d_idx, d_in + 27, 2); d_idx += 2; /* D28..D29 */ + memset(fr->d_bits + d_idx, F, 2); d_idx += 2; + fr->d_bits[d_idx++] = d_in[29]; /* D30 */ + memset(fr->d_bits + d_idx, F, 3); d_idx += 3; + + OSMO_ASSERT(d_idx == 48); + + return 0; +} + +static int v110_adapt_IR_to_Nx12000(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr) +{ + int d_idx = 0; + + if (out_len < 30) + return -ENOSPC; + + /* Table 6f / V.110 (12000 is one of Nx12000) */ + if (osmo_v110_e1e2e3_cmp(fr->e_bits, OSMO_V110_SYNC_RA1_12000)) + return -EINVAL; + + memcpy(d_out + 0, fr->d_bits + d_idx, 10); d_idx += 10; /* D1..D10 */ + d_idx += 2; + memcpy(d_out + 10, fr->d_bits + d_idx, 2); d_idx += 2; /* D11..D12 */ + d_idx += 2; + memcpy(d_out + 12, fr->d_bits + d_idx, 2); d_idx += 2; /* D13..D14 */ + d_idx += 2; + d_out[14] = fr->d_bits[d_idx++]; /* D15 */ + d_idx += 3; + memcpy(d_out + 15, fr->d_bits + d_idx, 10); d_idx += 10;/* D16..D25 */ + d_idx += 2; + memcpy(d_out + 25, fr->d_bits + d_idx, 2); d_idx += 2; /* D26..D27 */ + d_idx += 2; + memcpy(d_out + 27, fr->d_bits + d_idx, 2); d_idx += 2; /* D28..D29 */ + d_idx += 2; + d_out[29] = fr->d_bits[d_idx++]; /* D30 */ + d_idx += 3; + + OSMO_ASSERT(d_idx == 48); + + return 30; +} + +/* definition of a synchronous V.110 RA1 rate adaptation. There is one for each supported tuple + * of user data rate and intermediate rate (IR). */ +struct osmo_v110_sync_ra1 { + unsigned int data_rate; + unsigned int intermediate_rate; + unsigned int user_data_chunk_bits; + /*! RA1 function in user bitrate -> intermediate rate direction */ + int (*adapt_user_to_ir)(struct osmo_v110_decoded_frame *fr, const ubit_t *d_in, size_t in_len); + /*! RA1 function in intermediate rate -> user bitrate direction */ + int (*adapt_ir_to_user)(ubit_t *d_out, size_t out_len, const struct osmo_v110_decoded_frame *fr); +}; + +/* all of the synchronous data signalling rates; see Table 1/V.110 */ +static const struct osmo_v110_sync_ra1 osmo_v110_sync_ra1_def[_NUM_OSMO_V110_SYNC_RA1] = { + [OSMO_V110_SYNC_RA1_600] = { + .data_rate = 600, + .intermediate_rate = 8000, + .user_data_chunk_bits = 6, + .adapt_user_to_ir = v110_adapt_600_to_IR8000, + .adapt_ir_to_user = v110_adapt_IR8000_to_600, + }, + [OSMO_V110_SYNC_RA1_1200] = { + .data_rate = 1200, + .intermediate_rate = 8000, + .user_data_chunk_bits = 12, + .adapt_user_to_ir = v110_adapt_1200_to_IR8000, + .adapt_ir_to_user = v110_adapt_IR8000_to_1200, + }, + [OSMO_V110_SYNC_RA1_2400] = { + .data_rate = 2400, + .intermediate_rate = 8000, + .user_data_chunk_bits = 24, + .adapt_user_to_ir = v110_adapt_2400_to_IR8000, + .adapt_ir_to_user = v110_adapt_IR8000_to_2400, + }, + [OSMO_V110_SYNC_RA1_4800] = { + .data_rate = 4800, + .intermediate_rate = 8000, + .user_data_chunk_bits = 48, + .adapt_user_to_ir = v110_adapt_Nx4800_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx4800, + }, + [OSMO_V110_SYNC_RA1_7200] = { + .data_rate = 7200, + .intermediate_rate = 16000, + .user_data_chunk_bits = 36, + .adapt_user_to_ir = v110_adapt_Nx3600_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx3600, + }, + [OSMO_V110_SYNC_RA1_9600] = { + .data_rate = 9600, + .intermediate_rate = 16000, + .user_data_chunk_bits = 48, + .adapt_user_to_ir = v110_adapt_Nx4800_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx4800, + }, + [OSMO_V110_SYNC_RA1_12000] = { + .data_rate = 12000, + .intermediate_rate = 32000, + .user_data_chunk_bits = 30, + .adapt_user_to_ir = v110_adapt_Nx12000_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx12000, + }, + [OSMO_V110_SYNC_RA1_14400] = { + .data_rate = 14400, + .intermediate_rate = 32000, + .user_data_chunk_bits = 36, + .adapt_user_to_ir = v110_adapt_Nx3600_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx3600, + }, + [OSMO_V110_SYNC_RA1_19200] = { + .data_rate = 19200, + .intermediate_rate = 32000, + .user_data_chunk_bits = 48, + .adapt_user_to_ir = v110_adapt_Nx4800_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx4800, + }, + [OSMO_V110_SYNC_RA1_24000] = { + .data_rate = 24000, + .intermediate_rate = 64000, + .user_data_chunk_bits = 30, + .adapt_user_to_ir = v110_adapt_Nx12000_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx12000, + }, + [OSMO_V110_SYNC_RA1_28800] = { + .data_rate = 28800, + .intermediate_rate = 64000, + .user_data_chunk_bits = 36, + .adapt_user_to_ir = v110_adapt_Nx3600_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx3600, + }, + [OSMO_V110_SYNC_RA1_38400] = { + .data_rate = 38400, + .intermediate_rate = 64000, + .user_data_chunk_bits = 48, + .adapt_user_to_ir = v110_adapt_Nx4800_to_IR, + .adapt_ir_to_user = v110_adapt_IR_to_Nx4800, + }, +}; + +/*! obtain the size (in number of bits) of the user data bits in one V.110 + * frame for specified RA1 rate */ +int osmo_v110_sync_ra1_get_user_data_chunk_bitlen(enum osmo_v100_sync_ra1_rate rate) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].user_data_chunk_bits; +} + +/*! obtain the user data rate (in bits/s) for specified RA1 rate */ +int osmo_v110_sync_ra1_get_user_data_rate(enum osmo_v100_sync_ra1_rate rate) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].data_rate; +} + +/*! obtain the intermediate rate (in bits/s) for specified RA1 rate */ +int osmo_v110_sync_ra1_get_intermediate_rate(enum osmo_v100_sync_ra1_rate rate) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].intermediate_rate; +} + +/*! perform V.110 RA1 function in user rate -> intermediate rate direction. + * \param[in] rate specification of the user bitrate + * \param[out] fr caller-allocated output buffer for the [decoded] V.110 frame generated + * \param[in] d_in input user data (unpacked bits) + * \param[in] in_len length of user input data (in number of bits) + * \returns 0 on success; negative in case of error */ +int osmo_v110_sync_ra1_user_to_ir(enum osmo_v100_sync_ra1_rate rate, struct osmo_v110_decoded_frame *fr, + const ubit_t *d_in, size_t in_len) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].adapt_user_to_ir(fr, d_in, in_len); +} + +/*! perform V.110 RA1 function in intermediate rate -> user rate direction. + * \param[in] rate specification of the user bitrate + * \param[out] d_out caller-allocated output user data (unpacked bits) + * \param[out] out_len length of d_out output buffer + * \param[in] fr [decoded] V.110 frame used as input + * \returns number of unpacked bits written to d_out on success; negative in case of error */ +int osmo_v110_sync_ra1_ir_to_user(enum osmo_v100_sync_ra1_rate rate, ubit_t *d_out, size_t out_len, + const struct osmo_v110_decoded_frame *fr) +{ + if (rate < 0 || rate >= _NUM_OSMO_V110_SYNC_RA1) + return -EINVAL; + + return osmo_v110_sync_ra1_def[rate].adapt_ir_to_user(d_out, out_len, fr); +} diff --git a/src/isdn/v110_ta.c b/src/isdn/v110_ta.c new file mode 100644 index 00000000..6730b20c --- /dev/null +++ b/src/isdn/v110_ta.c @@ -0,0 +1,881 @@ +/*! \file v110_ta.c + * TA (Terminal Adapter) implementation as per ITU-T V.110. */ +/* + * (C) 2022 by Harald Welte <laforge@gnumonks.org> + * (C) 2023 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de> + * + * Initial (Work-in-Progress) implementation by Harald Welte, + * completed and co-authored by Vadim Yanitskiy. + * + * All Rights Reserved + * + * 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. + * + */ + +#include <stdbool.h> +#include <stdint.h> +#include <errno.h> + +#include <osmocom/core/logging.h> +#include <osmocom/core/talloc.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/bits.h> +#include <osmocom/core/tdef.h> +#include <osmocom/core/fsm.h> + +#include <osmocom/isdn/v110.h> +#include <osmocom/isdn/v110_ta.h> + +#define S(x) (1 << (x)) + +#define V24_FLAGMASK_IS_ON(flags, circuit) \ + (((flags) & S(circuit)) != 0) + +#define V24_FLAGMASK_IS_OFF(flags, circuit) \ + (((flags) & S(circuit)) == 0) + +#define V24_FLAGMASK_SET_ON(flags, circuit) \ + (flags) |= S(circuit) + +#define V24_FLAGMASK_SET_OFF(flags, circuit) \ + (flags) &= ~S(circuit) + +/* inverse logic: ON = binary 0; OFF = binary 1 */ +#define V110_SX_BIT_ON 0 +#define V110_SX_BIT_OFF 1 + +const struct value_string osmo_v110_ta_circuit_names[] = { + { OSMO_V110_TA_C_105, "105/RTS" }, + { OSMO_V110_TA_C_106, "106/CTS" }, + { OSMO_V110_TA_C_107, "107/DSR" }, + { OSMO_V110_TA_C_108, "108/DTR" }, + { OSMO_V110_TA_C_109, "109/DCD" }, + { OSMO_V110_TA_C_133, "133" }, + { 0, NULL } +}; + +const struct value_string osmo_v110_ta_circuit_descs[] = { + { OSMO_V110_TA_C_105, "Request to Send" }, + { OSMO_V110_TA_C_106, "Clear to Send" }, + { OSMO_V110_TA_C_107, "Data Set Ready" }, + { OSMO_V110_TA_C_108, "Data Terminal Ready" }, + { OSMO_V110_TA_C_109, "Data Carrier Detect" }, + { OSMO_V110_TA_C_133, "Ready for receiving" }, + { 0, NULL } +}; + +static const struct osmo_tdef v110_ta_tdef[] = { + { .T = OSMO_V110_TA_TIMER_X1, + .unit = OSMO_TDEF_MS, .default_val = 3000, /* suggested in 7.1.5 e) */ + .desc = "ITU-T V.110 7.1.5 Loss of frame synchronization: sync recovery timer" }, + { .T = OSMO_V110_TA_TIMER_T1, + .unit = OSMO_TDEF_MS, .default_val = 10000, /* suggested in 7.1.2.2 */ + .desc = "ITU-T V.110 7.1.2 Connect TA to line: sync establishment timer" }, + { .T = OSMO_V110_TA_TIMER_T2, + .unit = OSMO_TDEF_MS, .default_val = 5000, /* suggested in 7.1.4.1 */ + .desc = "ITU-T V.110 7.1.4 Disconnect mode: disconnect confirmation timer" }, + { /* end of list */ } +}; + +/********************************************************************************* + * V.110 TERMINAL ADAPTER FSM + *********************************************************************************/ + +enum v110_ta_fsm_state { + V110_TA_ST_IDLE_READY, /* 7.1.1 Idle (or ready) state */ + V110_TA_ST_CON_TA_TO_LINE, /* 7.1.2 Connect TA to line state */ + V110_TA_ST_DATA_TRANSFER, /* 7.1.3 Data transfer state */ + V110_TA_ST_DISCONNECTING, /* 7.1.4 Disconnect mode */ + V110_TA_ST_RESYNCING, /* 7.1.5 Re-synchronizing state */ +}; + +enum v110_ta_fsm_event { + V110_TA_EV_RX_FRAME_IND, /* a V.110 frame was received by the lower layer */ + V110_TA_EV_TX_FRAME_RTS, /* a V.110 frame is to be sent by the lower layer */ + V110_TA_EV_V24_STATUS_CHG, /* V.24 flag-mask has been updated by TE */ + V110_TA_EV_SYNC_IND, /* the lower layer has synchronized to the frame clock */ + V110_TA_EV_DESYNC_IND, /* the lower layer has lost frame clock synchronization */ + V110_TA_EV_TIMEOUT, /* generic event for handling a timeout condition */ +}; + +static const struct value_string v110_ta_fsm_event_names[] = { + { V110_TA_EV_RX_FRAME_IND, "RX_FRAME_IND" }, + { V110_TA_EV_TX_FRAME_RTS, "TX_FRAME_RTS" }, + { V110_TA_EV_V24_STATUS_CHG, "V24_STATUS_CHG" }, + { V110_TA_EV_SYNC_IND, "SYNC_IND" }, + { V110_TA_EV_DESYNC_IND, "DESYNC_IND" }, + { V110_TA_EV_TIMEOUT, "TIMEOUT" }, + { 0, NULL } +}; + +enum v110_ta_d_bit_mode { + V110_TA_DBIT_M_ALL_ZERO = 0, /* set all bits to binary '0' */ + V110_TA_DBIT_M_ALL_ONE = 1, /* set all bits to binary '1' */ + V110_TA_DBIT_M_FORWARD, /* forward D-bits to/from DTE */ +}; + +struct v110_ta_state { + /*! V.24 status flags shared between DTE (user) and DCE (TA, us) */ + unsigned int v24_flags; + struct { + /* what kind of D-bits to transmit in V.110 frames */ + enum v110_ta_d_bit_mode d_bit_mode; + /* what to put in S-bits of transmitted V.110 frames */ + ubit_t s_bits; + /* what to put in X-bits of transmitted V.110 frames */ + ubit_t x_bits; + } tx; + struct { + enum v110_ta_d_bit_mode d_bit_mode; + } rx; +}; + +struct osmo_v110_ta { + const char *name; + struct osmo_tdef *Tdefs; + struct osmo_fsm_inst *fi; + struct osmo_v110_ta_cfg *cfg; + struct v110_ta_state state; +}; + +static inline bool v110_df_x_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp) +{ + return (df->x_bits[0] == cmp) && (df->x_bits[1] == cmp); +} + +static inline bool v110_df_s_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp) +{ + /* ITU-T Table 2/V.110 (see also 5.1.2.3) defines the following S-bits: + * S1, S3, S4, S6, S8, S9 (6 bits total). However, fr->s_bits[] contains + * 9 (MAX_S_BITS) bits, including the undefined bits S2, S5, S7. + * Hence we must skip those undefined bits. */ + static const uint8_t sbit_map[] = { 0, 2, 3, 5, 7, 8 }; + + for (unsigned int i = 0; i < ARRAY_SIZE(sbit_map); i++) { + uint8_t idx = sbit_map[i]; + if (df->s_bits[idx] != cmp) + return false; + } + + return true; +} + +static inline bool v110_df_d_bits_are(const struct osmo_v110_decoded_frame *df, ubit_t cmp) +{ + for (unsigned int i = 0; i < MAX_D_BITS; i++) { + if (df->d_bits[i] != cmp) + return false; + } + + return true; +} + +/* handle one V.110 frame and forward user bits to the application */ +static void v110_ta_handle_frame(const struct osmo_v110_ta *ta, + const struct osmo_v110_decoded_frame *df) +{ + const struct osmo_v110_ta_cfg *cfg = ta->cfg; + const struct v110_ta_state *ts = &ta->state; + ubit_t user_bits[MAX_D_BITS]; + int num_user_bits; + int rc; + + switch (ts->rx.d_bit_mode) { + case V110_TA_DBIT_M_ALL_ZERO: + case V110_TA_DBIT_M_ALL_ONE: + /* generate as many user bits as needed for the configured rate */ + num_user_bits = osmo_v110_sync_ra1_get_user_data_chunk_bitlen(cfg->rate); + OSMO_ASSERT(num_user_bits > 0); + /* set them all to binary '0' or binary '1' */ + memset(&user_bits[0], (int)ts->rx.d_bit_mode, num_user_bits); + cfg->rx_cb(cfg->priv, &user_bits[0], num_user_bits); + break; + case V110_TA_DBIT_M_FORWARD: + rc = osmo_v110_sync_ra1_ir_to_user(cfg->rate, &user_bits[0], sizeof(user_bits), df); + if (rc > 0) + cfg->rx_cb(cfg->priv, &user_bits[0], rc); + /* XXX else: indicate an error somehow? */ + break; + } +} + +/* build one V.110 frame to transmit */ +static void v110_ta_build_frame(const struct osmo_v110_ta *ta, + struct osmo_v110_decoded_frame *df) +{ + const struct osmo_v110_ta_cfg *cfg = ta->cfg; + const struct v110_ta_state *ts = &ta->state; + ubit_t user_bits[MAX_D_BITS]; + int num_user_bits; + int rc; + + /* E-bits (E1/E2/E3 may be overwritten below) */ + memset(df->e_bits, 1, sizeof(df->e_bits)); + /* S-bits */ + memset(df->s_bits, ts->tx.s_bits, sizeof(df->s_bits)); + /* X-bits */ + memset(df->x_bits, ts->tx.x_bits, sizeof(df->x_bits)); + + /* D-bits */ + switch (ts->tx.d_bit_mode) { + case V110_TA_DBIT_M_ALL_ZERO: + case V110_TA_DBIT_M_ALL_ONE: + /* set them all to binary '0' or binary '1' */ + memset(df->d_bits, (int)ts->tx.d_bit_mode, sizeof(df->d_bits)); + break; + case V110_TA_DBIT_M_FORWARD: + /* how many user bits to retrieve */ + num_user_bits = osmo_v110_sync_ra1_get_user_data_chunk_bitlen(cfg->rate); + OSMO_ASSERT(num_user_bits > 0); + /* retrieve user bits from the application */ + cfg->tx_cb(cfg->priv, &user_bits[0], num_user_bits); + /* convert user bits to intermediate rate (store to df) */ + rc = osmo_v110_sync_ra1_user_to_ir(cfg->rate, df, &user_bits[0], num_user_bits); + OSMO_ASSERT(rc == 0); + break; + } +} + +static void v110_ta_flags_update(struct osmo_v110_ta *ta, unsigned int v24_flags) +{ + struct osmo_v110_ta_cfg *cfg = ta->cfg; + + if (ta->state.v24_flags == v24_flags) + return; + if (cfg->status_update_cb != NULL) + cfg->status_update_cb(cfg->priv, v24_flags); + ta->state.v24_flags = v24_flags; +} + +static const struct osmo_tdef_state_timeout v110_ta_fsm_timeouts[32] = { + [V110_TA_ST_RESYNCING] = { .T = OSMO_V110_TA_TIMER_X1 }, + [V110_TA_ST_CON_TA_TO_LINE] = { .T = OSMO_V110_TA_TIMER_T1 }, + [V110_TA_ST_DISCONNECTING] = { .T = OSMO_V110_TA_TIMER_T2 }, +}; + +#define v110_ta_fsm_state_chg(state) \ + osmo_tdef_fsm_inst_state_chg(fi, state, \ + v110_ta_fsm_timeouts, \ + ((struct osmo_v110_ta *)(fi->priv))->Tdefs, \ + 0) + +/* ITU-T V.110 Section 7.1.1 */ +static void v110_ta_fsm_idle_ready_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.1.2 During the idle (or ready) state the TA will transmit continuous binary 1s into the B-channel */ + ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; /* circuit 103: continuous binary '1' */ + ts->tx.s_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */ + ts->tx.x_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */ + + /* 7.1.1.3 During the idle (or ready) state the TA (DCE) will transmit the following toward the DTE: */ + /* - circuit 104: continuous binary '1' */ + ts->rx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; + /* - circuits 107, 106, 109 = OFF */ + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_106); + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107); + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_109); + v110_ta_flags_update(ta, v24_flags); +} + +/* ITU-T V.110 Section 7.1.1 */ +static void v110_ta_fsm_idle_ready(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + /* When the TA is to be switched to the data mode, circuit 108 must be ON */ + if (V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)) { + /* 7.12.2: Start timer T1 when switching to CON_TA_LINE */ + v110_ta_fsm_state_chg(V110_TA_ST_CON_TA_TO_LINE); + } + break; + case V110_TA_EV_RX_FRAME_IND: + v110_ta_handle_frame(ta, (const struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + default: + OSMO_ASSERT(0); + } +} + +/* ITU-T V.110 Section 7.1.2 */ +static void v110_ta_fsm_connect_ta_to_line_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + /* 7.1.2.1 Switching to the data mode causes the TA to transmit the following towards the ISDN: */ + /* a) frame synchronization pattern as described in 5.1.3.1 and 5.2.1 (done by the API user) */ + /* b) circuit 103: continuous binary '1' */ + ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; + /* c) status bits S = OFF and X = OFF */ + ts->tx.s_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */ + ts->tx.x_bits = V110_SX_BIT_OFF; /* OFF is binary '1' */ + + /* 7.1.2.2 ... the receiver in the TA will begin to search for the frame synchronization + * pattern in the received bit stream (see 5.1.3.1 and 5.2.1) and start timer T1. */ + OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_T1); +} + +/* ITU-T V.110 Section 7.1.2 */ +static void v110_ta_fsm_connect_ta_to_line(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + /* If circuit 108 is OFF, we go back to IDLE/READY */ + if (V24_FLAGMASK_IS_OFF(ts->v24_flags, OSMO_V110_TA_C_108)) + v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY); + break; + case V110_TA_EV_SYNC_IND: + /* 7.1.2.3 When the receiver recognizes the frame synchronization pattern, it causes the S- + * and X-bits in the transmitted frames to be turned ON (provided that circuit 108 is ON). */ + OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)); + ts->tx.s_bits = V110_SX_BIT_ON; + ts->tx.x_bits = V110_SX_BIT_ON; + break; + case V110_TA_EV_RX_FRAME_IND: + { + const struct osmo_v110_decoded_frame *df = data; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.2.4 When the receiver recognizes that the status of bits S and X are ON */ + if (v110_df_s_bits_are(df, V110_SX_BIT_ON) && + v110_df_x_bits_are(df, V110_SX_BIT_ON)) { + /* ... it will perform the following functions: */ + /* a) Turn ON circuit 107 toward the DTE and stop timer T1 */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_107); + osmo_timer_del(&fi->timer); + /* b) Then, circuit 103 may be connected to the data bits in the frame; however, the + * DTE must maintain a binary 1 condition on circuit 103 until circuit 106 is turned + * ON in the next portion of the sequence. */ + /* c) Turn ON circuit 109 and connect the data bits to circuit 104. */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_109); + ts->rx.d_bit_mode = V110_TA_DBIT_M_FORWARD; + /* d) After an interval of N bits (see 6.3), it will turn ON circuit 106. */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_106); + ts->tx.d_bit_mode = V110_TA_DBIT_M_FORWARD; + v110_ta_flags_update(ta, v24_flags); + /* Circuit 106 transitioning from OFF to ON will cause the transmitted data to + * transition from binary 1 to the data mode. */ + v110_ta_fsm_state_chg(V110_TA_ST_DATA_TRANSFER); + } + + v110_ta_handle_frame(ta, df); + break; + } + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_TIMEOUT: + v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY); + break; + default: + OSMO_ASSERT(0); + } +} + +/* ITU-T V.110 Section 7.1.3 */ +static void v110_ta_fsm_data_transfer_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.3.1 While in the data transfer state, the following circuit conditions exist: + * a): 105, 107, 108, and 109 are in the ON condition */ + /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_105)); */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_107); + /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)); */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_109); + /* b) data is being transmitted on circuit 103 and received on circuit 104 */ + ts->rx.d_bit_mode = V110_TA_DBIT_M_FORWARD; + ts->tx.d_bit_mode = V110_TA_DBIT_M_FORWARD; + /* c) circuits 133 (when implemented) and 106 are in the ON condition unless local out-of-band + * flow control is being used, either or both circuits may be in the ON or the OFF condition. */ + if (!ta->cfg->flow_ctrl.end_to_end) { + /* XXX: OSMO_ASSERT(V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_133)); */ + V24_FLAGMASK_SET_ON(v24_flags, OSMO_V110_TA_C_106); + } + v110_ta_flags_update(ta, v24_flags); + + /* 7.1.3.2 While in the data transfer state, the following status bit conditions exist: */ + /* a) status bits S in both directions are in the ON condition; */ + ts->tx.s_bits = V110_SX_BIT_ON; + /* b) status bits X in both directions are in the ON condition unless end-to-end flow control + * is being used, in which case status bit X in either or both directions may be in the + * ON or the OFF condition. */ + if (!ta->cfg->flow_ctrl.end_to_end) + ts->tx.x_bits = V110_SX_BIT_ON; +} + +/* ITU-T V.110 Section 7.1.3 */ +static void v110_ta_fsm_data_transfer(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + /* 7.1.3.3 While in the data transfer state: */ + /* a) the S status bits shall *not* be mapped to/from the interchange circuits */ + /* b) the X status bits shall *not* be mapped according to Table 3, + * unless end-to-end flow control is implemented */ + /* TODO: if (ta->cfg->flow_ctrl.end_to_end) { ... } */ + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + /* 7.1.4.1 At the completion of the data transfer phase, the local DTE will indicate a + * disconnect request by turning OFF circuit 108 */ + if (V24_FLAGMASK_IS_ON(ts->v24_flags, OSMO_V110_TA_C_108)) + break; + v110_ta_fsm_state_chg(V110_TA_ST_DISCONNECTING); + break; + case V110_TA_EV_DESYNC_IND: + v110_ta_fsm_state_chg(V110_TA_ST_RESYNCING); + break; + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_RX_FRAME_IND: + { + const struct osmo_v110_decoded_frame *df = data; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.4.2 ... this TA will recognize the transition of the status bits S from + * ON to OFF and the data bits from data to binary 0 as a disconnect request */ + if (v110_df_s_bits_are(df, V110_SX_BIT_OFF) && v110_df_d_bits_are(df, 0)) { + /* ... and it will turn OFF circuits 107 and 109. */ + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107); + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_109); + v110_ta_flags_update(ta, v24_flags); + /* DTE should respond by turning OFF circuit 108 */ + break; /* XXX: shall we forward D-bits to DTE anyway? */ + } + + v110_ta_handle_frame(ta, df); + break; + } + default: + OSMO_ASSERT(0); + } +} + +/* ITU-T V.110 Section 7.1.4 */ +static void v110_ta_fsm_disconnect_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + unsigned int v24_flags = ta->state.v24_flags; + + /* 7.1.4.1 At the completion of the data transfer phase, the local DTE will indicate a + * disconnect request by turning OFF circuit 108. This will cause the following to occur: */ + /* a) the status bits S in the frame toward ISDN will turn OFF, status bits X are kept ON */ + ts->tx.s_bits = V110_SX_BIT_OFF; + /* b) circuit 106 will be turned OFF */ + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_106); + v110_ta_flags_update(ta, v24_flags); + /* c) the data bits in the frame will be set to binary 0. */ + ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ZERO; + + /* To guard against the failure of the remote TA to respond to the disconnect request, + * the local TA may start a timer T2 (suggested value 5 s) which is stopped by the + * reception or transmission of any D-channel clearing message (DISCONNECT, RELEASE, + * RELEASE COMPLETE). */ + OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_T2); +} + +/* ITU-T V.110 Section 7.1.4 */ +static void v110_ta_fsm_disconnect(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + break; /* nothing to do */ + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_RX_FRAME_IND: + { + const struct osmo_v110_decoded_frame *df = data; + + /* 7.1.4.3 The TA at the station that originated the disconnect request will + * recognize reception of S = OFF or the loss of framing signals as a disconnect + * acknowledgement and turn OFF circuits 107 and 109. */ + if (v110_df_s_bits_are(df, V110_SX_BIT_OFF)) { + /* circuits 107 and 109 set to off in .onenter() */ + v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY); + } + + v110_ta_handle_frame(ta, df); + break; + } + case V110_TA_EV_DESYNC_IND: + case V110_TA_EV_TIMEOUT: + /* circuits 107 and 109 set to off in .onenter() */ + v110_ta_fsm_state_chg(V110_TA_ST_IDLE_READY); + break; + default: + OSMO_ASSERT(0); + } +} + +/* ITU-T V.110 Section 7.1.5 */ +static void v110_ta_fsm_resyncing_onenter(struct osmo_fsm_inst *fi, uint32_t prev_state) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + + /* 7.1.5 In the event of loss of frame synchronization, the (local) TA should + * attempt to resynchronize as follows: */ + /* a) Place circuit 104 in binary 1 condition (passes from the data mode) */ + ts->rx.d_bit_mode = V110_TA_DBIT_M_ALL_ONE; + /* b) Turn OFF status bit X in the transmitted frame */ + ts->tx.x_bits = V110_SX_BIT_OFF; + + /* guard timeout, see 7.1.5 e) */ + OSMO_ASSERT(fi->T == OSMO_V110_TA_TIMER_X1); +} + +/* ITU-T V.110 Section 7.1.5 */ +static void v110_ta_fsm_resyncing(struct osmo_fsm_inst *fi, uint32_t event, void *data) +{ + struct osmo_v110_ta *ta = (struct osmo_v110_ta *)fi->priv; + struct v110_ta_state *ts = &ta->state; + unsigned int v24_flags = ta->state.v24_flags; + + switch (event) { + case V110_TA_EV_V24_STATUS_CHG: + break; /* TODO: handle circuit 108 being set to OFF? */ + case V110_TA_EV_TX_FRAME_RTS: + v110_ta_build_frame(ta, (struct osmo_v110_decoded_frame *)data); + break; + case V110_TA_EV_SYNC_IND: + /* f) If resynchronization is achieved, the local TA should turn ON status bit X */ + ts->tx.x_bits = V110_SX_BIT_ON; + v110_ta_fsm_state_chg(V110_TA_ST_DATA_TRANSFER); + break; + case V110_TA_EV_TIMEOUT: + /* e) If after an interval of X1 seconds the local TA cannot attain synchronization, + * it should send a disconnect request by turning OFF all of the status bits for several + * (at least three) frames with data bits set to binary 0 and then disconnect by turning + * OFF circuit 107 and transferring to the disconnected mode as discussed in 7.1.4.2. */ + ts->tx.s_bits = V110_SX_BIT_OFF; + ts->tx.x_bits = V110_SX_BIT_OFF; + ts->tx.d_bit_mode = V110_TA_DBIT_M_ALL_ZERO; + /* TODO: actually Tx those frames (delay state transition) */ + V24_FLAGMASK_SET_OFF(v24_flags, OSMO_V110_TA_C_107); + v110_ta_flags_update(ta, v24_flags); + v110_ta_fsm_state_chg(V110_TA_ST_DISCONNECTING); + break; + default: + OSMO_ASSERT(0); + } +} + +static int v110_ta_timer_cb(struct osmo_fsm_inst *fi) +{ + osmo_fsm_inst_dispatch(fi, V110_TA_EV_TIMEOUT, NULL); + return 0; +} + +static const struct osmo_fsm_state v110_ta_states[] = { + [V110_TA_ST_IDLE_READY] = { + .name = "IDLE_READY", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_RX_FRAME_IND), + .out_state_mask = S(V110_TA_ST_IDLE_READY) + | S(V110_TA_ST_CON_TA_TO_LINE), + .action = &v110_ta_fsm_idle_ready, + .onenter = &v110_ta_fsm_idle_ready_onenter, + }, + [V110_TA_ST_CON_TA_TO_LINE] = { + .name = "CONNECT_TA_TO_LINE", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_TIMEOUT) + | S(V110_TA_EV_SYNC_IND) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_RX_FRAME_IND), + .out_state_mask = S(V110_TA_ST_DATA_TRANSFER) + | S(V110_TA_ST_IDLE_READY), + .action = &v110_ta_fsm_connect_ta_to_line, + .onenter = &v110_ta_fsm_connect_ta_to_line_onenter, + }, + [V110_TA_ST_DATA_TRANSFER] = { + .name = "DATA_TRANSFER", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_DESYNC_IND) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_RX_FRAME_IND), + .out_state_mask = S(V110_TA_ST_RESYNCING) + | S(V110_TA_ST_DISCONNECTING), + .action = &v110_ta_fsm_data_transfer, + .onenter = &v110_ta_fsm_data_transfer_onenter, + }, + [V110_TA_ST_DISCONNECTING] = { + .name = "DISCONNECTING", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_TIMEOUT) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_RX_FRAME_IND) + | S(V110_TA_EV_DESYNC_IND), + .out_state_mask = S(V110_TA_ST_IDLE_READY), + .action = &v110_ta_fsm_disconnect, + .onenter = &v110_ta_fsm_disconnect_onenter, + }, + [V110_TA_ST_RESYNCING] = { + .name = "RESYNCING", + .in_event_mask = S(V110_TA_EV_V24_STATUS_CHG) + | S(V110_TA_EV_TIMEOUT) + | S(V110_TA_EV_TX_FRAME_RTS) + | S(V110_TA_EV_SYNC_IND), + .out_state_mask = S(V110_TA_ST_IDLE_READY) + | S(V110_TA_ST_DATA_TRANSFER), + .action = &v110_ta_fsm_resyncing, + .onenter = &v110_ta_fsm_resyncing_onenter, + }, +}; + +static struct osmo_fsm osmo_v110_ta_fsm = { + .name = "V110-TA", + .states = v110_ta_states, + .num_states = ARRAY_SIZE(v110_ta_states), + .timer_cb = v110_ta_timer_cb, + .log_subsys = DLGLOBAL, + .event_names = v110_ta_fsm_event_names, +}; + +static __attribute__((constructor)) void on_dso_load(void) +{ + OSMO_ASSERT(osmo_fsm_register(&osmo_v110_ta_fsm) == 0); +} + +/*! Allocate a V.110 TA (Terminal Adapter) instance. + * \param[in] ctx parent talloc context. + * \param[in] name name of the TA instance. + * \param[in] cfg initial configuration of the TA instance. + * \returns pointer to allocated TA instance; NULL on error. */ +struct osmo_v110_ta *osmo_v110_ta_alloc(void *ctx, const char *name, + const struct osmo_v110_ta_cfg *cfg) +{ + struct osmo_v110_ta *ta; + + OSMO_ASSERT(cfg != NULL); + OSMO_ASSERT(cfg->rx_cb != NULL); + OSMO_ASSERT(cfg->tx_cb != NULL); + + /* local (TE-TA) flow control is not implemented */ + if (cfg->flow_ctrl.local != OSMO_V110_LOCAL_FLOW_CTRL_NONE) { + LOGP(DLGLOBAL, LOGL_ERROR, "Local (TE-TA) flow control is not implemented\n"); + return NULL; + } + + ta = talloc_zero(ctx, struct osmo_v110_ta); + if (ta == NULL) + return NULL; + + ta->name = talloc_strdup(ta, name); + ta->cfg = talloc_memdup(ta, cfg, sizeof(*cfg)); + if (ta->name == NULL || ta->cfg == NULL) + goto exit_free; + + ta->Tdefs = talloc_memdup(ta, v110_ta_tdef, sizeof(v110_ta_tdef)); + if (ta->Tdefs == NULL) + goto exit_free; + osmo_tdefs_reset(ta->Tdefs); /* apply default values */ + + ta->fi = osmo_fsm_inst_alloc(&osmo_v110_ta_fsm, ta, ta, LOGL_DEBUG, name); + if (ta->fi == NULL) + goto exit_free; + + /* perform a loop transition to init the internal state */ + osmo_fsm_inst_state_chg(ta->fi, V110_TA_ST_IDLE_READY, 0, 0); + + return ta; + +exit_free: + if (ta->fi != NULL) + osmo_fsm_inst_free(ta->fi); + talloc_free(ta); + return NULL; +} + +/*! Release memory taken by the given V.110 TA instance. + * \param[in] ta TA instance to be free()d. */ +void osmo_v110_ta_free(struct osmo_v110_ta *ta) +{ + if (ta == NULL) + return; + if (ta->fi != NULL) + osmo_fsm_inst_free(ta->fi); + talloc_free(ta); /* also free()s name and cfg */ +} + +/*! Configure a timer of the given V.110 TA instance. + * \param[in] ta TA instance to be configured. + * \param[in] timer a timer to be configured. + * \param[in] val_ms the new timeout value to set (in milliseconds). + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_set_timer_val_ms(struct osmo_v110_ta *ta, + enum osmo_v110_ta_timer timer, + unsigned long val_ms) +{ + return osmo_tdef_set(ta->Tdefs, (int)timer, val_ms, OSMO_TDEF_MS); +} + +/*! Feed a [decoded] V.110 frame into the given TA instance. + * + * This function, like its _out counterpart, is intended to be used by the lower layers + * receiving V.110 frames over some medium. The caller of this function is responsible + * for finding the synchronization pattern (if needed), aligning to the frame boundaries, + * and decoding frames using osmo_v110_decode_frame() or osmo_csd_*_decode_frame(). + * + * Bits E1/E2/E3 are expected to be set by the caller (if not being transmitted + * over the medium) in accordance with the configured synchronous user rate. + * + * Bits D1..D48 are passed to the bit rate adaption function RA1. The resulting output + * is then passed to the upper layer (application) via the configured .rx_cb(). Though, + * in certain states of the TA's FSM, bits D1..D48 are ignored and the upper layer + * gets a sequence of binary '0' or '1'. + * + * \param[in] ta TA instance to feed the given frame into. + * \param[in] in pointer to a [decoded] V.110 frame. + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_frame_in(struct osmo_v110_ta *ta, const struct osmo_v110_decoded_frame *in) +{ + return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_RX_FRAME_IND, (void *)in); +} + +/*! Pull a [decoded] V.110 frame out of the given TA instance. + * + * This function, like its _in counterpart, is intended to be used by the lower layers + * transmitting V.110 frames over some medium. The caller of this function is responsible + * for encoding the output frame using osmo_v110_encode_frame() or osmo_csd_*_encode_frame(). + * + * Bits E1/E2/E3 are set in accordance with the configured synchronous user rate. + * Bits E4/E5/E6/E7 are unconditionally set to binary '1'. + * + * Bits D1..D48 are set depending on the state of TA's FSM: + * + * - In data transfer mode, the user bits are obtained from the upper layer (application) + * via the configured .tx_cb(), and then passed to the bit rate adaption function RA1, + * which generates bits D1..D48. + * - In other modes, bits D1..D48 are all set to binary '0' or '1'. + * + * \param[in] ta TA instance to pull a frame from. + * \param[out] out where to store a [decoded] V.110 frame. + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_frame_out(struct osmo_v110_ta *ta, struct osmo_v110_decoded_frame *out) +{ + return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_TX_FRAME_RTS, (void *)out); +} + +/*! Indicate a synchronization establishment event. + * + * This function is intended to be called when the lower layer + * achieves synchronization to the frame clock. + * + * \param[in] ta TA instance to indicate the event to. + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_sync_ind(struct osmo_v110_ta *ta) +{ + return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_SYNC_IND, NULL); +} + +/*! Indicate a synchronization loss event. + * + * This function is intended to be called when the lower layer + * experiences a loss of synchronization with the frame clock. + * + * \param[in] ta TA instance to indicate the event to. + * \returns 0 in case of success; negative on error. */ +int osmo_v110_ta_desync_ind(struct osmo_v110_ta *ta) +{ + return osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_DESYNC_IND, NULL); +} + +/*! Get the V.24 status bit-mask of the given TA instance. + * \param[in] ta TA instance to get the circuit bit-mask. + * \returns bitmask of OSMO_V110_TA_C_*. */ +unsigned int osmo_v110_ta_get_status(const struct osmo_v110_ta *ta) +{ + return ta->state.v24_flags; +} + +/*! Set the V.24 status bit-mask of the given TA instance. + * \param[in] ta TA instance to update the circuit state. + * \param[in] status bit-mask of OSMO_V110_TA_C_*. + * \returns 0 on success; negative on error. */ +static int v110_ta_set_status(struct osmo_v110_ta *ta, unsigned int status) +{ + const unsigned int old_status = ta->state.v24_flags; + int rc = 0; + + ta->state.v24_flags = status; + if (status != old_status) + rc = osmo_fsm_inst_dispatch(ta->fi, V110_TA_EV_V24_STATUS_CHG, NULL); + + return rc; +} + +/*! Get state of a V.24 circuit of the given TA instance. + * \param[in] ta TA instance to get the circuit state. + * \param[in] circuit a V.24 circuit, one of OSMO_V110_TA_C_*. + * \returns circuit state: active (true) or inactive (false). */ +bool osmo_v110_ta_get_circuit(const struct osmo_v110_ta *ta, + enum osmo_v110_ta_circuit circuit) +{ + return V24_FLAGMASK_IS_ON(ta->state.v24_flags, circuit); +} + +/*! Activate/deactivate a V.24 circuit of the given TA instance. + * \param[in] ta TA instance to update the circuit state. + * \param[in] circuit a V.24 circuit, one of OSMO_V110_TA_C_* (DTE->DCE). + * \param[in] active activate (true) or deactivate (false) the circuit. + * \returns 0 on success; negative on error. */ +int osmo_v110_ta_set_circuit(struct osmo_v110_ta *ta, + enum osmo_v110_ta_circuit circuit, bool active) +{ + unsigned int status = ta->state.v24_flags; + + /* permit setting only DTE->DCE circuits */ + switch (circuit) { + case OSMO_V110_TA_C_105: + case OSMO_V110_TA_C_108: + case OSMO_V110_TA_C_133: + break; + default: + LOGPFSML(ta->fi, LOGL_ERROR, + "Setting circuit %s is not permitted (wrong direction?)\n", + osmo_v110_ta_circuit_name(circuit)); + return -EACCES; + } + + if (active) + V24_FLAGMASK_SET_ON(status, circuit); + else + V24_FLAGMASK_SET_OFF(status, circuit); + + return v110_ta_set_status(ta, status); +} |