diff options
Diffstat (limited to 'src/core/sercomm.c')
-rw-r--r-- | src/core/sercomm.c | 337 |
1 files changed, 337 insertions, 0 deletions
diff --git a/src/core/sercomm.c b/src/core/sercomm.c new file mode 100644 index 00000000..1798acec --- /dev/null +++ b/src/core/sercomm.c @@ -0,0 +1,337 @@ +/* (C) 2010,2017 by Harald Welte <laforge@gnumonks.org> + * + * 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 sercomm + * @{ + * Serial communications layer, based on HDLC. + * + * \file sercomm.c */ + +#include "config.h" + +#include <stdint.h> +#include <stdio.h> +#include <errno.h> + +#include <osmocom/core/msgb.h> +#include <osmocom/core/utils.h> +#include <osmocom/core/sercomm.h> +#include <osmocom/core/linuxlist.h> + +#ifndef EMBEDDED +# define DEFAULT_RX_MSG_SIZE 2048 +/*! Protect against IRQ context */ +void sercomm_drv_lock(unsigned long __attribute__((unused)) *flags) {} +/*! Release protection against IRQ context */ +void sercomm_drv_unlock(unsigned long __attribute__((unused)) *flags) {} +#else +# define DEFAULT_RX_MSG_SIZE 256 +#endif /* EMBEDDED */ + +/* weak symbols to be overridden by application */ +__attribute__((weak)) void sercomm_drv_start_tx(struct osmo_sercomm_inst *sercomm) {}; +__attribute__((weak)) int sercomm_drv_baudrate_chg(struct osmo_sercomm_inst *sercomm, uint32_t bdrt) +{ + return -1; +} + +#define HDLC_FLAG 0x7E +#define HDLC_ESCAPE 0x7D + +#define HDLC_C_UI 0x03 +#define HDLC_C_P_BIT (1 << 4) +#define HDLC_C_F_BIT (1 << 4) + +enum rx_state { + RX_ST_WAIT_START, + RX_ST_ADDR, + RX_ST_CTRL, + RX_ST_DATA, + RX_ST_ESCAPE, +}; + +/*! Initialize an Osmocom sercomm instance + * \param sercomm Caller-allocated sercomm instance to be initialized + * + * This function initializes the sercomm instance, including the + * registration of the ECHO service at the ECHO DLCI + */ +void osmo_sercomm_init(struct osmo_sercomm_inst *sercomm) +{ + unsigned int i; + for (i = 0; i < ARRAY_SIZE(sercomm->tx.dlci_queues); i++) + INIT_LLIST_HEAD(&sercomm->tx.dlci_queues[i]); + + sercomm->rx.msg = NULL; + if (!sercomm->rx.msg_size) + sercomm->rx.msg_size = DEFAULT_RX_MSG_SIZE; + sercomm->initialized = 1; + + /* set up the echo dlci */ + osmo_sercomm_register_rx_cb(sercomm, SC_DLCI_ECHO, &osmo_sercomm_sendmsg); +} + +/*! Determine if a given Osmocom sercomm instance has been initialized + * \param[in] sercomm Osmocom sercomm instance to be checked + * \returns 1 in case \a sercomm was previously initialized; 0 otherwise */ +int osmo_sercomm_initialized(struct osmo_sercomm_inst *sercomm) +{ + return sercomm->initialized; +} + +/*! User interface for transmitting messages for a given DLCI + * \param[in] sercomm Osmocom sercomm instance through which to transmit + * \param[in] dlci DLCI through whcih to transmit \a msg + * \param[in] msg Message buffer to be transmitted via \a dlci on \a * sercomm + **/ +void osmo_sercomm_sendmsg(struct osmo_sercomm_inst *sercomm, uint8_t dlci, struct msgb *msg) +{ + unsigned long flags; + uint8_t *hdr; + + /* prepend address + control octet */ + hdr = msgb_push(msg, 2); + hdr[0] = dlci; + hdr[1] = HDLC_C_UI; + + /* This functiion can be called from any context: FIQ, IRQ + * and supervisor context. Proper locking is important! */ + sercomm_drv_lock(&flags); + msgb_enqueue(&sercomm->tx.dlci_queues[dlci], msg); + sercomm_drv_unlock(&flags); + + /* tell UART that we have something to send */ + sercomm_drv_start_tx(sercomm); +} + +/*! How deep is the Tx queue for a given DLCI? + * \param[n] sercomm Osmocom sercomm instance on which to operate + * \param[in] dlci DLCI whose queue depthy is to be determined + * \returns number of elements in the per-DLCI transmit queue */ +unsigned int osmo_sercomm_tx_queue_depth(struct osmo_sercomm_inst *sercomm, uint8_t dlci) +{ + struct llist_head *le; + unsigned int num = 0; + + llist_for_each(le, &sercomm->tx.dlci_queues[dlci]) { + num++; + } + + return num; +} + +/*! wait until everything has been transmitted, then grab the lock and + * change the baud rate as requested + * \param[in] sercomm Osmocom sercomm instance + * \param[in] bdrt New UART Baud Rate + * \returns result of the operation as provided by sercomm_drv_baudrate_chg() + */ +int osmo_sercomm_change_speed(struct osmo_sercomm_inst *sercomm, uint32_t bdrt) +{ + unsigned int i, count; + unsigned long flags; + + while (1) { + /* count the number of pending messages */ + count = 0; + for (i = 0; i < ARRAY_SIZE(sercomm->tx.dlci_queues); i++) + count += osmo_sercomm_tx_queue_depth(sercomm, i); + /* if we still have any in the queue, restart */ + if (count == 0) + break; + } + + while (1) { + /* no messages in the queue, grab the lock to ensure it + * stays that way */ + sercomm_drv_lock(&flags); + if (!sercomm->tx.msg && !sercomm->tx.next_char) { + int rc; + /* change speed */ + rc = sercomm_drv_baudrate_chg(sercomm, bdrt); + sercomm_drv_unlock(&flags); + return rc; + } else + sercomm_drv_unlock(&flags); + } + return -1; +} + +/*! fetch one octet of to-be-transmitted serial data + * \param[in] sercomm Sercomm Instance from which to fetch pending data + * \param[out] ch pointer to caller-allocaed output memory + * \returns 1 in case of succss; 0 if no data available; negative on error */ +int osmo_sercomm_drv_pull(struct osmo_sercomm_inst *sercomm, uint8_t *ch) +{ + unsigned long flags; + + /* we may be called from interrupt context, but we stiff need to lock + * because sercomm could be accessed from a FIQ context ... */ + + sercomm_drv_lock(&flags); + + if (!sercomm->tx.msg) { + unsigned int i; + /* dequeue a new message from the queues */ + for (i = 0; i < ARRAY_SIZE(sercomm->tx.dlci_queues); i++) { + sercomm->tx.msg = msgb_dequeue(&sercomm->tx.dlci_queues[i]); + if (sercomm->tx.msg) + break; + } + if (sercomm->tx.msg) { + /* start of a new message, send start flag octet */ + *ch = HDLC_FLAG; + sercomm->tx.next_char = sercomm->tx.msg->data; + sercomm_drv_unlock(&flags); + return 1; + } else { + /* no more data avilable */ + sercomm_drv_unlock(&flags); + return 0; + } + } + + if (sercomm->tx.state == RX_ST_ESCAPE) { + /* we've already transmitted the ESCAPE octet, + * we now need to transmit the escaped data */ + *ch = *sercomm->tx.next_char++; + sercomm->tx.state = RX_ST_DATA; + } else if (sercomm->tx.next_char >= sercomm->tx.msg->tail) { + /* last character has already been transmitted, + * send end-of-message octet */ + *ch = HDLC_FLAG; + /* we've reached the end of the message buffer */ + msgb_free(sercomm->tx.msg); + sercomm->tx.msg = NULL; + sercomm->tx.next_char = NULL; + /* escaping for the two control octets */ + } else if (*sercomm->tx.next_char == HDLC_FLAG || + *sercomm->tx.next_char == HDLC_ESCAPE || + *sercomm->tx.next_char == 0x00) { + /* send an escape octet */ + *ch = HDLC_ESCAPE; + /* invert bit 5 of the next octet to be sent */ + *sercomm->tx.next_char ^= (1 << 5); + sercomm->tx.state = RX_ST_ESCAPE; + } else { + /* standard case, simply send next octet */ + *ch = *sercomm->tx.next_char++; + } + + sercomm_drv_unlock(&flags); + return 1; +} + +/*! Register a handler for a given DLCI + * \param sercomm Sercomm Instance in which caller wishes to register + * \param[in] dlci Data Ling Connection Identifier to register + * \param[in] cb Callback function for \a dlci + * \returns 0 on success; negative on error */ +int osmo_sercomm_register_rx_cb(struct osmo_sercomm_inst *sercomm, uint8_t dlci, dlci_cb_t cb) +{ + if (dlci >= ARRAY_SIZE(sercomm->rx.dlci_handler)) + return -EINVAL; + + if (sercomm->rx.dlci_handler[dlci]) + return -EBUSY; + + sercomm->rx.dlci_handler[dlci] = cb; + return 0; +} + +/* dispatch an incoming message once it is completely received */ +static void dispatch_rx_msg(struct osmo_sercomm_inst *sercomm, uint8_t dlci, struct msgb *msg) +{ + if (dlci >= ARRAY_SIZE(sercomm->rx.dlci_handler) || + !sercomm->rx.dlci_handler[dlci]) { + msgb_free(msg); + return; + } + sercomm->rx.dlci_handler[dlci](sercomm, dlci, msg); +} + +/*! the driver has received one byte, pass it into sercomm layer + * \param[in] sercomm Sercomm Instance for which a byte was received + * \param[in] ch byte that was received from line for said instance + * \returns 1 on success; 0 on unrecognized char; negative on error */ +int osmo_sercomm_drv_rx_char(struct osmo_sercomm_inst *sercomm, uint8_t ch) +{ + uint8_t *ptr; + + /* we are always called from interrupt context in this function, + * which means that any data structures we use need to be for + * our exclusive access */ + if (!sercomm->rx.msg) + sercomm->rx.msg = osmo_sercomm_alloc_msgb(sercomm->rx.msg_size); + + if (msgb_tailroom(sercomm->rx.msg) == 0) { + //cons_puts("sercomm_drv_rx_char() overflow!\n"); + msgb_free(sercomm->rx.msg); + sercomm->rx.msg = osmo_sercomm_alloc_msgb(sercomm->rx.msg_size); + sercomm->rx.state = RX_ST_WAIT_START; + return 0; + } + + switch (sercomm->rx.state) { + case RX_ST_WAIT_START: + if (ch != HDLC_FLAG) + break; + sercomm->rx.state = RX_ST_ADDR; + break; + case RX_ST_ADDR: + sercomm->rx.dlci = ch; + sercomm->rx.state = RX_ST_CTRL; + break; + case RX_ST_CTRL: + sercomm->rx.ctrl = ch; + sercomm->rx.state = RX_ST_DATA; + break; + case RX_ST_DATA: + if (ch == HDLC_ESCAPE) { + /* drop the escape octet, but change state */ + sercomm->rx.state = RX_ST_ESCAPE; + break; + } else if (ch == HDLC_FLAG) { + /* message is finished */ + dispatch_rx_msg(sercomm, sercomm->rx.dlci, sercomm->rx.msg); + /* allocate new buffer */ + sercomm->rx.msg = NULL; + /* start all over again */ + sercomm->rx.state = RX_ST_WAIT_START; + + /* do not add the control char */ + break; + } + /* default case: store the octet */ + ptr = msgb_put(sercomm->rx.msg, 1); + *ptr = ch; + break; + case RX_ST_ESCAPE: + /* store bif-5-inverted octet in buffer */ + ch ^= (1 << 5); + ptr = msgb_put(sercomm->rx.msg, 1); + *ptr = ch; + /* transition back to normal DATA state */ + sercomm->rx.state = RX_ST_DATA; + break; + } + + return 1; +} + +/*! @} */ |