subsys/modem: Add modem modules

This PR adds the following modem modules to the subsys/modem
folder:

- chat: Light implementation of the Linux chat program, used to
        send and receive text based commands statically created
        scripts.

- cmux: Implementation of the CMUX protocol
- pipe: Thread-safe async data-in/data-out binding layer between
        modem  modules.

- ppp: Implementation of the PPP protocol, binding the Zephyr PPP
       L2 stack with the data-in/data-out pipe.

These modules use the abstract pipes to communicate between each
other. To bind them with the hardware, the following backends
are provided:

- TTY: modem pipe <-> POSIX TTY file
- UART: modem pipe <-> UART, async and ISR APIs supported

The backends are used to abstract away the physical layer, UART,
TTY, IPC, I2C, SPI etc, to a modem modules friendly pipe.

Signed-off-by: Bjarki Arge Andreasen <baa@trackunit.com>
This commit is contained in:
Bjarki Arge Andreasen 2023-04-10 17:58:39 +02:00 committed by Carles Cufí
parent b7a172fa1a
commit b4cf54b8c3
22 changed files with 4306 additions and 0 deletions

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/device.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/modem/pipe.h>
#ifndef ZEPHYR_MODEM_BACKEND_TTY_
#define ZEPHYR_MODEM_BACKEND_TTY_
#ifdef __cplusplus
extern "C" {
#endif
struct modem_backend_tty {
const char *tty_path;
int tty_fd;
struct modem_pipe pipe;
struct k_thread thread;
k_thread_stack_t *stack;
size_t stack_size;
atomic_t state;
};
struct modem_backend_tty_config {
const char *tty_path;
k_thread_stack_t *stack;
size_t stack_size;
};
struct modem_pipe *modem_backend_tty_init(struct modem_backend_tty *backend,
const struct modem_backend_tty_config *config);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_BACKEND_TTY_ */

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/device.h>
#include <zephyr/drivers/uart.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/modem/pipe.h>
#ifndef ZEPHYR_MODEM_BACKEND_UART_
#define ZEPHYR_MODEM_BACKEND_UART_
#ifdef __cplusplus
extern "C" {
#endif
struct modem_backend_uart_isr {
struct ring_buf receive_rdb[2];
struct ring_buf transmit_rb;
atomic_t transmit_buf_len;
uint8_t receive_rdb_used;
uint32_t transmit_buf_put_limit;
};
struct modem_backend_uart_async {
uint8_t *receive_bufs[2];
uint32_t receive_buf_size;
struct ring_buf receive_rdb[2];
uint8_t *transmit_buf;
uint32_t transmit_buf_size;
atomic_t state;
};
struct modem_backend_uart {
const struct device *uart;
struct modem_pipe pipe;
struct k_work receive_ready_work;
union {
struct modem_backend_uart_isr isr;
struct modem_backend_uart_async async;
};
};
struct modem_backend_uart_config {
const struct device *uart;
uint8_t *receive_buf;
uint32_t receive_buf_size;
uint8_t *transmit_buf;
uint32_t transmit_buf_size;
};
struct modem_pipe *modem_backend_uart_init(struct modem_backend_uart *backend,
const struct modem_backend_uart_config *config);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_BACKEND_UART_ */

310
include/zephyr/modem/chat.h Normal file
View file

@ -0,0 +1,310 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/device.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/modem/pipe.h>
#ifndef ZEPHYR_MODEM_CHAT_
#define ZEPHYR_MODEM_CHAT_
#ifdef __cplusplus
extern "C" {
#endif
struct modem_chat;
/**
* @brief Callback called when matching chat is received
*
* @param chat Pointer to chat instance instance
* @param argv Pointer to array of parsed arguments
* @param argc Number of parsed arguments, arg 0 holds the exact match
* @param user_data Free to use user data set during modem_chat_init()
*/
typedef void (*modem_chat_match_callback)(struct modem_chat *chat, char **argv, uint16_t argc,
void *user_data);
/**
* @brief Modem chat match
*/
struct modem_chat_match {
/* Match array */
const uint8_t *match;
const uint8_t match_size;
/* Separators array */
const uint8_t *separators;
const uint8_t separators_size;
/* Set if modem chat instance shall use wildcards when matching */
const bool wildcards;
/* Type of modem chat instance */
const modem_chat_match_callback callback;
};
#define MODEM_CHAT_MATCH(_match, _separators, _callback) \
{ \
.match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1), \
.separators = (uint8_t *)(_separators), \
.separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = false, \
.callback = _callback, \
}
#define MODEM_CHAT_MATCH_WILDCARD(_match, _separators, _callback) \
{ \
.match = (uint8_t *)(_match), .match_size = (uint8_t)(sizeof(_match) - 1), \
.separators = (uint8_t *)(_separators), \
.separators_size = (uint8_t)(sizeof(_separators) - 1), .wildcards = true, \
.callback = _callback, \
}
#define MODEM_CHAT_MATCH_DEFINE(_sym, _match, _separators, _callback) \
const static struct modem_chat_match _sym = MODEM_CHAT_MATCH(_match, _separators, _callback)
#define MODEM_CHAT_MATCHES_DEFINE(_sym, ...) \
const static struct modem_chat_match _sym[] = {__VA_ARGS__}
/**
* @brief Modem chat script chat
*/
struct modem_chat_script_chat {
/** Request to send to modem formatted as char string */
const char *request;
/** Expected responses to request */
const struct modem_chat_match *const response_matches;
/** Number of elements in expected responses */
const uint16_t response_matches_size;
/** Timeout before chat script may continue to next step in milliseconds */
uint16_t timeout;
};
#define MODEM_CHAT_SCRIPT_CMD_RESP(_request, _response_match) \
{ \
.request = _request, .response_matches = &_response_match, \
.response_matches_size = 1, .timeout = 0, \
}
#define MODEM_CHAT_SCRIPT_CMD_RESP_MULT(_request, _response_matches) \
{ \
.request = _request, .response_matches = _response_matches, \
.response_matches_size = ARRAY_SIZE(_response_matches), .timeout = 0, \
}
#define MODEM_CHAT_SCRIPT_CMD_RESP_NONE(_request, _timeout) \
{ \
.request = _request, .response_matches = NULL, .response_matches_size = 0, \
.timeout = _timeout, \
}
#define MODEM_CHAT_SCRIPT_CMDS_DEFINE(_sym, ...) \
const static struct modem_chat_script_chat _sym[] = {__VA_ARGS__}
enum modem_chat_script_result {
MODEM_CHAT_SCRIPT_RESULT_SUCCESS,
MODEM_CHAT_SCRIPT_RESULT_ABORT,
MODEM_CHAT_SCRIPT_RESULT_TIMEOUT
};
/**
* @brief Callback called when script chat is received
*
* @param chat Pointer to chat instance instance
* @param result Result of script execution
* @param user_data Free to use user data set during modem_chat_init()
*/
typedef void (*modem_chat_script_callback)(struct modem_chat *chat,
enum modem_chat_script_result result, void *user_data);
/**
* @brief Modem chat script
*/
struct modem_chat_script {
/** Name of script */
const char *name;
/** Array of script chats */
const struct modem_chat_script_chat *script_chats;
/** Elements in array of script chats */
const uint16_t script_chats_size;
/** Array of abort matches */
const struct modem_chat_match *const abort_matches;
/** Number of elements in array of abort matches */
const uint16_t abort_matches_size;
/** Callback called when script execution terminates */
modem_chat_script_callback callback;
/** Timeout in seconds within which the script execution must terminate */
const uint32_t timeout;
};
#define MODEM_CHAT_SCRIPT_DEFINE(_sym, _script_chats, _abort_matches, _callback, _timeout) \
static struct modem_chat_script _sym = { \
.name = #_sym, \
.script_chats = _script_chats, \
.script_chats_size = ARRAY_SIZE(_script_chats), \
.abort_matches = _abort_matches, \
.abort_matches_size = ARRAY_SIZE(_abort_matches), \
.callback = _callback, \
.timeout = _timeout, \
}
enum modem_chat_script_send_state {
/* No data to send */
MODEM_CHAT_SCRIPT_SEND_STATE_IDLE,
/* Sending request */
MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST,
/* Sending delimiter */
MODEM_CHAT_SCRIPT_SEND_STATE_DELIMITER,
};
/**
* @brief Chat instance internal context
* @warning Do not modify any members of this struct directly
*/
struct modem_chat {
/* Pipe used to send and receive data */
struct modem_pipe *pipe;
/* User data passed with match callbacks */
void *user_data;
/* Receive buffer */
uint8_t *receive_buf;
uint16_t receive_buf_size;
uint16_t receive_buf_len;
/* Work buffer */
uint8_t work_buf[32];
uint16_t work_buf_len;
/* Chat delimiter */
uint8_t *delimiter;
uint16_t delimiter_size;
uint16_t delimiter_match_len;
/* Array of bytes which are discarded out by parser */
uint8_t *filter;
uint16_t filter_size;
/* Parsed arguments */
uint8_t **argv;
uint16_t argv_size;
uint16_t argc;
/* Matches
* Index 0 -> Response matches
* Index 1 -> Abort matches
* Index 2 -> Unsolicited matches
*/
const struct modem_chat_match *matches[3];
uint16_t matches_size[3];
/* Script execution */
const struct modem_chat_script *script;
const struct modem_chat_script *pending_script;
struct k_work script_run_work;
struct k_work_delayable script_timeout_work;
struct k_work script_abort_work;
uint16_t script_chat_it;
atomic_t script_state;
/* Script sending */
uint16_t script_send_request_pos;
uint16_t script_send_delimiter_pos;
struct k_work_delayable script_send_work;
struct k_work_delayable script_send_timeout_work;
/* Match parsing */
const struct modem_chat_match *parse_match;
uint16_t parse_match_len;
uint16_t parse_arg_len;
uint16_t parse_match_type;
/* Process received data */
struct k_work_delayable process_work;
k_timeout_t process_timeout;
};
/**
* @brief Chat configuration
*/
struct modem_chat_config {
/** Free to use user data passed with modem match callbacks */
void *user_data;
/** Receive buffer used to store parsed arguments */
uint8_t *receive_buf;
/** Size of receive buffer should be longest line + longest match */
uint16_t receive_buf_size;
/** Delimiter */
uint8_t *delimiter;
/** Size of delimiter */
uint8_t delimiter_size;
/** Bytes which are discarded by parser */
uint8_t *filter;
/** Size of filter */
uint8_t filter_size;
/** Array of pointers used to point to parsed arguments */
uint8_t **argv;
/** Elements in array of pointers */
uint16_t argv_size;
/** Array of unsolicited matches */
const struct modem_chat_match *unsol_matches;
/** Elements in array of unsolicited matches */
uint16_t unsol_matches_size;
/** Delay from receive ready event to pipe receive occurs */
k_timeout_t process_timeout;
};
/**
* @brief Initialize modem pipe chat instance
* @param chat Chat instance
* @param config Configuration which shall be applied to Chat instance
* @note Chat instance must be attached to pipe
*/
int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config);
/**
* @brief Attach modem chat instance to pipe
* @param chat Chat instance
* @param pipe Pipe instance to attach Chat instance to
* @returns 0 if successful
* @returns negative errno code if failure
* @note Chat instance is enabled if successful
*/
int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe);
/**
* @brief Run script
* @param chat Chat instance
* @param script Script to run
* @returns 0 if successful
* @returns -EBUSY if a script is currently running
* @returns -EPERM if modem pipe is not attached
* @returns -EINVAL if arguments or script is invalid
* @note Script runs asynchronously until complete or aborted.
*/
int modem_chat_script_run(struct modem_chat *chat, const struct modem_chat_script *script);
/**
* @brief Abort script
* @param chat Chat instance
*/
void modem_chat_script_abort(struct modem_chat *chat);
/**
* @brief Release pipe from chat instance
* @param chat Chat instance
*/
void modem_chat_release(struct modem_chat *chat);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_CHAT_ */

267
include/zephyr/modem/cmux.h Normal file
View file

@ -0,0 +1,267 @@
/*
* 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>
#ifndef ZEPHYR_MODEM_CMUX_
#define ZEPHYR_MODEM_CMUX_
#ifdef __cplusplus
extern "C" {
#endif
struct modem_cmux;
enum modem_cmux_state {
MODEM_CMUX_STATE_DISCONNECTED = 0,
MODEM_CMUX_STATE_CONNECTING,
MODEM_CMUX_STATE_CONNECTED,
MODEM_CMUX_STATE_DISCONNECTING,
};
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);
enum modem_cmux_receive_state {
MODEM_CMUX_RECEIVE_STATE_SOF = 0,
MODEM_CMUX_RECEIVE_STATE_RESYNC_0,
MODEM_CMUX_RECEIVE_STATE_RESYNC_1,
MODEM_CMUX_RECEIVE_STATE_RESYNC_2,
MODEM_CMUX_RECEIVE_STATE_RESYNC_3,
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,
};
enum modem_cmux_dlci_event {
MODEM_CMUX_DLCI_EVENT_OPENED,
MODEM_CMUX_DLCI_EVENT_CLOSED,
};
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;
};
struct modem_cmux_frame {
uint16_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;
/* 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;
/* 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;
};
/**
* @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 Initialize CMUX instance
*
* @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 When disconnected, the bus pipe can be used directly again
*/
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 When disconnected, the bus pipe can be used directly again
*/
int modem_cmux_disconnect_async(struct modem_cmux *cmux);
void modem_cmux_release(struct modem_cmux *cmux);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_CMUX_ */

170
include/zephyr/modem/pipe.h Normal file
View file

@ -0,0 +1,170 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/types.h>
#include <zephyr/kernel.h>
#ifndef ZEPHYR_MODEM_PIPE_
#define ZEPHYR_MODEM_PIPE_
#ifdef __cplusplus
extern "C" {
#endif
struct modem_pipe;
typedef int (*modem_pipe_api_open)(void *data);
typedef int (*modem_pipe_api_transmit)(void *data, const uint8_t *buf, size_t size);
typedef int (*modem_pipe_api_receive)(void *data, uint8_t *buf, size_t size);
typedef int (*modem_pipe_api_close)(void *data);
struct modem_pipe_api {
modem_pipe_api_open open;
modem_pipe_api_transmit transmit;
modem_pipe_api_receive receive;
modem_pipe_api_close close;
};
enum modem_pipe_state {
MODEM_PIPE_STATE_CLOSED = 0,
MODEM_PIPE_STATE_OPEN,
};
enum modem_pipe_event {
MODEM_PIPE_EVENT_OPENED = 0,
MODEM_PIPE_EVENT_RECEIVE_READY,
MODEM_PIPE_EVENT_CLOSED,
};
typedef void (*modem_pipe_api_callback)(struct modem_pipe *pipe, enum modem_pipe_event event,
void *user_data);
struct modem_pipe {
void *data;
struct modem_pipe_api *api;
modem_pipe_api_callback callback;
void *user_data;
enum modem_pipe_state state;
struct k_mutex lock;
struct k_condvar condvar;
};
/**
* @brief Initialize a modem pipe
*
* @param pipe Pipe instance to initialize
* @param data Pipe data to bind to pipe instance
* @param api Pipe API implementation to bind to pipe instance
*/
void modem_pipe_init(struct modem_pipe *pipe, void *data, struct modem_pipe_api *api);
/**
* @brief Open pipe
*
* @param pipe Pipe instance
*/
int modem_pipe_open(struct modem_pipe *pipe);
/**
* @brief Open pipe asynchronously
*
* @param pipe Pipe instance
*/
int modem_pipe_open_async(struct modem_pipe *pipe);
/**
* @brief Attach pipe to callback
*
* @param pipe Pipe instance
* @param callback Callback called when pipe event occurs
* @param user_data Free to use user data passed with callback
*/
void modem_pipe_attach(struct modem_pipe *pipe, modem_pipe_api_callback callback, void *user_data);
/**
* @brief Transmit data through pipe
*
* @param pipe Pipe to transmit through
* @param buf Destination for reveived data
* @param size Capacity of destination for recevied data
*
* @return Number of bytes placed in pipe
*
* @warning This call must be non-blocking
*/
int modem_pipe_transmit(struct modem_pipe *pipe, const uint8_t *buf, size_t size);
/**
* @brief Reveive data through pipe
*
* @param pipe Pipe to receive from
* @param buf Destination for reveived data
* @param size Capacity of destination for recevied data
*
* @return Number of bytes received from pipe if any
* @return -EPERM if pipe is closed
* @return -errno code on error
*
* @warning This call must be non-blocking
*/
int modem_pipe_receive(struct modem_pipe *pipe, uint8_t *buf, size_t size);
/**
* @brief Clear callback
*
* @param pipe Pipe instance
*/
void modem_pipe_release(struct modem_pipe *pipe);
/**
* @brief Close pipe
*
* @param pipe Pipe instance
*/
int modem_pipe_close(struct modem_pipe *pipe);
/**
* @brief Close pipe asynchronously
*
* @param pipe Pipe instance
*/
int modem_pipe_close_async(struct modem_pipe *pipe);
/**
* @brief Notify user of pipe that it has opened
*
* @param pipe Pipe instance
*
* @note Invoked from instance which initialized the pipe instance
*/
void modem_pipe_notify_opened(struct modem_pipe *pipe);
/**
* @brief Notify user of pipe that it has closed
*
* @param pipe Pipe instance
*
* @note Invoked from instance which initialized the pipe instance
*/
void modem_pipe_notify_closed(struct modem_pipe *pipe);
/**
* @brief Notify user of pipe that data is ready to be received
*
* @param pipe Pipe instance
*
* @note Invoked from instance which initialized the pipe instance
*/
void modem_pipe_notify_receive_ready(struct modem_pipe *pipe);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_PIPE_ */

167
include/zephyr/modem/ppp.h Normal file
View file

@ -0,0 +1,167 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/types.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_pkt.h>
#include <zephyr/sys/ring_buffer.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/modem/pipe.h>
#ifndef ZEPHYR_MODEM_PPP_
#define ZEPHYR_MODEM_PPP_
#ifdef __cplusplus
extern "C" {
#endif
enum modem_ppp_receive_state {
/* Searching for start of frame and header */
MODEM_PPP_RECEIVE_STATE_HDR_SOF = 0,
MODEM_PPP_RECEIVE_STATE_HDR_FF,
MODEM_PPP_RECEIVE_STATE_HDR_7D,
MODEM_PPP_RECEIVE_STATE_HDR_23,
/* Writing bytes to network packet */
MODEM_PPP_RECEIVE_STATE_WRITING,
/* Unescaping next byte before writing to network packet */
MODEM_PPP_RECEIVE_STATE_UNESCAPING,
};
enum modem_ppp_transmit_state {
/* Idle */
MODEM_PPP_TRANSMIT_STATE_IDLE = 0,
/* Writing header */
MODEM_PPP_TRANSMIT_STATE_SOF,
MODEM_PPP_TRANSMIT_STATE_HDR_FF,
MODEM_PPP_TRANSMIT_STATE_HDR_7D,
MODEM_PPP_TRANSMIT_STATE_HDR_23,
/* Writing protocol */
MODEM_PPP_TRANSMIT_STATE_PROTOCOL_HIGH,
MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_HIGH,
MODEM_PPP_TRANSMIT_STATE_PROTOCOL_LOW,
MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_LOW,
/* Writing data */
MODEM_PPP_TRANSMIT_STATE_DATA,
MODEM_PPP_TRANSMIT_STATE_ESCAPING_DATA,
/* Writing FCS */
MODEM_PPP_TRANSMIT_STATE_FCS_LOW,
MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_LOW,
MODEM_PPP_TRANSMIT_STATE_FCS_HIGH,
MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_HIGH,
/* Writing end of frame */
MODEM_PPP_TRANSMIT_STATE_EOF,
};
typedef void (*modem_ppp_init_iface)(struct net_if *iface);
struct modem_ppp {
/* Network interface instance is bound to */
struct net_if *iface;
/* Hook for PPP L2 network interface initialization */
modem_ppp_init_iface init_iface;
atomic_t state;
/* Buffers used for processing partial frames */
uint8_t *receive_buf;
uint8_t *transmit_buf;
uint16_t buf_size;
/* Wrapped PPP frames are sent and received through this pipe */
struct modem_pipe *pipe;
/* Receive PPP frame state */
enum modem_ppp_receive_state receive_state;
/* Allocated network packet being created */
struct net_pkt *rx_pkt;
/* Packet being sent */
enum modem_ppp_transmit_state transmit_state;
struct net_pkt *tx_pkt;
uint8_t tx_pkt_escaped;
uint16_t tx_pkt_protocol;
uint16_t tx_pkt_fcs;
/* Ring buffer used for transmitting partial PPP frame */
struct ring_buf transmit_rb;
struct k_fifo tx_pkt_fifo;
/* Work */
struct k_work send_work;
struct k_work process_work;
};
/**
* @brief Attach pipe to instance and connect
*
* @param ppp Modem PPP instance
* @param pipe Pipe to attach to modem PPP instance
*/
int modem_ppp_attach(struct modem_ppp *ppp, struct modem_pipe *pipe);
/**
* @brief Get network interface modem PPP instance is bound to
*
* @param ppp Modem PPP instance
* @returns Pointer to network interface modem PPP instance is bound to
*/
struct net_if *modem_ppp_get_iface(struct modem_ppp *ppp);
/**
* @brief Release pipe from instance
*
* @param ppp Modem PPP instance
*/
void modem_ppp_release(struct modem_ppp *ppp);
/**
* @brief Initialize modem PPP instance device
* @param dev Device instance associated with network interface
* @warning Should not be used directly
*/
int modem_ppp_init_internal(const struct device *dev);
/**
* @brief Define a modem PPP module and bind it to a network interface
*
* @details This macro defines the modem_ppp instance, initializes a PPP L2
* network device instance, and binds the modem_ppp instance to the PPP L2
* instance.
*
* @param _name Name of the statically defined modem_ppp instance
* @param _init_iface Hook for the PPP L2 network interface init function
* @param _prio Initialization priority of the PPP L2 net iface
* @param _mtu Max size of net_pkt data sent and received on PPP L2 net iface
* @param _buf_size Size of partial PPP frame transmit and receive buffers
*/
#define MODEM_PPP_DEFINE(_name, _init_iface, _prio, _mtu, _buf_size) \
extern const struct ppp_api modem_ppp_ppp_api; \
\
static uint8_t _CONCAT(_name, _receive_buf)[_buf_size]; \
static uint8_t _CONCAT(_name, _transmit_buf)[_buf_size]; \
\
static struct modem_ppp _name = { \
.init_iface = _init_iface, \
.receive_buf = _CONCAT(_name, _receive_buf), \
.transmit_buf = _CONCAT(_name, _transmit_buf), \
.buf_size = _buf_size, \
}; \
\
NET_DEVICE_INIT(_CONCAT(ppp_net_dev_, _name), "modem_ppp_" # _name, \
modem_ppp_init_internal, NULL, &_name, NULL, _prio, &modem_ppp_ppp_api, \
PPP_L2, NET_L2_GET_CTX_TYPE(PPP_L2), _mtu)
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_PPP_ */

View file

@ -40,6 +40,7 @@ add_subdirectory_ifdef(CONFIG_EMUL emul)
add_subdirectory_ifdef(CONFIG_IMG_MANAGER dfu) add_subdirectory_ifdef(CONFIG_IMG_MANAGER dfu)
add_subdirectory_ifdef(CONFIG_INPUT input) add_subdirectory_ifdef(CONFIG_INPUT input)
add_subdirectory_ifdef(CONFIG_JWT jwt) add_subdirectory_ifdef(CONFIG_JWT jwt)
add_subdirectory_ifdef(CONFIG_MODEM_MODULES modem)
add_subdirectory_ifdef(CONFIG_NET_BUF net) add_subdirectory_ifdef(CONFIG_NET_BUF net)
add_subdirectory_ifdef(CONFIG_RETENTION retention) add_subdirectory_ifdef(CONFIG_RETENTION retention)
add_subdirectory_ifdef(CONFIG_SENSING sensing) add_subdirectory_ifdef(CONFIG_SENSING sensing)

View file

@ -24,6 +24,7 @@ source "subsys/logging/Kconfig"
source "subsys/lorawan/Kconfig" source "subsys/lorawan/Kconfig"
source "subsys/mgmt/Kconfig" source "subsys/mgmt/Kconfig"
source "subsys/modbus/Kconfig" source "subsys/modbus/Kconfig"
source "subsys/modem/Kconfig"
source "subsys/net/Kconfig" source "subsys/net/Kconfig"
source "subsys/pm/Kconfig" source "subsys/pm/Kconfig"
source "subsys/portability/Kconfig" source "subsys/portability/Kconfig"

View file

@ -0,0 +1,15 @@
# Copyright (c) 2023 Trackunit Corporation
# SPDX-License-Identifier: Apache-2.0
if(CONFIG_MODEM_MODULES)
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_MODEM_CHAT modem_chat.c)
zephyr_library_sources_ifdef(CONFIG_MODEM_CMUX modem_cmux.c)
zephyr_library_sources_ifdef(CONFIG_MODEM_PIPE modem_pipe.c)
zephyr_library_sources_ifdef(CONFIG_MODEM_PPP modem_ppp.c)
add_subdirectory(backends)
endif()

54
subsys/modem/Kconfig Normal file
View file

@ -0,0 +1,54 @@
# Copyright (c) 2023 Trackunit Corporation
# SPDX-License-Identifier: Apache-2.0
menuconfig MODEM_MODULES
bool "Modem modules"
if MODEM_MODULES
config MODEM_CHAT
bool "Modem chat module"
select RING_BUFFER
select MODEM_PIPE
if MODEM_CHAT
config MODEM_CHAT_LOG_BUFFER
int "Modem chat log buffer size"
default 128
endif
config MODEM_CMUX
bool "Modem CMUX module"
select MODEM_PIPE
select RING_BUFFER
select EVENTS
select CRC
config MODEM_PIPE
bool "Modem pipe module"
config MODEM_PPP
bool "Modem PPP module"
depends on NET_L2_PPP
select MODEM_PIPE
select RING_BUFFER
select CRC
if MODEM_PPP
config MODEM_PPP_NET_BUF_FRAG_SIZE
int "Network buffer fragment size"
default NET_BUF_DATA_SIZE if NET_BUF_FIXED_DATA_SIZE
default 128
endif
module = MODEM_MODULES
module-str = modem_modules
source "subsys/logging/Kconfig.template.log_config"
rsource "backends/Kconfig"
endif

View file

@ -0,0 +1,9 @@
# Copyright (c) 2023 Trackunit Corporation
# SPDX-License-Identifier: Apache-2.0
zephyr_library()
zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_TTY modem_backend_tty.c)
zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART modem_backend_uart.c)
zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART_ISR modem_backend_uart_isr.c)
zephyr_library_sources_ifdef(CONFIG_MODEM_BACKEND_UART_ASYNC modem_backend_uart_async.c)

View file

@ -0,0 +1,24 @@
# Copyright (c) 2023 Trackunit Corporation
# SPDX-License-Identifier: Apache-2.0
config MODEM_BACKEND_TTY
bool "Modem TTY backend module"
select MODEM_PIPE
depends on ARCH_POSIX
config MODEM_BACKEND_UART
bool "Modem UART backend module"
select MODEM_PIPE
depends on UART_INTERRUPT_DRIVEN || UART_ASYNC_API
if MODEM_BACKEND_UART
config MODEM_BACKEND_UART_ISR
bool "Modem UART backend module interrupt driven implementation"
default y if UART_INTERRUPT_DRIVEN
config MODEM_BACKEND_UART_ASYNC
bool "Modem UART backend module async implementation"
default y if UART_ASYNC_API
endif # MODEM_BACKEND_UART

View file

@ -0,0 +1,124 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/modem/backend/tty.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_backend_tty);
#include <fcntl.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>
#define MODEM_BACKEND_TTY_THREAD_PRIO (10)
#define MODEM_BACKEND_TTY_THREAD_RUN_PERIOD_MS (1000)
#define MODEM_BACKEND_TTY_THREAD_POLL_DELAY (100)
#define MODEM_BACKEND_TTY_STATE_RUN_BIT (1)
static void modem_backend_tty_routine(void *p1, void *p2, void *p3)
{
struct modem_backend_tty *backend = (struct modem_backend_tty *)p1;
struct pollfd pd;
ARG_UNUSED(p2);
ARG_UNUSED(p3);
pd.fd = backend->tty_fd;
pd.events = POLLIN;
/* Run until run flag is cleared. Check every MODEM_BACKEND_TTY_THREAD_RUN_PERIOD_MS */
while (atomic_test_bit(&backend->state, MODEM_BACKEND_TTY_STATE_RUN_BIT)) {
/* Clear events */
pd.revents = 0;
if (poll(&pd, 1, MODEM_BACKEND_TTY_THREAD_RUN_PERIOD_MS) < 0) {
LOG_ERR("Poll operation failed");
break;
}
if (pd.revents & POLLIN) {
modem_pipe_notify_receive_ready(&backend->pipe);
}
k_sleep(K_MSEC(MODEM_BACKEND_TTY_THREAD_POLL_DELAY));
}
}
static int modem_backend_tty_open(void *data)
{
struct modem_backend_tty *backend = (struct modem_backend_tty *)data;
if (atomic_test_and_set_bit(&backend->state, MODEM_BACKEND_TTY_STATE_RUN_BIT)) {
return -EALREADY;
}
backend->tty_fd = open(backend->tty_path, (O_RDWR | O_NONBLOCK), 0644);
if (backend->tty_fd < 0) {
return -EPERM;
}
k_thread_create(&backend->thread, backend->stack, backend->stack_size,
modem_backend_tty_routine, backend, NULL, NULL,
MODEM_BACKEND_TTY_THREAD_PRIO, 0, K_NO_WAIT);
modem_pipe_notify_opened(&backend->pipe);
return 0;
}
static int modem_backend_tty_transmit(void *data, const uint8_t *buf, size_t size)
{
struct modem_backend_tty *backend = (struct modem_backend_tty *)data;
return write(backend->tty_fd, buf, size);
}
static int modem_backend_tty_receive(void *data, uint8_t *buf, size_t size)
{
int ret;
struct modem_backend_tty *backend = (struct modem_backend_tty *)data;
ret = read(backend->tty_fd, buf, size);
return (ret < 0) ? 0 : ret;
}
static int modem_backend_tty_close(void *data)
{
struct modem_backend_tty *backend = (struct modem_backend_tty *)data;
if (!atomic_test_and_clear_bit(&backend->state, MODEM_BACKEND_TTY_STATE_RUN_BIT)) {
return -EALREADY;
}
k_thread_join(&backend->thread, K_MSEC(MODEM_BACKEND_TTY_THREAD_RUN_PERIOD_MS * 2));
close(backend->tty_fd);
modem_pipe_notify_closed(&backend->pipe);
return 0;
}
struct modem_pipe_api modem_backend_tty_api = {
.open = modem_backend_tty_open,
.transmit = modem_backend_tty_transmit,
.receive = modem_backend_tty_receive,
.close = modem_backend_tty_close,
};
struct modem_pipe *modem_backend_tty_init(struct modem_backend_tty *backend,
const struct modem_backend_tty_config *config)
{
__ASSERT_NO_MSG(backend != NULL);
__ASSERT_NO_MSG(config != NULL);
__ASSERT_NO_MSG(config->tty_path != NULL);
memset(backend, 0x00, sizeof(*backend));
backend->tty_path = config->tty_path;
backend->stack = config->stack;
backend->stack_size = config->stack_size;
atomic_set(&backend->state, 0);
modem_pipe_init(&backend->pipe, backend, &modem_backend_tty_api);
return &backend->pipe;
}

View file

@ -0,0 +1,55 @@
/*
* Copyright (c) 2023 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "modem_backend_uart_isr.h"
#include "modem_backend_uart_async.h"
#include <zephyr/modem/backend/uart.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_backend_uart);
#include <string.h>
static void modem_backend_uart_receive_ready_handler(struct k_work *item)
{
struct modem_backend_uart *backend =
CONTAINER_OF(item, struct modem_backend_uart, receive_ready_work);
modem_pipe_notify_receive_ready(&backend->pipe);
}
struct modem_pipe *modem_backend_uart_init(struct modem_backend_uart *backend,
const struct modem_backend_uart_config *config)
{
__ASSERT_NO_MSG(config->uart != NULL);
__ASSERT_NO_MSG(config->receive_buf != NULL);
__ASSERT_NO_MSG(config->receive_buf_size > 1);
__ASSERT_NO_MSG((config->receive_buf_size % 2) == 0);
__ASSERT_NO_MSG(config->transmit_buf != NULL);
__ASSERT_NO_MSG(config->transmit_buf_size > 0);
memset(backend, 0x00, sizeof(*backend));
backend->uart = config->uart;
k_work_init(&backend->receive_ready_work, modem_backend_uart_receive_ready_handler);
#ifdef CONFIG_UART_ASYNC_API
if (modem_backend_uart_async_is_supported(backend)) {
modem_backend_uart_async_init(backend, config);
return &backend->pipe;
}
#endif /* CONFIG_UART_ASYNC_API */
#ifdef CONFIG_UART_INTERRUPT_DRIVEN
modem_backend_uart_isr_init(backend, config);
return &backend->pipe;
#endif /* CONFIG_UART_INTERRUPT_DRIVEN */
__ASSERT(0, "No supported UART API");
return NULL;
}

View file

@ -0,0 +1,260 @@
/*
* Copyright (c) 2023 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "modem_backend_uart_async.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(modem_backend_uart);
#include <zephyr/kernel.h>
#include <string.h>
#define MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT (0)
#define MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT (1)
#define MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF1_USED_BIT (2)
#define MODEM_BACKEND_UART_ASYNC_STATE_RX_RBUF_USED_INDEX_BIT (3)
#define MODEM_BACKEND_UART_ASYNC_BLOCK_MIN_SIZE (8)
static void modem_backend_uart_async_flush(struct modem_backend_uart *backend)
{
uint8_t c;
while (uart_fifo_read(backend->uart, &c, 1) > 0) {
continue;
}
}
static uint8_t modem_backend_uart_async_rx_rbuf_used_index(struct modem_backend_uart *backend)
{
return atomic_test_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_RBUF_USED_INDEX_BIT);
}
static void modem_backend_uart_async_rx_rbuf_used_swap(struct modem_backend_uart *backend)
{
uint8_t rx_rbuf_index = modem_backend_uart_async_rx_rbuf_used_index(backend);
if (rx_rbuf_index) {
atomic_clear_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_RBUF_USED_INDEX_BIT);
} else {
atomic_set_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_RBUF_USED_INDEX_BIT);
}
}
static void modem_backend_uart_async_event_handler(const struct device *dev,
struct uart_event *evt, void *user_data)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *) user_data;
uint8_t receive_rb_used_index;
uint32_t received;
switch (evt->type) {
case UART_TX_DONE:
atomic_clear_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT);
break;
case UART_RX_BUF_REQUEST:
if (!atomic_test_and_set_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT)) {
uart_rx_buf_rsp(backend->uart, backend->async.receive_bufs[0],
backend->async.receive_buf_size);
break;
}
if (!atomic_test_and_set_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF1_USED_BIT)) {
uart_rx_buf_rsp(backend->uart, backend->async.receive_bufs[1],
backend->async.receive_buf_size);
break;
}
LOG_WRN("No receive buffer available");
break;
case UART_RX_BUF_RELEASED:
if (evt->data.rx_buf.buf == backend->async.receive_bufs[0]) {
atomic_clear_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT);
break;
}
if (evt->data.rx_buf.buf == backend->async.receive_bufs[1]) {
atomic_clear_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF1_USED_BIT);
break;
}
LOG_WRN("Unknown receive buffer released");
break;
case UART_RX_RDY:
receive_rb_used_index = modem_backend_uart_async_rx_rbuf_used_index(backend);
received = ring_buf_put(&backend->async.receive_rdb[receive_rb_used_index],
&evt->data.rx.buf[evt->data.rx.offset],
evt->data.rx.len);
if (received < evt->data.rx.len) {
ring_buf_reset(&backend->async.receive_rdb[receive_rb_used_index]);
LOG_WRN("Receive buffer overrun");
break;
}
k_work_submit(&backend->receive_ready_work);
break;
case UART_TX_ABORTED:
LOG_WRN("Transmit aborted");
default:
break;
}
}
static int modem_backend_uart_async_open(void *data)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
int ret;
atomic_set(&backend->async.state, 0);
modem_backend_uart_async_flush(backend);
ring_buf_reset(&backend->async.receive_rdb[0]);
ring_buf_reset(&backend->async.receive_rdb[1]);
/* Reserve receive buffer 0 */
atomic_set_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT);
/*
* Receive buffer 0 is used internally by UART, receive ring buffer 0 is
* used to store received data.
*/
ret = uart_rx_enable(backend->uart, backend->async.receive_bufs[0],
backend->async.receive_buf_size, 3000);
if (ret < 0) {
return ret;
}
modem_pipe_notify_opened(&backend->pipe);
return 0;
}
static int modem_backend_uart_async_transmit(void *data, const uint8_t *buf, size_t size)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
bool transmitting;
uint32_t bytes_to_transmit;
int ret;
transmitting = atomic_test_and_set_bit(&backend->async.state,
MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT);
if (transmitting) {
return 0;
}
/* Determine amount of bytes to transmit */
bytes_to_transmit = (size < backend->async.transmit_buf_size)
? size
: backend->async.transmit_buf_size;
/* Copy buf to transmit buffer which is passed to UART */
memcpy(backend->async.transmit_buf, buf, bytes_to_transmit);
ret = uart_tx(backend->uart, backend->async.transmit_buf, bytes_to_transmit,
SYS_FOREVER_US);
if (ret < 0) {
LOG_WRN("Failed to start async transmit");
return ret;
}
return (int)bytes_to_transmit;
}
static int modem_backend_uart_async_receive(void *data, uint8_t *buf, size_t size)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
uint32_t received;
uint8_t receive_rdb_unused;
received = 0;
receive_rdb_unused = modem_backend_uart_async_rx_rbuf_used_index(backend) ? 0 : 1;
/* Read data from unused ring double buffer first */
received += ring_buf_get(&backend->async.receive_rdb[receive_rdb_unused], buf, size);
if (ring_buf_is_empty(&backend->async.receive_rdb[receive_rdb_unused]) == false) {
return (int)received;
}
/* Swap receive ring double buffer */
modem_backend_uart_async_rx_rbuf_used_swap(backend);
/* Read data from previously used buffer */
receive_rdb_unused = modem_backend_uart_async_rx_rbuf_used_index(backend) ? 0 : 1;
received += ring_buf_get(&backend->async.receive_rdb[receive_rdb_unused],
&buf[received], (size - received));
return (int)received;
}
static int modem_backend_uart_async_close(void *data)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
uart_rx_disable(backend->uart);
modem_pipe_notify_closed(&backend->pipe);
return 0;
}
struct modem_pipe_api modem_backend_uart_async_api = {
.open = modem_backend_uart_async_open,
.transmit = modem_backend_uart_async_transmit,
.receive = modem_backend_uart_async_receive,
.close = modem_backend_uart_async_close,
};
bool modem_backend_uart_async_is_supported(struct modem_backend_uart *backend)
{
return uart_callback_set(backend->uart, modem_backend_uart_async_event_handler,
backend) == 0;
}
void modem_backend_uart_async_init(struct modem_backend_uart *backend,
const struct modem_backend_uart_config *config)
{
uint32_t receive_buf_size_quarter = config->receive_buf_size / 4;
/* Split receive buffer into 4 buffers, use 2 parts for UART receive double buffer */
backend->async.receive_buf_size = receive_buf_size_quarter;
backend->async.receive_bufs[0] = &config->receive_buf[0];
backend->async.receive_bufs[1] = &config->receive_buf[receive_buf_size_quarter];
/* Use remaining 2 parts for receive double ring buffer */
ring_buf_init(&backend->async.receive_rdb[0], receive_buf_size_quarter,
&config->receive_buf[receive_buf_size_quarter * 2]);
ring_buf_init(&backend->async.receive_rdb[1], receive_buf_size_quarter,
&config->receive_buf[receive_buf_size_quarter * 3]);
backend->async.transmit_buf = config->transmit_buf;
backend->async.transmit_buf_size = config->transmit_buf_size;
modem_pipe_init(&backend->pipe, backend, &modem_backend_uart_async_api);
}

View file

@ -0,0 +1,25 @@
/*
* Copyright (c) 2023 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/modem/backend/uart.h>
#ifndef ZEPHYR_MODEM_BACKEND_UART_ASYNC_
#define ZEPHYR_MODEM_BACKEND_UART_ASYNC_
#ifdef __cplusplus
extern "C" {
#endif
bool modem_backend_uart_async_is_supported(struct modem_backend_uart *backend);
void modem_backend_uart_async_init(struct modem_backend_uart *backend,
const struct modem_backend_uart_config *config);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_BACKEND_UART_ASYNC_ */

View file

@ -0,0 +1,203 @@
/*
* Copyright (c) 2023 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include "modem_backend_uart_isr.h"
#include <zephyr/logging/log.h>
LOG_MODULE_DECLARE(modem_backend_uart);
#include <string.h>
static void modem_backend_uart_isr_flush(struct modem_backend_uart *backend)
{
uint8_t c;
while (uart_fifo_read(backend->uart, &c, 1) > 0) {
continue;
}
}
static void modem_backend_uart_isr_irq_handler_receive_ready(struct modem_backend_uart *backend)
{
uint32_t size;
struct ring_buf *receive_rb;
uint8_t *buffer;
int ret;
receive_rb = &backend->isr.receive_rdb[backend->isr.receive_rdb_used];
size = ring_buf_put_claim(receive_rb, &buffer, UINT32_MAX);
if (size == 0) {
LOG_WRN("Receive buffer overrun");
ring_buf_put_finish(receive_rb, 0);
ring_buf_reset(receive_rb);
size = ring_buf_put_claim(receive_rb, &buffer, UINT32_MAX);
}
ret = uart_fifo_read(backend->uart, buffer, size);
if (ret < 0) {
ring_buf_put_finish(receive_rb, 0);
} else {
ring_buf_put_finish(receive_rb, (uint32_t)ret);
}
if (ret > 0) {
k_work_submit(&backend->receive_ready_work);
}
}
static void modem_backend_uart_isr_irq_handler_transmit_ready(struct modem_backend_uart *backend)
{
uint32_t size;
uint8_t *buffer;
int ret;
if (ring_buf_is_empty(&backend->isr.transmit_rb) == true) {
uart_irq_tx_disable(backend->uart);
return;
}
size = ring_buf_get_claim(&backend->isr.transmit_rb, &buffer, UINT32_MAX);
ret = uart_fifo_fill(backend->uart, buffer, size);
if (ret < 0) {
ring_buf_get_finish(&backend->isr.transmit_rb, 0);
} else {
ring_buf_get_finish(&backend->isr.transmit_rb, (uint32_t)ret);
/* Update transmit buf capacity tracker */
atomic_sub(&backend->isr.transmit_buf_len, (uint32_t)ret);
}
}
static void modem_backend_uart_isr_irq_handler(const struct device *uart, void *user_data)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)user_data;
if (uart_irq_update(uart) < 1) {
return;
}
if (uart_irq_rx_ready(uart)) {
modem_backend_uart_isr_irq_handler_receive_ready(backend);
}
if (uart_irq_tx_ready(uart)) {
modem_backend_uart_isr_irq_handler_transmit_ready(backend);
}
}
static int modem_backend_uart_isr_open(void *data)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
ring_buf_reset(&backend->isr.receive_rdb[0]);
ring_buf_reset(&backend->isr.receive_rdb[1]);
ring_buf_reset(&backend->isr.transmit_rb);
atomic_set(&backend->isr.transmit_buf_len, 0);
modem_backend_uart_isr_flush(backend);
uart_irq_rx_enable(backend->uart);
uart_irq_tx_enable(backend->uart);
modem_pipe_notify_opened(&backend->pipe);
return 0;
}
static bool modem_backend_uart_isr_transmit_buf_above_limit(struct modem_backend_uart *backend)
{
return backend->isr.transmit_buf_put_limit < atomic_get(&backend->isr.transmit_buf_len);
}
static int modem_backend_uart_isr_transmit(void *data, const uint8_t *buf, size_t size)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
int written;
if (modem_backend_uart_isr_transmit_buf_above_limit(backend) == true) {
return 0;
}
uart_irq_tx_disable(backend->uart);
written = ring_buf_put(&backend->isr.transmit_rb, buf, size);
uart_irq_tx_enable(backend->uart);
/* Update transmit buf capacity tracker */
atomic_add(&backend->isr.transmit_buf_len, written);
return written;
}
static int modem_backend_uart_isr_receive(void *data, uint8_t *buf, size_t size)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
uint32_t read_bytes;
uint8_t receive_rdb_unused;
read_bytes = 0;
receive_rdb_unused = (backend->isr.receive_rdb_used == 1) ? 0 : 1;
/* Read data from unused ring double buffer first */
read_bytes += ring_buf_get(&backend->isr.receive_rdb[receive_rdb_unused], buf, size);
if (ring_buf_is_empty(&backend->isr.receive_rdb[receive_rdb_unused]) == false) {
return (int)read_bytes;
}
/* Swap receive ring double buffer */
uart_irq_rx_disable(backend->uart);
backend->isr.receive_rdb_used = receive_rdb_unused;
uart_irq_rx_enable(backend->uart);
/* Read data from previously used buffer */
receive_rdb_unused = (backend->isr.receive_rdb_used == 1) ? 0 : 1;
read_bytes += ring_buf_get(&backend->isr.receive_rdb[receive_rdb_unused],
&buf[read_bytes], (size - read_bytes));
return (int)read_bytes;
}
static int modem_backend_uart_isr_close(void *data)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
uart_irq_rx_disable(backend->uart);
uart_irq_tx_disable(backend->uart);
modem_pipe_notify_closed(&backend->pipe);
return 0;
}
struct modem_pipe_api modem_backend_uart_isr_api = {
.open = modem_backend_uart_isr_open,
.transmit = modem_backend_uart_isr_transmit,
.receive = modem_backend_uart_isr_receive,
.close = modem_backend_uart_isr_close,
};
void modem_backend_uart_isr_init(struct modem_backend_uart *backend,
const struct modem_backend_uart_config *config)
{
uint32_t receive_double_buf_size;
backend->isr.transmit_buf_put_limit =
config->transmit_buf_size - (config->transmit_buf_size / 4);
receive_double_buf_size = config->receive_buf_size / 2;
ring_buf_init(&backend->isr.receive_rdb[0], receive_double_buf_size,
&config->receive_buf[0]);
ring_buf_init(&backend->isr.receive_rdb[1], receive_double_buf_size,
&config->receive_buf[receive_double_buf_size]);
ring_buf_init(&backend->isr.transmit_rb, config->transmit_buf_size,
config->transmit_buf);
atomic_set(&backend->isr.transmit_buf_len, 0);
uart_irq_rx_disable(backend->uart);
uart_irq_tx_disable(backend->uart);
uart_irq_callback_user_data_set(backend->uart, modem_backend_uart_isr_irq_handler,
backend);
modem_pipe_init(&backend->pipe, backend, &modem_backend_uart_isr_api);
}

View file

@ -0,0 +1,23 @@
/*
* Copyright (c) 2023 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/modem/backend/uart.h>
#ifndef ZEPHYR_MODEM_BACKEND_UART_ISR_
#define ZEPHYR_MODEM_BACKEND_UART_ISR_
#ifdef __cplusplus
extern "C" {
#endif
void modem_backend_uart_isr_init(struct modem_backend_uart *backend,
const struct modem_backend_uart_config *config);
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_MODEM_BACKEND_UART_ISR_ */

788
subsys/modem/modem_chat.c Normal file
View file

@ -0,0 +1,788 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_chat, CONFIG_MODEM_MODULES_LOG_LEVEL);
#include <zephyr/kernel.h>
#include <string.h>
#include <zephyr/modem/chat.h>
#define MODEM_CHAT_MATCHES_INDEX_RESPONSE (0)
#define MODEM_CHAT_MATCHES_INDEX_ABORT (1)
#define MODEM_CHAT_MATCHES_INDEX_UNSOL (2)
#define MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT (0)
#if defined(CONFIG_LOG) && (CONFIG_MODEM_MODULES_LOG_LEVEL == LOG_LEVEL_DBG)
static char log_buffer[CONFIG_MODEM_CHAT_LOG_BUFFER];
static void modem_chat_log_received_command(struct modem_chat *chat)
{
uint16_t log_buffer_pos = 0;
uint16_t argv_len;
for (uint16_t i = 0; i < chat->argc; i++) {
argv_len = (uint16_t)strlen(chat->argv[i]);
/* Validate argument fits in log buffer including termination */
if (sizeof(log_buffer) < (log_buffer_pos + argv_len + 1)) {
LOG_WRN("log buffer overrun");
break;
}
/* Copy argument and append space */
memcpy(&log_buffer[log_buffer_pos], chat->argv[i], argv_len);
log_buffer_pos += argv_len;
log_buffer[log_buffer_pos] = ' ';
log_buffer_pos++;
}
/* Terminate line after last argument, overwriting trailing space */
log_buffer_pos = log_buffer_pos == 0 ? log_buffer_pos : log_buffer_pos - 1;
log_buffer[log_buffer_pos] = '\0';
LOG_DBG("%s", log_buffer);
}
#else
static void modem_chat_log_received_command(struct modem_chat *chat)
{
}
#endif
static void modem_chat_script_stop(struct modem_chat *chat, enum modem_chat_script_result result)
{
/* Handle result */
if (result == MODEM_CHAT_SCRIPT_RESULT_SUCCESS) {
LOG_DBG("%s: complete", chat->script->name);
} else if (result == MODEM_CHAT_SCRIPT_RESULT_ABORT) {
LOG_WRN("%s: aborted", chat->script->name);
} else {
LOG_WRN("%s: timed out", chat->script->name);
}
/* Clear script running state */
atomic_clear_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT);
/* Call back with result */
if (chat->script->callback != NULL) {
chat->script->callback(chat, result, chat->user_data);
}
/* Clear reference to script */
chat->script = NULL;
/* Clear response and abort commands */
chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = NULL;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = 0;
chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = NULL;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = 0;
/* Cancel timeout work */
k_work_cancel_delayable(&chat->script_timeout_work);
}
static void modem_chat_script_send(struct modem_chat *chat)
{
/* Initialize script send work */
chat->script_send_request_pos = 0;
chat->script_send_delimiter_pos = 0;
/* Schedule script send work */
k_work_schedule(&chat->script_send_work, K_NO_WAIT);
}
static void modem_chat_script_next(struct modem_chat *chat, bool initial)
{
const struct modem_chat_script_chat *script_chat;
/* Advance iterator if not initial */
if (initial == true) {
/* Reset iterator */
chat->script_chat_it = 0;
} else {
/* Advance iterator */
chat->script_chat_it++;
}
/* Check if end of script reached */
if (chat->script_chat_it == chat->script->script_chats_size) {
modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_SUCCESS);
return;
}
LOG_DBG("%s: step: %u", chat->script->name, chat->script_chat_it);
script_chat = &chat->script->script_chats[chat->script_chat_it];
/* Set response command handlers */
chat->matches[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_RESPONSE] = script_chat->response_matches_size;
/* Check if work must be sent */
if (strlen(script_chat->request) > 0) {
LOG_DBG("sending: %s", script_chat->request);
modem_chat_script_send(chat);
}
}
static void modem_chat_script_start(struct modem_chat *chat, const struct modem_chat_script *script)
{
/* Save script */
chat->script = script;
/* Set abort matches */
chat->matches[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_ABORT] = script->abort_matches_size;
LOG_DBG("running script: %s", chat->script->name);
/* Set first script command */
modem_chat_script_next(chat, true);
/* Start timeout work if script started */
if (chat->script != NULL) {
k_work_schedule(&chat->script_timeout_work, K_SECONDS(chat->script->timeout));
}
}
static void modem_chat_script_run_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_run_work);
/* Start script */
modem_chat_script_start(chat, chat->pending_script);
}
static void modem_chat_script_timeout_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_timeout_work);
/* Abort script */
modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_TIMEOUT);
}
static void modem_chat_script_abort_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_abort_work);
/* Validate script is currently running */
if (chat->script == NULL) {
return;
}
/* Abort script */
modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT);
}
static bool modem_chat_script_send_request(struct modem_chat *chat)
{
const struct modem_chat_script_chat *script_chat =
&chat->script->script_chats[chat->script_chat_it];
uint16_t script_chat_request_size = strlen(script_chat->request);
uint8_t *script_chat_request_start;
uint16_t script_chat_request_remaining;
int ret;
/* Validate data to send */
if (script_chat_request_size == chat->script_send_request_pos) {
return true;
}
script_chat_request_start = (uint8_t *)&script_chat->request[chat->script_send_request_pos];
script_chat_request_remaining = script_chat_request_size - chat->script_send_request_pos;
/* Send data through pipe */
ret = modem_pipe_transmit(chat->pipe, script_chat_request_start,
script_chat_request_remaining);
/* Validate transmit successful */
if (ret < 1) {
return false;
}
/* Update script send position */
chat->script_send_request_pos += (uint16_t)ret;
/* Check if data remains */
if (chat->script_send_request_pos < script_chat_request_size) {
return false;
}
return true;
}
static bool modem_chat_script_send_delimiter(struct modem_chat *chat)
{
uint8_t *script_chat_delimiter_start;
uint8_t script_chat_delimiter_remaining;
int ret;
/* Validate data to send */
if (chat->delimiter_size == chat->script_send_delimiter_pos) {
return true;
}
script_chat_delimiter_start = (uint8_t *)&chat->delimiter[chat->script_send_delimiter_pos];
script_chat_delimiter_remaining = chat->delimiter_size - chat->script_send_delimiter_pos;
/* Send data through pipe */
ret = modem_pipe_transmit(chat->pipe, script_chat_delimiter_start,
script_chat_delimiter_remaining);
/* Validate transmit successful */
if (ret < 1) {
return false;
}
/* Update script send position */
chat->script_send_delimiter_pos += (uint8_t)ret;
/* Check if data remains */
if (chat->script_send_delimiter_pos < chat->delimiter_size) {
return false;
}
return true;
}
static bool modem_chat_script_chat_is_no_response(struct modem_chat *chat)
{
const struct modem_chat_script_chat *script_chat =
&chat->script->script_chats[chat->script_chat_it];
return (script_chat->response_matches_size == 0) ? true : false;
}
static uint16_t modem_chat_script_chat_get_send_timeout(struct modem_chat *chat)
{
const struct modem_chat_script_chat *script_chat =
&chat->script->script_chats[chat->script_chat_it];
return script_chat->timeout;
}
static void modem_chat_script_send_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_send_work);
uint16_t timeout;
/* Validate script running */
if (chat->script == NULL) {
return;
}
/* Send request */
if (modem_chat_script_send_request(chat) == false) {
k_work_schedule(&chat->script_send_work, chat->process_timeout);
return;
}
/* Send delimiter */
if (modem_chat_script_send_delimiter(chat) == false) {
k_work_schedule(&chat->script_send_work, chat->process_timeout);
return;
}
/* Check if script command is no response */
if (modem_chat_script_chat_is_no_response(chat)) {
timeout = modem_chat_script_chat_get_send_timeout(chat);
if (timeout == 0) {
modem_chat_script_next(chat, false);
} else {
k_work_schedule(&chat->script_send_timeout_work, K_MSEC(timeout));
}
}
}
static void modem_chat_script_send_timeout_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, script_send_timeout_work);
/* Validate script is currently running */
if (chat->script == NULL) {
return;
}
modem_chat_script_next(chat, false);
}
static void modem_chat_parse_reset(struct modem_chat *chat)
{
/* Reset parameters used for parsing */
chat->receive_buf_len = 0;
chat->delimiter_match_len = 0;
chat->argc = 0;
chat->parse_match = NULL;
}
/* Exact match is stored at end of receive buffer */
static void modem_chat_parse_save_match(struct modem_chat *chat)
{
uint8_t *argv;
/* Store length of match including NULL to avoid overwriting it if buffer overruns */
chat->parse_match_len = chat->receive_buf_len + 1;
/* Copy match to end of receive buffer */
argv = &chat->receive_buf[chat->receive_buf_size - chat->parse_match_len];
/* Copy match to end of receive buffer (excluding NULL) */
memcpy(argv, &chat->receive_buf[0], chat->parse_match_len - 1);
/* Save match */
chat->argv[chat->argc] = argv;
/* Terminate match */
chat->receive_buf[chat->receive_buf_size - 1] = '\0';
/* Increment argument count */
chat->argc++;
}
static bool modem_chat_match_matches_received(struct modem_chat *chat,
const struct modem_chat_match *match)
{
for (uint16_t i = 0; i < match->match_size; i++) {
if ((match->match[i] == chat->receive_buf[i]) ||
(match->wildcards == true && match->match[i] == '?')) {
continue;
}
return false;
}
return true;
}
static bool modem_chat_parse_find_match(struct modem_chat *chat)
{
/* Find in all matches types */
for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) {
/* Find in all matches of matches type */
for (uint16_t u = 0; u < chat->matches_size[i]; u++) {
/* Validate match size matches received data length */
if (chat->matches[i][u].match_size != chat->receive_buf_len) {
continue;
}
/* Validate match */
if (modem_chat_match_matches_received(chat, &chat->matches[i][u]) ==
false) {
continue;
}
/* Complete match found */
chat->parse_match = &chat->matches[i][u];
chat->parse_match_type = i;
return true;
}
}
return false;
}
static bool modem_chat_parse_is_separator(struct modem_chat *chat)
{
for (uint16_t i = 0; i < chat->parse_match->separators_size; i++) {
if ((chat->parse_match->separators[i]) ==
(chat->receive_buf[chat->receive_buf_len - 1])) {
return true;
}
}
return false;
}
static bool modem_chat_parse_end_del_start(struct modem_chat *chat)
{
for (uint8_t i = 0; i < chat->delimiter_size; i++) {
if (chat->receive_buf[chat->receive_buf_len - 1] == chat->delimiter[i]) {
return true;
}
}
return false;
}
static bool modem_chat_parse_end_del_complete(struct modem_chat *chat)
{
/* Validate length of end delimiter */
if (chat->receive_buf_len < chat->delimiter_size) {
return false;
}
/* Compare end delimiter with receive buffer content */
return (memcmp(&chat->receive_buf[chat->receive_buf_len - chat->delimiter_size],
chat->delimiter, chat->delimiter_size) == 0)
? true
: false;
}
static void modem_chat_on_command_received_unsol(struct modem_chat *chat)
{
/* Callback */
if (chat->parse_match->callback != NULL) {
chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
}
}
static void modem_chat_on_command_received_abort(struct modem_chat *chat)
{
/* Callback */
if (chat->parse_match->callback != NULL) {
chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
}
/* Abort script */
modem_chat_script_stop(chat, MODEM_CHAT_SCRIPT_RESULT_ABORT);
}
static void modem_chat_on_command_received_resp(struct modem_chat *chat)
{
/* Callback */
if (chat->parse_match->callback != NULL) {
chat->parse_match->callback(chat, (char **)chat->argv, chat->argc, chat->user_data);
}
/* Advance script */
modem_chat_script_next(chat, false);
}
static bool modem_chat_parse_find_catch_all_match(struct modem_chat *chat)
{
/* Find in all matches types */
for (uint16_t i = 0; i < ARRAY_SIZE(chat->matches); i++) {
/* Find in all matches of matches type */
for (uint16_t u = 0; u < chat->matches_size[i]; u++) {
/* Validate match config is matching previous bytes */
if (chat->matches[i][u].match_size == 0) {
chat->parse_match = &chat->matches[i][u];
chat->parse_match_type = i;
return true;
}
}
}
return false;
}
static void modem_chat_on_command_received(struct modem_chat *chat)
{
modem_chat_log_received_command(chat);
switch (chat->parse_match_type) {
case MODEM_CHAT_MATCHES_INDEX_UNSOL:
modem_chat_on_command_received_unsol(chat);
break;
case MODEM_CHAT_MATCHES_INDEX_ABORT:
modem_chat_on_command_received_abort(chat);
break;
case MODEM_CHAT_MATCHES_INDEX_RESPONSE:
modem_chat_on_command_received_resp(chat);
break;
}
}
static void modem_chat_on_unknown_command_received(struct modem_chat *chat)
{
/* Terminate received command */
chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0';
/* Try to find catch all match */
if (modem_chat_parse_find_catch_all_match(chat) == false) {
LOG_DBG("%s", chat->receive_buf);
return;
}
/* Parse command */
chat->argv[0] = "";
chat->argv[1] = chat->receive_buf;
chat->argc = 2;
modem_chat_on_command_received(chat);
}
static void modem_chat_process_byte(struct modem_chat *chat, uint8_t byte)
{
/* Validate receive buffer not overrun */
if (chat->receive_buf_size == chat->receive_buf_len) {
LOG_WRN("receive buffer overrun");
modem_chat_parse_reset(chat);
return;
}
/* Validate argv buffer not overrun */
if (chat->argc == chat->argv_size) {
LOG_WRN("argv buffer overrun");
modem_chat_parse_reset(chat);
return;
}
/* Copy byte to receive buffer */
chat->receive_buf[chat->receive_buf_len] = byte;
chat->receive_buf_len++;
/* Validate end delimiter not complete */
if (modem_chat_parse_end_del_complete(chat) == true) {
/* Filter out empty lines */
if (chat->receive_buf_len == chat->delimiter_size) {
/* Reset parser */
modem_chat_parse_reset(chat);
return;
}
/* Check if match exists */
if (chat->parse_match == NULL) {
/* Handle unknown command */
modem_chat_on_unknown_command_received(chat);
/* Reset parser */
modem_chat_parse_reset(chat);
return;
}
/* Check if trailing argument exists */
if (chat->parse_arg_len > 0) {
chat->argv[chat->argc] =
&chat->receive_buf[chat->receive_buf_len - chat->delimiter_size -
chat->parse_arg_len];
chat->receive_buf[chat->receive_buf_len - chat->delimiter_size] = '\0';
chat->argc++;
}
/* Handle received command */
modem_chat_on_command_received(chat);
/* Reset parser */
modem_chat_parse_reset(chat);
return;
}
/* Validate end delimiter not started */
if (modem_chat_parse_end_del_start(chat) == true) {
return;
}
/* Find matching command if missing */
if (chat->parse_match == NULL) {
/* Find matching command */
if (modem_chat_parse_find_match(chat) == false) {
return;
}
/* Save match */
modem_chat_parse_save_match(chat);
/* Prepare argument parser */
chat->parse_arg_len = 0;
return;
}
/* Check if separator reached */
if (modem_chat_parse_is_separator(chat) == true) {
/* Check if argument is empty */
if (chat->parse_arg_len == 0) {
/* Save empty argument */
chat->argv[chat->argc] = "";
} else {
/* Save pointer to start of argument */
chat->argv[chat->argc] =
&chat->receive_buf[chat->receive_buf_len - chat->parse_arg_len - 1];
/* Replace separator with string terminator */
chat->receive_buf[chat->receive_buf_len - 1] = '\0';
}
/* Increment argument count */
chat->argc++;
/* Reset parse argument length */
chat->parse_arg_len = 0;
return;
}
/* Increment argument length */
chat->parse_arg_len++;
}
static bool modem_chat_discard_byte(struct modem_chat *chat, uint8_t byte)
{
for (uint8_t i = 0; i < chat->filter_size; i++) {
if (byte == chat->filter[i]) {
return true;
}
}
return false;
}
/* Process chunk of received bytes */
static void modem_chat_process_bytes(struct modem_chat *chat)
{
for (uint16_t i = 0; i < chat->work_buf_len; i++) {
if (modem_chat_discard_byte(chat, chat->work_buf[i])) {
continue;
}
modem_chat_process_byte(chat, chat->work_buf[i]);
}
}
static void modem_chat_process_handler(struct k_work *item)
{
struct modem_chat *chat = CONTAINER_OF(item, struct modem_chat, process_work);
int ret;
/* Fill work buffer */
ret = modem_pipe_receive(chat->pipe, chat->work_buf, sizeof(chat->work_buf));
if (ret < 1) {
return;
}
/* Save received data length */
chat->work_buf_len = (size_t)ret;
/* Process data */
modem_chat_process_bytes(chat);
k_work_schedule(&chat->process_work, K_NO_WAIT);
}
static void modem_chat_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
void *user_data)
{
struct modem_chat *chat = (struct modem_chat *)user_data;
if (event == MODEM_PIPE_EVENT_RECEIVE_READY) {
k_work_schedule(&chat->process_work, chat->process_timeout);
}
}
/*********************************************************
* GLOBAL FUNCTIONS
*********************************************************/
int modem_chat_init(struct modem_chat *chat, const struct modem_chat_config *config)
{
__ASSERT_NO_MSG(chat != NULL);
__ASSERT_NO_MSG(config != NULL);
__ASSERT_NO_MSG(config->receive_buf != NULL);
__ASSERT_NO_MSG(config->receive_buf_size > 0);
__ASSERT_NO_MSG(config->argv != NULL);
__ASSERT_NO_MSG(config->argv_size > 0);
__ASSERT_NO_MSG(config->delimiter != NULL);
__ASSERT_NO_MSG(config->delimiter_size > 0);
__ASSERT_NO_MSG(!((config->filter == NULL) && (config->filter > 0)));
__ASSERT_NO_MSG(!((config->unsol_matches == NULL) && (config->unsol_matches_size > 0)));
memset(chat, 0x00, sizeof(*chat));
chat->pipe = NULL;
chat->user_data = config->user_data;
chat->receive_buf = config->receive_buf;
chat->receive_buf_size = config->receive_buf_size;
chat->argv = config->argv;
chat->argv_size = config->argv_size;
chat->delimiter = config->delimiter;
chat->delimiter_size = config->delimiter_size;
chat->filter = config->filter;
chat->filter_size = config->filter_size;
chat->matches[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches;
chat->matches_size[MODEM_CHAT_MATCHES_INDEX_UNSOL] = config->unsol_matches_size;
chat->process_timeout = config->process_timeout;
atomic_set(&chat->script_state, 0);
k_work_init_delayable(&chat->process_work, modem_chat_process_handler);
k_work_init(&chat->script_run_work, modem_chat_script_run_handler);
k_work_init_delayable(&chat->script_timeout_work, modem_chat_script_timeout_handler);
k_work_init(&chat->script_abort_work, modem_chat_script_abort_handler);
k_work_init_delayable(&chat->script_send_work, modem_chat_script_send_handler);
k_work_init_delayable(&chat->script_send_timeout_work,
modem_chat_script_send_timeout_handler);
return 0;
}
int modem_chat_attach(struct modem_chat *chat, struct modem_pipe *pipe)
{
chat->pipe = pipe;
modem_chat_parse_reset(chat);
modem_pipe_attach(chat->pipe, modem_chat_pipe_callback, chat);
return 0;
}
int modem_chat_script_run(struct modem_chat *chat, const struct modem_chat_script *script)
{
bool script_is_running;
if (chat->pipe == NULL) {
return -EPERM;
}
/* Validate script */
if ((script->script_chats == NULL) || (script->script_chats_size == 0) ||
((script->abort_matches != NULL) && (script->abort_matches_size == 0))) {
return -EINVAL;
}
/* Validate script commands */
for (uint16_t i = 0; i < script->script_chats_size; i++) {
if ((strlen(script->script_chats[i].request) == 0) &&
(script->script_chats[i].response_matches_size == 0)) {
return -EINVAL;
}
}
script_is_running =
atomic_test_and_set_bit(&chat->script_state, MODEM_CHAT_SCRIPT_STATE_RUNNING_BIT);
if (script_is_running == true) {
return -EBUSY;
}
chat->pending_script = script;
k_work_submit(&chat->script_run_work);
return 0;
}
void modem_chat_script_abort(struct modem_chat *chat)
{
k_work_submit(&chat->script_abort_work);
}
void modem_chat_release(struct modem_chat *chat)
{
struct k_work_sync sync;
if (chat->pipe) {
modem_pipe_release(chat->pipe);
}
k_work_cancel_sync(&chat->script_run_work, &sync);
k_work_cancel_sync(&chat->script_abort_work, &sync);
k_work_cancel_delayable_sync(&chat->process_work, &sync);
k_work_cancel_delayable_sync(&chat->script_send_work, &sync);
chat->pipe = NULL;
chat->receive_buf_len = 0;
chat->work_buf_len = 0;
chat->argc = 0;
chat->script = NULL;
chat->script_chat_it = 0;
atomic_set(&chat->script_state, 0);
chat->script_send_request_pos = 0;
chat->script_send_delimiter_pos = 0;
chat->parse_match = NULL;
chat->parse_match_len = 0;
chat->parse_arg_len = 0;
}

1016
subsys/modem/modem_cmux.c Normal file

File diff suppressed because it is too large Load diff

176
subsys/modem/modem_pipe.c Normal file
View file

@ -0,0 +1,176 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/modem/pipe.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_pipe, CONFIG_MODEM_MODULES_LOG_LEVEL);
void modem_pipe_init(struct modem_pipe *pipe, void *data, struct modem_pipe_api *api)
{
__ASSERT_NO_MSG(pipe != NULL);
__ASSERT_NO_MSG(data != NULL);
__ASSERT_NO_MSG(api != NULL);
pipe->data = data;
pipe->api = api;
pipe->callback = NULL;
pipe->user_data = NULL;
pipe->state = MODEM_PIPE_STATE_CLOSED;
k_mutex_init(&pipe->lock);
k_condvar_init(&pipe->condvar);
}
int modem_pipe_open(struct modem_pipe *pipe)
{
int ret;
k_mutex_lock(&pipe->lock, K_FOREVER);
ret = pipe->api->open(pipe->data);
if (ret < 0) {
k_mutex_unlock(&pipe->lock);
return ret;
}
if (pipe->state == MODEM_PIPE_STATE_OPEN) {
k_mutex_unlock(&pipe->lock);
return 0;
}
k_condvar_wait(&pipe->condvar, &pipe->lock, K_MSEC(10000));
ret = (pipe->state == MODEM_PIPE_STATE_OPEN) ? 0 : -EAGAIN;
k_mutex_unlock(&pipe->lock);
return ret;
}
int modem_pipe_open_async(struct modem_pipe *pipe)
{
int ret;
k_mutex_lock(&pipe->lock, K_FOREVER);
ret = pipe->api->open(pipe->data);
k_mutex_unlock(&pipe->lock);
return ret;
}
void modem_pipe_attach(struct modem_pipe *pipe, modem_pipe_api_callback callback, void *user_data)
{
k_mutex_lock(&pipe->lock, K_FOREVER);
pipe->callback = callback;
pipe->user_data = user_data;
k_mutex_unlock(&pipe->lock);
}
int modem_pipe_transmit(struct modem_pipe *pipe, const uint8_t *buf, size_t size)
{
int ret;
k_mutex_lock(&pipe->lock, K_FOREVER);
if (pipe->state == MODEM_PIPE_STATE_CLOSED) {
k_mutex_unlock(&pipe->lock);
return -EPERM;
}
ret = pipe->api->transmit(pipe->data, buf, size);
k_mutex_unlock(&pipe->lock);
return ret;
}
int modem_pipe_receive(struct modem_pipe *pipe, uint8_t *buf, size_t size)
{
int ret;
k_mutex_lock(&pipe->lock, K_FOREVER);
if (pipe->state == MODEM_PIPE_STATE_CLOSED) {
k_mutex_unlock(&pipe->lock);
return -EPERM;
}
ret = pipe->api->receive(pipe->data, buf, size);
k_mutex_unlock(&pipe->lock);
return ret;
}
void modem_pipe_release(struct modem_pipe *pipe)
{
k_mutex_lock(&pipe->lock, K_FOREVER);
pipe->callback = NULL;
pipe->user_data = NULL;
k_mutex_unlock(&pipe->lock);
}
int modem_pipe_close(struct modem_pipe *pipe)
{
int ret;
k_mutex_lock(&pipe->lock, K_FOREVER);
ret = pipe->api->close(pipe->data);
if (ret < 0) {
k_mutex_unlock(&pipe->lock);
return ret;
}
if (pipe->state == MODEM_PIPE_STATE_CLOSED) {
k_mutex_unlock(&pipe->lock);
return 0;
}
k_condvar_wait(&pipe->condvar, &pipe->lock, K_MSEC(10000));
ret = (pipe->state == MODEM_PIPE_STATE_CLOSED) ? 0 : -EAGAIN;
k_mutex_unlock(&pipe->lock);
return ret;
}
int modem_pipe_close_async(struct modem_pipe *pipe)
{
int ret;
k_mutex_lock(&pipe->lock, K_FOREVER);
ret = pipe->api->close(pipe->data);
k_mutex_unlock(&pipe->lock);
return ret;
}
void modem_pipe_notify_opened(struct modem_pipe *pipe)
{
k_mutex_lock(&pipe->lock, K_FOREVER);
pipe->state = MODEM_PIPE_STATE_OPEN;
if (pipe->callback != NULL) {
pipe->callback(pipe, MODEM_PIPE_EVENT_OPENED, pipe->user_data);
}
k_condvar_signal(&pipe->condvar);
k_mutex_unlock(&pipe->lock);
}
void modem_pipe_notify_closed(struct modem_pipe *pipe)
{
k_mutex_lock(&pipe->lock, K_FOREVER);
pipe->state = MODEM_PIPE_STATE_CLOSED;
if (pipe->callback != NULL) {
pipe->callback(pipe, MODEM_PIPE_EVENT_CLOSED, pipe->user_data);
}
k_condvar_signal(&pipe->condvar);
k_mutex_unlock(&pipe->lock);
}
void modem_pipe_notify_receive_ready(struct modem_pipe *pipe)
{
k_mutex_lock(&pipe->lock, K_FOREVER);
if (pipe->callback != NULL) {
pipe->callback(pipe, MODEM_PIPE_EVENT_RECEIVE_READY, pipe->user_data);
}
k_mutex_unlock(&pipe->lock);
}

507
subsys/modem/modem_ppp.c Normal file
View file

@ -0,0 +1,507 @@
/*
* Copyright (c) 2022 Trackunit Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/net/ppp.h>
#include <zephyr/sys/crc.h>
#include <zephyr/modem/ppp.h>
#include <string.h>
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(modem_ppp, CONFIG_MODEM_MODULES_LOG_LEVEL);
#define MODEM_PPP_STATE_ATTACHED_BIT (0)
#define MODEM_PPP_FRAME_TAIL_SIZE (2)
#define MODEM_PPP_CODE_DELIMITER (0x7E)
#define MODEM_PPP_CODE_ESCAPE (0x7D)
#define MODEM_PPP_VALUE_ESCAPE (0x20)
static uint16_t modem_ppp_fcs_init(uint8_t byte)
{
return crc16_ccitt(0xFFFF, &byte, 1);
}
static uint16_t modem_ppp_fcs_update(uint16_t fcs, uint8_t byte)
{
return crc16_ccitt(fcs, &byte, 1);
}
static uint16_t modem_ppp_fcs_final(uint16_t fcs)
{
return fcs ^ 0xFFFF;
}
static uint16_t modem_ppp_ppp_protocol(struct net_pkt *pkt)
{
if (net_pkt_family(pkt) == AF_INET) {
return PPP_IP;
}
if (net_pkt_family(pkt) == AF_INET6) {
return PPP_IPV6;
}
LOG_WRN("Unsupported protocol");
return 0;
}
static uint8_t modem_ppp_wrap_net_pkt_byte(struct modem_ppp *ppp)
{
uint8_t byte;
switch (ppp->transmit_state) {
case MODEM_PPP_TRANSMIT_STATE_IDLE:
LOG_WRN("Invalid transmit state");
return 0;
/* Writing header */
case MODEM_PPP_TRANSMIT_STATE_SOF:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_HDR_FF;
return MODEM_PPP_CODE_DELIMITER;
case MODEM_PPP_TRANSMIT_STATE_HDR_FF:
net_pkt_cursor_init(ppp->tx_pkt);
ppp->tx_pkt_fcs = modem_ppp_fcs_init(0xFF);
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_HDR_7D;
return 0xFF;
case MODEM_PPP_TRANSMIT_STATE_HDR_7D:
ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, 0x03);
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_HDR_23;
return MODEM_PPP_CODE_ESCAPE;
case MODEM_PPP_TRANSMIT_STATE_HDR_23:
if (net_pkt_is_ppp(ppp->tx_pkt) == true) {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA;
} else {
ppp->tx_pkt_protocol = modem_ppp_ppp_protocol(ppp->tx_pkt);
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_PROTOCOL_HIGH;
}
return 0x23;
/* Writing protocol */
case MODEM_PPP_TRANSMIT_STATE_PROTOCOL_HIGH:
byte = (ppp->tx_pkt_protocol >> 8) & 0xFF;
ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, byte);
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_HIGH;
return MODEM_PPP_CODE_ESCAPE;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_PROTOCOL_LOW;
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_HIGH:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_PROTOCOL_LOW;
return ppp->tx_pkt_escaped;
case MODEM_PPP_TRANSMIT_STATE_PROTOCOL_LOW:
byte = ppp->tx_pkt_protocol & 0xFF;
ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, byte);
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_LOW;
return MODEM_PPP_CODE_ESCAPE;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA;
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_PROTOCOL_LOW:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA;
return ppp->tx_pkt_escaped;
/* Writing data */
case MODEM_PPP_TRANSMIT_STATE_DATA:
net_pkt_read_u8(ppp->tx_pkt, &byte);
ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, byte);
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_DATA;
return MODEM_PPP_CODE_ESCAPE;
}
if (net_pkt_remaining_data(ppp->tx_pkt) == 0) {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_LOW;
}
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_DATA:
if (net_pkt_remaining_data(ppp->tx_pkt) == 0) {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_LOW;
} else {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA;
}
return ppp->tx_pkt_escaped;
/* Writing FCS */
case MODEM_PPP_TRANSMIT_STATE_FCS_LOW:
ppp->tx_pkt_fcs = modem_ppp_fcs_final(ppp->tx_pkt_fcs);
byte = ppp->tx_pkt_fcs & 0xFF;
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_LOW;
return MODEM_PPP_CODE_ESCAPE;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_HIGH;
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_LOW:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_HIGH;
return ppp->tx_pkt_escaped;
case MODEM_PPP_TRANSMIT_STATE_FCS_HIGH:
byte = (ppp->tx_pkt_fcs >> 8) & 0xFF;
if ((byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) ||
(byte < MODEM_PPP_VALUE_ESCAPE)) {
ppp->tx_pkt_escaped = byte ^ MODEM_PPP_VALUE_ESCAPE;
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_HIGH;
return MODEM_PPP_CODE_ESCAPE;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_EOF;
return byte;
case MODEM_PPP_TRANSMIT_STATE_ESCAPING_FCS_HIGH:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_EOF;
return ppp->tx_pkt_escaped;
/* Writing end of frame */
case MODEM_PPP_TRANSMIT_STATE_EOF:
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_IDLE;
return MODEM_PPP_CODE_DELIMITER;
}
return 0;
}
static void modem_ppp_process_received_byte(struct modem_ppp *ppp, uint8_t byte)
{
switch (ppp->receive_state) {
case MODEM_PPP_RECEIVE_STATE_HDR_SOF:
if (byte == MODEM_PPP_CODE_DELIMITER) {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_FF;
}
break;
case MODEM_PPP_RECEIVE_STATE_HDR_FF:
if (byte == MODEM_PPP_CODE_DELIMITER) {
break;
}
if (byte == 0xFF) {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_7D;
} else {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
}
break;
case MODEM_PPP_RECEIVE_STATE_HDR_7D:
if (byte == MODEM_PPP_CODE_ESCAPE) {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_23;
} else {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
}
break;
case MODEM_PPP_RECEIVE_STATE_HDR_23:
if (byte == 0x23) {
ppp->rx_pkt = net_pkt_rx_alloc_with_buffer(ppp->iface,
CONFIG_MODEM_PPP_NET_BUF_FRAG_SIZE, AF_UNSPEC, 0, K_NO_WAIT);
if (ppp->rx_pkt == NULL) {
LOG_WRN("Dropped frame, no net_pkt available");
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
break;
}
LOG_DBG("Receiving PPP frame");
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_WRITING;
net_pkt_cursor_init(ppp->rx_pkt);
} else {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
}
break;
case MODEM_PPP_RECEIVE_STATE_WRITING:
if (byte == MODEM_PPP_CODE_DELIMITER) {
LOG_DBG("Received PPP frame");
/* Remove FCS */
net_pkt_remove_tail(ppp->rx_pkt, MODEM_PPP_FRAME_TAIL_SIZE);
net_pkt_cursor_init(ppp->rx_pkt);
net_pkt_set_ppp(ppp->rx_pkt, true);
if (net_recv_data(ppp->iface, ppp->rx_pkt) < 0) {
LOG_WRN("Net pkt could not be processed");
net_pkt_unref(ppp->rx_pkt);
}
ppp->rx_pkt = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
break;
}
if (net_pkt_available_buffer(ppp->rx_pkt) == 1) {
if (net_pkt_alloc_buffer(ppp->rx_pkt, CONFIG_MODEM_PPP_NET_BUF_FRAG_SIZE,
AF_INET, K_NO_WAIT) < 0) {
LOG_WRN("Failed to alloc buffer");
net_pkt_unref(ppp->rx_pkt);
ppp->rx_pkt = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
break;
}
}
if (byte == MODEM_PPP_CODE_ESCAPE) {
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_UNESCAPING;
break;
}
if (net_pkt_write_u8(ppp->rx_pkt, byte) < 0) {
LOG_WRN("Dropped PPP frame");
net_pkt_unref(ppp->rx_pkt);
ppp->rx_pkt = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
}
break;
case MODEM_PPP_RECEIVE_STATE_UNESCAPING:
if (net_pkt_write_u8(ppp->rx_pkt, (byte ^ MODEM_PPP_VALUE_ESCAPE)) < 0) {
LOG_WRN("Dropped PPP frame");
net_pkt_unref(ppp->rx_pkt);
ppp->rx_pkt = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
break;
}
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_WRITING;
break;
}
}
static void modem_ppp_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
void *user_data)
{
struct modem_ppp *ppp = (struct modem_ppp *)user_data;
if (event == MODEM_PIPE_EVENT_RECEIVE_READY) {
k_work_submit(&ppp->process_work);
}
}
static void modem_ppp_send_handler(struct k_work *item)
{
struct modem_ppp *ppp = CONTAINER_OF(item, struct modem_ppp, send_work);
uint8_t byte;
uint8_t *reserved;
uint32_t reserved_size;
int ret;
if (ppp->tx_pkt == NULL) {
ppp->tx_pkt = k_fifo_get(&ppp->tx_pkt_fifo, K_NO_WAIT);
}
if (ppp->tx_pkt != NULL) {
/* Initialize wrap */
if (ppp->transmit_state == MODEM_PPP_TRANSMIT_STATE_IDLE) {
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_SOF;
}
/* Fill transmit ring buffer */
while (ring_buf_space_get(&ppp->transmit_rb) > 0) {
byte = modem_ppp_wrap_net_pkt_byte(ppp);
ring_buf_put(&ppp->transmit_rb, &byte, 1);
if (ppp->transmit_state == MODEM_PPP_TRANSMIT_STATE_IDLE) {
net_pkt_unref(ppp->tx_pkt);
ppp->tx_pkt = k_fifo_get(&ppp->tx_pkt_fifo, K_NO_WAIT);
break;
}
}
}
reserved_size = ring_buf_get_claim(&ppp->transmit_rb, &reserved, UINT32_MAX);
if (reserved_size == 0) {
ring_buf_get_finish(&ppp->transmit_rb, 0);
return;
}
ret = modem_pipe_transmit(ppp->pipe, reserved, reserved_size);
if (ret < 0) {
ring_buf_get_finish(&ppp->transmit_rb, 0);
} else {
ring_buf_get_finish(&ppp->transmit_rb, (uint32_t)ret);
}
/* Resubmit send work if data remains */
if ((ring_buf_is_empty(&ppp->transmit_rb) == false) || (ppp->tx_pkt != NULL)) {
k_work_submit(&ppp->send_work);
}
}
static void modem_ppp_process_handler(struct k_work *item)
{
struct modem_ppp *ppp = CONTAINER_OF(item, struct modem_ppp, process_work);
int ret;
ret = modem_pipe_receive(ppp->pipe, ppp->receive_buf, ppp->buf_size);
if (ret < 1) {
return;
}
for (int i = 0; i < ret; i++) {
modem_ppp_process_received_byte(ppp, ppp->receive_buf[i]);
}
k_work_submit(&ppp->process_work);
}
static void modem_ppp_ppp_api_init(struct net_if *iface)
{
const struct device *dev = net_if_get_device(iface);
struct modem_ppp *ppp = (struct modem_ppp *)dev->data;
net_ppp_init(iface);
net_if_flag_set(iface, NET_IF_NO_AUTO_START);
net_if_carrier_off(iface);
if (ppp->init_iface != NULL) {
ppp->init_iface(iface);
}
ppp->iface = iface;
}
static int modem_ppp_ppp_api_start(const struct device *dev)
{
return 0;
}
static int modem_ppp_ppp_api_stop(const struct device *dev)
{
return 0;
}
static int modem_ppp_ppp_api_send(const struct device *dev, struct net_pkt *pkt)
{
struct modem_ppp *ppp = (struct modem_ppp *)dev->data;
if (atomic_test_bit(&ppp->state, MODEM_PPP_STATE_ATTACHED_BIT) == false) {
return -EPERM;
}
/* Validate packet protocol */
if ((net_pkt_is_ppp(pkt) == false) && (net_pkt_family(pkt) != AF_INET) &&
(net_pkt_family(pkt) != AF_INET6)) {
return -EPROTONOSUPPORT;
}
/* Validate packet data length */
if (((net_pkt_get_len(pkt) < 2) && (net_pkt_is_ppp(pkt) == true)) ||
((net_pkt_get_len(pkt) < 1))) {
return -ENODATA;
}
net_pkt_ref(pkt);
k_fifo_put(&ppp->tx_pkt_fifo, pkt);
k_work_submit(&ppp->send_work);
return 0;
}
const struct ppp_api modem_ppp_ppp_api = {
.iface_api.init = modem_ppp_ppp_api_init,
.start = modem_ppp_ppp_api_start,
.stop = modem_ppp_ppp_api_stop,
.send = modem_ppp_ppp_api_send,
};
int modem_ppp_attach(struct modem_ppp *ppp, struct modem_pipe *pipe)
{
if (atomic_test_and_set_bit(&ppp->state, MODEM_PPP_STATE_ATTACHED_BIT) == true) {
return 0;
}
modem_pipe_attach(pipe, modem_ppp_pipe_callback, ppp);
ppp->pipe = pipe;
return 0;
}
struct net_if *modem_ppp_get_iface(struct modem_ppp *ppp)
{
return ppp->iface;
}
void modem_ppp_release(struct modem_ppp *ppp)
{
struct k_work_sync sync;
struct net_pkt *pkt;
if (atomic_test_and_clear_bit(&ppp->state, MODEM_PPP_STATE_ATTACHED_BIT) == false) {
return;
}
modem_pipe_release(ppp->pipe);
k_work_cancel_sync(&ppp->send_work, &sync);
k_work_cancel_sync(&ppp->process_work, &sync);
ppp->pipe = NULL;
ppp->receive_state = MODEM_PPP_RECEIVE_STATE_HDR_SOF;
if (ppp->rx_pkt != NULL) {
net_pkt_unref(ppp->rx_pkt);
ppp->rx_pkt = NULL;
}
ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_IDLE;
if (ppp->tx_pkt != NULL) {
net_pkt_unref(ppp->tx_pkt);
ppp->tx_pkt = NULL;
}
while (1) {
pkt = k_fifo_get(&ppp->tx_pkt_fifo, K_NO_WAIT);
if (pkt == NULL) {
break;
}
net_pkt_unref(pkt);
}
}
int modem_ppp_init_internal(const struct device *dev)
{
struct modem_ppp *ppp = (struct modem_ppp *)dev->data;
atomic_set(&ppp->state, 0);
ring_buf_init(&ppp->transmit_rb, ppp->buf_size, ppp->transmit_buf);
k_work_init(&ppp->send_work, modem_ppp_send_handler);
k_work_init(&ppp->process_work, modem_ppp_process_handler);
k_fifo_init(&ppp->tx_pkt_fifo);
return 0;
}