041f0e5379
Logging v1 has been removed and log_strdup wrapper function is no longer needed. Removing the function and its use in the tree. Signed-off-by: Krzysztof Chruscinski <krzysztof.chruscinski@nordicsemi.no>
1536 lines
35 KiB
C
1536 lines
35 KiB
C
/*
|
|
* Copyright (c) 2020 Intel Corporation
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(gsm_mux, CONFIG_GSM_MUX_LOG_LEVEL);
|
|
|
|
#include <zephyr/kernel.h>
|
|
#include <zephyr/sys/util.h>
|
|
#include <zephyr/sys/crc.h>
|
|
#include <zephyr/net/buf.h>
|
|
#include <zephyr/net/ppp.h>
|
|
|
|
#include "uart_mux_internal.h"
|
|
#include "gsm_mux.h"
|
|
|
|
/* Default values are from the specification 07.10 */
|
|
#define T1_MSEC 100 /* 100 ms */
|
|
#define T2_MSEC 340 /* 333 ms */
|
|
|
|
#define N1 256 /* default I frame size, GSM 07.10 ch 6.2.2.1 */
|
|
#define N2 3 /* retry 3 times */
|
|
|
|
/* CRC8 is the reflected CRC8/ROHC algorithm */
|
|
#define FCS_POLYNOMIAL 0xe0 /* reversed crc8 */
|
|
#define FCS_INIT_VALUE 0xFF
|
|
#define FCS_GOOD_VALUE 0xCF
|
|
|
|
#define GSM_EA 0x01 /* Extension bit */
|
|
#define GSM_CR 0x02 /* Command / Response */
|
|
#define GSM_PF 0x10 /* Poll / Final */
|
|
|
|
/* Frame types */
|
|
#define FT_RR 0x01 /* Receive Ready */
|
|
#define FT_UI 0x03 /* Unnumbered Information */
|
|
#define FT_RNR 0x05 /* Receive Not Ready */
|
|
#define FT_REJ 0x09 /* Reject */
|
|
#define FT_DM 0x0F /* Disconnected Mode */
|
|
#define FT_SABM 0x2F /* Set Asynchronous Balanced Mode */
|
|
#define FT_DISC 0x43 /* Disconnect */
|
|
#define FT_UA 0x63 /* Unnumbered Acknowledgement */
|
|
#define FT_UIH 0xEF /* Unnumbered Information with Header check */
|
|
|
|
/* Control channel commands */
|
|
#define CMD_NSC 0x08 /* Non Supported Command Response */
|
|
#define CMD_TEST 0x10 /* Test Command */
|
|
#define CMD_PSC 0x20 /* Power Saving Control */
|
|
#define CMD_RLS 0x28 /* Remote Line Status Command */
|
|
#define CMD_FCOFF 0x30 /* Flow Control Off Command */
|
|
#define CMD_PN 0x40 /* DLC parameter negotiation */
|
|
#define CMD_RPN 0x48 /* Remote Port Negotiation Command */
|
|
#define CMD_FCON 0x50 /* Flow Control On Command */
|
|
#define CMD_CLD 0x60 /* Multiplexer close down */
|
|
#define CMD_SNC 0x68 /* Service Negotiation Command */
|
|
#define CMD_MSC 0x70 /* Modem Status Command */
|
|
|
|
/* Flag sequence field between messages (start of frame) */
|
|
#define SOF_MARKER 0xF9
|
|
|
|
/* Mux parsing states */
|
|
enum gsm_mux_state {
|
|
GSM_MUX_SOF, /* Start of frame */
|
|
GSM_MUX_ADDRESS, /* Address field */
|
|
GSM_MUX_CONTROL, /* Control field */
|
|
GSM_MUX_LEN_0, /* First length byte */
|
|
GSM_MUX_LEN_1, /* Second length byte */
|
|
GSM_MUX_DATA, /* Data */
|
|
GSM_MUX_FCS, /* Frame Check Sequence */
|
|
GSM_MUX_EOF /* End of frame */
|
|
};
|
|
|
|
struct gsm_mux {
|
|
/* UART device to use. This device is the real UART, not the
|
|
* muxed one.
|
|
*/
|
|
const struct device *uart;
|
|
|
|
/* Buf to use when TX mux packet (hdr + data). For RX it only contains
|
|
* the data (not hdr).
|
|
*/
|
|
struct net_buf *buf;
|
|
int mru;
|
|
|
|
enum gsm_mux_state state;
|
|
|
|
/* Control DLCI is not included in this list so -1 here */
|
|
uint8_t dlci_to_create[CONFIG_GSM_MUX_DLCI_MAX - 1];
|
|
|
|
uint16_t msg_len; /* message length */
|
|
uint16_t received; /* bytes so far received */
|
|
|
|
struct k_work_delayable t2_timer;
|
|
sys_slist_t pending_ctrls;
|
|
|
|
uint16_t t1_timeout_value; /* T1 default value */
|
|
uint16_t t2_timeout_value; /* T2 default value */
|
|
|
|
/* Information from currently read packet */
|
|
uint8_t address; /* dlci address (only one byte address supported) */
|
|
uint8_t control; /* type of the frame */
|
|
uint8_t fcs; /* calculated frame check sequence */
|
|
uint8_t received_fcs; /* packet fcs */
|
|
uint8_t retries; /* N2 counter */
|
|
|
|
bool in_use : 1;
|
|
bool is_initiator : 1; /* Did we initiate the connection attempt */
|
|
bool refuse_service : 1; /* Do not try to talk to this modem */
|
|
};
|
|
|
|
/* DLCI states */
|
|
enum gsm_dlci_state {
|
|
GSM_DLCI_CLOSED,
|
|
GSM_DLCI_OPENING,
|
|
GSM_DLCI_OPEN,
|
|
GSM_DLCI_CLOSING
|
|
};
|
|
|
|
enum gsm_dlci_mode {
|
|
GSM_DLCI_MODE_ABM = 0, /* Normal Asynchronous Balanced Mode */
|
|
GSM_DLCI_MODE_ADM = 1, /* Asynchronous Disconnected Mode */
|
|
};
|
|
|
|
typedef int (*dlci_process_msg_t)(struct gsm_dlci *dlci, bool cmd,
|
|
struct net_buf *buf);
|
|
typedef void (*dlci_command_cb_t)(struct gsm_dlci *dlci, bool connected);
|
|
|
|
struct gsm_dlci {
|
|
sys_snode_t node;
|
|
struct k_sem disconnect_sem;
|
|
struct gsm_mux *mux;
|
|
dlci_process_msg_t handler;
|
|
dlci_command_cb_t command_cb;
|
|
gsm_mux_dlci_created_cb_t dlci_created_cb;
|
|
void *user_data;
|
|
const struct device *uart;
|
|
enum gsm_dlci_state state;
|
|
enum gsm_dlci_mode mode;
|
|
int num;
|
|
uint32_t req_start;
|
|
uint8_t retries;
|
|
bool refuse_service : 1; /* Do not try to talk to this channel */
|
|
bool in_use : 1;
|
|
};
|
|
|
|
struct gsm_control_msg {
|
|
sys_snode_t node;
|
|
struct net_buf *buf;
|
|
uint32_t req_start;
|
|
uint8_t cmd;
|
|
bool finished : 1;
|
|
};
|
|
|
|
/* From 07.10, Maximum Frame Size [1 - 128] in Basic mode */
|
|
#define MAX_MRU CONFIG_GSM_MUX_MRU_MAX_LEN
|
|
|
|
/* Assume that there are 3 network buffers (one for RX and one for TX, and one
|
|
* extra when parsing data) going on at the same time.
|
|
*/
|
|
#define MIN_BUF_COUNT (CONFIG_GSM_MUX_MAX * 3)
|
|
|
|
NET_BUF_POOL_DEFINE(gsm_mux_pool, MIN_BUF_COUNT, MAX_MRU, 0, NULL);
|
|
|
|
#define BUF_ALLOC_TIMEOUT K_MSEC(50)
|
|
|
|
static struct gsm_mux muxes[CONFIG_GSM_MUX_MAX];
|
|
|
|
static struct gsm_dlci dlcis[CONFIG_GSM_MUX_DLCI_MAX];
|
|
static sys_slist_t dlci_free_entries;
|
|
static sys_slist_t dlci_active_t1_timers;
|
|
static struct k_work_delayable t1_timer;
|
|
|
|
static struct gsm_control_msg ctrls[CONFIG_GSM_MUX_PENDING_CMD_MAX];
|
|
static sys_slist_t ctrls_free_entries;
|
|
|
|
static bool gsm_mux_init_done;
|
|
|
|
static const char *get_frame_type_str(uint8_t frame_type)
|
|
{
|
|
switch (frame_type) {
|
|
case FT_RR:
|
|
return "RR";
|
|
case FT_UI:
|
|
return "UI";
|
|
case FT_RNR:
|
|
return "RNR";
|
|
case FT_REJ:
|
|
return "REJ";
|
|
case FT_DM:
|
|
return "DM";
|
|
case FT_SABM:
|
|
return "SABM";
|
|
case FT_DISC:
|
|
return "DISC";
|
|
case FT_UA:
|
|
return "UA";
|
|
case FT_UIH:
|
|
return "UIH";
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void hexdump_packet(const char *header, uint8_t address, bool cmd_rsp,
|
|
uint8_t control, const uint8_t *data, size_t len)
|
|
{
|
|
const char *frame_type;
|
|
char out[128];
|
|
int ret;
|
|
|
|
if (!IS_ENABLED(CONFIG_GSM_MUX_LOG_LEVEL_DBG)) {
|
|
return;
|
|
}
|
|
|
|
memset(out, 0, sizeof(out));
|
|
|
|
ret = snprintk(out, sizeof(out), "%s: DLCI %d %s ",
|
|
header, address, cmd_rsp ? "cmd" : "resp");
|
|
if (ret >= sizeof(out)) {
|
|
LOG_DBG("%d: Too long msg (%ld)", __LINE__, (long)(ret - sizeof(out)));
|
|
goto print;
|
|
}
|
|
|
|
frame_type = get_frame_type_str(control & ~GSM_PF);
|
|
if (frame_type) {
|
|
ret += snprintk(out + ret, sizeof(out) - ret, "%s ",
|
|
frame_type);
|
|
} else if (!(control & 0x01)) {
|
|
ret += snprintk(out + ret, sizeof(out) - ret,
|
|
"I N(S)%d N(R)%d ",
|
|
(control & 0x0E) >> 1,
|
|
(control & 0xE0) >> 5);
|
|
} else {
|
|
frame_type = get_frame_type_str(control & 0x0F);
|
|
if (frame_type) {
|
|
ret += snprintk(out + ret, sizeof(out) - ret,
|
|
"%s(%d) ", frame_type,
|
|
(control & 0xE0) >> 5);
|
|
} else {
|
|
ret += snprintk(out + ret, sizeof(out) - ret,
|
|
"[%02X] ", control);
|
|
}
|
|
}
|
|
|
|
if (ret >= sizeof(out)) {
|
|
LOG_DBG("%d: Too long msg (%ld)", __LINE__, (long)(ret - sizeof(out)));
|
|
goto print;
|
|
}
|
|
|
|
ret += snprintk(out + ret, sizeof(out) - ret, "%s", (control & GSM_PF) ? "(P)" : "(F)");
|
|
if (ret >= sizeof(out)) {
|
|
LOG_DBG("%d: Too long msg (%ld)", __LINE__, (long)(ret - sizeof(out)));
|
|
goto print;
|
|
}
|
|
|
|
print:
|
|
if (IS_ENABLED(CONFIG_GSM_MUX_VERBOSE_DEBUG)) {
|
|
if (len > 0) {
|
|
LOG_HEXDUMP_DBG(data, len, out);
|
|
} else {
|
|
LOG_DBG("%s", out);
|
|
}
|
|
} else {
|
|
LOG_DBG("%s", out);
|
|
}
|
|
}
|
|
|
|
static uint8_t gsm_mux_fcs_add_buf(uint8_t fcs, const uint8_t *buf, size_t len)
|
|
{
|
|
return crc8(buf, len, FCS_POLYNOMIAL, fcs, true);
|
|
}
|
|
|
|
static uint8_t gsm_mux_fcs_add(uint8_t fcs, uint8_t recv_byte)
|
|
{
|
|
return gsm_mux_fcs_add_buf(fcs, &recv_byte, 1);
|
|
}
|
|
|
|
static bool gsm_mux_read_ea(int *value, uint8_t recv_byte)
|
|
{
|
|
/* As the value can be larger than one byte, collect the read
|
|
* bytes to given variable.
|
|
*/
|
|
*value <<= 7;
|
|
*value |= recv_byte >> 1;
|
|
|
|
/* When the address has been read fully, the EA bit is 1 */
|
|
return recv_byte & GSM_EA;
|
|
}
|
|
|
|
static bool gsm_mux_read_msg_len(struct gsm_mux *mux, uint8_t recv_byte)
|
|
{
|
|
int value = mux->msg_len;
|
|
bool ret;
|
|
|
|
ret = gsm_mux_read_ea(&value, recv_byte);
|
|
|
|
mux->msg_len = value;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static struct net_buf *gsm_mux_alloc_buf(k_timeout_t timeout, void *user_data)
|
|
{
|
|
struct net_buf *buf;
|
|
|
|
ARG_UNUSED(user_data);
|
|
|
|
buf = net_buf_alloc(&gsm_mux_pool, timeout);
|
|
if (!buf) {
|
|
LOG_ERR("Cannot allocate buffer");
|
|
}
|
|
|
|
return buf;
|
|
}
|
|
|
|
static void hexdump_buf(const char *header, struct net_buf *buf)
|
|
{
|
|
if (IS_ENABLED(CONFIG_GSM_MUX_VERBOSE_DEBUG)) {
|
|
while (buf) {
|
|
LOG_HEXDUMP_DBG(buf->data, buf->len, header);
|
|
buf = buf->frags;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int gsm_dlci_process_data(struct gsm_dlci *dlci, bool cmd,
|
|
struct net_buf *buf)
|
|
{
|
|
int len = 0;
|
|
|
|
LOG_DBG("[%p] DLCI %d data %s", dlci->mux, dlci->num,
|
|
cmd ? "request" : "response");
|
|
hexdump_buf("buf", buf);
|
|
|
|
while (buf) {
|
|
uart_mux_recv(dlci->uart, dlci, buf->data, buf->len);
|
|
len += buf->len;
|
|
buf = buf->frags;
|
|
}
|
|
|
|
return len;
|
|
}
|
|
|
|
static struct gsm_dlci *gsm_dlci_get(struct gsm_mux *mux, uint8_t dlci_address)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dlcis); i++) {
|
|
if (dlcis[i].in_use &&
|
|
dlcis[i].mux == mux &&
|
|
dlcis[i].num == dlci_address) {
|
|
return &dlcis[i];
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static int gsm_mux_modem_send(struct gsm_mux *mux, const uint8_t *buf, size_t size)
|
|
{
|
|
if (mux->uart == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
if (size == 0) {
|
|
return 0;
|
|
}
|
|
|
|
return uart_mux_send(mux->uart, buf, size);
|
|
}
|
|
|
|
static int gsm_mux_send_data_msg(struct gsm_mux *mux, bool cmd,
|
|
struct gsm_dlci *dlci, uint8_t frame_type,
|
|
const uint8_t *buf, size_t size)
|
|
{
|
|
uint8_t hdr[7];
|
|
int pos;
|
|
int ret;
|
|
|
|
hdr[0] = SOF_MARKER;
|
|
hdr[1] = (dlci->num << 2) | ((uint8_t)cmd << 1) | GSM_EA;
|
|
hdr[2] = frame_type;
|
|
|
|
if (size < 128) {
|
|
hdr[3] = (size << 1) | GSM_EA;
|
|
pos = 4;
|
|
} else {
|
|
hdr[3] = (size & 127) << 1;
|
|
hdr[4] = (size >> 7);
|
|
pos = 5;
|
|
}
|
|
|
|
/* Write the header and data in smaller chunks in order to avoid
|
|
* allocating a big buffer.
|
|
*/
|
|
(void)gsm_mux_modem_send(mux, &hdr[0], pos);
|
|
|
|
if (size > 0) {
|
|
(void)gsm_mux_modem_send(mux, buf, size);
|
|
}
|
|
|
|
/* FSC is calculated only for address, type and length fields
|
|
* for UIH frames
|
|
*/
|
|
hdr[pos] = 0xFF - gsm_mux_fcs_add_buf(FCS_INIT_VALUE, &hdr[1],
|
|
pos - 1);
|
|
if ((frame_type & ~GSM_PF) != FT_UIH) {
|
|
hdr[pos] = gsm_mux_fcs_add_buf(hdr[pos], buf, size);
|
|
}
|
|
|
|
hdr[pos + 1] = SOF_MARKER;
|
|
|
|
ret = gsm_mux_modem_send(mux, &hdr[pos], 2);
|
|
|
|
hexdump_packet("Sending", dlci->num, cmd, frame_type,
|
|
buf, size);
|
|
return ret;
|
|
}
|
|
|
|
static int gsm_mux_send_control_msg(struct gsm_mux *mux, bool cmd,
|
|
uint8_t dlci_address, uint8_t frame_type)
|
|
{
|
|
uint8_t buf[6];
|
|
|
|
buf[0] = SOF_MARKER;
|
|
buf[1] = (dlci_address << 2) | ((uint8_t)cmd << 1) | GSM_EA;
|
|
buf[2] = frame_type;
|
|
buf[3] = GSM_EA;
|
|
buf[4] = 0xFF - gsm_mux_fcs_add_buf(FCS_INIT_VALUE, buf + 1, 3);
|
|
buf[5] = SOF_MARKER;
|
|
|
|
hexdump_packet("Sending", dlci_address, cmd, frame_type,
|
|
buf, sizeof(buf));
|
|
|
|
return gsm_mux_modem_send(mux, buf, sizeof(buf));
|
|
}
|
|
|
|
static int gsm_mux_send_command(struct gsm_mux *mux, uint8_t dlci_address,
|
|
uint8_t frame_type)
|
|
{
|
|
return gsm_mux_send_control_msg(mux, true, dlci_address, frame_type);
|
|
}
|
|
|
|
static int gsm_mux_send_response(struct gsm_mux *mux, uint8_t dlci_address,
|
|
uint8_t frame_type)
|
|
{
|
|
return gsm_mux_send_control_msg(mux, false, dlci_address, frame_type);
|
|
}
|
|
|
|
static void dlci_run_timer(uint32_t current_time)
|
|
{
|
|
struct gsm_dlci *dlci, *next;
|
|
uint32_t new_timer = UINT_MAX;
|
|
|
|
(void)k_work_cancel_delayable(&t1_timer);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&dlci_active_t1_timers,
|
|
dlci, next, node) {
|
|
uint32_t current_timer = dlci->req_start +
|
|
dlci->mux->t1_timeout_value - current_time;
|
|
|
|
new_timer = MIN(current_timer, new_timer);
|
|
}
|
|
|
|
if (new_timer != UINT_MAX) {
|
|
k_work_reschedule(&t1_timer, K_MSEC(new_timer));
|
|
}
|
|
}
|
|
|
|
static void gsm_dlci_open(struct gsm_dlci *dlci)
|
|
{
|
|
LOG_DBG("[%p/%d] DLCI id %d open", dlci, dlci->num, dlci->num);
|
|
dlci->state = GSM_DLCI_OPEN;
|
|
|
|
/* Remove this DLCI from pending T1 timers */
|
|
sys_slist_remove(&dlci_active_t1_timers, NULL, &dlci->node);
|
|
dlci_run_timer(k_uptime_get_32());
|
|
|
|
if (dlci->command_cb) {
|
|
dlci->command_cb(dlci, true);
|
|
}
|
|
}
|
|
|
|
static void gsm_dlci_close(struct gsm_dlci *dlci)
|
|
{
|
|
LOG_DBG("[%p/%d] DLCI id %d closed", dlci, dlci->num, dlci->num);
|
|
dlci->state = GSM_DLCI_CLOSED;
|
|
|
|
k_sem_give(&dlci->disconnect_sem);
|
|
|
|
/* Remove this DLCI from pending T1 timers */
|
|
sys_slist_remove(&dlci_active_t1_timers, NULL, &dlci->node);
|
|
dlci_run_timer(k_uptime_get_32());
|
|
|
|
if (dlci->command_cb) {
|
|
dlci->command_cb(dlci, false);
|
|
}
|
|
|
|
if (dlci->num == 0) {
|
|
dlci->mux->refuse_service = true;
|
|
}
|
|
}
|
|
|
|
/* Return true if we need to retry, false otherwise */
|
|
static bool handle_t1_timeout(struct gsm_dlci *dlci)
|
|
{
|
|
LOG_DBG("[%p/%d] T1 timeout", dlci, dlci->num);
|
|
|
|
if (dlci->state == GSM_DLCI_OPENING) {
|
|
dlci->retries--;
|
|
if (dlci->retries) {
|
|
dlci->req_start = k_uptime_get_32();
|
|
(void)gsm_mux_send_command(dlci->mux, dlci->num, FT_SABM | GSM_PF);
|
|
return true;
|
|
}
|
|
|
|
if (dlci->command_cb) {
|
|
dlci->command_cb(dlci, false);
|
|
}
|
|
|
|
if (dlci->num == 0 && dlci->mux->control == (FT_DM | GSM_PF)) {
|
|
LOG_DBG("DLCI %d -> ADM mode", dlci->num);
|
|
dlci->mode = GSM_DLCI_MODE_ADM;
|
|
gsm_dlci_open(dlci);
|
|
} else {
|
|
gsm_dlci_close(dlci);
|
|
}
|
|
} else if (dlci->state == GSM_DLCI_CLOSING) {
|
|
dlci->retries--;
|
|
if (dlci->retries) {
|
|
(void)gsm_mux_send_command(dlci->mux, dlci->num, FT_DISC | GSM_PF);
|
|
return true;
|
|
}
|
|
|
|
gsm_dlci_close(dlci);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static void dlci_t1_timeout(struct k_work *work)
|
|
{
|
|
uint32_t current_time = k_uptime_get_32();
|
|
struct gsm_dlci *entry, *next;
|
|
sys_snode_t *prev_node = NULL;
|
|
|
|
ARG_UNUSED(work);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&dlci_active_t1_timers,
|
|
entry, next, node) {
|
|
if ((int32_t)(entry->req_start +
|
|
entry->mux->t1_timeout_value - current_time) > 0) {
|
|
prev_node = &entry->node;
|
|
break;
|
|
}
|
|
|
|
if (!handle_t1_timeout(entry)) {
|
|
sys_slist_remove(&dlci_active_t1_timers, prev_node,
|
|
&entry->node);
|
|
}
|
|
}
|
|
|
|
dlci_run_timer(current_time);
|
|
}
|
|
|
|
static struct gsm_control_msg *gsm_ctrl_msg_get_free(void)
|
|
{
|
|
sys_snode_t *node;
|
|
|
|
node = sys_slist_peek_head(&ctrls_free_entries);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
|
|
sys_slist_remove(&ctrls_free_entries, NULL, node);
|
|
|
|
return CONTAINER_OF(node, struct gsm_control_msg, node);
|
|
}
|
|
|
|
static struct gsm_control_msg *gsm_mux_alloc_control_msg(struct net_buf *buf,
|
|
uint8_t cmd)
|
|
{
|
|
struct gsm_control_msg *msg;
|
|
|
|
msg = gsm_ctrl_msg_get_free();
|
|
if (!msg) {
|
|
return NULL;
|
|
}
|
|
|
|
msg->buf = buf;
|
|
msg->cmd = cmd;
|
|
|
|
return msg;
|
|
}
|
|
|
|
static void ctrl_msg_cleanup(struct gsm_control_msg *entry, bool pending)
|
|
{
|
|
if (pending) {
|
|
LOG_DBG("Releasing pending buf %p (ref %d)",
|
|
entry->buf, entry->buf->ref - 1);
|
|
net_buf_unref(entry->buf);
|
|
entry->buf = NULL;
|
|
}
|
|
}
|
|
|
|
/* T2 timeout is for control message retransmits */
|
|
static void gsm_mux_t2_timeout(struct k_work *work)
|
|
{
|
|
struct k_work_delayable *dwork = k_work_delayable_from_work(work);
|
|
struct gsm_mux *mux = CONTAINER_OF(dwork, struct gsm_mux, t2_timer);
|
|
uint32_t current_time = k_uptime_get_32();
|
|
struct gsm_control_msg *entry, *next;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&mux->pending_ctrls, entry, next,
|
|
node) {
|
|
if ((int32_t)(entry->req_start + T2_MSEC - current_time) > 0) {
|
|
break;
|
|
}
|
|
|
|
ctrl_msg_cleanup(entry, true);
|
|
|
|
sys_slist_remove(&mux->pending_ctrls, NULL, &entry->node);
|
|
sys_slist_append(&ctrls_free_entries, &entry->node);
|
|
|
|
entry = NULL;
|
|
}
|
|
|
|
if (entry) {
|
|
k_work_reschedule(
|
|
&mux->t2_timer,
|
|
K_MSEC(entry->req_start + T2_MSEC - current_time));
|
|
}
|
|
}
|
|
|
|
static int gsm_mux_send_control_message(struct gsm_mux *mux, uint8_t dlci_address,
|
|
int cmd, uint8_t *data, size_t data_len)
|
|
{
|
|
struct gsm_control_msg *ctrl;
|
|
struct net_buf *buf;
|
|
|
|
/* We create a net_buf for the control message so that we can
|
|
* resend it easily if needed.
|
|
*/
|
|
buf = gsm_mux_alloc_buf(BUF_ALLOC_TIMEOUT, NULL);
|
|
if (!buf) {
|
|
LOG_ERR("[%p] Cannot allocate header", mux);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
if (data && data_len > 0) {
|
|
size_t added;
|
|
|
|
added = net_buf_append_bytes(buf, data_len, data,
|
|
BUF_ALLOC_TIMEOUT,
|
|
gsm_mux_alloc_buf, NULL);
|
|
if (added != data_len) {
|
|
net_buf_unref(buf);
|
|
return -ENOMEM;
|
|
}
|
|
}
|
|
|
|
ctrl = gsm_mux_alloc_control_msg(buf, cmd);
|
|
if (!ctrl) {
|
|
net_buf_unref(buf);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
sys_slist_append(&mux->pending_ctrls, &ctrl->node);
|
|
ctrl->req_start = k_uptime_get_32();
|
|
|
|
/* Let's start the timer if necessary */
|
|
if (!k_work_delayable_remaining_get(&mux->t2_timer)) {
|
|
k_work_reschedule(&mux->t2_timer, K_MSEC(T2_MSEC));
|
|
}
|
|
|
|
return gsm_mux_modem_send(mux, buf->data, buf->len);
|
|
}
|
|
|
|
static int gsm_dlci_opening_or_closing(struct gsm_dlci *dlci,
|
|
enum gsm_dlci_state state,
|
|
int command,
|
|
dlci_command_cb_t cb)
|
|
{
|
|
dlci->retries = dlci->mux->retries;
|
|
dlci->req_start = k_uptime_get_32();
|
|
dlci->state = state;
|
|
dlci->command_cb = cb;
|
|
|
|
/* Let's start the timer if necessary */
|
|
if (!k_work_delayable_remaining_get(&t1_timer)) {
|
|
k_work_reschedule(&t1_timer,
|
|
K_MSEC(dlci->mux->t1_timeout_value));
|
|
}
|
|
|
|
sys_slist_append(&dlci_active_t1_timers, &dlci->node);
|
|
|
|
return gsm_mux_send_command(dlci->mux, dlci->num, command | GSM_PF);
|
|
}
|
|
|
|
static int gsm_dlci_closing(struct gsm_dlci *dlci, dlci_command_cb_t cb)
|
|
{
|
|
if (dlci->state == GSM_DLCI_CLOSED ||
|
|
dlci->state == GSM_DLCI_CLOSING) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
LOG_DBG("[%p] DLCI %d closing", dlci, dlci->num);
|
|
|
|
return gsm_dlci_opening_or_closing(dlci, GSM_DLCI_CLOSING, FT_DISC,
|
|
cb);
|
|
}
|
|
|
|
static int gsm_dlci_opening(struct gsm_dlci *dlci, dlci_command_cb_t cb)
|
|
{
|
|
if (dlci->state == GSM_DLCI_OPEN || dlci->state == GSM_DLCI_OPENING) {
|
|
return -EALREADY;
|
|
}
|
|
|
|
LOG_DBG("[%p] DLCI %d opening", dlci, dlci->num);
|
|
|
|
return gsm_dlci_opening_or_closing(dlci, GSM_DLCI_OPENING, FT_SABM,
|
|
cb);
|
|
}
|
|
|
|
int gsm_mux_disconnect(struct gsm_mux *mux, k_timeout_t timeout)
|
|
{
|
|
struct gsm_dlci *dlci;
|
|
|
|
dlci = gsm_dlci_get(mux, 0);
|
|
if (dlci == NULL) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
(void)gsm_mux_send_control_message(dlci->mux, dlci->num,
|
|
CMD_CLD, NULL, 0);
|
|
|
|
(void)k_work_cancel_delayable(&mux->t2_timer);
|
|
|
|
(void)gsm_dlci_closing(dlci, NULL);
|
|
|
|
return k_sem_take(&dlci->disconnect_sem, timeout);
|
|
}
|
|
|
|
static int gsm_mux_control_reply(struct gsm_dlci *dlci, bool sub_cr,
|
|
uint8_t sub_cmd, const uint8_t *buf, size_t len)
|
|
{
|
|
/* As this is a reply to received command, set the value according
|
|
* to initiator status. See GSM 07.10 page 17.
|
|
*/
|
|
bool cmd = !dlci->mux->is_initiator;
|
|
|
|
return gsm_mux_send_data_msg(dlci->mux, cmd, dlci, FT_UIH | GSM_PF, buf, len);
|
|
}
|
|
|
|
static bool get_field(struct net_buf *buf, int *ret_value)
|
|
{
|
|
int value = 0;
|
|
uint8_t recv_byte;
|
|
|
|
while (buf->len) {
|
|
recv_byte = net_buf_pull_u8(buf);
|
|
|
|
if (gsm_mux_read_ea(&value, recv_byte)) {
|
|
*ret_value = value;
|
|
return true;
|
|
}
|
|
|
|
if (buf->len == 0) {
|
|
buf = net_buf_frag_del(NULL, buf);
|
|
if (buf == NULL) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static int gsm_mux_msc_reply(struct gsm_dlci *dlci, bool cmd,
|
|
struct net_buf *buf, size_t len)
|
|
{
|
|
uint32_t modem_sig = 0, break_sig = 0;
|
|
int ret;
|
|
|
|
ret = get_field(buf, &modem_sig);
|
|
if (!ret) {
|
|
LOG_DBG("[%p] Malformed data", dlci->mux);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (buf->len > 0) {
|
|
ret = get_field(buf, &break_sig);
|
|
if (!ret) {
|
|
LOG_DBG("[%p] Malformed data", dlci->mux);
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
|
|
LOG_DBG("Modem signal 0x%02x break signal 0x%02x", modem_sig,
|
|
break_sig);
|
|
|
|
/* FIXME to return proper status back */
|
|
|
|
return gsm_mux_control_reply(dlci, cmd, CMD_MSC, buf->data, len);
|
|
}
|
|
|
|
static int gsm_mux_control_message(struct gsm_dlci *dlci, struct net_buf *buf)
|
|
{
|
|
uint32_t command = 0, len = 0;
|
|
int ret = 0;
|
|
bool cr;
|
|
|
|
__ASSERT_NO_MSG(dlci != NULL);
|
|
|
|
/* Remove the C/R bit from sub-command */
|
|
cr = buf->data[0] & GSM_CR;
|
|
buf->data[0] &= ~GSM_CR;
|
|
|
|
ret = get_field(buf, &command);
|
|
if (!ret) {
|
|
LOG_DBG("[%p] Malformed data", dlci->mux);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = get_field(buf, &len);
|
|
if (!ret) {
|
|
LOG_DBG("[%p] Malformed data", dlci->mux);
|
|
return -EINVAL;
|
|
}
|
|
|
|
LOG_DBG("[%p] DLCI %d %s 0x%02x len %u", dlci->mux, dlci->num,
|
|
cr ? "cmd" : "rsp", command, len);
|
|
|
|
/* buf->data should now point to start of dlci command data */
|
|
|
|
switch (command) {
|
|
case CMD_CLD:
|
|
/* Modem closing down */
|
|
dlci->mux->refuse_service = true;
|
|
dlci->refuse_service = true;
|
|
gsm_dlci_closing(dlci, NULL);
|
|
break;
|
|
|
|
case CMD_FCOFF:
|
|
/* Do not accept data */
|
|
ret = gsm_mux_control_reply(dlci, cr, CMD_FCOFF, NULL, 0);
|
|
break;
|
|
|
|
case CMD_FCON:
|
|
/* Accepting data */
|
|
ret = gsm_mux_control_reply(dlci, cr, CMD_FCON, NULL, 0);
|
|
break;
|
|
|
|
case CMD_MSC:
|
|
/* Modem status information */
|
|
/* FIXME: WIP: MSC reply does not work */
|
|
if (0) {
|
|
ret = gsm_mux_msc_reply(dlci, cr, buf, len);
|
|
}
|
|
|
|
break;
|
|
|
|
case CMD_PSC:
|
|
/* Modem wants to enter power saving state */
|
|
ret = gsm_mux_control_reply(dlci, cr, CMD_PSC, NULL, len);
|
|
break;
|
|
|
|
case CMD_RLS:
|
|
/* Out of band error reception for a DLCI */
|
|
break;
|
|
|
|
case CMD_TEST:
|
|
/* Send test message back */
|
|
ret = gsm_mux_control_reply(dlci, cr, CMD_TEST,
|
|
buf->data, len);
|
|
break;
|
|
|
|
/* Optional and currently unsupported commands */
|
|
case CMD_PN: /* Parameter negotiation */
|
|
case CMD_RPN: /* Remote port negotiation */
|
|
case CMD_SNC: /* Service negotiation command */
|
|
default:
|
|
/* Reply to bad commands with an NSC */
|
|
buf->data[0] = command | (cr ? GSM_CR : 0);
|
|
buf->len = 1;
|
|
ret = gsm_mux_control_reply(dlci, cr, CMD_NSC, buf->data, len);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Handle a response to our control message */
|
|
static int gsm_mux_control_response(struct gsm_dlci *dlci, struct net_buf *buf)
|
|
{
|
|
struct gsm_control_msg *entry, *next;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&dlci->mux->pending_ctrls,
|
|
entry, next, node) {
|
|
if (dlci->mux->control == entry->cmd) {
|
|
sys_slist_remove(&dlci->mux->pending_ctrls, NULL,
|
|
&entry->node);
|
|
sys_slist_append(&ctrls_free_entries, &entry->node);
|
|
entry->finished = true;
|
|
|
|
if (dlci->command_cb) {
|
|
dlci->command_cb(dlci, true);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gsm_dlci_process_command(struct gsm_dlci *dlci, bool cmd,
|
|
struct net_buf *buf)
|
|
{
|
|
int ret;
|
|
|
|
LOG_DBG("[%p] DLCI %d control %s", dlci->mux, dlci->num,
|
|
cmd ? "request" : "response");
|
|
hexdump_buf("buf", buf);
|
|
|
|
if (cmd) {
|
|
ret = gsm_mux_control_message(dlci, buf);
|
|
} else {
|
|
ret = gsm_mux_control_response(dlci, buf);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void gsm_dlci_free(struct gsm_mux *mux, uint8_t address)
|
|
{
|
|
struct gsm_dlci *dlci;
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dlcis); i++) {
|
|
if (!dlcis[i].in_use) {
|
|
continue;
|
|
}
|
|
|
|
dlci = &dlcis[i];
|
|
|
|
if (dlci->mux == mux && dlci->num == address) {
|
|
dlci->in_use = false;
|
|
|
|
sys_slist_prepend(&dlci_free_entries, &dlci->node);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
static struct gsm_dlci *gsm_dlci_get_free(void)
|
|
{
|
|
sys_snode_t *node;
|
|
|
|
node = sys_slist_peek_head(&dlci_free_entries);
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
|
|
sys_slist_remove(&dlci_free_entries, NULL, node);
|
|
|
|
return CONTAINER_OF(node, struct gsm_dlci, node);
|
|
}
|
|
|
|
static struct gsm_dlci *gsm_dlci_alloc(struct gsm_mux *mux, uint8_t address,
|
|
const struct device *uart,
|
|
gsm_mux_dlci_created_cb_t dlci_created_cb,
|
|
void *user_data)
|
|
{
|
|
struct gsm_dlci *dlci;
|
|
|
|
dlci = gsm_dlci_get_free();
|
|
if (!dlci) {
|
|
return NULL;
|
|
}
|
|
|
|
k_sem_init(&dlci->disconnect_sem, 1, 1);
|
|
|
|
dlci->mux = mux;
|
|
dlci->num = address;
|
|
dlci->in_use = true;
|
|
dlci->retries = mux->retries;
|
|
dlci->state = GSM_DLCI_CLOSED;
|
|
dlci->uart = uart;
|
|
dlci->user_data = user_data;
|
|
dlci->dlci_created_cb = dlci_created_cb;
|
|
|
|
/* Command channel (0) handling is separated from data */
|
|
if (dlci->num) {
|
|
dlci->handler = gsm_dlci_process_data;
|
|
} else {
|
|
dlci->handler = gsm_dlci_process_command;
|
|
}
|
|
|
|
return dlci;
|
|
}
|
|
|
|
static int gsm_mux_process_pkt(struct gsm_mux *mux)
|
|
{
|
|
uint8_t dlci_address = mux->address >> 2;
|
|
int ret = 0;
|
|
bool cmd; /* C/R bit, command (true) / response (false) */
|
|
struct gsm_dlci *dlci;
|
|
|
|
/* This function is only called for received packets so if the
|
|
* command is set, then it means a response if we are initiator.
|
|
*/
|
|
cmd = (mux->address >> 1) & 0x01;
|
|
|
|
if (mux->is_initiator) {
|
|
cmd = !cmd;
|
|
}
|
|
|
|
hexdump_packet("Received", dlci_address, cmd, mux->control,
|
|
mux->buf ? mux->buf->data : NULL,
|
|
mux->buf ? mux->buf->len : 0);
|
|
|
|
dlci = gsm_dlci_get(mux, dlci_address);
|
|
|
|
/* What to do next */
|
|
switch (mux->control) {
|
|
case FT_SABM | GSM_PF:
|
|
if (cmd == false) {
|
|
ret = -ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
if (dlci == NULL) {
|
|
const struct device *uart;
|
|
|
|
uart = uart_mux_find(dlci_address);
|
|
if (uart == NULL) {
|
|
ret = -ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
dlci = gsm_dlci_alloc(mux, dlci_address, uart, NULL,
|
|
NULL);
|
|
if (dlci == NULL) {
|
|
ret = -ENOENT;
|
|
goto fail;
|
|
}
|
|
}
|
|
|
|
if (dlci->refuse_service) {
|
|
ret = gsm_mux_send_response(mux, dlci_address, FT_DM);
|
|
} else {
|
|
ret = gsm_mux_send_response(mux, dlci_address, FT_UA);
|
|
gsm_dlci_open(dlci);
|
|
}
|
|
|
|
break;
|
|
|
|
case FT_DISC | GSM_PF:
|
|
if (cmd == false) {
|
|
ret = -ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
if (dlci == NULL || dlci->state == GSM_DLCI_CLOSED) {
|
|
(void)gsm_mux_send_response(mux, dlci_address, FT_DM);
|
|
ret = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
ret = gsm_mux_send_command(mux, dlci_address, FT_UA);
|
|
gsm_dlci_close(dlci);
|
|
break;
|
|
|
|
case FT_UA | GSM_PF:
|
|
case FT_UA:
|
|
if (cmd == true || dlci == NULL) {
|
|
ret = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
switch (dlci->state) {
|
|
case GSM_DLCI_CLOSING:
|
|
gsm_dlci_close(dlci);
|
|
break;
|
|
case GSM_DLCI_OPENING:
|
|
gsm_dlci_open(dlci);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
|
|
case FT_DM | GSM_PF:
|
|
case FT_DM:
|
|
if (cmd == true || dlci == NULL) {
|
|
ret = -ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
gsm_dlci_close(dlci);
|
|
break;
|
|
|
|
case FT_UI | GSM_PF:
|
|
case FT_UI:
|
|
case FT_UIH | GSM_PF:
|
|
case FT_UIH:
|
|
if (dlci == NULL || dlci->state != GSM_DLCI_OPEN) {
|
|
(void)gsm_mux_send_command(mux, dlci_address, FT_DM | GSM_PF);
|
|
ret = -ENOENT;
|
|
goto out;
|
|
}
|
|
|
|
ret = dlci->handler(dlci, cmd, mux->buf);
|
|
|
|
if (mux->buf) {
|
|
net_buf_unref(mux->buf);
|
|
mux->buf = NULL;
|
|
}
|
|
|
|
break;
|
|
|
|
default:
|
|
ret = -EINVAL;
|
|
goto fail;
|
|
}
|
|
|
|
out:
|
|
return ret;
|
|
|
|
fail:
|
|
LOG_ERR("Cannot handle command (0x%02x) (%d)", mux->control, ret);
|
|
return ret;
|
|
}
|
|
|
|
static bool is_UI(struct gsm_mux *mux)
|
|
{
|
|
return (mux->control & ~GSM_PF) == FT_UI;
|
|
}
|
|
|
|
static const char *gsm_mux_state_str(enum gsm_mux_state state)
|
|
{
|
|
#if (CONFIG_GSM_MUX_LOG_LEVEL >= LOG_LEVEL_DBG) || defined(CONFIG_NET_SHELL)
|
|
switch (state) {
|
|
case GSM_MUX_SOF:
|
|
return "Start-Of-Frame";
|
|
case GSM_MUX_ADDRESS:
|
|
return "Address";
|
|
case GSM_MUX_CONTROL:
|
|
return "Control";
|
|
case GSM_MUX_LEN_0:
|
|
return "Len0";
|
|
case GSM_MUX_LEN_1:
|
|
return "Len1";
|
|
case GSM_MUX_DATA:
|
|
return "Data";
|
|
case GSM_MUX_FCS:
|
|
return "FCS";
|
|
case GSM_MUX_EOF:
|
|
return "End-Of-Frame";
|
|
}
|
|
#else
|
|
ARG_UNUSED(state);
|
|
#endif
|
|
|
|
return "";
|
|
}
|
|
|
|
#if CONFIG_GSM_MUX_LOG_LEVEL >= LOG_LEVEL_DBG
|
|
static void validate_state_transition(enum gsm_mux_state current,
|
|
enum gsm_mux_state new)
|
|
{
|
|
static const uint8_t valid_transitions[] = {
|
|
[GSM_MUX_SOF] = 1 << GSM_MUX_ADDRESS,
|
|
[GSM_MUX_ADDRESS] = 1 << GSM_MUX_CONTROL,
|
|
[GSM_MUX_CONTROL] = 1 << GSM_MUX_LEN_0,
|
|
[GSM_MUX_LEN_0] = 1 << GSM_MUX_LEN_1 |
|
|
1 << GSM_MUX_DATA |
|
|
1 << GSM_MUX_FCS |
|
|
1 << GSM_MUX_SOF,
|
|
[GSM_MUX_LEN_1] = 1 << GSM_MUX_DATA |
|
|
1 << GSM_MUX_FCS |
|
|
1 << GSM_MUX_SOF,
|
|
[GSM_MUX_DATA] = 1 << GSM_MUX_FCS |
|
|
1 << GSM_MUX_SOF,
|
|
[GSM_MUX_FCS] = 1 << GSM_MUX_EOF,
|
|
[GSM_MUX_EOF] = 1 << GSM_MUX_SOF
|
|
};
|
|
|
|
if (!(valid_transitions[current] & 1 << new)) {
|
|
LOG_DBG("Invalid state transition: %s (%d) => %s (%d)",
|
|
gsm_mux_state_str(current), current,
|
|
gsm_mux_state_str(new), new);
|
|
}
|
|
}
|
|
#else
|
|
static inline void validate_state_transition(enum gsm_mux_state current,
|
|
enum gsm_mux_state new)
|
|
{
|
|
ARG_UNUSED(current);
|
|
ARG_UNUSED(new);
|
|
}
|
|
#endif
|
|
|
|
static inline enum gsm_mux_state gsm_mux_get_state(const struct gsm_mux *mux)
|
|
{
|
|
return (enum gsm_mux_state)mux->state;
|
|
}
|
|
|
|
void gsm_mux_change_state(struct gsm_mux *mux, enum gsm_mux_state new_state)
|
|
{
|
|
__ASSERT_NO_MSG(mux);
|
|
|
|
if (gsm_mux_get_state(mux) == new_state) {
|
|
return;
|
|
}
|
|
|
|
LOG_DBG("[%p] state %s (%d) => %s (%d)",
|
|
mux, gsm_mux_state_str(mux->state), mux->state,
|
|
gsm_mux_state_str(new_state), new_state);
|
|
|
|
validate_state_transition(mux->state, new_state);
|
|
|
|
mux->state = new_state;
|
|
}
|
|
|
|
static void gsm_mux_process_data(struct gsm_mux *mux, uint8_t recv_byte)
|
|
{
|
|
size_t bytes_added;
|
|
|
|
switch (mux->state) {
|
|
case GSM_MUX_SOF:
|
|
/* This is the initial state where we look for SOF char */
|
|
if (recv_byte == SOF_MARKER) {
|
|
gsm_mux_change_state(mux, GSM_MUX_ADDRESS);
|
|
mux->fcs = FCS_INIT_VALUE;
|
|
mux->received = 0;
|
|
|
|
/* Avoid memory leak by freeing all the allocated
|
|
* buffers at start.
|
|
*/
|
|
if (mux->buf) {
|
|
net_buf_unref(mux->buf);
|
|
mux->buf = NULL;
|
|
}
|
|
}
|
|
|
|
break;
|
|
|
|
case GSM_MUX_ADDRESS:
|
|
/* DLCI (Data Link Connection Identifier) address we want to
|
|
* talk. This address field also contains C/R bit.
|
|
* Currently we only support one byte addresses.
|
|
*/
|
|
mux->address = recv_byte;
|
|
LOG_DBG("[%p] recv %d address %d C/R %d", mux, recv_byte,
|
|
mux->address >> 2, !!(mux->address & GSM_CR));
|
|
gsm_mux_change_state(mux, GSM_MUX_CONTROL);
|
|
mux->fcs = gsm_mux_fcs_add(mux->fcs, recv_byte);
|
|
break;
|
|
|
|
case GSM_MUX_CONTROL:
|
|
mux->control = recv_byte;
|
|
LOG_DBG("[%p] recv %s (0x%02x) control 0x%02x P/F %d", mux,
|
|
get_frame_type_str(recv_byte & ~GSM_PF), recv_byte,
|
|
mux->control & ~GSM_PF, !!(mux->control & GSM_PF));
|
|
gsm_mux_change_state(mux, GSM_MUX_LEN_0);
|
|
mux->fcs = gsm_mux_fcs_add(mux->fcs, recv_byte);
|
|
break;
|
|
|
|
case GSM_MUX_LEN_0:
|
|
mux->fcs = gsm_mux_fcs_add(mux->fcs, recv_byte);
|
|
mux->msg_len = 0;
|
|
|
|
if (gsm_mux_read_msg_len(mux, recv_byte)) {
|
|
if (mux->msg_len > mux->mru) {
|
|
gsm_mux_change_state(mux, GSM_MUX_SOF);
|
|
} else if (mux->msg_len == 0) {
|
|
gsm_mux_change_state(mux, GSM_MUX_FCS);
|
|
} else {
|
|
gsm_mux_change_state(mux, GSM_MUX_DATA);
|
|
|
|
LOG_DBG("[%p] data len %d", mux, mux->msg_len);
|
|
}
|
|
} else {
|
|
gsm_mux_change_state(mux, GSM_MUX_LEN_1);
|
|
}
|
|
|
|
break;
|
|
|
|
case GSM_MUX_LEN_1:
|
|
mux->fcs = gsm_mux_fcs_add(mux->fcs, recv_byte);
|
|
|
|
mux->msg_len |= recv_byte << 7;
|
|
if (mux->msg_len > mux->mru) {
|
|
gsm_mux_change_state(mux, GSM_MUX_SOF);
|
|
} else if (mux->msg_len == 0) {
|
|
gsm_mux_change_state(mux, GSM_MUX_FCS);
|
|
} else {
|
|
gsm_mux_change_state(mux, GSM_MUX_DATA);
|
|
|
|
LOG_DBG("[%p] data len %d", mux, mux->msg_len);
|
|
}
|
|
|
|
break;
|
|
|
|
case GSM_MUX_DATA:
|
|
if (mux->buf == NULL) {
|
|
mux->buf = net_buf_alloc(&gsm_mux_pool,
|
|
BUF_ALLOC_TIMEOUT);
|
|
if (mux->buf == NULL) {
|
|
LOG_ERR("[%p] Can't allocate RX data! "
|
|
"Skipping data!", mux);
|
|
gsm_mux_change_state(mux, GSM_MUX_SOF);
|
|
break;
|
|
}
|
|
}
|
|
|
|
bytes_added = net_buf_append_bytes(mux->buf, 1,
|
|
(void *)&recv_byte,
|
|
BUF_ALLOC_TIMEOUT,
|
|
gsm_mux_alloc_buf,
|
|
&gsm_mux_pool);
|
|
if (bytes_added != 1) {
|
|
gsm_mux_change_state(mux, GSM_MUX_SOF);
|
|
} else if (++mux->received == mux->msg_len) {
|
|
gsm_mux_change_state(mux, GSM_MUX_FCS);
|
|
}
|
|
|
|
break;
|
|
|
|
case GSM_MUX_FCS:
|
|
mux->received_fcs = recv_byte;
|
|
|
|
/* Update the FCS for Unnumbered Information field (UI) */
|
|
if (is_UI(mux)) {
|
|
struct net_buf *buf = mux->buf;
|
|
|
|
while (buf) {
|
|
mux->fcs = gsm_mux_fcs_add_buf(mux->fcs,
|
|
buf->data,
|
|
buf->len);
|
|
buf = buf->frags;
|
|
}
|
|
}
|
|
|
|
mux->fcs = gsm_mux_fcs_add(mux->fcs, mux->received_fcs);
|
|
if (mux->fcs == FCS_GOOD_VALUE) {
|
|
int ret = gsm_mux_process_pkt(mux);
|
|
|
|
if (ret < 0) {
|
|
LOG_DBG("[%p] Cannot process pkt (%d)", mux,
|
|
ret);
|
|
}
|
|
}
|
|
|
|
gsm_mux_change_state(mux, GSM_MUX_EOF);
|
|
break;
|
|
|
|
case GSM_MUX_EOF:
|
|
if (recv_byte == SOF_MARKER) {
|
|
gsm_mux_change_state(mux, GSM_MUX_SOF);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void gsm_mux_recv_buf(struct gsm_mux *mux, uint8_t *buf, int len)
|
|
{
|
|
int i = 0;
|
|
|
|
LOG_DBG("Received %d bytes", len);
|
|
|
|
while (i < len) {
|
|
gsm_mux_process_data(mux, buf[i++]);
|
|
}
|
|
}
|
|
|
|
static void dlci_done(struct gsm_dlci *dlci, bool connected)
|
|
{
|
|
LOG_DBG("[%p] DLCI id %d %screated", dlci, dlci->num,
|
|
connected == false ? "not " : "");
|
|
|
|
/* Let the UART mux to continue */
|
|
if (dlci->dlci_created_cb) {
|
|
dlci->dlci_created_cb(dlci, connected, dlci->user_data);
|
|
}
|
|
}
|
|
|
|
int gsm_dlci_create(struct gsm_mux *mux,
|
|
const struct device *uart,
|
|
int dlci_address,
|
|
gsm_mux_dlci_created_cb_t dlci_created_cb,
|
|
void *user_data,
|
|
struct gsm_dlci **dlci)
|
|
{
|
|
int ret;
|
|
|
|
*dlci = gsm_dlci_alloc(mux, dlci_address, uart, dlci_created_cb,
|
|
user_data);
|
|
if (!*dlci) {
|
|
LOG_ERR("[%p] Cannot allocate DLCI %d", mux, dlci_address);
|
|
ret = -ENOMEM;
|
|
goto fail;
|
|
}
|
|
|
|
ret = gsm_dlci_opening(*dlci, dlci_done);
|
|
if (ret < 0 && ret != -EALREADY) {
|
|
LOG_ERR("[%p] Cannot open DLCI %d", mux, dlci_address);
|
|
gsm_dlci_free(mux, dlci_address);
|
|
*dlci = NULL;
|
|
} else {
|
|
ret = 0;
|
|
}
|
|
|
|
fail:
|
|
return ret;
|
|
}
|
|
|
|
int gsm_dlci_send(struct gsm_dlci *dlci, const uint8_t *buf, size_t size)
|
|
{
|
|
/* Mux the data and send to UART */
|
|
return gsm_mux_send_data_msg(dlci->mux, true, dlci, FT_UIH, buf, size);
|
|
}
|
|
|
|
int gsm_dlci_id(struct gsm_dlci *dlci)
|
|
{
|
|
return dlci->num;
|
|
}
|
|
|
|
struct gsm_mux *gsm_mux_create(const struct device *uart)
|
|
{
|
|
struct gsm_mux *mux = NULL;
|
|
int i;
|
|
|
|
if (!gsm_mux_init_done) {
|
|
LOG_ERR("GSM mux not initialized!");
|
|
return NULL;
|
|
}
|
|
|
|
for (i = 0; i < ARRAY_SIZE(muxes); i++) {
|
|
if (muxes[i].in_use) {
|
|
/* If the mux was already created, return it */
|
|
if (uart && muxes[i].uart == uart) {
|
|
return &muxes[i];
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
mux = &muxes[i];
|
|
|
|
memset(mux, 0, sizeof(*mux));
|
|
|
|
mux->in_use = true;
|
|
mux->uart = uart;
|
|
mux->mru = CONFIG_GSM_MUX_MRU_DEFAULT_LEN;
|
|
mux->retries = N2;
|
|
mux->t1_timeout_value = CONFIG_GSM_MUX_T1_TIMEOUT ?
|
|
CONFIG_GSM_MUX_T1_TIMEOUT : T1_MSEC;
|
|
mux->t2_timeout_value = T2_MSEC;
|
|
mux->is_initiator = CONFIG_GSM_MUX_INITIATOR;
|
|
mux->state = GSM_MUX_SOF;
|
|
mux->buf = NULL;
|
|
|
|
k_work_init_delayable(&mux->t2_timer, gsm_mux_t2_timeout);
|
|
sys_slist_init(&mux->pending_ctrls);
|
|
|
|
/* The system will continue after the control DLCI is
|
|
* created or timeout occurs.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
return mux;
|
|
}
|
|
|
|
int gsm_mux_send(struct gsm_mux *mux, uint8_t dlci_address,
|
|
const uint8_t *buf, size_t size)
|
|
{
|
|
struct gsm_dlci *dlci;
|
|
|
|
dlci = gsm_dlci_get(mux, dlci_address);
|
|
if (!dlci) {
|
|
return -ENOENT;
|
|
}
|
|
|
|
/* Mux the data and send to UART */
|
|
return gsm_mux_send_data_msg(mux, true, dlci, FT_UIH, buf, size);
|
|
}
|
|
|
|
void gsm_mux_detach(struct gsm_mux *mux)
|
|
{
|
|
struct gsm_dlci *dlci;
|
|
|
|
for (int i = 0; i < ARRAY_SIZE(dlcis); i++) {
|
|
dlci = &dlcis[i];
|
|
|
|
if (mux != dlci->mux || !dlci->in_use) {
|
|
continue;
|
|
}
|
|
|
|
dlci->in_use = false;
|
|
sys_slist_prepend(&dlci_free_entries, &dlci->node);
|
|
}
|
|
}
|
|
|
|
void gsm_mux_init(void)
|
|
{
|
|
int i;
|
|
|
|
if (gsm_mux_init_done) {
|
|
return;
|
|
}
|
|
|
|
gsm_mux_init_done = true;
|
|
|
|
sys_slist_init(&ctrls_free_entries);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(ctrls); i++) {
|
|
sys_slist_prepend(&ctrls_free_entries, &ctrls[i].node);
|
|
}
|
|
|
|
sys_slist_init(&dlci_free_entries);
|
|
|
|
for (i = 0; i < ARRAY_SIZE(dlcis); i++) {
|
|
sys_slist_prepend(&dlci_free_entries, &dlcis[i].node);
|
|
}
|
|
|
|
k_work_init_delayable(&t1_timer, dlci_t1_timeout);
|
|
}
|