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>
diff --git a/include/zephyr/modem/backend/tty.h b/include/zephyr/modem/backend/tty.h
new file mode 100644
index 0000000..f992ce0
--- /dev/null
+++ b/include/zephyr/modem/backend/tty.h
@@ -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_ */
diff --git a/include/zephyr/modem/backend/uart.h b/include/zephyr/modem/backend/uart.h
new file mode 100644
index 0000000..600d543
--- /dev/null
+++ b/include/zephyr/modem/backend/uart.h
@@ -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_ */
diff --git a/include/zephyr/modem/chat.h b/include/zephyr/modem/chat.h
new file mode 100644
index 0000000..5f2ff4a
--- /dev/null
+++ b/include/zephyr/modem/chat.h
@@ -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_ */
diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h
new file mode 100644
index 0000000..b64c00b
--- /dev/null
+++ b/include/zephyr/modem/cmux.h
@@ -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_ */
diff --git a/include/zephyr/modem/pipe.h b/include/zephyr/modem/pipe.h
new file mode 100644
index 0000000..4fdd981
--- /dev/null
+++ b/include/zephyr/modem/pipe.h
@@ -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_ */
diff --git a/include/zephyr/modem/ppp.h b/include/zephyr/modem/ppp.h
new file mode 100644
index 0000000..dc592fb
--- /dev/null
+++ b/include/zephyr/modem/ppp.h
@@ -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_ */
diff --git a/subsys/CMakeLists.txt b/subsys/CMakeLists.txt
index 3c508d6..e4f155e 100644
--- a/subsys/CMakeLists.txt
+++ b/subsys/CMakeLists.txt
@@ -40,6 +40,7 @@
add_subdirectory_ifdef(CONFIG_IMG_MANAGER dfu)
add_subdirectory_ifdef(CONFIG_INPUT input)
add_subdirectory_ifdef(CONFIG_JWT jwt)
+add_subdirectory_ifdef(CONFIG_MODEM_MODULES modem)
add_subdirectory_ifdef(CONFIG_NET_BUF net)
add_subdirectory_ifdef(CONFIG_RETENTION retention)
add_subdirectory_ifdef(CONFIG_SENSING sensing)
diff --git a/subsys/Kconfig b/subsys/Kconfig
index c2b4b91..322ce24 100644
--- a/subsys/Kconfig
+++ b/subsys/Kconfig
@@ -24,6 +24,7 @@
source "subsys/lorawan/Kconfig"
source "subsys/mgmt/Kconfig"
source "subsys/modbus/Kconfig"
+source "subsys/modem/Kconfig"
source "subsys/net/Kconfig"
source "subsys/pm/Kconfig"
source "subsys/portability/Kconfig"
diff --git a/subsys/modem/CMakeLists.txt b/subsys/modem/CMakeLists.txt
new file mode 100644
index 0000000..d491e19
--- /dev/null
+++ b/subsys/modem/CMakeLists.txt
@@ -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()
diff --git a/subsys/modem/Kconfig b/subsys/modem/Kconfig
new file mode 100644
index 0000000..7ad98d2
--- /dev/null
+++ b/subsys/modem/Kconfig
@@ -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
diff --git a/subsys/modem/backends/CMakeLists.txt b/subsys/modem/backends/CMakeLists.txt
new file mode 100644
index 0000000..471452e
--- /dev/null
+++ b/subsys/modem/backends/CMakeLists.txt
@@ -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)
diff --git a/subsys/modem/backends/Kconfig b/subsys/modem/backends/Kconfig
new file mode 100644
index 0000000..f2b45d9
--- /dev/null
+++ b/subsys/modem/backends/Kconfig
@@ -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
diff --git a/subsys/modem/backends/modem_backend_tty.c b/subsys/modem/backends/modem_backend_tty.c
new file mode 100644
index 0000000..b8864fc
--- /dev/null
+++ b/subsys/modem/backends/modem_backend_tty.c
@@ -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;
+}
diff --git a/subsys/modem/backends/modem_backend_uart.c b/subsys/modem/backends/modem_backend_uart.c
new file mode 100644
index 0000000..91e062b
--- /dev/null
+++ b/subsys/modem/backends/modem_backend_uart.c
@@ -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;
+}
diff --git a/subsys/modem/backends/modem_backend_uart_async.c b/subsys/modem/backends/modem_backend_uart_async.c
new file mode 100644
index 0000000..a10822a
--- /dev/null
+++ b/subsys/modem/backends/modem_backend_uart_async.c
@@ -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);
+}
diff --git a/subsys/modem/backends/modem_backend_uart_async.h b/subsys/modem/backends/modem_backend_uart_async.h
new file mode 100644
index 0000000..dce7e2b
--- /dev/null
+++ b/subsys/modem/backends/modem_backend_uart_async.h
@@ -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_ */
diff --git a/subsys/modem/backends/modem_backend_uart_isr.c b/subsys/modem/backends/modem_backend_uart_isr.c
new file mode 100644
index 0000000..7c88cb4
--- /dev/null
+++ b/subsys/modem/backends/modem_backend_uart_isr.c
@@ -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);
+}
diff --git a/subsys/modem/backends/modem_backend_uart_isr.h b/subsys/modem/backends/modem_backend_uart_isr.h
new file mode 100644
index 0000000..31c0a71
--- /dev/null
+++ b/subsys/modem/backends/modem_backend_uart_isr.h
@@ -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_ */
diff --git a/subsys/modem/modem_chat.c b/subsys/modem/modem_chat.c
new file mode 100644
index 0000000..f603cfb
--- /dev/null
+++ b/subsys/modem/modem_chat.c
@@ -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;
+}
diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c
new file mode 100644
index 0000000..cd45d7f
--- /dev/null
+++ b/subsys/modem/modem_cmux.c
@@ -0,0 +1,1016 @@
+/*
+ * Copyright (c) 2022 Trackunit Corporation
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <zephyr/logging/log.h>
+LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_MODULES_LOG_LEVEL);
+
+#include <zephyr/kernel.h>
+#include <zephyr/sys/crc.h>
+#include <zephyr/modem/cmux.h>
+
+#include <string.h>
+
+#define MODEM_CMUX_FCS_POLYNOMIAL (0xE0)
+#define MODEM_CMUX_FCS_INIT_VALUE (0xFF)
+#define MODEM_CMUX_EA (0x01)
+#define MODEM_CMUX_CR (0x02)
+#define MODEM_CMUX_PF (0x10)
+#define MODEM_CMUX_FRAME_SIZE_MAX (0x08)
+#define MODEM_CMUX_DATA_SIZE_MIN (0x08)
+#define MODEM_CMUX_DATA_FRAME_SIZE_MIN (MODEM_CMUX_FRAME_SIZE_MAX + \
+ MODEM_CMUX_DATA_SIZE_MIN)
+
+#define MODEM_CMUX_CMD_DATA_SIZE_MAX (0x04)
+#define MODEM_CMUX_CMD_FRAME_SIZE_MAX (MODEM_CMUX_FRAME_SIZE_MAX + \
+ MODEM_CMUX_CMD_DATA_SIZE_MAX)
+
+#define MODEM_CMUX_T1_TIMEOUT (K_MSEC(330))
+#define MODEM_CMUX_T2_TIMEOUT (K_MSEC(660))
+
+#define MODEM_CMUX_EVENT_CONNECTED_BIT (BIT(0))
+#define MODEM_CMUX_EVENT_DISCONNECTED_BIT (BIT(1))
+
+enum modem_cmux_frame_types {
+ MODEM_CMUX_FRAME_TYPE_RR = 0x01,
+ MODEM_CMUX_FRAME_TYPE_UI = 0x03,
+ MODEM_CMUX_FRAME_TYPE_RNR = 0x05,
+ MODEM_CMUX_FRAME_TYPE_REJ = 0x09,
+ MODEM_CMUX_FRAME_TYPE_DM = 0x0F,
+ MODEM_CMUX_FRAME_TYPE_SABM = 0x2F,
+ MODEM_CMUX_FRAME_TYPE_DISC = 0x43,
+ MODEM_CMUX_FRAME_TYPE_UA = 0x63,
+ MODEM_CMUX_FRAME_TYPE_UIH = 0xEF,
+};
+
+enum modem_cmux_command_types {
+ MODEM_CMUX_COMMAND_NSC = 0x04,
+ MODEM_CMUX_COMMAND_TEST = 0x08,
+ MODEM_CMUX_COMMAND_PSC = 0x10,
+ MODEM_CMUX_COMMAND_RLS = 0x14,
+ MODEM_CMUX_COMMAND_FCOFF = 0x18,
+ MODEM_CMUX_COMMAND_PN = 0x20,
+ MODEM_CMUX_COMMAND_RPN = 0x24,
+ MODEM_CMUX_COMMAND_FCON = 0x28,
+ MODEM_CMUX_COMMAND_CLD = 0x30,
+ MODEM_CMUX_COMMAND_SNC = 0x34,
+ MODEM_CMUX_COMMAND_MSC = 0x38,
+};
+
+struct modem_cmux_command_type {
+ uint8_t ea: 1;
+ uint8_t cr: 1;
+ uint8_t value: 6;
+};
+
+struct modem_cmux_command_length {
+ uint8_t ea: 1;
+ uint8_t value: 7;
+};
+
+struct modem_cmux_command {
+ struct modem_cmux_command_type type;
+ struct modem_cmux_command_length length;
+ uint8_t value[];
+};
+
+static int modem_cmux_wrap_command(struct modem_cmux_command **command, const uint8_t *data,
+ uint16_t data_len)
+{
+ if ((data == NULL) || (data_len < 2)) {
+ return -EINVAL;
+ }
+
+ (*command) = (struct modem_cmux_command *)data;
+
+ if (((*command)->length.ea == 0) || ((*command)->type.ea == 0)) {
+ return -EINVAL;
+ }
+
+ if ((*command)->length.value != (data_len - 2)) {
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static struct modem_cmux_command *modem_cmux_command_wrap(uint8_t *data)
+{
+ return (struct modem_cmux_command *)data;
+}
+
+static void modem_cmux_log_unknown_frame(struct modem_cmux *cmux)
+{
+ char data[24];
+ uint8_t data_cnt = (cmux->frame.data_len < 8) ? cmux->frame.data_len : 8;
+
+ for (uint8_t i = 0; i < data_cnt; i++) {
+ snprintk(&data[i * 3], sizeof(data) - (i * 3), "%02X,", cmux->frame.data[i]);
+ }
+
+ /* Remove trailing */
+ if (data_cnt > 0) {
+ data[(data_cnt * 3) - 1] = '\0';
+ }
+
+ LOG_DBG("ch:%u, type:%u, data:%s", cmux->frame.dlci_address, cmux->frame.type, data);
+}
+
+static void modem_cmux_raise_event(struct modem_cmux *cmux, enum modem_cmux_event event)
+{
+ if (cmux->callback == NULL) {
+ return;
+ }
+
+ cmux->callback(cmux, event, cmux->user_data);
+}
+
+static void modem_cmux_bus_callback(struct modem_pipe *pipe, enum modem_pipe_event event,
+ void *user_data)
+{
+ struct modem_cmux *cmux = (struct modem_cmux *)user_data;
+
+ if (event == MODEM_PIPE_EVENT_RECEIVE_READY) {
+ k_work_schedule(&cmux->receive_work, K_NO_WAIT);
+ }
+}
+
+static uint16_t modem_cmux_transmit_frame(struct modem_cmux *cmux,
+ const struct modem_cmux_frame *frame)
+{
+ uint8_t byte;
+ uint8_t fcs;
+ uint16_t space;
+ uint16_t data_len;
+
+ space = ring_buf_space_get(&cmux->transmit_rb) - MODEM_CMUX_FRAME_SIZE_MAX;
+ data_len = (space < frame->data_len) ? space : frame->data_len;
+
+ /* SOF */
+ byte = 0xF9;
+ ring_buf_put(&cmux->transmit_rb, &byte, 1);
+
+ /* DLCI Address (Max 63) */
+ byte = 0x01 | (frame->cr << 1) | (frame->dlci_address << 2);
+ fcs = crc8(&byte, 1, MODEM_CMUX_FCS_POLYNOMIAL, MODEM_CMUX_FCS_INIT_VALUE, true);
+ ring_buf_put(&cmux->transmit_rb, &byte, 1);
+
+ /* Frame type and poll/final */
+ byte = frame->type | (frame->pf << 4);
+ fcs = crc8(&byte, 1, MODEM_CMUX_FCS_POLYNOMIAL, fcs, true);
+ ring_buf_put(&cmux->transmit_rb, &byte, 1);
+
+ /* Data length */
+ if (data_len > 127) {
+ byte = data_len << 1;
+ fcs = crc8(&byte, 1, MODEM_CMUX_FCS_POLYNOMIAL, fcs, true);
+ ring_buf_put(&cmux->transmit_rb, &byte, 1);
+ byte = 0x01 | (data_len >> 7);
+ ring_buf_put(&cmux->transmit_rb, &byte, 1);
+ } else {
+ byte = 0x01 | (data_len << 1);
+ ring_buf_put(&cmux->transmit_rb, &byte, 1);
+ }
+
+ /* FCS final */
+ if (frame->type == MODEM_CMUX_FRAME_TYPE_UIH) {
+ fcs = 0xFF - crc8(&byte, 1, MODEM_CMUX_FCS_POLYNOMIAL, fcs, true);
+ } else {
+ fcs = crc8(&byte, 1, MODEM_CMUX_FCS_POLYNOMIAL, fcs, true);
+ fcs = 0xFF - crc8(frame->data, data_len, MODEM_CMUX_FCS_POLYNOMIAL, fcs, true);
+ }
+
+ /* Data */
+ ring_buf_put(&cmux->transmit_rb, frame->data, data_len);
+
+ /* FCS */
+ ring_buf_put(&cmux->transmit_rb, &fcs, 1);
+
+ /* EOF */
+ byte = 0xF9;
+ ring_buf_put(&cmux->transmit_rb, &byte, 1);
+ k_work_schedule(&cmux->transmit_work, K_NO_WAIT);
+ return data_len;
+}
+
+static bool modem_cmux_transmit_cmd_frame(struct modem_cmux *cmux,
+ const struct modem_cmux_frame *frame)
+{
+ uint16_t space;
+
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+ space = ring_buf_space_get(&cmux->transmit_rb);
+
+ if (space < MODEM_CMUX_CMD_FRAME_SIZE_MAX) {
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ return false;
+ }
+
+ modem_cmux_transmit_frame(cmux, frame);
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ return true;
+}
+
+static int16_t modem_cmux_transmit_data_frame(struct modem_cmux *cmux,
+ const struct modem_cmux_frame *frame)
+{
+ uint16_t space;
+ int ret;
+
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+
+ if (cmux->flow_control_on == false) {
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ return 0;
+ }
+
+ space = ring_buf_space_get(&cmux->transmit_rb);
+
+ /*
+ * Two command frames are reserved for command channel, and we shall prefer
+ * waiting for more than MODEM_CMUX_DATA_FRAME_SIZE_MIN bytes available in the
+ * transmit buffer rather than transmitting a few bytes at a time. This avoids
+ * excessive wrapping overhead, since transmitting a single byte will require 8
+ * bytes of wrapping.
+ */
+ if (space < ((MODEM_CMUX_CMD_FRAME_SIZE_MAX * 2) + MODEM_CMUX_DATA_FRAME_SIZE_MIN)) {
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ return -ENOMEM;
+ }
+
+ ret = modem_cmux_transmit_frame(cmux, frame);
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ return ret;
+}
+
+static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux)
+{
+ struct modem_cmux_command *command;
+ struct modem_cmux_frame frame;
+ uint8_t data[MODEM_CMUX_CMD_DATA_SIZE_MAX];
+
+ if (sizeof(data) < cmux->frame.data_len) {
+ LOG_WRN("Command acknowledge buffer overrun");
+ return;
+ }
+
+ memcpy(&frame, &cmux->frame, sizeof(cmux->frame));
+ memcpy(data, cmux->frame.data, cmux->frame.data_len);
+ modem_cmux_wrap_command(&command, data, cmux->frame.data_len);
+ command->type.cr = 0;
+ frame.data = data;
+ frame.data_len = cmux->frame.data_len;
+
+ if (modem_cmux_transmit_cmd_frame(cmux, &frame) == false) {
+ LOG_WRN("Command acknowledge buffer overrun");
+ }
+}
+
+static void modem_cmux_on_msc_command(struct modem_cmux *cmux)
+{
+ modem_cmux_acknowledge_received_frame(cmux);
+}
+
+static void modem_cmux_on_fcon_command(struct modem_cmux *cmux)
+{
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+ cmux->flow_control_on = true;
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ modem_cmux_acknowledge_received_frame(cmux);
+}
+
+static void modem_cmux_on_fcoff_command(struct modem_cmux *cmux)
+{
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+ cmux->flow_control_on = false;
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ modem_cmux_acknowledge_received_frame(cmux);
+}
+
+static void modem_cmux_on_cld_command(struct modem_cmux *cmux)
+{
+ if (cmux->state != MODEM_CMUX_STATE_DISCONNECTING) {
+ LOG_WRN("Unexpected close down");
+ }
+
+ cmux->state = MODEM_CMUX_STATE_DISCONNECTED;
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+ cmux->flow_control_on = false;
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ k_work_cancel_delayable(&cmux->disconnect_work);
+ modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED);
+ k_event_clear(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT);
+ k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT);
+}
+
+static void modem_cmux_on_control_frame_ua(struct modem_cmux *cmux)
+{
+ if (cmux->state != MODEM_CMUX_STATE_CONNECTING) {
+ LOG_DBG("Unexpected UA frame");
+
+ return;
+ }
+
+ cmux->state = MODEM_CMUX_STATE_CONNECTED;
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+ cmux->flow_control_on = true;
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ k_work_cancel_delayable(&cmux->connect_work);
+ modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_CONNECTED);
+ k_event_clear(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT);
+ k_event_post(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT);
+}
+
+static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux)
+{
+ struct modem_cmux_command *command;
+
+ if ((cmux->state != MODEM_CMUX_STATE_CONNECTED) &&
+ (cmux->state != MODEM_CMUX_STATE_DISCONNECTING)) {
+ LOG_DBG("Unexpected UIH frame");
+ return;
+ }
+
+ if (modem_cmux_wrap_command(&command, cmux->frame.data, cmux->frame.data_len) < 0) {
+ LOG_WRN("Invalid command");
+ return;
+ }
+
+ switch (command->type.value) {
+ case MODEM_CMUX_COMMAND_CLD:
+ modem_cmux_on_cld_command(cmux);
+ break;
+
+ case MODEM_CMUX_COMMAND_MSC:
+ modem_cmux_on_msc_command(cmux);
+ break;
+
+ case MODEM_CMUX_COMMAND_FCON:
+ modem_cmux_on_fcon_command(cmux);
+ break;
+
+ case MODEM_CMUX_COMMAND_FCOFF:
+ modem_cmux_on_fcoff_command(cmux);
+ break;
+
+ default:
+ LOG_DBG("Unknown command");
+ break;
+ }
+}
+
+static void modem_cmux_on_control_frame(struct modem_cmux *cmux)
+{
+ switch (cmux->frame.type) {
+ case MODEM_CMUX_FRAME_TYPE_UA:
+ modem_cmux_on_control_frame_ua(cmux);
+ break;
+
+ case MODEM_CMUX_FRAME_TYPE_UIH:
+ modem_cmux_on_control_frame_uih(cmux);
+ break;
+
+ default:
+ modem_cmux_log_unknown_frame(cmux);
+ break;
+ }
+}
+
+static struct modem_cmux_dlci *modem_cmux_find_dlci(struct modem_cmux *cmux)
+{
+ sys_snode_t *node;
+ struct modem_cmux_dlci *dlci;
+
+ SYS_SLIST_FOR_EACH_NODE(&cmux->dlcis, node) {
+ dlci = (struct modem_cmux_dlci *)node;
+
+ if (dlci->dlci_address == cmux->frame.dlci_address) {
+ return dlci;
+ }
+ }
+
+ return NULL;
+}
+
+static void modem_cmux_on_dlci_frame_ua(struct modem_cmux_dlci *dlci)
+{
+ switch (dlci->state) {
+ case MODEM_CMUX_DLCI_STATE_OPENING:
+ dlci->state = MODEM_CMUX_DLCI_STATE_OPEN;
+ modem_pipe_notify_opened(&dlci->pipe);
+ k_work_cancel_delayable(&dlci->open_work);
+ break;
+
+ case MODEM_CMUX_DLCI_STATE_CLOSING:
+ dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED;
+ modem_pipe_notify_closed(&dlci->pipe);
+ k_work_cancel_delayable(&dlci->close_work);
+ break;
+
+ default:
+ LOG_DBG("Unexpected UA frame");
+ break;
+ }
+}
+
+static void modem_cmux_on_dlci_frame_uih(struct modem_cmux_dlci *dlci)
+{
+ struct modem_cmux *cmux = dlci->cmux;
+
+ if (dlci->state != MODEM_CMUX_DLCI_STATE_OPEN) {
+ LOG_DBG("Unexpected UIH frame");
+ return;
+ }
+
+ k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER);
+ ring_buf_put(&dlci->receive_rb, cmux->frame.data, cmux->frame.data_len);
+ k_mutex_unlock(&dlci->receive_rb_lock);
+ modem_pipe_notify_receive_ready(&dlci->pipe);
+}
+
+static void modem_cmux_on_dlci_frame(struct modem_cmux *cmux)
+{
+ struct modem_cmux_dlci *dlci;
+
+ dlci = modem_cmux_find_dlci(cmux);
+
+ if (dlci == NULL) {
+ LOG_WRN("Could not find DLCI: %u", cmux->frame.dlci_address);
+
+ return;
+ }
+
+ switch (cmux->frame.type) {
+ case MODEM_CMUX_FRAME_TYPE_UA:
+ modem_cmux_on_dlci_frame_ua(dlci);
+ break;
+
+ case MODEM_CMUX_FRAME_TYPE_UIH:
+ modem_cmux_on_dlci_frame_uih(dlci);
+ break;
+
+ default:
+ modem_cmux_log_unknown_frame(cmux);
+ break;
+ }
+}
+
+static void modem_cmux_on_frame(struct modem_cmux *cmux)
+{
+ if (cmux->frame.dlci_address == 0) {
+ modem_cmux_on_control_frame(cmux);
+ return;
+ }
+
+ modem_cmux_on_dlci_frame(cmux);
+}
+
+static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t byte)
+{
+ uint8_t fcs;
+ static const uint8_t resync[3] = {0xF9, 0xF9, 0xF9};
+
+ switch (cmux->receive_state) {
+ case MODEM_CMUX_RECEIVE_STATE_SOF:
+ if (byte == 0xF9) {
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_ADDRESS;
+ break;
+ }
+
+ /* Send resync flags */
+ modem_pipe_transmit(cmux->pipe, resync, sizeof(resync));
+
+ /* Await resync flags */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC_0;
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_RESYNC_0:
+ if (byte == 0xF9) {
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC_1;
+ }
+
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_RESYNC_1:
+ if (byte == 0xF9) {
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC_2;
+ } else {
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC_0;
+ }
+
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_RESYNC_2:
+ if (byte == 0xF9) {
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC_3;
+ } else {
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC_0;
+ }
+
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_RESYNC_3:
+ if (byte == 0xF9) {
+ break;
+ }
+
+ case MODEM_CMUX_RECEIVE_STATE_ADDRESS:
+ /* Initialize */
+ cmux->receive_buf_len = 0;
+ cmux->frame_header_len = 0;
+
+ /* Store header for FCS */
+ cmux->frame_header[cmux->frame_header_len] = byte;
+ cmux->frame_header_len++;
+
+ /* Get CR */
+ cmux->frame.cr = (byte & 0x02) ? true : false;
+
+ /* Get DLCI address */
+ cmux->frame.dlci_address = (byte >> 2) & 0x3F;
+
+ /* Await control */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_CONTROL;
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_CONTROL:
+ /* Store header for FCS */
+ cmux->frame_header[cmux->frame_header_len] = byte;
+ cmux->frame_header_len++;
+
+ /* Get PF */
+ cmux->frame.pf = (byte & MODEM_CMUX_PF) ? true : false;
+
+ /* Get frame type */
+ cmux->frame.type = byte & (~MODEM_CMUX_PF);
+
+ /* Await data length */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_LENGTH;
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_LENGTH:
+ /* Store header for FCS */
+ cmux->frame_header[cmux->frame_header_len] = byte;
+ cmux->frame_header_len++;
+
+ /* Get first 7 bits of data length */
+ cmux->frame.data_len = (byte >> 1);
+
+ /* Check if length field continues */
+ if ((byte & MODEM_CMUX_EA) == 0) {
+ /* Await continued length field */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_LENGTH_CONT;
+ break;
+ }
+
+ /* Check if no data field */
+ if (cmux->frame.data_len == 0) {
+ /* Await FCS */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_FCS;
+ break;
+ }
+
+ /* Await data */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DATA;
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_LENGTH_CONT:
+ /* Store header for FCS */
+ cmux->frame_header[cmux->frame_header_len] = byte;
+ cmux->frame_header_len++;
+
+ /* Get last 8 bits of data length */
+ cmux->frame.data_len |= ((uint16_t)byte) << 7;
+
+ /* Await data */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DATA;
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_DATA:
+ /* Copy byte to data */
+ cmux->receive_buf[cmux->receive_buf_len] = byte;
+ cmux->receive_buf_len++;
+
+ /* Check if datalen reached */
+ if (cmux->frame.data_len == cmux->receive_buf_len) {
+ /* Await FCS */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_FCS;
+ break;
+ }
+
+ /* Check if receive buffer overrun */
+ if (cmux->receive_buf_len == cmux->receive_buf_size) {
+ LOG_DBG("Receive buf overrun");
+
+ /* Drop frame */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_EOF;
+ break;
+ }
+
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_FCS:
+ /* Compute FCS */
+ if (cmux->frame.type == MODEM_CMUX_FRAME_TYPE_UIH) {
+ fcs = 0xFF - crc8(cmux->frame_header, cmux->frame_header_len,
+ MODEM_CMUX_FCS_POLYNOMIAL, MODEM_CMUX_FCS_INIT_VALUE,
+ true);
+ } else {
+ fcs = crc8(cmux->frame_header, cmux->frame_header_len,
+ MODEM_CMUX_FCS_POLYNOMIAL, MODEM_CMUX_FCS_INIT_VALUE, true);
+
+ fcs = 0xFF - crc8(cmux->frame.data, cmux->frame.data_len,
+ MODEM_CMUX_FCS_POLYNOMIAL, fcs, true);
+ }
+
+ /* Validate FCS */
+ if (fcs != byte) {
+ LOG_WRN("Frame FCS error");
+
+ /* Drop frame */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP;
+ break;
+ }
+
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_EOF;
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_DROP:
+ LOG_WRN("Dropped frame");
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_SOF;
+ break;
+
+ case MODEM_CMUX_RECEIVE_STATE_EOF:
+ /* Validate byte is EOF */
+ if (byte != 0xF9) {
+ /* Unexpected byte */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_SOF;
+ break;
+ }
+
+ LOG_DBG("Received frame");
+
+ /* Process frame */
+ cmux->frame.data = cmux->receive_buf;
+ modem_cmux_on_frame(cmux);
+
+ /* Await start of next frame */
+ cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_SOF;
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void modem_cmux_receive_handler(struct k_work *item)
+{
+ struct modem_cmux *cmux = CONTAINER_OF(item, struct modem_cmux, receive_work);
+ uint8_t buf[16];
+ int ret;
+
+ /* Receive data from pipe */
+ ret = modem_pipe_receive(cmux->pipe, buf, sizeof(buf));
+ if (ret < 1) {
+ return;
+ }
+
+ /* Process received data */
+ for (uint16_t i = 0; i < (uint16_t)ret; i++) {
+ modem_cmux_process_received_byte(cmux, buf[i]);
+ }
+
+ /* Reschedule received work */
+ k_work_schedule(&cmux->receive_work, K_NO_WAIT);
+}
+
+static void modem_cmux_transmit_handler(struct k_work *item)
+{
+ struct modem_cmux *cmux = CONTAINER_OF(item, struct modem_cmux, transmit_work);
+ uint8_t *reserved;
+ uint32_t reserved_size;
+ int ret;
+
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+
+ /* Reserve data to transmit from transmit ring buffer */
+ reserved_size = ring_buf_get_claim(&cmux->transmit_rb, &reserved, UINT32_MAX);
+
+ /* Transmit reserved data */
+ ret = modem_pipe_transmit(cmux->pipe, reserved, reserved_size);
+ if (ret < 1) {
+ ring_buf_get_finish(&cmux->transmit_rb, 0);
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ k_work_schedule(&cmux->transmit_work, K_NO_WAIT);
+
+ return;
+ }
+
+ /* Release remaining reserved data */
+ ring_buf_get_finish(&cmux->transmit_rb, ret);
+
+ /* Resubmit transmit work if data remains */
+ if (ring_buf_is_empty(&cmux->transmit_rb) == false) {
+ k_work_schedule(&cmux->transmit_work, K_NO_WAIT);
+ }
+
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+}
+
+static void modem_cmux_connect_handler(struct k_work *item)
+{
+ struct modem_cmux *cmux = CONTAINER_OF(item, struct modem_cmux, connect_work);
+
+ cmux->state = MODEM_CMUX_STATE_CONNECTING;
+
+ struct modem_cmux_frame frame = {
+ .dlci_address = 0,
+ .cr = true,
+ .pf = true,
+ .type = MODEM_CMUX_FRAME_TYPE_SABM,
+ .data = NULL,
+ .data_len = 0,
+ };
+
+ modem_cmux_transmit_cmd_frame(cmux, &frame);
+ k_work_schedule(&cmux->connect_work, MODEM_CMUX_T1_TIMEOUT);
+}
+
+static void modem_cmux_disconnect_handler(struct k_work *item)
+{
+ struct modem_cmux *cmux = CONTAINER_OF(item, struct modem_cmux, disconnect_work);
+ struct modem_cmux_command *command;
+ uint8_t data[2];
+
+ cmux->state = MODEM_CMUX_STATE_DISCONNECTING;
+
+ command = modem_cmux_command_wrap(data);
+ command->type.ea = 1;
+ command->type.cr = 1;
+ command->type.value = MODEM_CMUX_COMMAND_CLD;
+ command->length.ea = 1;
+ command->length.value = 0;
+
+ struct modem_cmux_frame frame = {
+ .dlci_address = 0,
+ .cr = true,
+ .pf = false,
+ .type = MODEM_CMUX_FRAME_TYPE_UIH,
+ .data = data,
+ .data_len = sizeof(data),
+ };
+
+ /* Transmit close down command */
+ modem_cmux_transmit_cmd_frame(cmux, &frame);
+ k_work_schedule(&cmux->disconnect_work, MODEM_CMUX_T1_TIMEOUT);
+}
+
+static int modem_cmux_dlci_pipe_api_open(void *data)
+{
+ struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data;
+
+ if (k_work_delayable_is_pending(&dlci->open_work) == true) {
+ return -EBUSY;
+ }
+
+ k_work_schedule(&dlci->open_work, K_NO_WAIT);
+ return 0;
+}
+
+static int modem_cmux_dlci_pipe_api_transmit(void *data, const uint8_t *buf, size_t size)
+{
+ struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data;
+ struct modem_cmux *cmux = dlci->cmux;
+
+ struct modem_cmux_frame frame = {
+ .dlci_address = dlci->dlci_address,
+ .cr = false,
+ .pf = false,
+ .type = MODEM_CMUX_FRAME_TYPE_UIH,
+ .data = buf,
+ .data_len = size,
+ };
+
+ return modem_cmux_transmit_data_frame(cmux, &frame);
+}
+
+static int modem_cmux_dlci_pipe_api_receive(void *data, uint8_t *buf, size_t size)
+{
+ struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data;
+ uint32_t ret;
+
+ k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER);
+ ret = ring_buf_get(&dlci->receive_rb, buf, size);
+ k_mutex_unlock(&dlci->receive_rb_lock);
+ return ret;
+}
+
+static int modem_cmux_dlci_pipe_api_close(void *data)
+{
+ struct modem_cmux_dlci *dlci = (struct modem_cmux_dlci *)data;
+
+ if (k_work_delayable_is_pending(&dlci->close_work) == true) {
+ return -EBUSY;
+ }
+
+ k_work_schedule(&dlci->close_work, K_NO_WAIT);
+ return 0;
+}
+
+struct modem_pipe_api modem_cmux_dlci_pipe_api = {
+ .open = modem_cmux_dlci_pipe_api_open,
+ .transmit = modem_cmux_dlci_pipe_api_transmit,
+ .receive = modem_cmux_dlci_pipe_api_receive,
+ .close = modem_cmux_dlci_pipe_api_close,
+};
+
+static void modem_cmux_dlci_open_handler(struct k_work *item)
+{
+ struct modem_cmux_dlci *dlci = CONTAINER_OF(item, struct modem_cmux_dlci, open_work);
+
+ dlci->state = MODEM_CMUX_DLCI_STATE_OPENING;
+
+ struct modem_cmux_frame frame = {
+ .dlci_address = dlci->dlci_address,
+ .cr = true,
+ .pf = true,
+ .type = MODEM_CMUX_FRAME_TYPE_SABM,
+ .data = NULL,
+ .data_len = 0,
+ };
+
+ modem_cmux_transmit_cmd_frame(dlci->cmux, &frame);
+ k_work_schedule(&dlci->open_work, MODEM_CMUX_T1_TIMEOUT);
+}
+
+static void modem_cmux_dlci_close_handler(struct k_work *item)
+{
+ struct modem_cmux_dlci *dlci = CONTAINER_OF(item, struct modem_cmux_dlci, close_work);
+ struct modem_cmux *cmux = dlci->cmux;
+
+ dlci->state = MODEM_CMUX_DLCI_STATE_CLOSING;
+
+ struct modem_cmux_frame frame = {
+ .dlci_address = dlci->dlci_address,
+ .cr = true,
+ .pf = true,
+ .type = MODEM_CMUX_FRAME_TYPE_DISC,
+ .data = NULL,
+ .data_len = 0,
+ };
+
+ modem_cmux_transmit_cmd_frame(cmux, &frame);
+ k_work_schedule(&dlci->close_work, MODEM_CMUX_T1_TIMEOUT);
+}
+
+static void modem_cmux_dlci_pipes_notify_closed(struct modem_cmux *cmux)
+{
+ sys_snode_t *node;
+ struct modem_cmux_dlci *dlci;
+
+ SYS_SLIST_FOR_EACH_NODE(&cmux->dlcis, node) {
+ dlci = (struct modem_cmux_dlci *)node;
+ modem_pipe_notify_closed(&dlci->pipe);
+ }
+}
+
+void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *config)
+{
+ __ASSERT_NO_MSG(cmux != NULL);
+ __ASSERT_NO_MSG(config != NULL);
+ __ASSERT_NO_MSG(config->receive_buf != NULL);
+ __ASSERT_NO_MSG(config->receive_buf_size >= 126);
+ __ASSERT_NO_MSG(config->transmit_buf != NULL);
+ __ASSERT_NO_MSG(config->transmit_buf_size >= 148);
+
+ memset(cmux, 0x00, sizeof(*cmux));
+ cmux->callback = config->callback;
+ cmux->user_data = config->user_data;
+ cmux->receive_buf = config->receive_buf;
+ cmux->receive_buf_size = config->receive_buf_size;
+ sys_slist_init(&cmux->dlcis);
+ cmux->state = MODEM_CMUX_STATE_DISCONNECTED;
+ ring_buf_init(&cmux->transmit_rb, config->transmit_buf_size, config->transmit_buf);
+ k_mutex_init(&cmux->transmit_rb_lock);
+ k_work_init_delayable(&cmux->receive_work, modem_cmux_receive_handler);
+ k_work_init_delayable(&cmux->transmit_work, modem_cmux_transmit_handler);
+ k_work_init_delayable(&cmux->connect_work, modem_cmux_connect_handler);
+ k_work_init_delayable(&cmux->disconnect_work, modem_cmux_disconnect_handler);
+ k_event_init(&cmux->event);
+ k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT);
+}
+
+struct modem_pipe *modem_cmux_dlci_init(struct modem_cmux *cmux, struct modem_cmux_dlci *dlci,
+ const struct modem_cmux_dlci_config *config)
+{
+ __ASSERT_NO_MSG(cmux != NULL);
+ __ASSERT_NO_MSG(dlci != NULL);
+ __ASSERT_NO_MSG(config != NULL);
+ __ASSERT_NO_MSG(config->dlci_address < 64);
+ __ASSERT_NO_MSG(config->receive_buf != NULL);
+ __ASSERT_NO_MSG(config->receive_buf_size >= 126);
+
+ memset(dlci, 0x00, sizeof(*dlci));
+ dlci->cmux = cmux;
+ dlci->dlci_address = config->dlci_address;
+ ring_buf_init(&dlci->receive_rb, config->receive_buf_size, config->receive_buf);
+ k_mutex_init(&dlci->receive_rb_lock);
+ modem_pipe_init(&dlci->pipe, dlci, &modem_cmux_dlci_pipe_api);
+ k_work_init_delayable(&dlci->open_work, modem_cmux_dlci_open_handler);
+ k_work_init_delayable(&dlci->close_work, modem_cmux_dlci_close_handler);
+ dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED;
+ sys_slist_append(&dlci->cmux->dlcis, &dlci->node);
+ return &dlci->pipe;
+}
+
+int modem_cmux_attach(struct modem_cmux *cmux, struct modem_pipe *pipe)
+{
+ cmux->pipe = pipe;
+ ring_buf_reset(&cmux->transmit_rb);
+ modem_pipe_attach(cmux->pipe, modem_cmux_bus_callback, cmux);
+ return 0;
+}
+
+int modem_cmux_connect(struct modem_cmux *cmux)
+{
+ __ASSERT_NO_MSG(cmux->pipe != NULL);
+
+ if (k_event_wait(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT, false, K_NO_WAIT)) {
+ return -EALREADY;
+ }
+
+ if (k_work_delayable_is_pending(&cmux->connect_work) == false) {
+ k_work_schedule(&cmux->connect_work, K_NO_WAIT);
+ }
+
+ if (k_event_wait(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT, false,
+ MODEM_CMUX_T2_TIMEOUT) == 0) {
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+int modem_cmux_connect_async(struct modem_cmux *cmux)
+{
+ __ASSERT_NO_MSG(cmux->pipe != NULL);
+
+ if (k_work_delayable_is_pending(&cmux->connect_work) == true) {
+ return -EBUSY;
+ }
+
+ k_work_schedule(&cmux->connect_work, K_NO_WAIT);
+ return 0;
+}
+
+int modem_cmux_disconnect(struct modem_cmux *cmux)
+{
+ if (k_event_wait(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT, false, K_NO_WAIT)) {
+ return -EALREADY;
+ }
+
+ if (k_work_delayable_is_pending(&cmux->disconnect_work) == false) {
+ k_work_schedule(&cmux->disconnect_work, K_NO_WAIT);
+ }
+
+ if (k_event_wait(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT, false,
+ MODEM_CMUX_T2_TIMEOUT) == 0) {
+ return -EAGAIN;
+ }
+
+ return 0;
+}
+
+int modem_cmux_disconnect_async(struct modem_cmux *cmux)
+{
+ if (k_work_delayable_is_pending(&cmux->disconnect_work) == true) {
+ return -EBUSY;
+ }
+
+ k_work_schedule(&cmux->disconnect_work, K_NO_WAIT);
+ return 0;
+}
+
+void modem_cmux_release(struct modem_cmux *cmux)
+{
+ struct k_work_sync sync;
+
+ /* Close DLCI pipes */
+ modem_cmux_dlci_pipes_notify_closed(cmux);
+
+ /* Release bus pipe */
+ if (cmux->pipe) {
+ modem_pipe_release(cmux->pipe);
+ }
+
+ /* Cancel all work */
+ k_work_cancel_delayable_sync(&cmux->connect_work, &sync);
+ k_work_cancel_delayable_sync(&cmux->disconnect_work, &sync);
+ k_work_cancel_delayable_sync(&cmux->transmit_work, &sync);
+ k_work_cancel_delayable_sync(&cmux->receive_work, &sync);
+
+ /* Unreference pipe */
+ cmux->pipe = NULL;
+}
diff --git a/subsys/modem/modem_pipe.c b/subsys/modem/modem_pipe.c
new file mode 100644
index 0000000..ca2a260
--- /dev/null
+++ b/subsys/modem/modem_pipe.c
@@ -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);
+}
diff --git a/subsys/modem/modem_ppp.c b/subsys/modem/modem_ppp.c
new file mode 100644
index 0000000..48a550d
--- /dev/null
+++ b/subsys/modem/modem_ppp.c
@@ -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;
+}