blob: f1fa0beec32a544371a2b98966d0ed64cd370cde [file] [log] [blame]
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
/*
* This library uses CMUX to create multiple data channels, called DLCIs, on a single serial bus.
* Each DLCI has an address from 1 to 63. DLCI address 0 is reserved for control commands.
*
* Design overview:
*
* DLCI1 <-----------+ +-------> DLCI1
* v v
* DLCI2 <---> CMUX instance <--> Serial bus <--> Client <--> DLCI2
* ^ ^
* DLCI3 <-----------+ +-------> DLCI3
*
* Writing to and from the CMUX instances is done using the modem_pipe API.
*/
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/modem/pipe.h>
#include <zephyr/modem/stats.h>
#ifndef ZEPHYR_MODEM_CMUX_
#define ZEPHYR_MODEM_CMUX_
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Modem CMUX
* @defgroup modem_cmux Modem CMUX
* @ingroup modem
* @{
*/
struct modem_cmux;
enum modem_cmux_event {
MODEM_CMUX_EVENT_CONNECTED = 0,
MODEM_CMUX_EVENT_DISCONNECTED,
};
typedef void (*modem_cmux_callback)(struct modem_cmux *cmux, enum modem_cmux_event event,
void *user_data);
/**
* @cond INTERNAL_HIDDEN
*/
enum modem_cmux_state {
MODEM_CMUX_STATE_DISCONNECTED = 0,
MODEM_CMUX_STATE_CONNECTING,
MODEM_CMUX_STATE_CONNECTED,
MODEM_CMUX_STATE_DISCONNECTING,
};
enum modem_cmux_receive_state {
MODEM_CMUX_RECEIVE_STATE_SOF = 0,
MODEM_CMUX_RECEIVE_STATE_RESYNC,
MODEM_CMUX_RECEIVE_STATE_ADDRESS,
MODEM_CMUX_RECEIVE_STATE_ADDRESS_CONT,
MODEM_CMUX_RECEIVE_STATE_CONTROL,
MODEM_CMUX_RECEIVE_STATE_LENGTH,
MODEM_CMUX_RECEIVE_STATE_LENGTH_CONT,
MODEM_CMUX_RECEIVE_STATE_DATA,
MODEM_CMUX_RECEIVE_STATE_FCS,
MODEM_CMUX_RECEIVE_STATE_DROP,
MODEM_CMUX_RECEIVE_STATE_EOF,
};
enum modem_cmux_dlci_state {
MODEM_CMUX_DLCI_STATE_CLOSED,
MODEM_CMUX_DLCI_STATE_OPENING,
MODEM_CMUX_DLCI_STATE_OPEN,
MODEM_CMUX_DLCI_STATE_CLOSING,
};
struct modem_cmux_dlci {
sys_snode_t node;
/* Pipe */
struct modem_pipe pipe;
/* Context */
uint16_t dlci_address;
struct modem_cmux *cmux;
/* Receive buffer */
struct ring_buf receive_rb;
struct k_mutex receive_rb_lock;
/* Work */
struct k_work_delayable open_work;
struct k_work_delayable close_work;
/* State */
enum modem_cmux_dlci_state state;
/* Statistics */
#if CONFIG_MODEM_STATS
struct modem_stats_buffer receive_buf_stats;
#endif
};
struct modem_cmux_frame {
uint8_t dlci_address;
bool cr;
bool pf;
uint8_t type;
const uint8_t *data;
uint16_t data_len;
};
struct modem_cmux_work {
struct k_work_delayable dwork;
struct modem_cmux *cmux;
};
struct modem_cmux {
/* Bus pipe */
struct modem_pipe *pipe;
/* Event handler */
modem_cmux_callback callback;
void *user_data;
/* DLCI channel contexts */
sys_slist_t dlcis;
/* State */
enum modem_cmux_state state;
bool flow_control_on;
/* Work lock */
bool attached;
struct k_spinlock work_lock;
/* Receive state*/
enum modem_cmux_receive_state receive_state;
/* Receive buffer */
uint8_t *receive_buf;
uint16_t receive_buf_size;
uint16_t receive_buf_len;
uint8_t work_buf[CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE];
/* Transmit buffer */
struct ring_buf transmit_rb;
struct k_mutex transmit_rb_lock;
/* Received frame */
struct modem_cmux_frame frame;
uint8_t frame_header[5];
uint16_t frame_header_len;
/* Work */
struct k_work_delayable receive_work;
struct k_work_delayable transmit_work;
struct k_work_delayable connect_work;
struct k_work_delayable disconnect_work;
/* Synchronize actions */
struct k_event event;
/* Statistics */
#if CONFIG_MODEM_STATS
struct modem_stats_buffer receive_buf_stats;
struct modem_stats_buffer transmit_buf_stats;
#endif
};
/**
* @endcond
*/
/**
* @brief Contains CMUX instance configuration data
*/
struct modem_cmux_config {
/** Invoked when event occurs */
modem_cmux_callback callback;
/** Free to use pointer passed to event handler when invoked */
void *user_data;
/** Receive buffer */
uint8_t *receive_buf;
/** Size of receive buffer in bytes [127, ...] */
uint16_t receive_buf_size;
/** Transmit buffer */
uint8_t *transmit_buf;
/** Size of transmit buffer in bytes [149, ...] */
uint16_t transmit_buf_size;
};
/**
* @brief Initialize CMUX instance
* @param cmux CMUX instance
* @param config Configuration to apply to CMUX instance
*/
void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *config);
/**
* @brief CMUX DLCI configuration
*/
struct modem_cmux_dlci_config {
/** DLCI channel address */
uint8_t dlci_address;
/** Receive buffer used by pipe */
uint8_t *receive_buf;
/** Size of receive buffer used by pipe [127, ...] */
uint16_t receive_buf_size;
};
/**
* @brief Initialize DLCI instance and register it with CMUX instance
*
* @param cmux CMUX instance which the DLCI will be registered to
* @param dlci DLCI instance which will be registered and configured
* @param config Configuration to apply to DLCI instance
*/
struct modem_pipe *modem_cmux_dlci_init(struct modem_cmux *cmux, struct modem_cmux_dlci *dlci,
const struct modem_cmux_dlci_config *config);
/**
* @brief Attach CMUX instance to pipe
*
* @param cmux CMUX instance
* @param pipe Pipe instance to attach CMUX instance to
*/
int modem_cmux_attach(struct modem_cmux *cmux, struct modem_pipe *pipe);
/**
* @brief Connect CMUX instance
*
* @details This will send a CMUX connect request to target on the serial bus. If successful,
* DLCI channels can be now be opened using modem_pipe_open()
*
* @param cmux CMUX instance
*
* @note When connected, the bus pipe must not be used directly
*/
int modem_cmux_connect(struct modem_cmux *cmux);
/**
* @brief Connect CMUX instance asynchronously
*
* @details This will send a CMUX connect request to target on the serial bus. If successful,
* DLCI channels can be now be opened using modem_pipe_open().
*
* @param cmux CMUX instance
*
* @note When connected, the bus pipe must not be used directly
*/
int modem_cmux_connect_async(struct modem_cmux *cmux);
/**
* @brief Close down and disconnect CMUX instance
*
* @details This will close all open DLCI channels, and close down the CMUX connection.
*
* @param cmux CMUX instance
*
* @note The bus pipe must be released using modem_cmux_release() after disconnecting
* before being reused.
*/
int modem_cmux_disconnect(struct modem_cmux *cmux);
/**
* @brief Close down and disconnect CMUX instance asynchronously
*
* @details This will close all open DLCI channels, and close down the CMUX connection.
*
* @param cmux CMUX instance
*
* @note The bus pipe must be released using modem_cmux_release() after disconnecting
* before being reused.
*/
int modem_cmux_disconnect_async(struct modem_cmux *cmux);
/**
* @brief Release CMUX instance from pipe
*
* @details Releases the pipe and hard resets the CMUX instance internally. CMUX should
* be disconnected using modem_cmux_disconnect().
*
* @param cmux CMUX instance
*
* @note The bus pipe can be used directly again after CMUX instance is released.
*/
void modem_cmux_release(struct modem_cmux *cmux);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_CMUX_ */