You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1439 lines
36 KiB
1439 lines
36 KiB
/* |
|
* Copyright (c) 2022 Trackunit Corporation |
|
* |
|
* SPDX-License-Identifier: Apache-2.0 |
|
*/ |
|
|
|
#include <zephyr/logging/log.h> |
|
LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL); |
|
|
|
#include <zephyr/kernel.h> |
|
#include <zephyr/sys/crc.h> |
|
#include <zephyr/modem/cmux.h> |
|
|
|
#include <string.h> |
|
|
|
#define MODEM_CMUX_FCS_POLYNOMIAL (0xE0) |
|
#define MODEM_CMUX_FCS_INIT_VALUE (0xFF) |
|
#define MODEM_CMUX_EA (0x01) |
|
#define MODEM_CMUX_CR (0x02) |
|
#define MODEM_CMUX_PF (0x10) |
|
#define MODEM_CMUX_FRAME_SIZE_MAX (0x07) |
|
#define MODEM_CMUX_DATA_SIZE_MIN (0x08) |
|
#define MODEM_CMUX_DATA_FRAME_SIZE_MIN (MODEM_CMUX_FRAME_SIZE_MAX + \ |
|
MODEM_CMUX_DATA_SIZE_MIN) |
|
|
|
#define MODEM_CMUX_CMD_DATA_SIZE_MAX (0x08) |
|
#define MODEM_CMUX_CMD_FRAME_SIZE_MAX (MODEM_CMUX_FRAME_SIZE_MAX + \ |
|
MODEM_CMUX_CMD_DATA_SIZE_MAX) |
|
|
|
#define MODEM_CMUX_T1_TIMEOUT (K_MSEC(330)) |
|
#define MODEM_CMUX_T2_TIMEOUT (K_MSEC(660)) |
|
|
|
#define MODEM_CMUX_EVENT_CONNECTED_BIT (BIT(0)) |
|
#define MODEM_CMUX_EVENT_DISCONNECTED_BIT (BIT(1)) |
|
|
|
enum modem_cmux_frame_types { |
|
MODEM_CMUX_FRAME_TYPE_RR = 0x01, |
|
MODEM_CMUX_FRAME_TYPE_UI = 0x03, |
|
MODEM_CMUX_FRAME_TYPE_RNR = 0x05, |
|
MODEM_CMUX_FRAME_TYPE_REJ = 0x09, |
|
MODEM_CMUX_FRAME_TYPE_DM = 0x0F, |
|
MODEM_CMUX_FRAME_TYPE_SABM = 0x2F, |
|
MODEM_CMUX_FRAME_TYPE_DISC = 0x43, |
|
MODEM_CMUX_FRAME_TYPE_UA = 0x63, |
|
MODEM_CMUX_FRAME_TYPE_UIH = 0xEF, |
|
}; |
|
|
|
enum modem_cmux_command_types { |
|
MODEM_CMUX_COMMAND_NSC = 0x04, |
|
MODEM_CMUX_COMMAND_TEST = 0x08, |
|
MODEM_CMUX_COMMAND_PSC = 0x10, |
|
MODEM_CMUX_COMMAND_RLS = 0x14, |
|
MODEM_CMUX_COMMAND_FCOFF = 0x18, |
|
MODEM_CMUX_COMMAND_PN = 0x20, |
|
MODEM_CMUX_COMMAND_RPN = 0x24, |
|
MODEM_CMUX_COMMAND_FCON = 0x28, |
|
MODEM_CMUX_COMMAND_CLD = 0x30, |
|
MODEM_CMUX_COMMAND_SNC = 0x34, |
|
MODEM_CMUX_COMMAND_MSC = 0x38, |
|
}; |
|
|
|
struct modem_cmux_command_type { |
|
uint8_t ea: 1; |
|
uint8_t cr: 1; |
|
uint8_t value: 6; |
|
}; |
|
|
|
struct modem_cmux_command_length { |
|
uint8_t ea: 1; |
|
uint8_t value: 7; |
|
}; |
|
|
|
struct modem_cmux_command { |
|
struct modem_cmux_command_type type; |
|
struct modem_cmux_command_length length; |
|
uint8_t value[]; |
|
}; |
|
|
|
static int modem_cmux_wrap_command(struct modem_cmux_command **command, const uint8_t *data, |
|
uint16_t data_len) |
|
{ |
|
if ((data == NULL) || (data_len < 2)) { |
|
return -EINVAL; |
|
} |
|
|
|
(*command) = (struct modem_cmux_command *)data; |
|
|
|
if (((*command)->length.ea == 0) || ((*command)->type.ea == 0)) { |
|
return -EINVAL; |
|
} |
|
|
|
if ((*command)->length.value != (data_len - 2)) { |
|
return -EINVAL; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
static struct modem_cmux_command *modem_cmux_command_wrap(const uint8_t *data) |
|
{ |
|
return (struct modem_cmux_command *)data; |
|
} |
|
|
|
static const char *modem_cmux_frame_type_to_str(enum modem_cmux_frame_types frame_type) |
|
{ |
|
switch (frame_type) { |
|
case MODEM_CMUX_FRAME_TYPE_RR: |
|
return "RR"; |
|
case MODEM_CMUX_FRAME_TYPE_UI: |
|
return "UI"; |
|
case MODEM_CMUX_FRAME_TYPE_RNR: |
|
return "RNR"; |
|
case MODEM_CMUX_FRAME_TYPE_REJ: |
|
return "REJ"; |
|
case MODEM_CMUX_FRAME_TYPE_DM: |
|
return "DM"; |
|
case MODEM_CMUX_FRAME_TYPE_SABM: |
|
return "SABM"; |
|
case MODEM_CMUX_FRAME_TYPE_DISC: |
|
return "DISC"; |
|
case MODEM_CMUX_FRAME_TYPE_UA: |
|
return "UA"; |
|
case MODEM_CMUX_FRAME_TYPE_UIH: |
|
return "UIH"; |
|
} |
|
return ""; |
|
} |
|
|
|
static void modem_cmux_log_frame(const struct modem_cmux_frame *frame, |
|
const char *action, size_t hexdump_len) |
|
{ |
|
LOG_DBG("%s ch:%u cr:%u pf:%u type:%s dlen:%u", action, frame->dlci_address, |
|
frame->cr, frame->pf, modem_cmux_frame_type_to_str(frame->type), frame->data_len); |
|
LOG_HEXDUMP_DBG(frame->data, hexdump_len, "data:"); |
|
} |
|
|
|
static void modem_cmux_log_transmit_frame(const struct modem_cmux_frame *frame) |
|
{ |
|
modem_cmux_log_frame(frame, "tx", frame->data_len); |
|
} |
|
|
|
static void modem_cmux_log_received_frame(const struct modem_cmux_frame *frame) |
|
{ |
|
modem_cmux_log_frame(frame, "rcvd", frame->data_len); |
|
} |
|
|
|
#if CONFIG_MODEM_STATS |
|
static uint32_t modem_cmux_get_receive_buf_length(struct modem_cmux *cmux) |
|
{ |
|
return cmux->receive_buf_len; |
|
} |
|
|
|
static uint32_t modem_cmux_get_receive_buf_size(struct modem_cmux *cmux) |
|
{ |
|
return cmux->receive_buf_size; |
|
} |
|
|
|
static uint32_t modem_cmux_get_transmit_buf_length(struct modem_cmux *cmux) |
|
{ |
|
return ring_buf_size_get(&cmux->transmit_rb); |
|
} |
|
|
|
static uint32_t modem_cmux_get_transmit_buf_size(struct modem_cmux *cmux) |
|
{ |
|
return ring_buf_capacity_get(&cmux->transmit_rb); |
|
} |
|
|
|
static void modem_cmux_init_buf_stats(struct modem_cmux *cmux) |
|
{ |
|
uint32_t size; |
|
|
|
size = modem_cmux_get_receive_buf_size(cmux); |
|
modem_stats_buffer_init(&cmux->receive_buf_stats, "cmux_rx", size); |
|
size = modem_cmux_get_transmit_buf_size(cmux); |
|
modem_stats_buffer_init(&cmux->transmit_buf_stats, "cmux_tx", size); |
|
} |
|
|
|
static void modem_cmux_advertise_transmit_buf_stats(struct modem_cmux *cmux) |
|
{ |
|
uint32_t length; |
|
|
|
length = modem_cmux_get_transmit_buf_length(cmux); |
|
modem_stats_buffer_advertise_length(&cmux->transmit_buf_stats, length); |
|
} |
|
|
|
static void modem_cmux_advertise_receive_buf_stats(struct modem_cmux *cmux) |
|
{ |
|
uint32_t length; |
|
|
|
length = modem_cmux_get_receive_buf_length(cmux); |
|
modem_stats_buffer_advertise_length(&cmux->receive_buf_stats, length); |
|
} |
|
#endif |
|
|
|
static const char *modem_cmux_command_type_to_str(enum modem_cmux_command_types command_type) |
|
{ |
|
switch (command_type) { |
|
case MODEM_CMUX_COMMAND_NSC: |
|
return "NSC"; |
|
case MODEM_CMUX_COMMAND_TEST: |
|
return "TEST"; |
|
case MODEM_CMUX_COMMAND_PSC: |
|
return "PSC"; |
|
case MODEM_CMUX_COMMAND_RLS: |
|
return "RLS"; |
|
case MODEM_CMUX_COMMAND_FCOFF: |
|
return "FCOFF"; |
|
case MODEM_CMUX_COMMAND_PN: |
|
return "PN"; |
|
case MODEM_CMUX_COMMAND_RPN: |
|
return "RPN"; |
|
case MODEM_CMUX_COMMAND_FCON: |
|
return "FCON"; |
|
case MODEM_CMUX_COMMAND_CLD: |
|
return "CLD"; |
|
case MODEM_CMUX_COMMAND_SNC: |
|
return "SNC"; |
|
case MODEM_CMUX_COMMAND_MSC: |
|
return "MSC"; |
|
} |
|
return ""; |
|
} |
|
|
|
static void modem_cmux_log_transmit_command(const struct modem_cmux_command *command) |
|
{ |
|
LOG_DBG("ea:%u,cr:%u,type:%s", command->type.ea, command->type.cr, |
|
modem_cmux_command_type_to_str(command->type.value)); |
|
LOG_HEXDUMP_DBG(command->value, command->length.value, "data:"); |
|
} |
|
|
|
static void modem_cmux_log_received_command(const struct modem_cmux_command *command) |
|
{ |
|
LOG_DBG("ea:%u,cr:%u,type:%s", command->type.ea, command->type.cr, |
|
modem_cmux_command_type_to_str(command->type.value)); |
|
LOG_HEXDUMP_DBG(command->value, command->length.value, "data:"); |
|
} |
|
|
|
static void modem_cmux_raise_event(struct modem_cmux *cmux, enum modem_cmux_event event) |
|
{ |
|
if (cmux->callback == NULL) { |
|
return; |
|
} |
|
|
|
cmux->callback(cmux, event, cmux->user_data); |
|
} |
|
|
|
static void modem_cmux_bus_callback(struct modem_pipe *pipe, enum modem_pipe_event event, |
|
void *user_data) |
|
{ |
|
struct modem_cmux *cmux = (struct modem_cmux *)user_data; |
|
|
|
switch (event) { |
|
case MODEM_PIPE_EVENT_RECEIVE_READY: |
|
k_work_schedule(&cmux->receive_work, K_NO_WAIT); |
|
break; |
|
|
|
case MODEM_PIPE_EVENT_TRANSMIT_IDLE: |
|
k_work_schedule(&cmux->transmit_work, K_NO_WAIT); |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
|
|
static uint16_t modem_cmux_transmit_frame(struct modem_cmux *cmux, |
|
const struct modem_cmux_frame *frame) |
|
{ |
|
uint8_t buf[MODEM_CMUX_FRAME_SIZE_MAX]; |
|
uint8_t fcs; |
|
uint16_t space; |
|
uint16_t data_len; |
|
uint16_t buf_idx; |
|
|
|
space = ring_buf_space_get(&cmux->transmit_rb) - MODEM_CMUX_FRAME_SIZE_MAX; |
|
data_len = MIN(space, frame->data_len); |
|
data_len = MIN(data_len, CONFIG_MODEM_CMUX_MTU); |
|
|
|
/* SOF */ |
|
buf[0] = 0xF9; |
|
|
|
/* DLCI Address (Max 63) */ |
|
buf[1] = 0x01 | (frame->cr << 1) | (frame->dlci_address << 2); |
|
|
|
/* Frame type and poll/final */ |
|
buf[2] = frame->type | (frame->pf << 4); |
|
|
|
/* Data length */ |
|
if (data_len > 127) { |
|
buf[3] = data_len << 1; |
|
buf[4] = data_len >> 7; |
|
buf_idx = 5; |
|
} else { |
|
buf[3] = 0x01 | (data_len << 1); |
|
buf_idx = 4; |
|
} |
|
|
|
/* Compute FCS for the header (exclude SOF) */ |
|
fcs = crc8_rohc(MODEM_CMUX_FCS_INIT_VALUE, &buf[1], (buf_idx - 1)); |
|
|
|
/* FCS final */ |
|
if (frame->type == MODEM_CMUX_FRAME_TYPE_UIH) { |
|
fcs = 0xFF - fcs; |
|
} else { |
|
fcs = 0xFF - crc8_rohc(fcs, frame->data, data_len); |
|
} |
|
|
|
/* Frame header */ |
|
ring_buf_put(&cmux->transmit_rb, buf, buf_idx); |
|
|
|
/* Data */ |
|
ring_buf_put(&cmux->transmit_rb, frame->data, data_len); |
|
|
|
/* FCS and EOF will be put on the same call */ |
|
buf[0] = fcs; |
|
buf[1] = 0xF9; |
|
ring_buf_put(&cmux->transmit_rb, buf, 2); |
|
k_work_schedule(&cmux->transmit_work, K_NO_WAIT); |
|
return data_len; |
|
} |
|
|
|
static bool modem_cmux_transmit_cmd_frame(struct modem_cmux *cmux, |
|
const struct modem_cmux_frame *frame) |
|
{ |
|
uint16_t space; |
|
struct modem_cmux_command *command; |
|
|
|
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); |
|
space = ring_buf_space_get(&cmux->transmit_rb); |
|
|
|
if (space < MODEM_CMUX_CMD_FRAME_SIZE_MAX) { |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
return false; |
|
} |
|
|
|
modem_cmux_log_transmit_frame(frame); |
|
if (modem_cmux_wrap_command(&command, frame->data, frame->data_len) == 0) { |
|
modem_cmux_log_transmit_command(command); |
|
} |
|
|
|
modem_cmux_transmit_frame(cmux, frame); |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
return true; |
|
} |
|
|
|
static int16_t modem_cmux_transmit_data_frame(struct modem_cmux *cmux, |
|
const struct modem_cmux_frame *frame) |
|
{ |
|
uint16_t space; |
|
int ret; |
|
|
|
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); |
|
|
|
if (cmux->flow_control_on == false) { |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
return 0; |
|
} |
|
|
|
space = ring_buf_space_get(&cmux->transmit_rb); |
|
|
|
/* |
|
* One command frame is reserved for command channel, and we shall prefer |
|
* waiting for more than MODEM_CMUX_DATA_FRAME_SIZE_MIN bytes available in the |
|
* transmit buffer rather than transmitting a few bytes at a time. This avoids |
|
* excessive wrapping overhead, since transmitting a single byte will require 8 |
|
* bytes of wrapping. |
|
*/ |
|
if (space < (MODEM_CMUX_CMD_FRAME_SIZE_MAX + MODEM_CMUX_DATA_FRAME_SIZE_MIN)) { |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
return 0; |
|
} |
|
|
|
modem_cmux_log_transmit_frame(frame); |
|
ret = modem_cmux_transmit_frame(cmux, frame); |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
return ret; |
|
} |
|
|
|
static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux) |
|
{ |
|
struct modem_cmux_command *command; |
|
struct modem_cmux_frame frame; |
|
uint8_t data[MODEM_CMUX_CMD_DATA_SIZE_MAX]; |
|
|
|
if (sizeof(data) < cmux->frame.data_len) { |
|
LOG_WRN("Command acknowledge buffer overrun"); |
|
return; |
|
} |
|
|
|
memcpy(&frame, &cmux->frame, sizeof(cmux->frame)); |
|
memcpy(data, cmux->frame.data, cmux->frame.data_len); |
|
modem_cmux_wrap_command(&command, data, cmux->frame.data_len); |
|
command->type.cr = 0; |
|
frame.data = data; |
|
frame.data_len = cmux->frame.data_len; |
|
|
|
if (modem_cmux_transmit_cmd_frame(cmux, &frame) == false) { |
|
LOG_WRN("Command acknowledge buffer overrun"); |
|
} |
|
} |
|
|
|
static void modem_cmux_on_msc_command(struct modem_cmux *cmux, struct modem_cmux_command *command) |
|
{ |
|
if (command->type.cr) { |
|
modem_cmux_acknowledge_received_frame(cmux); |
|
} |
|
} |
|
|
|
static void modem_cmux_on_fcon_command(struct modem_cmux *cmux) |
|
{ |
|
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); |
|
cmux->flow_control_on = true; |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
modem_cmux_acknowledge_received_frame(cmux); |
|
} |
|
|
|
static void modem_cmux_on_fcoff_command(struct modem_cmux *cmux) |
|
{ |
|
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); |
|
cmux->flow_control_on = false; |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
modem_cmux_acknowledge_received_frame(cmux); |
|
} |
|
|
|
static void modem_cmux_on_cld_command(struct modem_cmux *cmux, struct modem_cmux_command *command) |
|
{ |
|
if (command->type.cr) { |
|
modem_cmux_acknowledge_received_frame(cmux); |
|
} |
|
|
|
if (cmux->state != MODEM_CMUX_STATE_DISCONNECTING && |
|
cmux->state != MODEM_CMUX_STATE_CONNECTED) { |
|
LOG_WRN("Unexpected close down"); |
|
return; |
|
} |
|
|
|
if (cmux->state == MODEM_CMUX_STATE_DISCONNECTING) { |
|
k_work_cancel_delayable(&cmux->disconnect_work); |
|
} |
|
|
|
cmux->state = MODEM_CMUX_STATE_DISCONNECTED; |
|
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); |
|
cmux->flow_control_on = false; |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
|
|
modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED); |
|
k_event_clear(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); |
|
k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); |
|
} |
|
|
|
static void modem_cmux_on_control_frame_ua(struct modem_cmux *cmux) |
|
{ |
|
if (cmux->state != MODEM_CMUX_STATE_CONNECTING) { |
|
LOG_DBG("Unexpected UA frame"); |
|
return; |
|
} |
|
|
|
cmux->state = MODEM_CMUX_STATE_CONNECTED; |
|
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); |
|
cmux->flow_control_on = true; |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
k_work_cancel_delayable(&cmux->connect_work); |
|
modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_CONNECTED); |
|
k_event_clear(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); |
|
k_event_post(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); |
|
} |
|
|
|
static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) |
|
{ |
|
struct modem_cmux_command *command; |
|
|
|
if ((cmux->state != MODEM_CMUX_STATE_CONNECTED) && |
|
(cmux->state != MODEM_CMUX_STATE_DISCONNECTING)) { |
|
LOG_DBG("Unexpected UIH frame"); |
|
return; |
|
} |
|
|
|
if (modem_cmux_wrap_command(&command, cmux->frame.data, cmux->frame.data_len) < 0) { |
|
LOG_WRN("Invalid command"); |
|
return; |
|
} |
|
|
|
modem_cmux_log_received_command(command); |
|
|
|
switch (command->type.value) { |
|
case MODEM_CMUX_COMMAND_CLD: |
|
modem_cmux_on_cld_command(cmux, command); |
|
break; |
|
|
|
case MODEM_CMUX_COMMAND_MSC: |
|
modem_cmux_on_msc_command(cmux, command); |
|
break; |
|
|
|
case MODEM_CMUX_COMMAND_FCON: |
|
modem_cmux_on_fcon_command(cmux); |
|
break; |
|
|
|
case MODEM_CMUX_COMMAND_FCOFF: |
|
modem_cmux_on_fcoff_command(cmux); |
|
break; |
|
|
|
default: |
|
LOG_DBG("Unknown control command"); |
|
break; |
|
} |
|
} |
|
|
|
static void modem_cmux_connect_response_transmit(struct modem_cmux *cmux) |
|
{ |
|
if (cmux == NULL) { |
|
return; |
|
} |
|
|
|
struct modem_cmux_frame frame = { |
|
.dlci_address = cmux->frame.dlci_address, |
|
.cr = cmux->frame.cr, |
|
.pf = cmux->frame.pf, |
|
.type = MODEM_CMUX_FRAME_TYPE_UA, |
|
.data = NULL, |
|
.data_len = 0, |
|
}; |
|
|
|
LOG_DBG("SABM/DISC request state send ack"); |
|
modem_cmux_transmit_cmd_frame(cmux, &frame); |
|
} |
|
|
|
static void modem_cmux_on_control_frame_sabm(struct modem_cmux *cmux) |
|
{ |
|
modem_cmux_connect_response_transmit(cmux); |
|
|
|
if ((cmux->state == MODEM_CMUX_STATE_CONNECTED) || |
|
(cmux->state == MODEM_CMUX_STATE_DISCONNECTING)) { |
|
LOG_DBG("Connect request not accepted"); |
|
return; |
|
} |
|
|
|
cmux->state = MODEM_CMUX_STATE_CONNECTED; |
|
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); |
|
cmux->flow_control_on = true; |
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_CONNECTED); |
|
k_event_clear(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); |
|
k_event_post(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); |
|
} |
|
|
|
static void modem_cmux_on_control_frame(struct modem_cmux *cmux) |
|
{ |
|
modem_cmux_log_received_frame(&cmux->frame); |
|
|
|
switch (cmux->frame.type) { |
|
case MODEM_CMUX_FRAME_TYPE_UA: |
|
modem_cmux_on_control_frame_ua(cmux); |
|
break; |
|
|
|
case MODEM_CMUX_FRAME_TYPE_UIH: |
|
modem_cmux_on_control_frame_uih(cmux); |
|
break; |
|
|
|
case MODEM_CMUX_FRAME_TYPE_SABM: |
|
modem_cmux_on_control_frame_sabm(cmux); |
|
break; |
|
|
|
default: |
|
LOG_WRN("Unknown %s frame type", "control"); |
|
break; |
|
} |
|
} |
|
|
|
static struct modem_cmux_dlci *modem_cmux_find_dlci(struct modem_cmux *cmux) |
|
{ |
|
sys_snode_t *node; |
|
struct modem_cmux_dlci *dlci; |
|
|
|
SYS_SLIST_FOR_EACH_NODE(&cmux->dlcis, node) { |
|
dlci = (struct modem_cmux_dlci *)node; |
|
|
|
if (dlci->dlci_address == cmux->frame.dlci_address) { |
|
return dlci; |
|
} |
|
} |
|
|
|
return NULL; |
|
} |
|
|
|
static void modem_cmux_on_dlci_frame_ua(struct modem_cmux_dlci *dlci) |
|
{ |
|
switch (dlci->state) { |
|
case MODEM_CMUX_DLCI_STATE_OPENING: |
|
dlci->state = MODEM_CMUX_DLCI_STATE_OPEN; |
|
modem_pipe_notify_opened(&dlci->pipe); |
|
k_work_cancel_delayable(&dlci->open_work); |
|
k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER); |
|
ring_buf_reset(&dlci->receive_rb); |
|
k_mutex_unlock(&dlci->receive_rb_lock); |
|
break; |
|
|
|
case MODEM_CMUX_DLCI_STATE_CLOSING: |
|
dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED; |
|
modem_pipe_notify_closed(&dlci->pipe); |
|
k_work_cancel_delayable(&dlci->close_work); |
|
break; |
|
|
|
default: |
|
LOG_DBG("Unexpected UA frame"); |
|
break; |
|
} |
|
} |
|
|
|
static void modem_cmux_on_dlci_frame_uih(struct modem_cmux_dlci *dlci) |
|
{ |
|
struct modem_cmux *cmux = dlci->cmux; |
|
uint32_t written; |
|
|
|
if (dlci->state != MODEM_CMUX_DLCI_STATE_OPEN) { |
|
LOG_DBG("Unexpected UIH frame"); |
|
return; |
|
} |
|
|
|
k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER); |
|
written = ring_buf_put(&dlci->receive_rb, cmux->frame.data, cmux->frame.data_len); |
|
k_mutex_unlock(&dlci->receive_rb_lock); |
|
if (written != cmux->frame.data_len) { |
|
LOG_WRN("DLCI %u receive buffer overrun (dropped %u out of %u bytes)", |
|
dlci->dlci_address, cmux->frame.data_len - written, cmux->frame.data_len); |
|
} |
|
modem_pipe_notify_receive_ready(&dlci->pipe); |
|
} |
|
|
|
static void modem_cmux_on_dlci_frame_sabm(struct modem_cmux_dlci *dlci) |
|
{ |
|
struct modem_cmux *cmux = dlci->cmux; |
|
|
|
modem_cmux_connect_response_transmit(cmux); |
|
|
|
if (dlci->state == MODEM_CMUX_DLCI_STATE_OPEN) { |
|
LOG_DBG("Unexpected SABM frame"); |
|
return; |
|
} |
|
|
|
dlci->state = MODEM_CMUX_DLCI_STATE_OPEN; |
|
modem_pipe_notify_opened(&dlci->pipe); |
|
k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER); |
|
ring_buf_reset(&dlci->receive_rb); |
|
k_mutex_unlock(&dlci->receive_rb_lock); |
|
} |
|
|
|
static void modem_cmux_on_dlci_frame_disc(struct modem_cmux_dlci *dlci) |
|
{ |
|
struct modem_cmux *cmux = dlci->cmux; |
|
|
|
modem_cmux_connect_response_transmit(cmux); |
|
|
|
if (dlci->state != MODEM_CMUX_DLCI_STATE_OPEN) { |
|
LOG_DBG("Unexpected Disc frame"); |
|
return; |
|
} |
|
|
|
dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED; |
|
modem_pipe_notify_closed(&dlci->pipe); |
|
} |
|
|
|
static void modem_cmux_on_dlci_frame(struct modem_cmux *cmux) |
|
{ |
|
struct modem_cmux_dlci *dlci; |
|
|
|
modem_cmux_log_received_frame(&cmux->frame); |
|
|
|
dlci = modem_cmux_find_dlci(cmux); |
|
if (dlci == NULL) { |
|
LOG_WRN("Ignoring frame intended for unconfigured DLCI %u.", |
|
cmux->frame.dlci_address); |
|
return; |
|
} |
|
|
|
switch (cmux->frame.type) { |
|
case MODEM_CMUX_FRAME_TYPE_UA: |
|
modem_cmux_on_dlci_frame_ua(dlci); |
|
break; |
|
|
|
case MODEM_CMUX_FRAME_TYPE_UIH: |
|
modem_cmux_on_dlci_frame_uih(dlci); |
|
break; |
|
|
|
case MODEM_CMUX_FRAME_TYPE_SABM: |
|
modem_cmux_on_dlci_frame_sabm(dlci); |
|
break; |
|
|
|
case MODEM_CMUX_FRAME_TYPE_DISC: |
|
modem_cmux_on_dlci_frame_disc(dlci); |
|
break; |
|
|
|
default: |
|
LOG_WRN("Unknown %s frame type", "DLCI"); |
|
break; |
|
} |
|
} |
|
|
|
static void modem_cmux_on_frame(struct modem_cmux *cmux) |
|
{ |
|
#if CONFIG_MODEM_STATS |
|
modem_cmux_advertise_receive_buf_stats(cmux); |
|
#endif |
|
|
|
if (cmux->frame.dlci_address == 0) { |
|
modem_cmux_on_control_frame(cmux); |
|
} else { |
|
modem_cmux_on_dlci_frame(cmux); |
|
} |
|
} |
|
|
|
static void modem_cmux_drop_frame(struct modem_cmux *cmux) |
|
{ |
|
#if CONFIG_MODEM_STATS |
|
modem_cmux_advertise_receive_buf_stats(cmux); |
|
#endif |
|
|
|
LOG_WRN("Dropped frame"); |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_SOF; |
|
|
|
#if defined(CONFIG_MODEM_CMUX_LOG_LEVEL_DBG) |
|
struct modem_cmux_frame *frame = &cmux->frame; |
|
|
|
frame->data = cmux->receive_buf; |
|
modem_cmux_log_frame(frame, "dropped", MIN(frame->data_len, cmux->receive_buf_size)); |
|
#endif |
|
} |
|
|
|
static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t byte) |
|
{ |
|
uint8_t fcs; |
|
|
|
switch (cmux->receive_state) { |
|
case MODEM_CMUX_RECEIVE_STATE_SOF: |
|
if (byte == 0xF9) { |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC; |
|
break; |
|
} |
|
|
|
break; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_RESYNC: |
|
/* |
|
* Allow any number of consecutive flags (0xF9). |
|
* 0xF9 could also be a valid address field for DLCI 62. |
|
*/ |
|
if (byte == 0xF9) { |
|
break; |
|
} |
|
|
|
__fallthrough; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_ADDRESS: |
|
/* Initialize */ |
|
cmux->receive_buf_len = 0; |
|
cmux->frame_header_len = 0; |
|
|
|
/* Store header for FCS */ |
|
cmux->frame_header[cmux->frame_header_len] = byte; |
|
cmux->frame_header_len++; |
|
|
|
/* Get CR */ |
|
cmux->frame.cr = (byte & 0x02) ? true : false; |
|
|
|
/* Get DLCI address */ |
|
cmux->frame.dlci_address = (byte >> 2) & 0x3F; |
|
|
|
/* Await control */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_CONTROL; |
|
break; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_CONTROL: |
|
/* Store header for FCS */ |
|
cmux->frame_header[cmux->frame_header_len] = byte; |
|
cmux->frame_header_len++; |
|
|
|
/* Get PF */ |
|
cmux->frame.pf = (byte & MODEM_CMUX_PF) ? true : false; |
|
|
|
/* Get frame type */ |
|
cmux->frame.type = byte & (~MODEM_CMUX_PF); |
|
|
|
/* Await data length */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_LENGTH; |
|
break; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_LENGTH: |
|
/* Store header for FCS */ |
|
cmux->frame_header[cmux->frame_header_len] = byte; |
|
cmux->frame_header_len++; |
|
|
|
/* Get first 7 bits of data length */ |
|
cmux->frame.data_len = (byte >> 1); |
|
|
|
/* Check if length field continues */ |
|
if ((byte & MODEM_CMUX_EA) == 0) { |
|
/* Await continued length field */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_LENGTH_CONT; |
|
break; |
|
} |
|
|
|
if (cmux->frame.data_len > CONFIG_MODEM_CMUX_MTU) { |
|
LOG_ERR("Too large frame"); |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; |
|
break; |
|
} |
|
|
|
/* Check if no data field */ |
|
if (cmux->frame.data_len == 0) { |
|
/* Await FCS */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_FCS; |
|
break; |
|
} |
|
|
|
/* Await data */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DATA; |
|
break; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_LENGTH_CONT: |
|
/* Store header for FCS */ |
|
cmux->frame_header[cmux->frame_header_len] = byte; |
|
cmux->frame_header_len++; |
|
|
|
/* Get last 8 bits of data length */ |
|
cmux->frame.data_len |= ((uint16_t)byte) << 7; |
|
|
|
if (cmux->frame.data_len > CONFIG_MODEM_CMUX_MTU) { |
|
LOG_ERR("Too large frame"); |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; |
|
break; |
|
} |
|
|
|
if (cmux->frame.data_len > cmux->receive_buf_size) { |
|
LOG_ERR("Indicated frame data length %u exceeds receive buffer size %u", |
|
cmux->frame.data_len, cmux->receive_buf_size); |
|
|
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; |
|
break; |
|
} |
|
|
|
/* Await data */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DATA; |
|
break; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_DATA: |
|
/* Copy byte to data */ |
|
if (cmux->receive_buf_len < cmux->receive_buf_size) { |
|
cmux->receive_buf[cmux->receive_buf_len] = byte; |
|
} |
|
cmux->receive_buf_len++; |
|
|
|
/* Check if datalen reached */ |
|
if (cmux->frame.data_len == cmux->receive_buf_len) { |
|
/* Await FCS */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_FCS; |
|
} |
|
|
|
break; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_FCS: |
|
if (cmux->receive_buf_len > cmux->receive_buf_size) { |
|
LOG_WRN("Receive buffer overrun (%u > %u)", |
|
cmux->receive_buf_len, cmux->receive_buf_size); |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; |
|
break; |
|
} |
|
|
|
/* Compute FCS */ |
|
fcs = crc8_rohc(MODEM_CMUX_FCS_INIT_VALUE, cmux->frame_header, |
|
cmux->frame_header_len); |
|
if (cmux->frame.type == MODEM_CMUX_FRAME_TYPE_UIH) { |
|
fcs = 0xFF - fcs; |
|
} else { |
|
fcs = 0xFF - crc8_rohc(fcs, cmux->frame.data, cmux->frame.data_len); |
|
} |
|
|
|
/* Validate FCS */ |
|
if (fcs != byte) { |
|
LOG_WRN("Frame FCS error"); |
|
|
|
/* Drop frame */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; |
|
break; |
|
} |
|
|
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_EOF; |
|
break; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_DROP: |
|
modem_cmux_drop_frame(cmux); |
|
break; |
|
|
|
case MODEM_CMUX_RECEIVE_STATE_EOF: |
|
/* Validate byte is EOF */ |
|
if (byte != 0xF9) { |
|
/* Unexpected byte */ |
|
modem_cmux_drop_frame(cmux); |
|
break; |
|
} |
|
|
|
/* Process frame */ |
|
cmux->frame.data = cmux->receive_buf; |
|
modem_cmux_on_frame(cmux); |
|
|
|
/* Await start of next frame */ |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_SOF; |
|
break; |
|
|
|
default: |
|
break; |
|
} |
|
} |
|
|
|
static void modem_cmux_receive_handler(struct k_work *item) |
|
{ |
|
struct k_work_delayable *dwork = k_work_delayable_from_work(item); |
|
struct modem_cmux *cmux = CONTAINER_OF(dwork, struct modem_cmux, receive_work); |
|
int ret; |
|
|
|
/* Receive data from pipe */ |
|
ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf)); |
|
if (ret < 1) { |
|
if (ret < 0) { |
|
LOG_ERR("Pipe receiving error: %d", ret); |
|
} |
|
return; |
|
} |
|
|
|
/* Process received data */ |
|
for (int i = 0; i < ret; i++) { |
|
modem_cmux_process_received_byte(cmux, cmux->work_buf[i]); |
|
} |
|
|
|
/* Reschedule received work */ |
|
k_work_schedule(&cmux->receive_work, K_NO_WAIT); |
|
} |
|
|
|
static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux) |
|
{ |
|
sys_snode_t *node; |
|
struct modem_cmux_dlci *dlci; |
|
|
|
SYS_SLIST_FOR_EACH_NODE(&cmux->dlcis, node) { |
|
dlci = (struct modem_cmux_dlci *)node; |
|
modem_pipe_notify_transmit_idle(&dlci->pipe); |
|
} |
|
} |
|
|
|
static void modem_cmux_transmit_handler(struct k_work *item) |
|
{ |
|
struct k_work_delayable *dwork = k_work_delayable_from_work(item); |
|
struct modem_cmux *cmux = CONTAINER_OF(dwork, struct modem_cmux, transmit_work); |
|
uint8_t *reserved; |
|
uint32_t reserved_size; |
|
int ret; |
|
bool transmit_rb_empty; |
|
|
|
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); |
|
|
|
#if CONFIG_MODEM_STATS |
|
modem_cmux_advertise_transmit_buf_stats(cmux); |
|
#endif |
|
|
|
while (true) { |
|
transmit_rb_empty = ring_buf_is_empty(&cmux->transmit_rb); |
|
|
|
if (transmit_rb_empty) { |
|
break; |
|
} |
|
|
|
reserved_size = ring_buf_get_claim(&cmux->transmit_rb, &reserved, UINT32_MAX); |
|
|
|
ret = modem_pipe_transmit(cmux->pipe, reserved, reserved_size); |
|
if (ret < 0) { |
|
ring_buf_get_finish(&cmux->transmit_rb, 0); |
|
if (ret != -EPERM) { |
|
LOG_ERR("Failed to %s %u bytes. (%d)", |
|
"transmit", reserved_size, ret); |
|
} |
|
break; |
|
} |
|
|
|
ring_buf_get_finish(&cmux->transmit_rb, (uint32_t)ret); |
|
|
|
if (ret < reserved_size) { |
|
LOG_DBG("Transmitted only %u out of %u bytes at once.", ret, reserved_size); |
|
break; |
|
} |
|
} |
|
|
|
k_mutex_unlock(&cmux->transmit_rb_lock); |
|
|
|
if (transmit_rb_empty) { |
|
modem_cmux_dlci_notify_transmit_idle(cmux); |
|
} |
|
} |
|
|
|
static void modem_cmux_connect_handler(struct k_work *item) |
|
{ |
|
struct k_work_delayable *dwork; |
|
struct modem_cmux *cmux; |
|
|
|
if (item == NULL) { |
|
return; |
|
} |
|
|
|
dwork = k_work_delayable_from_work(item); |
|
cmux = CONTAINER_OF(dwork, struct modem_cmux, connect_work); |
|
|
|
cmux->state = MODEM_CMUX_STATE_CONNECTING; |
|
|
|
static const struct modem_cmux_frame frame = { |
|
.dlci_address = 0, |
|
.cr = true, |
|
.pf = true, |
|
.type = MODEM_CMUX_FRAME_TYPE_SABM, |
|
.data = NULL, |
|
.data_len = 0, |
|
}; |
|
|
|
modem_cmux_transmit_cmd_frame(cmux, &frame); |
|
k_work_schedule(&cmux->connect_work, MODEM_CMUX_T1_TIMEOUT); |
|
} |
|
|
|
static void modem_cmux_disconnect_handler(struct k_work *item) |
|
{ |
|
struct k_work_delayable *dwork = k_work_delayable_from_work(item); |
|
struct modem_cmux *cmux = CONTAINER_OF(dwork, struct modem_cmux, disconnect_work); |
|
struct modem_cmux_command *command; |
|
uint8_t data[2]; |
|
|
|
cmux->state = MODEM_CMUX_STATE_DISCONNECTING; |
|
|
|
command = modem_cmux_command_wrap(data); |
|
command->type.ea = 1; |
|
command->type.cr = 1; |
|
command->type.value = MODEM_CMUX_COMMAND_CLD; |
|
command->length.ea = 1; |
|
command->length.value = 0; |
|
|
|
struct modem_cmux_frame frame = { |
|
.dlci_address = 0, |
|
.cr = true, |
|
.pf = false, |
|
.type = MODEM_CMUX_FRAME_TYPE_UIH, |
|
.data = data, |
|
.data_len = sizeof(data), |
|
}; |
|
|
|
/* Transmit close down command */ |
|
modem_cmux_transmit_cmd_frame(cmux, &frame); |
|
k_work_schedule(&cmux->disconnect_work, MODEM_CMUX_T1_TIMEOUT); |
|
} |
|
|
|
#if CONFIG_MODEM_STATS |
|
static uint32_t modem_cmux_dlci_get_receive_buf_length(struct modem_cmux_dlci *dlci) |
|
{ |
|
return ring_buf_size_get(&dlci->receive_rb); |
|
} |
|
|
|
static uint32_t modem_cmux_dlci_get_receive_buf_size(struct modem_cmux_dlci *dlci) |
|
{ |
|
return ring_buf_capacity_get(&dlci->receive_rb); |
|
} |
|
|
|
static void modem_cmux_dlci_init_buf_stats(struct modem_cmux_dlci *dlci) |
|
{ |
|
uint32_t size; |
|
char name[sizeof("dlci_xxxxx_rx")]; |
|
|
|
size = modem_cmux_dlci_get_receive_buf_size(dlci); |
|
snprintk(name, sizeof(name), "dlci_%u_rx", dlci->dlci_address); |
|
modem_stats_buffer_init(&dlci->receive_buf_stats, name, size); |
|
} |
|
|
|
static void modem_cmux_dlci_advertise_receive_buf_stat(struct modem_cmux_dlci *dlci) |
|
{ |
|
uint32_t length; |
|
|
|
length = modem_cmux_dlci_get_receive_buf_length(dlci); |
|
modem_stats_buffer_advertise_length(&dlci->receive_buf_stats, length); |
|
} |
|
#endif |
|
|
|
static int modem_cmux_dlci_pipe_api_open(void *data) |
|
{ |
|
struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data; |
|
struct modem_cmux *cmux = dlci->cmux; |
|
int ret = 0; |
|
|
|
K_SPINLOCK(&cmux->work_lock) { |
|
if (!cmux->attached) { |
|
ret = -EPERM; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
if (k_work_delayable_is_pending(&dlci->open_work) == true) { |
|
ret = -EBUSY; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
k_work_schedule(&dlci->open_work, K_NO_WAIT); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int modem_cmux_dlci_pipe_api_transmit(void *data, const uint8_t *buf, size_t size) |
|
{ |
|
struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data; |
|
struct modem_cmux *cmux = dlci->cmux; |
|
int ret = 0; |
|
|
|
K_SPINLOCK(&cmux->work_lock) { |
|
if (!cmux->attached) { |
|
ret = -EPERM; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
struct modem_cmux_frame frame = { |
|
.dlci_address = dlci->dlci_address, |
|
.cr = true, |
|
.pf = false, |
|
.type = MODEM_CMUX_FRAME_TYPE_UIH, |
|
.data = buf, |
|
.data_len = size, |
|
}; |
|
|
|
ret = modem_cmux_transmit_data_frame(cmux, &frame); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static int modem_cmux_dlci_pipe_api_receive(void *data, uint8_t *buf, size_t size) |
|
{ |
|
struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data; |
|
uint32_t ret; |
|
|
|
k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER); |
|
|
|
#if CONFIG_MODEM_STATS |
|
modem_cmux_dlci_advertise_receive_buf_stat(dlci); |
|
#endif |
|
|
|
ret = ring_buf_get(&dlci->receive_rb, buf, size); |
|
k_mutex_unlock(&dlci->receive_rb_lock); |
|
return ret; |
|
} |
|
|
|
static int modem_cmux_dlci_pipe_api_close(void *data) |
|
{ |
|
struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data; |
|
struct modem_cmux *cmux = dlci->cmux; |
|
int ret = 0; |
|
|
|
K_SPINLOCK(&cmux->work_lock) { |
|
if (!cmux->attached) { |
|
ret = -EPERM; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
if (k_work_delayable_is_pending(&dlci->close_work) == true) { |
|
ret = -EBUSY; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
k_work_schedule(&dlci->close_work, K_NO_WAIT); |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
static const struct modem_pipe_api modem_cmux_dlci_pipe_api = { |
|
.open = modem_cmux_dlci_pipe_api_open, |
|
.transmit = modem_cmux_dlci_pipe_api_transmit, |
|
.receive = modem_cmux_dlci_pipe_api_receive, |
|
.close = modem_cmux_dlci_pipe_api_close, |
|
}; |
|
|
|
static void modem_cmux_dlci_open_handler(struct k_work *item) |
|
{ |
|
struct k_work_delayable *dwork; |
|
struct modem_cmux_dlci *dlci; |
|
|
|
if (item == NULL) { |
|
return; |
|
} |
|
|
|
dwork = k_work_delayable_from_work(item); |
|
dlci = CONTAINER_OF(dwork, struct modem_cmux_dlci, open_work); |
|
|
|
dlci->state = MODEM_CMUX_DLCI_STATE_OPENING; |
|
|
|
struct modem_cmux_frame frame = { |
|
.dlci_address = dlci->dlci_address, |
|
.cr = true, |
|
.pf = true, |
|
.type = MODEM_CMUX_FRAME_TYPE_SABM, |
|
.data = NULL, |
|
.data_len = 0, |
|
}; |
|
|
|
modem_cmux_transmit_cmd_frame(dlci->cmux, &frame); |
|
k_work_schedule(&dlci->open_work, MODEM_CMUX_T1_TIMEOUT); |
|
} |
|
|
|
static void modem_cmux_dlci_close_handler(struct k_work *item) |
|
{ |
|
struct k_work_delayable *dwork; |
|
struct modem_cmux_dlci *dlci; |
|
struct modem_cmux *cmux; |
|
|
|
if (item == NULL) { |
|
return; |
|
} |
|
|
|
dwork = k_work_delayable_from_work(item); |
|
dlci = CONTAINER_OF(dwork, struct modem_cmux_dlci, close_work); |
|
cmux = dlci->cmux; |
|
|
|
dlci->state = MODEM_CMUX_DLCI_STATE_CLOSING; |
|
|
|
struct modem_cmux_frame frame = { |
|
.dlci_address = dlci->dlci_address, |
|
.cr = true, |
|
.pf = true, |
|
.type = MODEM_CMUX_FRAME_TYPE_DISC, |
|
.data = NULL, |
|
.data_len = 0, |
|
}; |
|
|
|
modem_cmux_transmit_cmd_frame(cmux, &frame); |
|
k_work_schedule(&dlci->close_work, MODEM_CMUX_T1_TIMEOUT); |
|
} |
|
|
|
static void modem_cmux_dlci_pipes_release(struct modem_cmux *cmux) |
|
{ |
|
sys_snode_t *node; |
|
struct modem_cmux_dlci *dlci; |
|
struct k_work_sync sync; |
|
|
|
SYS_SLIST_FOR_EACH_NODE(&cmux->dlcis, node) { |
|
dlci = (struct modem_cmux_dlci *)node; |
|
modem_pipe_notify_closed(&dlci->pipe); |
|
k_work_cancel_delayable_sync(&dlci->open_work, &sync); |
|
k_work_cancel_delayable_sync(&dlci->close_work, &sync); |
|
} |
|
} |
|
|
|
void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *config) |
|
{ |
|
__ASSERT_NO_MSG(cmux != NULL); |
|
__ASSERT_NO_MSG(config != NULL); |
|
__ASSERT_NO_MSG(config->receive_buf != NULL); |
|
__ASSERT_NO_MSG(config->receive_buf_size >= |
|
(CONFIG_MODEM_CMUX_MTU + MODEM_CMUX_FRAME_SIZE_MAX)); |
|
__ASSERT_NO_MSG(config->transmit_buf != NULL); |
|
__ASSERT_NO_MSG(config->transmit_buf_size >= |
|
(CONFIG_MODEM_CMUX_MTU + MODEM_CMUX_FRAME_SIZE_MAX)); |
|
|
|
memset(cmux, 0x00, sizeof(*cmux)); |
|
cmux->callback = config->callback; |
|
cmux->user_data = config->user_data; |
|
cmux->receive_buf = config->receive_buf; |
|
cmux->receive_buf_size = config->receive_buf_size; |
|
sys_slist_init(&cmux->dlcis); |
|
cmux->state = MODEM_CMUX_STATE_DISCONNECTED; |
|
ring_buf_init(&cmux->transmit_rb, config->transmit_buf_size, config->transmit_buf); |
|
k_mutex_init(&cmux->transmit_rb_lock); |
|
k_work_init_delayable(&cmux->receive_work, modem_cmux_receive_handler); |
|
k_work_init_delayable(&cmux->transmit_work, modem_cmux_transmit_handler); |
|
k_work_init_delayable(&cmux->connect_work, modem_cmux_connect_handler); |
|
k_work_init_delayable(&cmux->disconnect_work, modem_cmux_disconnect_handler); |
|
k_event_init(&cmux->event); |
|
k_event_clear(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); |
|
k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); |
|
|
|
#if CONFIG_MODEM_STATS |
|
modem_cmux_init_buf_stats(cmux); |
|
#endif |
|
} |
|
|
|
struct modem_pipe *modem_cmux_dlci_init(struct modem_cmux *cmux, struct modem_cmux_dlci *dlci, |
|
const struct modem_cmux_dlci_config *config) |
|
{ |
|
__ASSERT_NO_MSG(cmux != NULL); |
|
__ASSERT_NO_MSG(dlci != NULL); |
|
__ASSERT_NO_MSG(config != NULL); |
|
__ASSERT_NO_MSG(config->dlci_address < 64); |
|
__ASSERT_NO_MSG(config->receive_buf != NULL); |
|
__ASSERT_NO_MSG(config->receive_buf_size >= 126); |
|
|
|
memset(dlci, 0x00, sizeof(*dlci)); |
|
dlci->cmux = cmux; |
|
dlci->dlci_address = config->dlci_address; |
|
ring_buf_init(&dlci->receive_rb, config->receive_buf_size, config->receive_buf); |
|
k_mutex_init(&dlci->receive_rb_lock); |
|
modem_pipe_init(&dlci->pipe, dlci, &modem_cmux_dlci_pipe_api); |
|
k_work_init_delayable(&dlci->open_work, modem_cmux_dlci_open_handler); |
|
k_work_init_delayable(&dlci->close_work, modem_cmux_dlci_close_handler); |
|
dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED; |
|
sys_slist_append(&dlci->cmux->dlcis, &dlci->node); |
|
|
|
#if CONFIG_MODEM_STATS |
|
modem_cmux_dlci_init_buf_stats(dlci); |
|
#endif |
|
|
|
return &dlci->pipe; |
|
} |
|
|
|
int modem_cmux_attach(struct modem_cmux *cmux, struct modem_pipe *pipe) |
|
{ |
|
if (cmux->pipe != NULL) { |
|
return -EALREADY; |
|
} |
|
|
|
cmux->pipe = pipe; |
|
ring_buf_reset(&cmux->transmit_rb); |
|
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_SOF; |
|
modem_pipe_attach(cmux->pipe, modem_cmux_bus_callback, cmux); |
|
|
|
K_SPINLOCK(&cmux->work_lock) { |
|
cmux->attached = true; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int modem_cmux_connect(struct modem_cmux *cmux) |
|
{ |
|
int ret; |
|
|
|
ret = modem_cmux_connect_async(cmux); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
if (k_event_wait(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT, false, |
|
MODEM_CMUX_T2_TIMEOUT) == 0) { |
|
return -EAGAIN; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int modem_cmux_connect_async(struct modem_cmux *cmux) |
|
{ |
|
int ret = 0; |
|
|
|
if (k_event_test(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT)) { |
|
return -EALREADY; |
|
} |
|
|
|
K_SPINLOCK(&cmux->work_lock) { |
|
if (!cmux->attached) { |
|
ret = -EPERM; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
if (k_work_delayable_is_pending(&cmux->connect_work) == false) { |
|
k_work_schedule(&cmux->connect_work, K_NO_WAIT); |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
int modem_cmux_disconnect(struct modem_cmux *cmux) |
|
{ |
|
int ret; |
|
|
|
ret = modem_cmux_disconnect_async(cmux); |
|
if (ret < 0) { |
|
return ret; |
|
} |
|
|
|
if (k_event_wait(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT, false, |
|
MODEM_CMUX_T2_TIMEOUT) == 0) { |
|
return -EAGAIN; |
|
} |
|
|
|
return 0; |
|
} |
|
|
|
int modem_cmux_disconnect_async(struct modem_cmux *cmux) |
|
{ |
|
int ret = 0; |
|
|
|
if (k_event_test(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT)) { |
|
return -EALREADY; |
|
} |
|
|
|
K_SPINLOCK(&cmux->work_lock) { |
|
if (!cmux->attached) { |
|
ret = -EPERM; |
|
K_SPINLOCK_BREAK; |
|
} |
|
|
|
if (k_work_delayable_is_pending(&cmux->disconnect_work) == false) { |
|
k_work_schedule(&cmux->disconnect_work, K_NO_WAIT); |
|
} |
|
} |
|
|
|
return ret; |
|
} |
|
|
|
void modem_cmux_release(struct modem_cmux *cmux) |
|
{ |
|
struct k_work_sync sync; |
|
|
|
if (cmux->pipe == NULL) { |
|
return; |
|
} |
|
|
|
K_SPINLOCK(&cmux->work_lock) { |
|
cmux->attached = false; |
|
} |
|
|
|
/* Close DLCI pipes and cancel DLCI work */ |
|
modem_cmux_dlci_pipes_release(cmux); |
|
|
|
/* Release bus pipe */ |
|
if (cmux->pipe) { |
|
modem_pipe_release(cmux->pipe); |
|
} |
|
|
|
/* Cancel all work */ |
|
k_work_cancel_delayable_sync(&cmux->connect_work, &sync); |
|
k_work_cancel_delayable_sync(&cmux->disconnect_work, &sync); |
|
k_work_cancel_delayable_sync(&cmux->transmit_work, &sync); |
|
k_work_cancel_delayable_sync(&cmux->receive_work, &sync); |
|
|
|
/* Unreference pipe */ |
|
cmux->pipe = NULL; |
|
|
|
/* Reset events */ |
|
k_event_clear(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); |
|
k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); |
|
}
|
|
|