| /* |
| * 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); |
| } |