diff --git a/doc/releases/migration-guide-4.3.rst b/doc/releases/migration-guide-4.3.rst index 83dc5e771201..a3aec0898c8e 100644 --- a/doc/releases/migration-guide-4.3.rst +++ b/doc/releases/migration-guide-4.3.rst @@ -153,6 +153,13 @@ Networking .. zephyr-keep-sorted-stop +Modem +***** + +* ``CONFIG_MODEM_AT_SHELL_USER_PIPE`` has been renamed to :kconfig:option:`CONFIG_MODEM_AT_USER_PIPE`. +* ``CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE`` has been updated to :kconfig:option:`CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE_EXTRA`, + which only takes the number of extra bytes desired over the default of (:kconfig:option:`CONFIG_MODEM_CMUX_MTU` + 7). + Display ******* diff --git a/doc/releases/release-notes-4.3.rst b/doc/releases/release-notes-4.3.rst index 6dfc304b3ef5..718e4ad3ba33 100644 --- a/doc/releases/release-notes-4.3.rst +++ b/doc/releases/release-notes-4.3.rst @@ -171,6 +171,17 @@ New APIs and options * :kconfig:option:`CONFIG_HAWKBIT_REBOOT_NONE` +* Modem + + * :kconfig:option:`CONFIG_MODEM_DEDICATED_WORKQUEUE` + +* Networking + + * Sockets + + * :c:func:`zsock_listen` now implements the ``backlog`` parameter support. The TCP server + socket will limit the number of pending incoming connections to that value. + * Power management * :c:func:`pm_device_driver_deinit` diff --git a/drivers/modem/CMakeLists.txt b/drivers/modem/CMakeLists.txt index 62b84bd79294..adc6614dc7fe 100644 --- a/drivers/modem/CMakeLists.txt +++ b/drivers/modem/CMakeLists.txt @@ -36,4 +36,5 @@ if (CONFIG_MODEM_SIM7080) endif() zephyr_library_sources_ifdef(CONFIG_MODEM_CELLULAR modem_cellular.c) +zephyr_library_sources_ifdef(CONFIG_MODEM_AT_USER_PIPE modem_at_user_pipe.c) zephyr_library_sources_ifdef(CONFIG_MODEM_AT_SHELL modem_at_shell.c) diff --git a/drivers/modem/Kconfig.at_shell b/drivers/modem/Kconfig.at_shell index b2f6f42e3936..3d399e2338ce 100644 --- a/drivers/modem/Kconfig.at_shell +++ b/drivers/modem/Kconfig.at_shell @@ -1,22 +1,30 @@ # Copyright (c) 2024 Trackunit Corporation # SPDX-License-Identifier: Apache-2.0 -config MODEM_AT_SHELL - bool "AT command shell based on modem modules" - select MODEM_MODULES +config MODEM_AT_USER_PIPE + bool "Modem AT command user pipe helpers" + depends on $(dt_alias_enabled,modem) select MODEM_CHAT select MODEM_PIPE select MODEM_PIPELINK + help + Utility functions for managing access to user pipes + for arbitrary AT commands + +config MODEM_AT_USER_PIPE_IDX + int "User pipe number to use" + depends on MODEM_AT_USER_PIPE + default 0 + +config MODEM_AT_SHELL + bool "AT command shell based on modem modules" + select MODEM_MODULES + select MODEM_AT_USER_PIPE depends on !MODEM_SHELL depends on !SHELL_WILDCARD - depends on $(dt_alias_enabled,modem) if MODEM_AT_SHELL -config MODEM_AT_SHELL_USER_PIPE - int "User pipe number to use" - default 0 - config MODEM_AT_SHELL_RESPONSE_TIMEOUT_S int "Timeout waiting for response to AT command in seconds" default 5 diff --git a/drivers/modem/modem_at_shell.c b/drivers/modem/modem_at_shell.c index 5d2820d7483e..7d1930b6b04b 100644 --- a/drivers/modem/modem_at_shell.c +++ b/drivers/modem/modem_at_shell.c @@ -6,6 +6,7 @@ #include #include +#include #include #include #include @@ -13,17 +14,6 @@ #include LOG_MODULE_REGISTER(modem_at_shell, CONFIG_MODEM_LOG_LEVEL); -#define AT_SHELL_MODEM_NODE DT_ALIAS(modem) -#define AT_SHELL_PIPELINK_NAME _CONCAT(user_pipe_, CONFIG_MODEM_AT_SHELL_USER_PIPE) - -#define AT_SHELL_STATE_ATTACHED_BIT 0 -#define AT_SHELL_STATE_SCRIPT_RUNNING_BIT 1 - -MODEM_PIPELINK_DT_DECLARE(AT_SHELL_MODEM_NODE, AT_SHELL_PIPELINK_NAME); - -static struct modem_pipelink *at_shell_pipelink = - MODEM_PIPELINK_DT_GET(AT_SHELL_MODEM_NODE, AT_SHELL_PIPELINK_NAME); - static struct modem_chat at_shell_chat; static uint8_t at_shell_chat_receive_buf[CONFIG_MODEM_AT_SHELL_CHAT_RECEIVE_BUF_SIZE]; static uint8_t *at_shell_chat_argv_buf[2]; @@ -32,10 +22,6 @@ static struct modem_chat_script_chat at_shell_script_chat[1]; static struct modem_chat_match at_shell_script_chat_matches[2]; static uint8_t at_shell_match_buf[CONFIG_MODEM_AT_SHELL_RESPONSE_MAX_SIZE]; static const struct shell *at_shell_active_shell; -static struct k_work at_shell_open_pipe_work; -static struct k_work at_shell_attach_chat_work; -static struct k_work at_shell_release_chat_work; -static atomic_t at_shell_state; static void at_shell_print_any_match(struct modem_chat *chat, char **argv, uint16_t argc, void *user_data) @@ -74,7 +60,7 @@ static void at_shell_script_callback(struct modem_chat *chat, enum modem_chat_script_result result, void *user_data) { - atomic_clear_bit(&at_shell_state, AT_SHELL_STATE_SCRIPT_RUNNING_BIT); + modem_at_user_pipe_release(); } MODEM_CHAT_SCRIPT_DEFINE( @@ -85,83 +71,6 @@ MODEM_CHAT_SCRIPT_DEFINE( CONFIG_MODEM_AT_SHELL_RESPONSE_TIMEOUT_S ); -static void at_shell_pipe_callback(struct modem_pipe *pipe, - enum modem_pipe_event event, - void *user_data) -{ - ARG_UNUSED(user_data); - - switch (event) { - case MODEM_PIPE_EVENT_OPENED: - LOG_INF("pipe opened"); - k_work_submit(&at_shell_attach_chat_work); - break; - - default: - break; - } -} - -void at_shell_pipelink_callback(struct modem_pipelink *link, - enum modem_pipelink_event event, - void *user_data) -{ - ARG_UNUSED(user_data); - - switch (event) { - case MODEM_PIPELINK_EVENT_CONNECTED: - LOG_INF("pipe connected"); - k_work_submit(&at_shell_open_pipe_work); - break; - - case MODEM_PIPELINK_EVENT_DISCONNECTED: - LOG_INF("pipe disconnected"); - k_work_submit(&at_shell_release_chat_work); - break; - - default: - break; - } -} - -static void at_shell_open_pipe_handler(struct k_work *work) -{ - ARG_UNUSED(work); - - LOG_INF("opening pipe"); - - modem_pipe_attach(modem_pipelink_get_pipe(at_shell_pipelink), - at_shell_pipe_callback, - NULL); - - modem_pipe_open_async(modem_pipelink_get_pipe(at_shell_pipelink)); -} - -static void at_shell_attach_chat_handler(struct k_work *work) -{ - ARG_UNUSED(work); - - modem_chat_attach(&at_shell_chat, modem_pipelink_get_pipe(at_shell_pipelink)); - atomic_set_bit(&at_shell_state, AT_SHELL_STATE_ATTACHED_BIT); - LOG_INF("chat attached"); -} - -static void at_shell_release_chat_handler(struct k_work *work) -{ - ARG_UNUSED(work); - - modem_chat_release(&at_shell_chat); - atomic_clear_bit(&at_shell_state, AT_SHELL_STATE_ATTACHED_BIT); - LOG_INF("chat released"); -} - -static void at_shell_init_work(void) -{ - k_work_init(&at_shell_open_pipe_work, at_shell_open_pipe_handler); - k_work_init(&at_shell_attach_chat_work, at_shell_attach_chat_handler); - k_work_init(&at_shell_release_chat_work, at_shell_release_chat_handler); -} - static void at_shell_init_chat(void) { const struct modem_chat_config at_shell_chat_config = { @@ -204,17 +113,11 @@ static void at_shell_init_script_chat(void) CONFIG_MODEM_AT_SHELL_RESPONSE_TIMEOUT_S); } -static void at_shell_init_pipelink(void) -{ - modem_pipelink_attach(at_shell_pipelink, at_shell_pipelink_callback, NULL); -} - static int at_shell_init(void) { - at_shell_init_work(); at_shell_init_chat(); at_shell_init_script_chat(); - at_shell_init_pipelink(); + modem_at_user_pipe_init(&at_shell_chat); return 0; } @@ -228,14 +131,19 @@ static int at_shell_cmd_handler(const struct shell *sh, size_t argc, char **argv return -EINVAL; } - if (!atomic_test_bit(&at_shell_state, AT_SHELL_STATE_ATTACHED_BIT)) { - shell_error(sh, "modem is not ready"); - return -EPERM; - } - - if (atomic_test_and_set_bit(&at_shell_state, AT_SHELL_STATE_SCRIPT_RUNNING_BIT)) { - shell_error(sh, "script is already running"); - return -EBUSY; + ret = modem_at_user_pipe_claim(); + if (ret < 0) { + switch (ret) { + case -EPERM: + shell_error(sh, "modem is not ready"); + break; + case -EBUSY: + shell_error(sh, "script is already running"); + break; + default: + shell_error(sh, "unknown"); + } + return ret; } strncpy(at_shell_request_buf, argv[1], sizeof(at_shell_request_buf) - 1); @@ -260,7 +168,7 @@ static int at_shell_cmd_handler(const struct shell *sh, size_t argc, char **argv ret = modem_chat_run_script_async(&at_shell_chat, &at_shell_script); if (ret < 0) { shell_error(sh, "failed to start script"); - atomic_clear_bit(&at_shell_state, AT_SHELL_STATE_SCRIPT_RUNNING_BIT); + modem_at_user_pipe_release(); } return ret; diff --git a/drivers/modem/modem_at_user_pipe.c b/drivers/modem/modem_at_user_pipe.c new file mode 100644 index 000000000000..6fb0046f0fef --- /dev/null +++ b/drivers/modem/modem_at_user_pipe.c @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2025 Embeint Holdings Pty Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#define AT_UTIL_MODEM_NODE DT_ALIAS(modem) +#define AT_UTIL_PIPELINK_NAME _CONCAT(user_pipe_, CONFIG_MODEM_AT_USER_PIPE_IDX) + +#define AT_UTIL_STATE_ATTACHED_BIT 0 +#define AT_UTIL_STATE_SCRIPT_RUNNING_BIT 1 + +MODEM_PIPELINK_DT_DECLARE(AT_UTIL_MODEM_NODE, AT_UTIL_PIPELINK_NAME); + +static struct modem_pipelink *at_util_pipelink = + MODEM_PIPELINK_DT_GET(AT_UTIL_MODEM_NODE, AT_UTIL_PIPELINK_NAME); + +static struct k_work at_util_open_pipe_work; +static struct k_work at_util_attach_chat_work; +static struct k_work at_util_release_chat_work; +static struct modem_chat *at_util_chat; +static atomic_t at_util_state; + +LOG_MODULE_REGISTER(modem_at_user_pipe, CONFIG_MODEM_LOG_LEVEL); + +static void at_util_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event, + void *user_data) +{ + ARG_UNUSED(user_data); + + switch (event) { + case MODEM_PIPE_EVENT_OPENED: + LOG_INF("pipe opened"); + k_work_submit(&at_util_attach_chat_work); + break; + + default: + break; + } +} + +void at_util_pipelink_callback(struct modem_pipelink *link, enum modem_pipelink_event event, + void *user_data) +{ + ARG_UNUSED(user_data); + + switch (event) { + case MODEM_PIPELINK_EVENT_CONNECTED: + LOG_INF("pipe connected"); + k_work_submit(&at_util_open_pipe_work); + break; + + case MODEM_PIPELINK_EVENT_DISCONNECTED: + LOG_INF("pipe disconnected"); + k_work_submit(&at_util_release_chat_work); + break; + + default: + break; + } +} + +static void at_util_open_pipe_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + LOG_INF("opening pipe"); + + modem_pipe_attach(modem_pipelink_get_pipe(at_util_pipelink), at_util_pipe_callback, NULL); + + modem_pipe_open_async(modem_pipelink_get_pipe(at_util_pipelink)); +} + +static void at_util_attach_chat_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + modem_chat_attach(at_util_chat, modem_pipelink_get_pipe(at_util_pipelink)); + atomic_set_bit(&at_util_state, AT_UTIL_STATE_ATTACHED_BIT); + LOG_INF("chat attached"); +} + +static void at_util_release_chat_handler(struct k_work *work) +{ + ARG_UNUSED(work); + + modem_chat_release(at_util_chat); + atomic_clear_bit(&at_util_state, AT_UTIL_STATE_ATTACHED_BIT); + LOG_INF("chat released"); +} + +void modem_at_user_pipe_init(struct modem_chat *chat) +{ + at_util_chat = chat; + /* Initialise workers and setup callbacks */ + k_work_init(&at_util_open_pipe_work, at_util_open_pipe_handler); + k_work_init(&at_util_attach_chat_work, at_util_attach_chat_handler); + k_work_init(&at_util_release_chat_work, at_util_release_chat_handler); + modem_pipelink_attach(at_util_pipelink, at_util_pipelink_callback, NULL); +} + +int modem_at_user_pipe_claim(void) +{ + if (!atomic_test_bit(&at_util_state, AT_UTIL_STATE_ATTACHED_BIT)) { + return -EPERM; + } + + if (atomic_test_and_set_bit(&at_util_state, AT_UTIL_STATE_SCRIPT_RUNNING_BIT)) { + return -EBUSY; + } + + return 0; +} + +void modem_at_user_pipe_release(void) +{ + atomic_clear_bit(&at_util_state, AT_UTIL_STATE_SCRIPT_RUNNING_BIT); +} diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index 3e7b3a30eff1..dfa2c719a8af 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -83,6 +85,7 @@ enum modem_cellular_event { MODEM_CELLULAR_EVENT_SCRIPT_SUCCESS, MODEM_CELLULAR_EVENT_SCRIPT_FAILED, MODEM_CELLULAR_EVENT_CMUX_CONNECTED, + MODEM_CELLULAR_EVENT_CMUX_DISCONNECTED, MODEM_CELLULAR_EVENT_DLCI1_OPENED, MODEM_CELLULAR_EVENT_DLCI2_OPENED, MODEM_CELLULAR_EVENT_TIMEOUT, @@ -93,6 +96,7 @@ enum modem_cellular_event { MODEM_CELLULAR_EVENT_PPP_DEAD, MODEM_CELLULAR_EVENT_MODEM_READY, MODEM_CELLULAR_EVENT_APN_SET, + MODEM_CELLULAR_EVENT_RING, }; struct modem_cellular_event_cb { @@ -110,8 +114,8 @@ struct modem_cellular_data { /* CMUX */ struct modem_cmux cmux; - uint8_t cmux_receive_buf[CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE]; - uint8_t cmux_transmit_buf[CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE]; + uint8_t cmux_receive_buf[MODEM_CMUX_WORK_BUFFER_SIZE]; + uint8_t cmux_transmit_buf[MODEM_CMUX_WORK_BUFFER_SIZE]; struct modem_cmux_dlci dlci1; struct modem_cmux_dlci dlci2; @@ -119,9 +123,9 @@ struct modem_cellular_data { struct modem_pipe *dlci2_pipe; /* Points to dlci2_pipe or NULL. Used for shutdown script if not NULL */ struct modem_pipe *cmd_pipe; - uint8_t dlci1_receive_buf[CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE]; + uint8_t dlci1_receive_buf[MODEM_CMUX_WORK_BUFFER_SIZE]; /* DLCI 2 is only used for chat scripts. */ - uint8_t dlci2_receive_buf[CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE]; + uint8_t dlci2_receive_buf[MODEM_CMUX_WORK_BUFFER_SIZE]; /* Modem chat */ struct modem_chat chat; @@ -163,11 +167,13 @@ struct modem_cellular_data { /* Event dispatcher */ struct k_work event_dispatch_work; uint8_t event_buf[8]; - struct ring_buf event_rb; - struct k_mutex event_rb_lock; + struct k_pipe event_pipe; struct k_mutex api_lock; struct modem_cellular_event_cb cb; + + /* Ring interrupt */ + struct gpio_callback ring_gpio_cb; }; struct modem_cellular_user_pipe { @@ -184,11 +190,16 @@ struct modem_cellular_config { struct gpio_dt_spec power_gpio; struct gpio_dt_spec reset_gpio; struct gpio_dt_spec wake_gpio; + struct gpio_dt_spec ring_gpio; + struct gpio_dt_spec dtr_gpio; uint16_t power_pulse_duration_ms; uint16_t reset_pulse_duration_ms; uint16_t startup_time_ms; uint16_t shutdown_time_ms; bool autostarts; + bool cmux_enable_runtime_power_save; + bool cmux_close_pipe_on_power_save; + k_timeout_t cmux_idle_timeout; const struct modem_chat_script *init_chat_script; const struct modem_chat_script *dial_chat_script; const struct modem_chat_script *periodic_chat_script; @@ -257,6 +268,8 @@ static const char *modem_cellular_event_str(enum modem_cellular_event event) return "script failed"; case MODEM_CELLULAR_EVENT_CMUX_CONNECTED: return "cmux connected"; + case MODEM_CELLULAR_EVENT_CMUX_DISCONNECTED: + return "cmux disconnected"; case MODEM_CELLULAR_EVENT_DLCI1_OPENED: return "dlci1 opened"; case MODEM_CELLULAR_EVENT_DLCI2_OPENED: @@ -277,6 +290,8 @@ static const char *modem_cellular_event_str(enum modem_cellular_event event) return "modem ready"; case MODEM_CELLULAR_EVENT_APN_SET: return "apn set"; + case MODEM_CELLULAR_EVENT_RING: + return "RING"; } return ""; @@ -702,26 +717,18 @@ static void modem_cellular_event_dispatch_handler(struct k_work *item) struct modem_cellular_data *data = CONTAINER_OF(item, struct modem_cellular_data, event_dispatch_work); - uint8_t events[sizeof(data->event_buf)]; - uint8_t events_cnt; - - k_mutex_lock(&data->event_rb_lock, K_FOREVER); + enum modem_cellular_event event; + const size_t len = sizeof(event); - events_cnt = (uint8_t)ring_buf_get(&data->event_rb, events, sizeof(data->event_buf)); - - k_mutex_unlock(&data->event_rb_lock); - - for (uint8_t i = 0; i < events_cnt; i++) { - modem_cellular_event_handler(data, (enum modem_cellular_event)events[i]); + while (k_pipe_read(&data->event_pipe, (uint8_t *)&event, len, K_NO_WAIT) == len) { + modem_cellular_event_handler(data, (enum modem_cellular_event)event); } } static void modem_cellular_delegate_event(struct modem_cellular_data *data, enum modem_cellular_event evt) { - k_mutex_lock(&data->event_rb_lock, K_FOREVER); - ring_buf_put(&data->event_rb, (uint8_t *)&evt, 1); - k_mutex_unlock(&data->event_rb_lock); + k_pipe_write(&data->event_pipe, (const uint8_t *)&evt, sizeof(evt), K_NO_WAIT); k_work_submit(&data->event_dispatch_work); } @@ -1242,7 +1249,10 @@ static void modem_cellular_run_dial_script_event_handler(struct modem_cellular_d case MODEM_CELLULAR_EVENT_SUSPEND: modem_cellular_enter_state(data, MODEM_CELLULAR_STATE_INIT_POWER_OFF); break; - + case MODEM_CELLULAR_EVENT_RING: + LOG_INF("RING received!"); + modem_pipe_open_async(data->uart_pipe); + break; default: break; } @@ -1287,7 +1297,10 @@ static void modem_cellular_await_registered_event_handler(struct modem_cellular_ case MODEM_CELLULAR_EVENT_SUSPEND: modem_cellular_enter_state(data, MODEM_CELLULAR_STATE_INIT_POWER_OFF); break; - + case MODEM_CELLULAR_EVENT_RING: + LOG_INF("RING received!"); + modem_pipe_open_async(data->uart_pipe); + break; default: break; } @@ -1332,7 +1345,10 @@ static void modem_cellular_carrier_on_event_handler(struct modem_cellular_data * modem_ppp_release(data->ppp); modem_cellular_enter_state(data, MODEM_CELLULAR_STATE_INIT_POWER_OFF); break; - + case MODEM_CELLULAR_EVENT_RING: + LOG_INF("RING received!"); + modem_pipe_open_async(data->uart_pipe); + break; default: break; } @@ -1377,6 +1393,7 @@ static int modem_cellular_on_dormant_state_leave(struct modem_cellular_data *dat static int modem_cellular_on_init_power_off_state_enter(struct modem_cellular_data *data) { + modem_cmux_disconnect_async(&data->cmux); modem_cellular_start_timer(data, K_MSEC(2000)); return 0; } @@ -1388,6 +1405,9 @@ static void modem_cellular_init_power_off_event_handler(struct modem_cellular_da (const struct modem_cellular_config *)data->dev->config; switch (evt) { + case MODEM_CELLULAR_EVENT_CMUX_DISCONNECTED: + modem_cellular_stop_timer(data); + __fallthrough; case MODEM_CELLULAR_EVENT_TIMEOUT: /* Shutdown script can only be used if cmd_pipe is available, i.e. we are not in * some intermediary state without a pipe for commands available @@ -1775,7 +1795,9 @@ static void modem_cellular_cmux_handler(struct modem_cmux *cmux, enum modem_cmux case MODEM_CMUX_EVENT_CONNECTED: modem_cellular_delegate_event(data, MODEM_CELLULAR_EVENT_CMUX_CONNECTED); break; - + case MODEM_CMUX_EVENT_DISCONNECTED: + modem_cellular_delegate_event(data, MODEM_CELLULAR_EVENT_CMUX_DISCONNECTED); + break; default: break; } @@ -2063,6 +2085,15 @@ static void net_mgmt_event_handler(struct net_mgmt_event_callback *cb, uint64_t } } +static void modem_cellular_ring_gpio_callback(const struct device *dev, struct gpio_callback *cb, + uint32_t pins) +{ + struct modem_cellular_data *data = + CONTAINER_OF(cb, struct modem_cellular_data, ring_gpio_cb); + + modem_cellular_delegate_event(data, MODEM_CELLULAR_EVENT_RING); +} + static void modem_cellular_init_apn(struct modem_cellular_data *data) { #ifdef CONFIG_MODEM_CELLULAR_APN @@ -2086,14 +2117,14 @@ static int modem_cellular_init(const struct device *dev) { struct modem_cellular_data *data = (struct modem_cellular_data *)dev->data; struct modem_cellular_config *config = (struct modem_cellular_config *)dev->config; + const struct gpio_dt_spec *dtr_gpio = NULL; data->dev = dev; k_mutex_init(&data->api_lock); k_work_init_delayable(&data->timeout_work, modem_cellular_timeout_handler); - k_work_init(&data->event_dispatch_work, modem_cellular_event_dispatch_handler); - ring_buf_init(&data->event_rb, sizeof(data->event_buf), data->event_buf); + k_pipe_init(&data->event_pipe, data->event_buf, sizeof(data->event_buf)); k_sem_init(&data->suspended_sem, 0, 1); @@ -2109,9 +2140,42 @@ static int modem_cellular_init(const struct device *dev) gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_ACTIVE); } + if (modem_cellular_gpio_is_enabled(&config->ring_gpio)) { + int ret; + + ret = gpio_pin_configure_dt(&config->ring_gpio, GPIO_INPUT); + if (ret < 0) { + LOG_ERR("Failed to configure ring GPIO (%d)", ret); + return ret; + } + + gpio_init_callback(&data->ring_gpio_cb, modem_cellular_ring_gpio_callback, + BIT(config->ring_gpio.pin)); + + ret = gpio_add_callback(config->ring_gpio.port, &data->ring_gpio_cb); + if (ret < 0) { + LOG_ERR("Failed to add ring GPIO callback (%d)", ret); + return ret; + } + + ret = gpio_pin_interrupt_configure_dt(&config->ring_gpio, GPIO_INT_EDGE_TO_ACTIVE); + if (ret < 0) { + LOG_ERR("Failed to configure ring GPIO interrupt (%d)", ret); + return ret; + } + + LOG_DBG("Ring GPIO interrupt configured"); + } + + if (modem_cellular_gpio_is_enabled(&config->dtr_gpio)) { + gpio_pin_configure_dt(&config->dtr_gpio, GPIO_OUTPUT_INACTIVE); + dtr_gpio = &config->dtr_gpio; + } + { const struct modem_backend_uart_config uart_backend_config = { .uart = config->uart, + .dtr_gpio = dtr_gpio, .receive_buf = data->uart_backend_receive_buf, .receive_buf_size = ARRAY_SIZE(data->uart_backend_receive_buf), .transmit_buf = data->uart_backend_transmit_buf, @@ -2132,6 +2196,9 @@ static int modem_cellular_init(const struct device *dev) .receive_buf_size = ARRAY_SIZE(data->cmux_receive_buf), .transmit_buf = data->cmux_transmit_buf, .transmit_buf_size = ARRAY_SIZE(data->cmux_transmit_buf), + .enable_runtime_power_management = config->cmux_enable_runtime_power_save, + .close_pipe_on_power_save = config->cmux_close_pipe_on_power_save, + .idle_timeout = config->cmux_idle_timeout, }; modem_cmux_init(&data->cmux, &cmux_config); @@ -2773,12 +2840,8 @@ MODEM_CHAT_SCRIPT_CMDS_DEFINE(nordic_nrf91_slm_dial_chat_script_cmds, MODEM_CHAT_SCRIPT_DEFINE(nordic_nrf91_slm_dial_chat_script, nordic_nrf91_slm_dial_chat_script_cmds, dial_abort_matches, modem_cellular_chat_callback_handler, 10); -MODEM_CHAT_SCRIPT_CMDS_DEFINE(nordic_nrf91_slm_periodic_chat_script_cmds, - MODEM_CHAT_SCRIPT_CMD_RESP("AT+CEREG?", ok_match)); +MODEM_CHAT_SCRIPT_EMPTY_DEFINE(nordic_nrf91_slm_periodic_chat_script); -MODEM_CHAT_SCRIPT_DEFINE(nordic_nrf91_slm_periodic_chat_script, - nordic_nrf91_slm_periodic_chat_script_cmds, abort_matches, - modem_cellular_chat_callback_handler, 4); #endif #if DT_HAS_COMPAT_STATUS_OKAY(sqn_gm02s) @@ -2873,26 +2936,30 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, /* Helper to define modem instance */ #define MODEM_CELLULAR_DEFINE_INSTANCE(inst, power_ms, reset_ms, startup_ms, shutdown_ms, start, \ - set_baudrate_script, \ - init_script, \ - dial_script, \ - periodic_script, \ - shutdown_script) \ + set_baudrate_script, init_script, dial_script, \ + periodic_script, shutdown_script) \ static const struct modem_cellular_config MODEM_CELLULAR_INST_NAME(config, inst) = { \ .uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \ .power_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_power_gpios, {}), \ .reset_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_reset_gpios, {}), \ .wake_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_wake_gpios, {}), \ + .ring_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_ring_gpios, {}), \ + .dtr_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_dtr_gpios, {}), \ .power_pulse_duration_ms = (power_ms), \ .reset_pulse_duration_ms = (reset_ms), \ - .startup_time_ms = (startup_ms), \ + .startup_time_ms = (startup_ms), \ .shutdown_time_ms = (shutdown_ms), \ .autostarts = DT_INST_PROP_OR(inst, autostarts, (start)), \ - .set_baudrate_chat_script = (set_baudrate_script), \ - .init_chat_script = (init_script), \ - .dial_chat_script = (dial_script), \ + .cmux_enable_runtime_power_save = \ + DT_INST_PROP_OR(inst, cmux_enable_runtime_power_save, 0), \ + .cmux_close_pipe_on_power_save = \ + DT_INST_PROP_OR(inst, cmux_close_pipe_on_power_save, 0), \ + .cmux_idle_timeout = K_MSEC(DT_INST_PROP_OR(inst, cmux_idle_timeout_ms, 0)), \ + .set_baudrate_chat_script = (set_baudrate_script), \ + .init_chat_script = (init_script), \ + .dial_chat_script = (dial_script), \ .periodic_chat_script = (periodic_script), \ - .shutdown_chat_script = (shutdown_script), \ + .shutdown_chat_script = (shutdown_script), \ .user_pipes = MODEM_CELLULAR_GET_USER_PIPES(inst), \ .user_pipes_size = ARRAY_SIZE(MODEM_CELLULAR_GET_USER_PIPES(inst)), \ }; \ @@ -2905,7 +2972,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &modem_cellular_api); #define MODEM_CELLULAR_DEVICE_QUECTEL_BG9X(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -2925,7 +2992,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &quectel_bg9x_shutdown_chat_script) #define MODEM_CELLULAR_DEVICE_QUECTEL_EG25_G(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -2944,7 +3011,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &quectel_eg25_g_periodic_chat_script, NULL) #define MODEM_CELLULAR_DEVICE_QUECTEL_EG800Q(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -2963,7 +3030,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &quectel_eg800q_periodic_chat_script, NULL) #define MODEM_CELLULAR_DEVICE_SIMCOM_SIM7080(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -2982,7 +3049,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &simcom_sim7080_periodic_chat_script, NULL) #define MODEM_CELLULAR_DEVICE_SIMCOM_A76XX(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -3002,7 +3069,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &simcom_a76xx_shutdown_chat_script) #define MODEM_CELLULAR_DEVICE_U_BLOX_SARA_R4(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -3021,7 +3088,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &u_blox_sara_r4_periodic_chat_script, NULL) #define MODEM_CELLULAR_DEVICE_U_BLOX_SARA_R5(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -3040,7 +3107,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &u_blox_sara_r5_periodic_chat_script, NULL) #define MODEM_CELLULAR_DEVICE_U_BLOX_LARA_R6(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -3060,7 +3127,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, NULL) #define MODEM_CELLULAR_DEVICE_SWIR_HL7800(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -3079,7 +3146,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &swir_hl7800_periodic_chat_script, NULL) #define MODEM_CELLULAR_DEVICE_TELIT_ME910G1(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -3098,7 +3165,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, NULL) #define MODEM_CELLULAR_DEVICE_TELIT_ME310G1(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ @@ -3117,7 +3184,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &telit_me310g1_shutdown_chat_script) #define MODEM_CELLULAR_DEVICE_NORDIC_NRF91_SLM(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 1500); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 1500); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r\n", \ @@ -3131,10 +3198,11 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, NULL, \ &nordic_nrf91_slm_init_chat_script, \ &nordic_nrf91_slm_dial_chat_script, \ - &nordic_nrf91_slm_periodic_chat_script, NULL) + &nordic_nrf91_slm_periodic_chat_script, \ + NULL) #define MODEM_CELLULAR_DEVICE_SQN_GM02S(inst) \ - MODEM_PPP_DEFINE(MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ + MODEM_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ \ static struct modem_cellular_data MODEM_CELLULAR_INST_NAME(data, inst) = { \ .chat_delimiter = "\r", \ diff --git a/dts/bindings/modem/nordic,nrf91-slm.yaml b/dts/bindings/modem/nordic,nrf91-slm.yaml index 3f06696197d2..11bf39018696 100644 --- a/dts/bindings/modem/nordic,nrf91-slm.yaml +++ b/dts/bindings/modem/nordic,nrf91-slm.yaml @@ -2,8 +2,4 @@ description: Nordic nRF91 series running the Serial LTE Modem application compatible: "nordic,nrf91-slm" -include: uart-device.yaml - -properties: - mdm-power-gpios: - type: phandle-array +include: zephyr,cellular-modem-device.yaml diff --git a/dts/bindings/modem/quectel,bg95.yaml b/dts/bindings/modem/quectel,bg95.yaml index 9b1f6c734be4..940cd41ea27e 100644 --- a/dts/bindings/modem/quectel,bg95.yaml +++ b/dts/bindings/modem/quectel,bg95.yaml @@ -5,7 +5,7 @@ description: Quectel BG95 modem compatible: "quectel,bg95" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: diff --git a/dts/bindings/modem/quectel,bg9x.yaml b/dts/bindings/modem/quectel,bg9x.yaml index 836df0ba36e5..41f32d0d4c26 100644 --- a/dts/bindings/modem/quectel,bg9x.yaml +++ b/dts/bindings/modem/quectel,bg9x.yaml @@ -5,16 +5,13 @@ description: quectel BG9x modem compatible: "quectel,bg9x" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: type: phandle-array required: true - mdm-reset-gpios: - type: phandle-array - mdm-dtr-gpios: type: phandle-array diff --git a/dts/bindings/modem/quectel,eg25-g.yaml b/dts/bindings/modem/quectel,eg25-g.yaml index 45284ddf174b..2e688e587dea 100644 --- a/dts/bindings/modem/quectel,eg25-g.yaml +++ b/dts/bindings/modem/quectel,eg25-g.yaml @@ -2,7 +2,7 @@ description: Quectel EG25-G modem compatible: "quectel,eg25-g" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-reset-gpios: diff --git a/dts/bindings/modem/quectel,eg800q.yaml b/dts/bindings/modem/quectel,eg800q.yaml index 80c591b90350..6b86456bcc91 100644 --- a/dts/bindings/modem/quectel,eg800q.yaml +++ b/dts/bindings/modem/quectel,eg800q.yaml @@ -2,7 +2,7 @@ description: Quectel EG800Q modem compatible: "quectel,eg800q" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: diff --git a/dts/bindings/modem/simcom,a76xx.yaml b/dts/bindings/modem/simcom,a76xx.yaml index 586b356de474..428977b42a13 100644 --- a/dts/bindings/modem/simcom,a76xx.yaml +++ b/dts/bindings/modem/simcom,a76xx.yaml @@ -5,7 +5,7 @@ description: Simcom A76XX modem compatible: "simcom,a76xx" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: diff --git a/dts/bindings/modem/simcom,sim7080.yaml b/dts/bindings/modem/simcom,sim7080.yaml index 952ce6b514b6..deb5a0ec4dd6 100644 --- a/dts/bindings/modem/simcom,sim7080.yaml +++ b/dts/bindings/modem/simcom,sim7080.yaml @@ -5,7 +5,7 @@ description: Simcom Sim7080 modem compatible: "simcom,sim7080" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: diff --git a/dts/bindings/modem/sqn,gm02s.yaml b/dts/bindings/modem/sqn,gm02s.yaml index 584fc878e18d..4875f6dcc471 100644 --- a/dts/bindings/modem/sqn,gm02s.yaml +++ b/dts/bindings/modem/sqn,gm02s.yaml @@ -5,12 +5,9 @@ description: Sequans Monarch 2 GM02S Modem compatible: "sqn,gm02s" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-reset-gpios: type: phandle-array required: true - - mdm-wake-gpios: - type: phandle-array diff --git a/dts/bindings/modem/swir,hl7800.yaml b/dts/bindings/modem/swir,hl7800.yaml index b937031455ff..3daf4b383d38 100644 --- a/dts/bindings/modem/swir,hl7800.yaml +++ b/dts/bindings/modem/swir,hl7800.yaml @@ -8,7 +8,7 @@ description: Sierra Wireless HL7800 Modem compatible: "swir,hl7800" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-wake-gpios: diff --git a/dts/bindings/modem/telit,me310g1.yaml b/dts/bindings/modem/telit,me310g1.yaml index 2ee36fce5958..ff354dcc2951 100644 --- a/dts/bindings/modem/telit,me310g1.yaml +++ b/dts/bindings/modem/telit,me310g1.yaml @@ -2,7 +2,7 @@ description: Telit ME310G1 Modem compatible: "telit,me310g1" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: diff --git a/dts/bindings/modem/telit,me910g1.yaml b/dts/bindings/modem/telit,me910g1.yaml index 2599b6cd237d..5ffa10af0347 100644 --- a/dts/bindings/modem/telit,me910g1.yaml +++ b/dts/bindings/modem/telit,me910g1.yaml @@ -5,7 +5,7 @@ description: Telit ME910G1 Modem compatible: "telit,me910g1" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: diff --git a/dts/bindings/modem/u-blox,lara-r6.yaml b/dts/bindings/modem/u-blox,lara-r6.yaml index 89e02d86dba1..12c802391e6b 100644 --- a/dts/bindings/modem/u-blox,lara-r6.yaml +++ b/dts/bindings/modem/u-blox,lara-r6.yaml @@ -5,12 +5,9 @@ description: u-blox LARA-R6 modem compatible: "u-blox,lara-r6" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: type: phandle-array required: true - - mdm-reset-gpios: - type: phandle-array diff --git a/dts/bindings/modem/u-blox,sara-r4.yaml b/dts/bindings/modem/u-blox,sara-r4.yaml index f24bc57e0a74..c9ec63dc7381 100644 --- a/dts/bindings/modem/u-blox,sara-r4.yaml +++ b/dts/bindings/modem/u-blox,sara-r4.yaml @@ -5,15 +5,12 @@ description: u-blox SARA-R4 modem compatible: "u-blox,sara-r4" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-power-gpios: type: phandle-array required: true - mdm-reset-gpios: - type: phandle-array - mdm-vint-gpios: type: phandle-array diff --git a/dts/bindings/modem/u-blox,sara-r5.yaml b/dts/bindings/modem/u-blox,sara-r5.yaml index af6c3318fd07..40e846fd4eae 100644 --- a/dts/bindings/modem/u-blox,sara-r5.yaml +++ b/dts/bindings/modem/u-blox,sara-r5.yaml @@ -5,11 +5,4 @@ description: u-blox SARA-R5 modem compatible: "u-blox,sara-r5" -include: uart-device.yaml - -properties: - mdm-power-gpios: - type: phandle-array - - mdm-reset-gpios: - type: phandle-array +include: zephyr,cellular-modem-device.yaml diff --git a/dts/bindings/modem/wnc,m14a2a.yaml b/dts/bindings/modem/wnc,m14a2a.yaml index db00b6fbeb64..71f9d2775938 100644 --- a/dts/bindings/modem/wnc,m14a2a.yaml +++ b/dts/bindings/modem/wnc,m14a2a.yaml @@ -5,7 +5,7 @@ description: WNC-M14A2A LTE-M modem compatible: "wnc,m14a2a" -include: uart-device.yaml +include: zephyr,cellular-modem-device.yaml properties: mdm-boot-mode-sel-gpios: diff --git a/dts/bindings/modem/zephyr,cellular-modem-device.yaml b/dts/bindings/modem/zephyr,cellular-modem-device.yaml new file mode 100644 index 000000000000..d2ab0056841b --- /dev/null +++ b/dts/bindings/modem/zephyr,cellular-modem-device.yaml @@ -0,0 +1,50 @@ +# Copyright 2025 Nordic Semiconductor ASA +# SPDX-License-Identifier: Apache-2.0 + +description: Common properties for Zephyr cellular modems + +include: uart-device.yaml + +properties: + mdm-power-gpios: + type: phandle-array + description: GPIO for modem power control + + mdm-reset-gpios: + type: phandle-array + description: GPIO for modem reset + + mdm-wake-gpios: + type: phandle-array + description: GPIO for modem wake + + mdm-ring-gpios: + type: phandle-array + description: GPIO for modem ring indicator + + mdm-dtr-gpios: + type: phandle-array + description: | + GPIO for modem data terminal ready. + + Asserted (logical high) when UART is active and + deasserted (logical low) when UART is inactive, powered down or in low power mode. + + cmux-enable-runtime-power-save: + type: boolean + description: | + Enable runtime power saving using CMUX PSC commands. + This requires modem to support CMUX and PSC commands while keeping the data + connection active. + + cmux-close-pipe-on-power-save: + type: boolean + description: | + Close the modem pipe when entering power save mode. + When runtime power management is enabled, this closes the UART. + This requires modem to support waking up the UART using RING signal. + + cmux-idle-timeout-ms: + type: int + description: Time in milliseconds after which CMUX will enter power save mode. + default: 10000 diff --git a/include/zephyr/modem/at/user_pipe.h b/include/zephyr/modem/at/user_pipe.h new file mode 100644 index 000000000000..9732f5131111 --- /dev/null +++ b/include/zephyr/modem/at/user_pipe.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2025 Embeint Holdings Pty Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_MODEM_AT_USER_PIPE_ +#define ZEPHYR_MODEM_AT_USER_PIPE_ + +#include + +/** + * @brief Initialise the AT command user pipe + * + * @param chat Chat instance that will be used with the user pipe + */ +void modem_at_user_pipe_init(struct modem_chat *chat); + +/** + * @brief Claim the AT command user pipe to run commands + * + * + * @retval 0 On success + * @retval -EPERM Modem is not ready + * @retval -EBUSY User pipe already claimed + */ +int modem_at_user_pipe_claim(void); + +/** + * @brief Release the AT command user pipe to other users + * + * Must be called after @ref modem_at_user_pipe_claim when pipe is no longer + * in use. + */ +void modem_at_user_pipe_release(void); + +#endif /* ZEPHYR_MODEM_AT_USER_PIPE_ */ diff --git a/include/zephyr/modem/backend/uart.h b/include/zephyr/modem/backend/uart.h index 73054b792d8e..6407af8cf6af 100644 --- a/include/zephyr/modem/backend/uart.h +++ b/include/zephyr/modem/backend/uart.h @@ -68,6 +68,7 @@ struct modem_backend_uart_async { struct modem_backend_uart { const struct device *uart; + const struct gpio_dt_spec *dtr_gpio; struct modem_pipe pipe; struct k_work_delayable receive_ready_work; struct k_work transmit_idle_work; @@ -85,6 +86,7 @@ struct modem_backend_uart { struct modem_backend_uart_config { const struct device *uart; + const struct gpio_dt_spec *dtr_gpio; /* Address must be word-aligned when CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC is enabled. */ uint8_t *receive_buf; uint32_t receive_buf_size; diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h index 3332c40bd18e..0faa8d5d4b38 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -53,14 +53,53 @@ enum modem_cmux_event { typedef void (*modem_cmux_callback)(struct modem_cmux *cmux, enum modem_cmux_event event, void *user_data); +/** + * @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; + /** Enable runtime power management */ + bool enable_runtime_power_management; + /** Close pipe on power save */ + bool close_pipe_on_power_save; + /** Idle timeout for power save */ + k_timeout_t idle_timeout; +}; + /** * @cond INTERNAL_HIDDEN */ +#if CONFIG_MODEM_CMUX_MTU > 127 +#define MODEM_CMUX_HEADER_SIZE 7 +#else +#define MODEM_CMUX_HEADER_SIZE 6 +#endif + + +/* Total size of the CMUX work buffers */ +#define MODEM_CMUX_WORK_BUFFER_SIZE (CONFIG_MODEM_CMUX_MTU + MODEM_CMUX_HEADER_SIZE + \ + CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE_EXTRA) + enum modem_cmux_state { MODEM_CMUX_STATE_DISCONNECTED = 0, MODEM_CMUX_STATE_CONNECTING, MODEM_CMUX_STATE_CONNECTED, + MODEM_CMUX_STATE_ENTER_POWERSAVE, + MODEM_CMUX_STATE_POWERSAVE, + MODEM_CMUX_STATE_CONFIRM_POWERSAVE, + MODEM_CMUX_STATE_WAKEUP, MODEM_CMUX_STATE_DISCONNECTING, }; @@ -74,7 +113,6 @@ enum modem_cmux_receive_state { 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, }; @@ -110,6 +148,10 @@ struct modem_cmux_dlci { #if CONFIG_MODEM_STATS struct modem_stats_buffer receive_buf_stats; #endif + /* Flow control */ + bool flow_control : 1; + bool rx_full : 1; + bool msc_sent : 1; }; struct modem_cmux_frame { @@ -130,30 +172,23 @@ 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; + bool flow_control_on : 1; + bool initiator : 1; /* Work lock */ - bool attached; + bool attached : 1; struct k_spinlock work_lock; /* Receive state*/ enum modem_cmux_receive_state receive_state; - - /* Receive buffer */ - uint8_t *receive_buf; - uint16_t receive_buf_size; uint16_t receive_buf_len; - uint8_t work_buf[CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE]; + uint8_t work_buf[MODEM_CMUX_WORK_BUFFER_SIZE]; /* Transmit buffer */ struct ring_buf transmit_rb; @@ -169,39 +204,25 @@ struct modem_cmux { struct k_work_delayable transmit_work; struct k_work_delayable connect_work; struct k_work_delayable disconnect_work; + struct k_work_delayable runtime_pm_work; /* Synchronize actions */ struct k_event event; + k_timepoint_t t3_timepoint; + k_timepoint_t idle_timepoint; /* Statistics */ #if CONFIG_MODEM_STATS struct modem_stats_buffer receive_buf_stats; struct modem_stats_buffer transmit_buf_stats; #endif + struct modem_cmux_config config; }; /** * @endcond */ -/** - * @brief Contains CMUX instance configuration data - */ -struct modem_cmux_config { - /** Invoked when event occurs */ - modem_cmux_callback callback; - /** Free to use pointer passed to event handler when invoked */ - void *user_data; - /** Receive buffer */ - uint8_t *receive_buf; - /** Size of receive buffer in bytes [127, ...] */ - uint16_t receive_buf_size; - /** Transmit buffer */ - uint8_t *transmit_buf; - /** Size of transmit buffer in bytes [149, ...] */ - uint16_t transmit_buf_size; -}; - /** * @brief Initialize CMUX instance * @param cmux CMUX instance diff --git a/include/zephyr/modem/ppp.h b/include/zephyr/modem/ppp.h index 2b91e51a35c9..69b35897e2d2 100644 --- a/include/zephyr/modem/ppp.h +++ b/include/zephyr/modem/ppp.h @@ -50,27 +50,10 @@ enum modem_ppp_receive_state { }; 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_PROTOCOL, 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, }; @@ -100,8 +83,6 @@ struct modem_ppp { /* 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 */ @@ -123,6 +104,10 @@ struct modem_ppp { #endif }; +struct modem_ppp_config { + const struct device *dev; +}; + /** * @endcond */ @@ -172,13 +157,17 @@ int modem_ppp_init_internal(const struct device *dev); * network device instance, and binds the modem_ppp instance to the PPP L2 * instance. * + * If underlying cellular device is given, the PPP interface will manage the + * power state of the cellular device when starting and stopping the PPP. + * + * @param _dev Cellular device instance for power management or NULL if not used * @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) \ +#define MODEM_DEV_PPP_DEFINE(_dev, _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]; \ @@ -189,11 +178,30 @@ int modem_ppp_init_internal(const struct device *dev); .receive_buf = _CONCAT(_name, _receive_buf), \ .transmit_buf = _CONCAT(_name, _transmit_buf), \ .buf_size = _buf_size, \ + }; \ + static const struct modem_ppp_config _CONCAT(_name, _config) = { \ + .dev = _dev, \ }; \ \ - 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) + NET_DEVICE_INIT(_CONCAT(ppp_net_dev_, _name), "modem_ppp_" #_name, \ + modem_ppp_init_internal, NULL, &_name, &_CONCAT(_name, _config), _prio, \ + &modem_ppp_ppp_api, PPP_L2, NET_L2_GET_CTX_TYPE(PPP_L2), _mtu) + +/** + * @brief Define a modem PPP module for cellular device tree instance. + * + * @see MODEM_DEV_PPP_DEFINE + */ +#define MODEM_DT_INST_PPP_DEFINE(inst, _name, _init_iface, _prio, _mtu, _buf_size) \ + MODEM_DEV_PPP_DEFINE(DEVICE_DT_INST_GET(inst), _name, _init_iface, _prio, _mtu, _buf_size) + +/** + * @brief Define a modem PPP module without a device and bind it to a network interface. + * + * @see MODEM_DEV_PPP_DEFINE + */ +#define MODEM_PPP_DEFINE(_name, _init_iface, _prio, _mtu, _buf_size) \ + MODEM_DEV_PPP_DEFINE(NULL, _name, _init_iface, _prio, _mtu, _buf_size) /** * @} diff --git a/subsys/modem/CMakeLists.txt b/subsys/modem/CMakeLists.txt index c374bcab9cff..a1db3ab90361 100644 --- a/subsys/modem/CMakeLists.txt +++ b/subsys/modem/CMakeLists.txt @@ -5,6 +5,7 @@ if(CONFIG_MODEM_MODULES) zephyr_library() +zephyr_library_sources_ifdef(CONFIG_MODEM_DEDICATED_WORKQUEUE modem_workqueue.c) 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) diff --git a/subsys/modem/Kconfig b/subsys/modem/Kconfig index b7c3f7ba9014..8b4a411c26f6 100644 --- a/subsys/modem/Kconfig +++ b/subsys/modem/Kconfig @@ -6,6 +6,21 @@ menuconfig MODEM_MODULES if MODEM_MODULES +config MODEM_DEDICATED_WORKQUEUE + bool "Use a dedicated workqueue for modem operations" + +if MODEM_DEDICATED_WORKQUEUE + +config MODEM_DEDICATED_WORKQUEUE_STACK_SIZE + int "Modem dedicated workqueue stack size" + default 1024 + +config MODEM_DEDICATED_WORKQUEUE_PRIORITY + int "Modem dedicated workqueue priority" + default SYSTEM_WORKQUEUE_PRIORITY + +endif # MODEM_DEDICATED_WORKQUEUE + config MODEM_CHAT bool "Modem chat module" select RING_BUFFER @@ -42,19 +57,31 @@ config MODEM_CMUX_MTU Maximum Transmission Unit (MTU) size for the CMUX module. Linux ldattach defaults to 127 bytes, 3GPP TS 27.010 to 31. -config MODEM_CMUX_WORK_BUFFER_SIZE - int "CMUX module work buffer size in bytes" - range 23 1507 - default 134 if MODEM_CMUX_DEFAULT_MTU_127 - default 38 +config MODEM_CMUX_WORK_BUFFER_SIZE_EXTRA + int "CMUX module extra work buffer size in bytes" + default 0 help - Size of the work buffer used by the CMUX module. - Recommended size is MODEM_CMUX_MTU + 7 (CMUX header size). + Extra bytes to add to the work buffers used by the CMUX module. + The default size of these buffers is MODEM_CMUX_MTU + 7 (CMUX header size). + +config MODEM_CMUX_T3_TIMEOUT + int "CMUX T3 timeout in seconds" + range 1 255 + default 10 + help + Response Timer for wake-up procedure(T3). + Time in seconds before the link is considered dead. module = MODEM_CMUX module-str = modem_cmux source "subsys/logging/Kconfig.template.log_config" +config MODEM_CMUX_LOG_FRAMES + bool "Log CMUX frames" + depends on MODEM_CMUX_LOG_LEVEL_DBG + help + Enable logging of CMUX frames. This can produce a lot of log data. + endif config MODEM_PIPE @@ -71,6 +98,7 @@ config MODEM_PPP select MODEM_PIPE select RING_BUFFER select CRC + select PM_DEVICE_RUNTIME_ASYNC if PM_DEVICE_RUNTIME if MODEM_PPP diff --git a/subsys/modem/backends/modem_backend_uart.c b/subsys/modem/backends/modem_backend_uart.c index 82242a76c019..13a35406f182 100644 --- a/subsys/modem/backends/modem_backend_uart.c +++ b/subsys/modem/backends/modem_backend_uart.c @@ -39,6 +39,7 @@ struct modem_pipe *modem_backend_uart_init(struct modem_backend_uart *backend, memset(backend, 0x00, sizeof(*backend)); backend->uart = config->uart; + backend->dtr_gpio = config->dtr_gpio; k_work_init_delayable(&backend->receive_ready_work, modem_backend_uart_receive_ready_handler); k_work_init(&backend->transmit_idle_work, modem_backend_uart_transmit_idle_handler); diff --git a/subsys/modem/backends/modem_backend_uart_async.c b/subsys/modem/backends/modem_backend_uart_async.c index d96f513d6061..0cfcff9e86ed 100644 --- a/subsys/modem/backends/modem_backend_uart_async.c +++ b/subsys/modem/backends/modem_backend_uart_async.c @@ -5,11 +5,14 @@ */ #include "modem_backend_uart_async.h" +#include "../modem_workqueue.h" #include LOG_MODULE_REGISTER(modem_backend_uart_async, CONFIG_MODEM_MODULES_LOG_LEVEL); #include +#include +#include #include enum { @@ -58,7 +61,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, case UART_TX_DONE: atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT); - k_work_submit(&backend->transmit_idle_work); + modem_work_submit(&backend->transmit_idle_work); break; case UART_TX_ABORTED: @@ -67,7 +70,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, } atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_TRANSMITTING_BIT); - k_work_submit(&backend->transmit_idle_work); + modem_work_submit(&backend->transmit_idle_work); break; @@ -127,7 +130,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, } k_spin_unlock(&backend->async.receive_rb_lock, key); - k_work_schedule(&backend->receive_ready_work, K_NO_WAIT); + modem_work_schedule(&backend->receive_ready_work, K_NO_WAIT); break; case UART_RX_DISABLED: @@ -144,7 +147,7 @@ static void modem_backend_uart_async_event_handler(const struct device *dev, } if (modem_backend_uart_async_is_uart_stopped(backend)) { - k_work_submit(&backend->async.common.rx_disabled_work); + modem_work_submit(&backend->async.common.rx_disabled_work); } } @@ -156,6 +159,15 @@ static int modem_backend_uart_async_open(void *data) atomic_clear(&backend->async.common.state); ring_buf_reset(&backend->async.receive_rb); + ret = pm_device_runtime_get(backend->uart); + if (ret < 0) { + LOG_ERR("Failed to power on UART: %d", ret); + return ret; + } + if (backend->dtr_gpio) { + gpio_pin_set_dt(backend->dtr_gpio, 1); + } + atomic_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT); atomic_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RECEIVING_BIT); @@ -254,7 +266,7 @@ static int modem_backend_uart_async_receive(void *data, uint8_t *buf, size_t siz k_spin_unlock(&backend->async.receive_rb_lock, key); if (!empty) { - k_work_schedule(&backend->receive_ready_work, K_NO_WAIT); + modem_work_schedule(&backend->receive_ready_work, K_NO_WAIT); } return (int)received; @@ -263,10 +275,20 @@ static int modem_backend_uart_async_receive(void *data, uint8_t *buf, size_t siz static int modem_backend_uart_async_close(void *data) { struct modem_backend_uart *backend = (struct modem_backend_uart *)data; + int ret; atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); uart_tx_abort(backend->uart); uart_rx_disable(backend->uart); + if (backend->dtr_gpio) { + gpio_pin_set_dt(backend->dtr_gpio, 0); + } + ret = pm_device_runtime_put_async(backend->uart, K_NO_WAIT); + if (ret < 0) { + LOG_ERR("Failed to power off UART: %d", ret); + return ret; + } + modem_pipe_notify_closed(&backend->pipe); return 0; } diff --git a/subsys/modem/backends/modem_backend_uart_async_hwfc.c b/subsys/modem/backends/modem_backend_uart_async_hwfc.c index 78855daa4eb3..df69c40b6679 100644 --- a/subsys/modem/backends/modem_backend_uart_async_hwfc.c +++ b/subsys/modem/backends/modem_backend_uart_async_hwfc.c @@ -5,11 +5,14 @@ */ #include "modem_backend_uart_async.h" +#include "../modem_workqueue.h" #include LOG_MODULE_REGISTER(modem_backend_uart_async_hwfc, CONFIG_MODEM_MODULES_LOG_LEVEL); #include +#include +#include #include struct rx_buf_t { @@ -136,7 +139,7 @@ static void modem_backend_uart_async_hwfc_event_handler(const struct device *dev case UART_TX_DONE: atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_TRANSMIT_BIT); - k_work_submit(&backend->transmit_idle_work); + modem_work_submit(&backend->transmit_idle_work); break; case UART_TX_ABORTED: @@ -145,7 +148,7 @@ static void modem_backend_uart_async_hwfc_event_handler(const struct device *dev } atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_TRANSMIT_BIT); - k_work_submit(&backend->transmit_idle_work); + modem_work_submit(&backend->transmit_idle_work); break; @@ -182,7 +185,7 @@ static void modem_backend_uart_async_hwfc_event_handler(const struct device *dev rx_buf_unref(&backend->async, evt->data.rx.buf); break; } - k_work_schedule(&backend->receive_ready_work, K_NO_WAIT); + modem_work_schedule(&backend->receive_ready_work, K_NO_WAIT); } break; @@ -191,7 +194,7 @@ static void modem_backend_uart_async_hwfc_event_handler(const struct device *dev MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT)) { if (!atomic_test_and_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RECOVERY_BIT)) { - k_work_schedule(&backend->receive_ready_work, K_NO_WAIT); + modem_work_schedule(&backend->receive_ready_work, K_NO_WAIT); LOG_DBG("RX recovery started"); } } @@ -206,7 +209,7 @@ static void modem_backend_uart_async_hwfc_event_handler(const struct device *dev } if (modem_backend_uart_async_hwfc_is_uart_stopped(backend)) { - k_work_submit(&backend->async.common.rx_disabled_work); + modem_work_submit(&backend->async.common.rx_disabled_work); } } @@ -220,6 +223,15 @@ static int modem_backend_uart_async_hwfc_open(void *data) return -ENOMEM; } + ret = pm_device_runtime_get(backend->uart); + if (ret < 0) { + LOG_ERR("Failed to power on UART: %d", ret); + return ret; + } + if (backend->dtr_gpio) { + gpio_pin_set_dt(backend->dtr_gpio, 1); + } + atomic_clear(&backend->async.common.state); atomic_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); @@ -335,7 +347,7 @@ static int modem_backend_uart_async_hwfc_receive(void *data, uint8_t *buf, size_ if (backend->async.rx_event.len != 0 || k_msgq_num_used_get(&backend->async.rx_queue) != 0) { - k_work_schedule(&backend->receive_ready_work, K_NO_WAIT); + modem_work_schedule(&backend->receive_ready_work, K_NO_WAIT); } modem_backend_uart_async_hwfc_rx_recovery(backend); @@ -346,6 +358,7 @@ static int modem_backend_uart_async_hwfc_receive(void *data, uint8_t *buf, size_ static int modem_backend_uart_async_hwfc_close(void *data) { struct modem_backend_uart *backend = (struct modem_backend_uart *)data; + int ret; atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT); uart_tx_abort(backend->uart); @@ -356,6 +369,16 @@ static int modem_backend_uart_async_hwfc_close(void *data) uart_rx_disable(backend->uart); } + if (backend->dtr_gpio) { + gpio_pin_set_dt(backend->dtr_gpio, 0); + } + ret = pm_device_runtime_put_async(backend->uart, K_NO_WAIT); + if (ret < 0) { + LOG_ERR("Failed to power off UART: %d", ret); + return ret; + } + modem_pipe_notify_closed(&backend->pipe); + return 0; } diff --git a/subsys/modem/backends/modem_backend_uart_isr.c b/subsys/modem/backends/modem_backend_uart_isr.c index a9266cc86657..6c6c27c2db2e 100644 --- a/subsys/modem/backends/modem_backend_uart_isr.c +++ b/subsys/modem/backends/modem_backend_uart_isr.c @@ -5,6 +5,7 @@ */ #include "modem_backend_uart_isr.h" +#include "../modem_workqueue.h" #include LOG_MODULE_REGISTER(modem_backend_uart_isr, CONFIG_MODEM_MODULES_LOG_LEVEL); @@ -54,11 +55,11 @@ static void modem_backend_uart_isr_irq_handler_receive_ready(struct modem_backen * It temporarily disables the UART RX IRQ when swapping buffers * which can cause byte loss at higher baud rates. */ - k_work_schedule(&backend->receive_ready_work, - K_MSEC(CONFIG_MODEM_BACKEND_UART_ISR_RECEIVE_IDLE_TIMEOUT_MS)); + modem_work_schedule(&backend->receive_ready_work, + K_MSEC(CONFIG_MODEM_BACKEND_UART_ISR_RECEIVE_IDLE_TIMEOUT_MS)); } else { /* The buffer is getting full. Run the work item immediately to free up space. */ - k_work_reschedule(&backend->receive_ready_work, K_NO_WAIT); + modem_work_reschedule(&backend->receive_ready_work, K_NO_WAIT); } } @@ -70,7 +71,7 @@ static void modem_backend_uart_isr_irq_handler_transmit_ready(struct modem_backe if (ring_buf_is_empty(&backend->isr.transmit_rb) == true) { uart_irq_tx_disable(backend->uart); - k_work_submit(&backend->transmit_idle_work); + modem_work_submit(&backend->transmit_idle_work); return; } diff --git a/subsys/modem/modem_chat.c b/subsys/modem/modem_chat.c index 088a2bc038dc..001be1c7e707 100644 --- a/subsys/modem/modem_chat.c +++ b/subsys/modem/modem_chat.c @@ -15,6 +15,8 @@ LOG_MODULE_REGISTER(modem_chat, CONFIG_MODEM_MODULES_LOG_LEVEL); #include +#include "modem_workqueue.h" + const struct modem_chat_match modem_chat_any_match = MODEM_CHAT_MATCH("", "", NULL); const struct modem_chat_match modem_chat_empty_matches[0]; const struct modem_chat_script_chat modem_chat_empty_script_chats[0]; @@ -127,7 +129,7 @@ static void modem_chat_set_script_send_state(struct modem_chat *chat, static void modem_chat_script_send(struct modem_chat *chat) { modem_chat_set_script_send_state(chat, MODEM_CHAT_SCRIPT_SEND_STATE_REQUEST); - k_work_submit(&chat->script_send_work); + modem_work_submit(&chat->script_send_work); } static void modem_chat_script_set_response_matches(struct modem_chat *chat) @@ -178,7 +180,7 @@ static void modem_chat_script_chat_schedule_send_timeout(struct modem_chat *chat { uint16_t timeout = modem_chat_script_chat_get_send_timeout(chat); - k_work_schedule(&chat->script_send_timeout_work, K_MSEC(timeout)); + modem_work_schedule(&chat->script_send_timeout_work, K_MSEC(timeout)); } static void modem_chat_script_next(struct modem_chat *chat, bool initial) @@ -233,7 +235,7 @@ static void modem_chat_script_start(struct modem_chat *chat, const struct modem_ /* Start timeout work if script started */ if (chat->script != NULL) { - k_work_schedule(&chat->script_timeout_work, K_SECONDS(chat->script->timeout)); + modem_work_schedule(&chat->script_timeout_work, K_SECONDS(chat->script->timeout)); } } @@ -742,7 +744,7 @@ static void modem_chat_process_handler(struct k_work *item) /* Process data */ modem_chat_process_bytes(chat); - k_work_submit(&chat->receive_work); + modem_work_submit(&chat->receive_work); } static void modem_chat_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_event event, @@ -752,11 +754,11 @@ static void modem_chat_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_ev switch (event) { case MODEM_PIPE_EVENT_RECEIVE_READY: - k_work_submit(&chat->receive_work); + modem_work_submit(&chat->receive_work); break; case MODEM_PIPE_EVENT_TRANSMIT_IDLE: - k_work_submit(&chat->script_send_work); + modem_work_submit(&chat->script_send_work); break; default: @@ -880,7 +882,7 @@ int modem_chat_run_script_async(struct modem_chat *chat, const struct modem_chat k_sem_reset(&chat->script_stopped_sem); chat->pending_script = script; - k_work_submit(&chat->script_run_work); + modem_work_submit(&chat->script_run_work); return 0; } @@ -903,7 +905,7 @@ int modem_chat_run_script(struct modem_chat *chat, const struct modem_chat_scrip void modem_chat_script_abort(struct modem_chat *chat) { - k_work_submit(&chat->script_abort_work); + modem_work_submit(&chat->script_abort_work); } void modem_chat_release(struct modem_chat *chat) diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index d5547e03da28..1edf3feaff41 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -10,28 +10,36 @@ LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL); #include #include #include +#include +#include #include +#include "modem_workqueue.h" + +#define MODEM_CMUX_SOF (0xF9) #define MODEM_CMUX_FCS_POLYNOMIAL (0xE0) #define MODEM_CMUX_FCS_INIT_VALUE (0xFF) #define MODEM_CMUX_EA (0x01) #define MODEM_CMUX_CR (0x02) #define MODEM_CMUX_PF (0x10) -#define MODEM_CMUX_FRAME_SIZE_MAX (0x07) -#define MODEM_CMUX_DATA_SIZE_MIN (0x08) -#define MODEM_CMUX_DATA_FRAME_SIZE_MIN (MODEM_CMUX_FRAME_SIZE_MAX + \ - MODEM_CMUX_DATA_SIZE_MIN) +#define MODEM_CMUX_DATA_SIZE_MIN 8 +#define MODEM_CMUX_DATA_FRAME_SIZE_MIN (MODEM_CMUX_HEADER_SIZE + MODEM_CMUX_DATA_SIZE_MIN) +#define MODEM_CMUX_DATA_FRAME_SIZE_MAX (MODEM_CMUX_HEADER_SIZE + CONFIG_MODEM_CMUX_MTU) -#define MODEM_CMUX_CMD_DATA_SIZE_MAX (0x08) -#define MODEM_CMUX_CMD_FRAME_SIZE_MAX (MODEM_CMUX_FRAME_SIZE_MAX + \ - MODEM_CMUX_CMD_DATA_SIZE_MAX) +/* Biggest supported Multiplexer control commands in UIH frame + * Modem Status Command (MSC) - 5 bytes when Break is included. + * + * PN would be 10 bytes, but that is not implemented + */ +#define MODEM_CMUX_CMD_DATA_SIZE_MAX 5 /* Max size of information field of UIH frame */ +#define MODEM_CMUX_CMD_HEADER_SIZE 2 /* command type + length */ +#define MODEM_CMUX_CMD_FRAME_SIZE_MAX (MODEM_CMUX_HEADER_SIZE + \ + 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)) +#define MODEM_CMUX_T3_TIMEOUT (K_SECONDS(CONFIG_MODEM_CMUX_T3_TIMEOUT)) enum modem_cmux_frame_types { MODEM_CMUX_FRAME_TYPE_RR = 0x01, @@ -73,34 +81,268 @@ struct modem_cmux_command_length { struct modem_cmux_command { struct modem_cmux_command_type type; struct modem_cmux_command_length length; - uint8_t value[]; + uint8_t value[MODEM_CMUX_CMD_DATA_SIZE_MAX - 2]; /* Subtract type and length bytes */ +}; + +struct modem_cmux_msc_signals { + uint8_t ea: 1; /**< Last octet, always 1 */ + uint8_t fc: 1; /**< Flow Control */ + uint8_t rtc: 1; /**< Ready to Communicate */ + uint8_t rtr: 1; /**< Ready to Transmit */ + uint8_t res_0: 2; /**< Reserved, set to zero */ + uint8_t ic: 1; /**< Incoming call indicator */ + uint8_t dv: 1; /**< Data Valid */ }; +struct modem_cmux_msc_addr { + uint8_t ea: 1; /**< Last octet, always 1 */ + uint8_t pad_one: 1; /**< Set to 1 */ + uint8_t dlci_address: 6; /**< DLCI channel address */ +}; + +static struct modem_cmux_dlci *modem_cmux_find_dlci(struct modem_cmux *cmux, uint8_t dlci_address); +static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux); +static void modem_cmux_tx_bypass(struct modem_cmux *cmux, const uint8_t *data, size_t len); +static void runtime_pm_keepalive(struct modem_cmux *cmux); + +static void set_state(struct modem_cmux *cmux, enum modem_cmux_state state) +{ + cmux->state = state; + k_event_set(&cmux->event, BIT(state)); +} + +static bool wait_state(struct modem_cmux *cmux, enum modem_cmux_state state, k_timeout_t timeout) +{ + return k_event_wait(&cmux->event, BIT(state), false, timeout) == BIT(state); +} + +static bool is_connected(struct modem_cmux *cmux) +{ + return cmux->state == MODEM_CMUX_STATE_CONNECTED; +} + +static bool is_powersaving(struct modem_cmux *cmux) +{ + return cmux->state == MODEM_CMUX_STATE_POWERSAVE; +} + +static bool is_waking_up(struct modem_cmux *cmux) +{ + return cmux->state == MODEM_CMUX_STATE_WAKEUP; +} + +static bool is_transitioning_to_powersave(struct modem_cmux *cmux) +{ + return (cmux->state == MODEM_CMUX_STATE_ENTER_POWERSAVE || + cmux->state == MODEM_CMUX_STATE_CONFIRM_POWERSAVE); +} + +static bool modem_cmux_command_type_is_valid(const struct modem_cmux_command_type type) +{ + /* All commands are only 7 bits, so EA is always set */ + if (type.ea == 0) { + return false; + } + switch (type.value) { + case MODEM_CMUX_COMMAND_NSC: + case MODEM_CMUX_COMMAND_TEST: + case MODEM_CMUX_COMMAND_PSC: + case MODEM_CMUX_COMMAND_RLS: + case MODEM_CMUX_COMMAND_FCOFF: + case MODEM_CMUX_COMMAND_PN: + case MODEM_CMUX_COMMAND_RPN: + case MODEM_CMUX_COMMAND_FCON: + case MODEM_CMUX_COMMAND_CLD: + case MODEM_CMUX_COMMAND_SNC: + case MODEM_CMUX_COMMAND_MSC: + return true; + default: + return false; + } +} + +static bool modem_cmux_command_length_is_valid(const struct modem_cmux_command_length length) +{ + /* All commands are shorter than 127 bytes, so EA is always set */ + if (length.ea == 0) { + return false; + } + if (length.value > (MODEM_CMUX_CMD_DATA_SIZE_MAX - MODEM_CMUX_CMD_HEADER_SIZE)) { + return false; + } + return true; +} + +static bool modem_cmux_command_is_valid(const struct modem_cmux_command *command) +{ + if (!modem_cmux_command_type_is_valid(command->type)) { + return false; + } + if (!modem_cmux_command_length_is_valid(command->length)) { + return false; + } + /* Verify known value sizes as specified in 3GPP TS 27.010 + * section 5.4.6.3 Message Type and Actions + */ + switch (command->type.value) { + case MODEM_CMUX_COMMAND_PN: + return command->length.value == 8; + case MODEM_CMUX_COMMAND_TEST: + return (command->length.value > 0 && + command->length.value <= MODEM_CMUX_CMD_DATA_SIZE_MAX - 2); + case MODEM_CMUX_COMMAND_MSC: + return command->length.value == 2 || command->length.value == 3; + case MODEM_CMUX_COMMAND_NSC: + return command->length.value == 1; + case MODEM_CMUX_COMMAND_RPN: + return command->length.value == 1 || command->length.value == 8; + case MODEM_CMUX_COMMAND_RLS: + return command->length.value == 2; + case MODEM_CMUX_COMMAND_SNC: + return command->length.value == 1 || command->length.value == 3; + default: + return command->length.value == 0; + } +} -static int modem_cmux_wrap_command(struct modem_cmux_command **command, const uint8_t *data, - uint16_t data_len) +static struct modem_cmux_command_type modem_cmux_command_type_decode(const uint8_t byte) { - if ((data == NULL) || (data_len < 2)) { - return -EINVAL; + struct modem_cmux_command_type type = { + .ea = (byte & MODEM_CMUX_EA) ? 1 : 0, + .cr = (byte & MODEM_CMUX_CR) ? 1 : 0, + .value = (byte >> 2) & 0x3F, + }; + + if (type.ea == 0) { + return (struct modem_cmux_command_type){0}; } - (*command) = (struct modem_cmux_command *)data; + return type; +} + +static uint8_t modem_cmux_command_type_encode(const struct modem_cmux_command_type type) +{ + return (type.ea ? MODEM_CMUX_EA : 0) | + (type.cr ? MODEM_CMUX_CR : 0) | + ((type.value & 0x3F) << 2); +} + +static struct modem_cmux_command_length modem_cmux_command_length_decode(const uint8_t byte) +{ + struct modem_cmux_command_length length = { + .ea = (byte & MODEM_CMUX_EA) ? 1 : 0, + .value = (byte >> 1) & 0x7F, + }; - if (((*command)->length.ea == 0) || ((*command)->type.ea == 0)) { - return -EINVAL; + if (length.ea == 0) { + return (struct modem_cmux_command_length){0}; } - if ((*command)->length.value != (data_len - 2)) { - return -EINVAL; + return length; +} + +static uint8_t modem_cmux_command_length_encode(const struct modem_cmux_command_length length) +{ + return (length.ea ? MODEM_CMUX_EA : 0) | + ((length.value & 0x7F) << 1); +} + +static struct modem_cmux_command modem_cmux_command_decode(const uint8_t *data, size_t len) +{ + if (len < 2) { + return (struct modem_cmux_command){0}; } - return 0; + struct modem_cmux_command command = { + .type = modem_cmux_command_type_decode(data[0]), + .length = modem_cmux_command_length_decode(data[1]), + }; + + if (command.type.ea == 0 || command.length.ea == 0 || + command.length.value > MODEM_CMUX_CMD_DATA_SIZE_MAX || + (2 + command.length.value) > len) { + return (struct modem_cmux_command){0}; + } + + memcpy(&command.value[0], &data[2], command.length.value); + + return command; } -static struct modem_cmux_command *modem_cmux_command_wrap(const uint8_t *data) +/** + * @brief Encode command into a shared buffer + * + * Not a thread safe, so can only be used within a workqueue context and data + * must be copied out to a TX ringbuffer. + * + * @param command command to encode + * @param len encoded length of the command is written here + * @return pointer to encoded command buffer on success, NULL on failure + */ +static uint8_t *modem_cmux_command_encode(struct modem_cmux_command *command, uint16_t *len) +{ + static uint8_t buf[MODEM_CMUX_CMD_DATA_SIZE_MAX]; + + __ASSERT_NO_MSG(len != NULL); + __ASSERT_NO_MSG(command != NULL); + __ASSERT_NO_MSG(modem_cmux_command_is_valid(command)); + + buf[0] = modem_cmux_command_type_encode(command->type); + buf[1] = modem_cmux_command_length_encode(command->length); + if (command->length.value > 0) { + memcpy(&buf[2], &command->value[0], command->length.value); + } + *len = 2 + command->length.value; + return buf; +} + +static struct modem_cmux_msc_signals modem_cmux_msc_signals_decode(const uint8_t byte) +{ + struct modem_cmux_msc_signals signals; + + /* 3GPP TS 27.010 MSC signals octet: + * |0 |1 |2 |3 |4 |5 |6 |7 | + * |EA |FC|RTC|RTR|0 |0 |IC|DV| + */ + signals.ea = (byte & BIT(0)) ? 1 : 0; + signals.fc = (byte & BIT(1)) ? 1 : 0; + signals.rtc = (byte & BIT(2)) ? 1 : 0; + signals.rtr = (byte & BIT(3)) ? 1 : 0; + signals.ic = (byte & BIT(6)) ? 1 : 0; + signals.dv = (byte & BIT(7)) ? 1 : 0; + + return signals; +} + +static uint8_t modem_cmux_msc_signals_encode(const struct modem_cmux_msc_signals signals) +{ + return (signals.ea ? BIT(0) : 0) | (signals.fc ? BIT(1) : 0) | + (signals.rtc ? BIT(2) : 0) | (signals.rtr ? BIT(3) : 0) | + (signals.ic ? BIT(6) : 0) | (signals.dv ? BIT(7) : 0); +} + +static struct modem_cmux_msc_addr modem_cmux_msc_addr_decode(const uint8_t byte) +{ + struct modem_cmux_msc_addr addr; + + /* 3GPP TS 27.010 MSC address octet: + * |0 |1 |2 |3 |4 |5 |6 |7 | + * |EA |1 | DLCI | + */ + addr.ea = (byte & BIT(0)) ? 1 : 0; + addr.pad_one = 1; + addr.dlci_address = (byte >> 2) & 0x3F; + + return addr; +} + +static uint8_t modem_cmux_msc_addr_encode(const struct modem_cmux_msc_addr a) { - return (struct modem_cmux_command *)data; + return (a.ea ? BIT(0) : 0) | BIT(1) | + ((a.dlci_address & 0x3F) << 2); } + +#if CONFIG_MODEM_CMUX_LOG_FRAMES static const char *modem_cmux_frame_type_to_str(enum modem_cmux_frame_types frame_type) { switch (frame_type) { @@ -131,8 +373,14 @@ static void modem_cmux_log_frame(const struct modem_cmux_frame *frame, { LOG_DBG("%s ch:%u cr:%u pf:%u type:%s dlen:%u", action, frame->dlci_address, frame->cr, frame->pf, modem_cmux_frame_type_to_str(frame->type), frame->data_len); - LOG_HEXDUMP_DBG(frame->data, hexdump_len, "data:"); + if (hexdump_len > 0) { + LOG_HEXDUMP_DBG(frame->data, hexdump_len, "data:"); + } } +#else +#define modem_cmux_log_frame(frame, action, hexdump_len) \ + do { ARG_UNUSED(frame); ARG_UNUSED(action); ARG_UNUSED(hexdump_len); } while (0) +#endif /* CONFIG_MODEM_CMUX_LOG_FRAMES */ static void modem_cmux_log_transmit_frame(const struct modem_cmux_frame *frame) { @@ -152,7 +400,7 @@ static uint32_t modem_cmux_get_receive_buf_length(struct modem_cmux *cmux) static uint32_t modem_cmux_get_receive_buf_size(struct modem_cmux *cmux) { - return cmux->receive_buf_size; + return cmux->config.receive_buf_size; } static uint32_t modem_cmux_get_transmit_buf_length(struct modem_cmux *cmux) @@ -225,23 +473,21 @@ static void modem_cmux_log_transmit_command(const struct modem_cmux_command *com { LOG_DBG("ea:%u,cr:%u,type:%s", command->type.ea, command->type.cr, modem_cmux_command_type_to_str(command->type.value)); - LOG_HEXDUMP_DBG(command->value, command->length.value, "data:"); } static void modem_cmux_log_received_command(const struct modem_cmux_command *command) { LOG_DBG("ea:%u,cr:%u,type:%s", command->type.ea, command->type.cr, modem_cmux_command_type_to_str(command->type.value)); - LOG_HEXDUMP_DBG(command->value, command->length.value, "data:"); } static void modem_cmux_raise_event(struct modem_cmux *cmux, enum modem_cmux_event event) { - if (cmux->callback == NULL) { + if (cmux->config.callback == NULL) { return; } - cmux->callback(cmux, event, cmux->user_data); + cmux->config.callback(cmux, event, cmux->config.user_data); } static void modem_cmux_bus_callback(struct modem_pipe *pipe, enum modem_pipe_event event, @@ -251,13 +497,20 @@ static void modem_cmux_bus_callback(struct modem_pipe *pipe, enum modem_pipe_eve switch (event) { case MODEM_PIPE_EVENT_RECEIVE_READY: - k_work_schedule(&cmux->receive_work, K_NO_WAIT); + modem_work_schedule(&cmux->receive_work, K_NO_WAIT); break; + case MODEM_PIPE_EVENT_OPENED: + cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_SOF; + modem_work_schedule(&cmux->transmit_work, K_NO_WAIT); + break; case MODEM_PIPE_EVENT_TRANSMIT_IDLE: - k_work_schedule(&cmux->transmit_work, K_NO_WAIT); + /* If we keep UART open in power-save, we should avoid waking up on RX idle */ + if (!cmux->config.close_pipe_on_power_save && is_powersaving(cmux)) { + break; + } + modem_work_schedule(&cmux->transmit_work, K_NO_WAIT); break; - default: break; } @@ -266,18 +519,18 @@ static void modem_cmux_bus_callback(struct modem_pipe *pipe, enum modem_pipe_eve static uint16_t modem_cmux_transmit_frame(struct modem_cmux *cmux, const struct modem_cmux_frame *frame) { - uint8_t buf[MODEM_CMUX_FRAME_SIZE_MAX]; + uint8_t buf[MODEM_CMUX_HEADER_SIZE]; uint8_t fcs; uint16_t space; uint16_t data_len; uint16_t buf_idx; - space = ring_buf_space_get(&cmux->transmit_rb) - MODEM_CMUX_FRAME_SIZE_MAX; + space = ring_buf_space_get(&cmux->transmit_rb) - MODEM_CMUX_HEADER_SIZE; data_len = MIN(space, frame->data_len); data_len = MIN(data_len, CONFIG_MODEM_CMUX_MTU); /* SOF */ - buf[0] = 0xF9; + buf[0] = MODEM_CMUX_SOF; /* DLCI Address (Max 63) */ buf[1] = 0x01 | (frame->cr << 1) | (frame->dlci_address << 2); @@ -313,9 +566,9 @@ static uint16_t modem_cmux_transmit_frame(struct modem_cmux *cmux, /* FCS and EOF will be put on the same call */ buf[0] = fcs; - buf[1] = 0xF9; + buf[1] = MODEM_CMUX_SOF; ring_buf_put(&cmux->transmit_rb, buf, 2); - k_work_schedule(&cmux->transmit_work, K_NO_WAIT); + modem_work_schedule(&cmux->transmit_work, K_NO_WAIT); return data_len; } @@ -323,19 +576,21 @@ static bool modem_cmux_transmit_cmd_frame(struct modem_cmux *cmux, const struct modem_cmux_frame *frame) { uint16_t space; - struct modem_cmux_command *command; + struct modem_cmux_command command; k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); space = ring_buf_space_get(&cmux->transmit_rb); if (space < MODEM_CMUX_CMD_FRAME_SIZE_MAX) { k_mutex_unlock(&cmux->transmit_rb_lock); + LOG_WRN("CMD buffer overflow"); return false; } modem_cmux_log_transmit_frame(frame); - if (modem_cmux_wrap_command(&command, frame->data, frame->data_len) == 0) { - modem_cmux_log_transmit_command(command); + command = modem_cmux_command_decode(frame->data, frame->data_len); + if (modem_cmux_command_is_valid(&command)) { + modem_cmux_log_transmit_command(&command); } modem_cmux_transmit_frame(cmux, frame); @@ -351,7 +606,7 @@ static int16_t modem_cmux_transmit_data_frame(struct modem_cmux *cmux, k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); - if (cmux->flow_control_on == false) { + if (cmux->flow_control_on == false || is_transitioning_to_powersave(cmux)) { k_mutex_unlock(&cmux->transmit_rb_lock); return 0; } @@ -378,7 +633,7 @@ static int16_t modem_cmux_transmit_data_frame(struct modem_cmux *cmux, static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux) { - struct modem_cmux_command *command; + struct modem_cmux_command_type command; struct modem_cmux_frame frame; uint8_t data[MODEM_CMUX_CMD_DATA_SIZE_MAX]; @@ -389,8 +644,9 @@ static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux) 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; + command = modem_cmux_command_type_decode(data[0]); + command.cr = 0; + data[0] = modem_cmux_command_type_encode(command); frame.data = data; frame.data_len = cmux->frame.data_len; @@ -399,10 +655,125 @@ static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux) } } +static void modem_cmux_send_psc(struct modem_cmux *cmux) +{ + uint16_t len; + uint8_t *data = modem_cmux_command_encode(&(struct modem_cmux_command){ + .type.ea = 1, + .type.cr = 1, + .type.value = MODEM_CMUX_COMMAND_PSC, + .length.ea = 1, + .length.value = 0, + }, &len); + + if (data == NULL) { + return; + } + + struct modem_cmux_frame frame = { + .dlci_address = 0, + .cr = cmux->initiator, + .pf = true, + .type = MODEM_CMUX_FRAME_TYPE_UIH, + .data = data, + .data_len = len, + }; + + LOG_DBG("Sending PSC command"); + modem_cmux_transmit_cmd_frame(cmux, &frame); +} + +static void modem_cmux_send_msc(struct modem_cmux *cmux, struct modem_cmux_dlci *dlci) +{ + if (cmux == NULL || dlci == NULL) { + return; + } + + struct modem_cmux_msc_addr addr = { + .ea = 1, + .pad_one = 1, + .dlci_address = dlci->dlci_address, + }; + struct modem_cmux_msc_signals signals = { + .ea = 1, + .fc = dlci->rx_full ? 1 : 0, + .rtc = dlci->state == MODEM_CMUX_DLCI_STATE_OPEN ? 1 : 0, + .rtr = dlci->state == MODEM_CMUX_DLCI_STATE_OPEN ? 1 : 0, + .dv = 1, + }; + struct modem_cmux_command cmd = { + .type = { + .ea = 1, + .cr = 1, + .value = MODEM_CMUX_COMMAND_MSC, + }, + .length = { + .ea = 1, + .value = 2, + }, + .value[0] = modem_cmux_msc_addr_encode(addr), + .value[1] = modem_cmux_msc_signals_encode(signals), + }; + + uint16_t len; + uint8_t *data = modem_cmux_command_encode(&cmd, &len); + + if (data == NULL) { + return; + } + + struct modem_cmux_frame frame = { + .dlci_address = 0, + .cr = cmux->initiator, + .pf = false, + .type = MODEM_CMUX_FRAME_TYPE_UIH, + .data = data, + .data_len = len, + }; + + LOG_DBG("Sending MSC command for DLCI %u, FC:%d RTR: %d DV: %d", addr.dlci_address, + signals.fc, signals.rtr, signals.dv); + modem_cmux_transmit_cmd_frame(cmux, &frame); +} + static void modem_cmux_on_msc_command(struct modem_cmux *cmux, struct modem_cmux_command *command) { - if (command->type.cr) { - modem_cmux_acknowledge_received_frame(cmux); + if (!command->type.cr) { + return; + } + + modem_cmux_acknowledge_received_frame(cmux); + + uint8_t len = command->length.value; + + if (len != 2 && len != 3) { + LOG_WRN("Unexpected MSC command length %d", (int)len); + return; + } + + struct modem_cmux_msc_addr msc = modem_cmux_msc_addr_decode(command->value[0]); + struct modem_cmux_msc_signals signals = modem_cmux_msc_signals_decode(command->value[1]); + struct modem_cmux_dlci *dlci = modem_cmux_find_dlci(cmux, msc.dlci_address); + + if (dlci) { + LOG_DBG("MSC command received for DLCI %u", msc.dlci_address); + bool fc_signal = signals.fc || !signals.rtr; + + if (fc_signal != dlci->flow_control) { + if (fc_signal) { + dlci->flow_control = true; + LOG_DBG("DLCI %u flow control ON", dlci->dlci_address); + } else { + dlci->flow_control = false; + LOG_DBG("DLCI %u flow control OFF", dlci->dlci_address); + modem_pipe_notify_transmit_idle(&dlci->pipe); + } + } + /* As we have received MSC, send also our MSC */ + if (!dlci->msc_sent && dlci->state == MODEM_CMUX_DLCI_STATE_OPEN) { + dlci->msc_sent = true; + modem_cmux_send_msc(cmux, dlci); + } } } @@ -412,6 +783,7 @@ static void modem_cmux_on_fcon_command(struct modem_cmux *cmux) cmux->flow_control_on = true; k_mutex_unlock(&cmux->transmit_rb_lock); modem_cmux_acknowledge_received_frame(cmux); + modem_cmux_dlci_notify_transmit_idle(cmux); } static void modem_cmux_on_fcoff_command(struct modem_cmux *cmux) @@ -422,6 +794,17 @@ static void modem_cmux_on_fcoff_command(struct modem_cmux *cmux) modem_cmux_acknowledge_received_frame(cmux); } +static void disconnect(struct modem_cmux *cmux) +{ + LOG_DBG("CMUX disconnected"); + k_work_cancel_delayable(&cmux->disconnect_work); + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED); + k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); + cmux->flow_control_on = false; + k_mutex_unlock(&cmux->transmit_rb_lock); + modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED); +} + static void modem_cmux_on_cld_command(struct modem_cmux *cmux, struct modem_cmux_command *command) { if (command->type.cr) { @@ -435,60 +818,129 @@ static void modem_cmux_on_cld_command(struct modem_cmux *cmux, struct modem_cmux } if (cmux->state == MODEM_CMUX_STATE_DISCONNECTING) { - k_work_cancel_delayable(&cmux->disconnect_work); + disconnect(cmux); + } else { + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTING); + k_work_schedule(&cmux->disconnect_work, MODEM_CMUX_T1_TIMEOUT); } - - cmux->state = MODEM_CMUX_STATE_DISCONNECTED; - k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); - cmux->flow_control_on = false; - k_mutex_unlock(&cmux->transmit_rb_lock); - - modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED); - k_event_clear(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); - k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); } static void modem_cmux_on_control_frame_ua(struct modem_cmux *cmux) { if (cmux->state != MODEM_CMUX_STATE_CONNECTING) { - LOG_DBG("Unexpected UA frame"); + LOG_DBG("Unexpected UA frame in state %d", cmux->state); return; } - cmux->state = MODEM_CMUX_STATE_CONNECTED; + LOG_DBG("CMUX connected"); + set_state(cmux, MODEM_CMUX_STATE_CONNECTED); + cmux->initiator = true; 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_respond_unsupported_cmd(struct modem_cmux *cmux) +{ + struct modem_cmux_frame frame = cmux->frame; + struct modem_cmux_command cmd = modem_cmux_command_decode(frame.data, frame.data_len); + + if (!modem_cmux_command_is_valid(&cmd)) { + LOG_WRN("Invalid command"); + return; + } + + + /* 3GPP TS 27.010: 5.4.6.3.8 Non Supported Command Response (NSC) */ + struct modem_cmux_command nsc_cmd = { + .type = { + .ea = 1, + .cr = 0, + .value = MODEM_CMUX_COMMAND_NSC, + }, + .length = { + .ea = 1, + .value = 1, + }, + .value[0] = modem_cmux_command_type_encode(cmd.type), + }; + + uint16_t len; + uint8_t *data = modem_cmux_command_encode(&nsc_cmd, &len); + + if (data == NULL) { + return; + } + + frame.data = data; + frame.data_len = len; + + modem_cmux_transmit_cmd_frame(cmux, &frame); +} + +static void modem_cmux_on_psc_command(struct modem_cmux *cmux) +{ + LOG_DBG("Received power saving command"); + k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); + set_state(cmux, MODEM_CMUX_STATE_CONFIRM_POWERSAVE); + modem_cmux_acknowledge_received_frame(cmux); + k_mutex_unlock(&cmux->transmit_rb_lock); +} + +static void modem_cmux_on_psc_response(struct modem_cmux *cmux) +{ + LOG_DBG("Enter power saving"); + k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); + set_state(cmux, MODEM_CMUX_STATE_POWERSAVE); + k_mutex_unlock(&cmux->transmit_rb_lock); + + if (cmux->config.close_pipe_on_power_save) { + modem_pipe_close_async(cmux->pipe); + } } static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) { - struct modem_cmux_command *command; + struct modem_cmux_command command; - if ((cmux->state != MODEM_CMUX_STATE_CONNECTED) && - (cmux->state != MODEM_CMUX_STATE_DISCONNECTING)) { + if (cmux->state < MODEM_CMUX_STATE_CONNECTED) { LOG_DBG("Unexpected UIH frame"); return; } - if (modem_cmux_wrap_command(&command, cmux->frame.data, cmux->frame.data_len) < 0) { + command = modem_cmux_command_decode(cmux->frame.data, cmux->frame.data_len); + if (!modem_cmux_command_is_valid(&command)) { LOG_WRN("Invalid command"); return; } - modem_cmux_log_received_command(command); + modem_cmux_log_received_command(&command); - switch (command->type.value) { + if (!command.type.cr) { + LOG_DBG("Received response command"); + switch (command.type.value) { + case MODEM_CMUX_COMMAND_CLD: + modem_cmux_on_cld_command(cmux, &command); + break; + case MODEM_CMUX_COMMAND_PSC: + modem_cmux_on_psc_response(cmux); + break; + default: + /* Responses to other commands are ignored */ + break; + } + return; + } + + switch (command.type.value) { case MODEM_CMUX_COMMAND_CLD: - modem_cmux_on_cld_command(cmux, command); + modem_cmux_on_cld_command(cmux, &command); break; case MODEM_CMUX_COMMAND_MSC: - modem_cmux_on_msc_command(cmux, command); + modem_cmux_on_msc_command(cmux, &command); break; case MODEM_CMUX_COMMAND_FCON: @@ -499,8 +951,13 @@ static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) modem_cmux_on_fcoff_command(cmux); break; + case MODEM_CMUX_COMMAND_PSC: + modem_cmux_on_psc_command(cmux); + break; + default: LOG_DBG("Unknown control command"); + modem_cmux_respond_unsupported_cmd(cmux); break; } } @@ -524,29 +981,52 @@ static void modem_cmux_connect_response_transmit(struct modem_cmux *cmux) modem_cmux_transmit_cmd_frame(cmux, &frame); } -static void modem_cmux_on_control_frame_sabm(struct modem_cmux *cmux) +static void modem_cmux_dm_response_transmit(struct modem_cmux *cmux) { - modem_cmux_connect_response_transmit(cmux); + if (cmux == NULL) { + return; + } + /* 3GPP TS 27.010: 5.3.3 Disconnected Mode (DM) response */ + struct modem_cmux_frame frame = { + .dlci_address = cmux->frame.dlci_address, + .cr = cmux->frame.cr, + .pf = 1, + .type = MODEM_CMUX_FRAME_TYPE_DM, + .data = NULL, + .data_len = 0, + }; + + LOG_DBG("Send DM response"); + modem_cmux_transmit_cmd_frame(cmux, &frame); +} + +static void modem_cmux_on_control_frame_sabm(struct modem_cmux *cmux) +{ if ((cmux->state == MODEM_CMUX_STATE_CONNECTED) || (cmux->state == MODEM_CMUX_STATE_DISCONNECTING)) { LOG_DBG("Connect request not accepted"); return; } - cmux->state = MODEM_CMUX_STATE_CONNECTED; + LOG_DBG("CMUX connection request received"); + cmux->initiator = false; + set_state(cmux, MODEM_CMUX_STATE_CONNECTED); + modem_cmux_connect_response_transmit(cmux); k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); cmux->flow_control_on = true; k_mutex_unlock(&cmux->transmit_rb_lock); modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_CONNECTED); - k_event_clear(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); - k_event_post(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); } static void modem_cmux_on_control_frame(struct modem_cmux *cmux) { modem_cmux_log_received_frame(&cmux->frame); + if (is_connected(cmux) && cmux->frame.cr == cmux->initiator) { + LOG_DBG("Received a response frame"); + } + switch (cmux->frame.type) { case MODEM_CMUX_FRAME_TYPE_UA: modem_cmux_on_control_frame_ua(cmux); @@ -561,12 +1041,12 @@ static void modem_cmux_on_control_frame(struct modem_cmux *cmux) break; default: - LOG_WRN("Unknown %s frame type", "control"); + LOG_WRN("Unknown %s frame type %d", "control", cmux->frame.type); break; } } -static struct modem_cmux_dlci *modem_cmux_find_dlci(struct modem_cmux *cmux) +static struct modem_cmux_dlci *modem_cmux_find_dlci(struct modem_cmux *cmux, uint8_t dlci_address) { sys_snode_t *node; struct modem_cmux_dlci *dlci; @@ -574,7 +1054,7 @@ static struct modem_cmux_dlci *modem_cmux_find_dlci(struct modem_cmux *cmux) SYS_SLIST_FOR_EACH_NODE(&cmux->dlcis, node) { dlci = (struct modem_cmux_dlci *)node; - if (dlci->dlci_address == cmux->frame.dlci_address) { + if (dlci->dlci_address == dlci_address) { return dlci; } } @@ -582,22 +1062,41 @@ static struct modem_cmux_dlci *modem_cmux_find_dlci(struct modem_cmux *cmux) return NULL; } +static void modem_cmux_on_dlci_frame_dm(struct modem_cmux_dlci *dlci) +{ + dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED; + modem_pipe_notify_closed(&dlci->pipe); + k_work_cancel_delayable(&dlci->close_work); +} + static void modem_cmux_on_dlci_frame_ua(struct modem_cmux_dlci *dlci) { + /* Drop invalid UA frames */ + if (dlci->cmux->frame.cr != dlci->cmux->initiator) { + LOG_DBG("Received a response frame, dropping"); + return; + } + switch (dlci->state) { case MODEM_CMUX_DLCI_STATE_OPENING: + LOG_DBG("DLCI %u opened", dlci->dlci_address); dlci->state = MODEM_CMUX_DLCI_STATE_OPEN; modem_pipe_notify_opened(&dlci->pipe); k_work_cancel_delayable(&dlci->open_work); k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER); ring_buf_reset(&dlci->receive_rb); k_mutex_unlock(&dlci->receive_rb_lock); + if (dlci->cmux->initiator) { + modem_cmux_send_msc(dlci->cmux, dlci); + dlci->msc_sent = true; + } else { + dlci->msc_sent = false; + } 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); + LOG_DBG("DLCI %u closed", dlci->dlci_address); + modem_cmux_on_dlci_frame_dm(dlci); break; default: @@ -606,6 +1105,8 @@ static void modem_cmux_on_dlci_frame_ua(struct modem_cmux_dlci *dlci) } } + + static void modem_cmux_on_dlci_frame_uih(struct modem_cmux_dlci *dlci) { struct modem_cmux *cmux = dlci->cmux; @@ -623,6 +1124,12 @@ static void modem_cmux_on_dlci_frame_uih(struct modem_cmux_dlci *dlci) LOG_WRN("DLCI %u receive buffer overrun (dropped %u out of %u bytes)", dlci->dlci_address, cmux->frame.data_len - written, cmux->frame.data_len); } + if (written < cmux->frame.data_len || + ring_buf_space_get(&dlci->receive_rb) < MODEM_CMUX_DATA_FRAME_SIZE_MAX) { + LOG_WRN("DLCI %u receive buffer is full", dlci->dlci_address); + dlci->rx_full = true; + modem_cmux_send_msc(cmux, dlci); + } modem_pipe_notify_receive_ready(&dlci->pipe); } @@ -637,7 +1144,9 @@ static void modem_cmux_on_dlci_frame_sabm(struct modem_cmux_dlci *dlci) return; } + LOG_DBG("DLCI %u SABM request accepted, DLCI opened", dlci->dlci_address); dlci->state = MODEM_CMUX_DLCI_STATE_OPEN; + dlci->msc_sent = false; modem_pipe_notify_opened(&dlci->pipe); k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER); ring_buf_reset(&dlci->receive_rb); @@ -651,10 +1160,11 @@ static void modem_cmux_on_dlci_frame_disc(struct modem_cmux_dlci *dlci) modem_cmux_connect_response_transmit(cmux); if (dlci->state != MODEM_CMUX_DLCI_STATE_OPEN) { - LOG_DBG("Unexpected Disc frame"); + modem_cmux_dm_response_transmit(cmux); return; } + LOG_DBG("DLCI %u disconnected", dlci->dlci_address); dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED; modem_pipe_notify_closed(&dlci->pipe); } @@ -665,10 +1175,16 @@ static void modem_cmux_on_dlci_frame(struct modem_cmux *cmux) modem_cmux_log_received_frame(&cmux->frame); - dlci = modem_cmux_find_dlci(cmux); + if (cmux->state != MODEM_CMUX_STATE_CONNECTED) { + LOG_DBG("Unexpected DLCI frame in state %d", cmux->state); + return; + } + + dlci = modem_cmux_find_dlci(cmux, cmux->frame.dlci_address); if (dlci == NULL) { - LOG_WRN("Ignoring frame intended for unconfigured DLCI %u.", + LOG_WRN("Frame intended for unconfigured DLCI %u.", cmux->frame.dlci_address); + modem_cmux_dm_response_transmit(cmux); return; } @@ -688,9 +1204,12 @@ static void modem_cmux_on_dlci_frame(struct modem_cmux *cmux) case MODEM_CMUX_FRAME_TYPE_DISC: modem_cmux_on_dlci_frame_disc(dlci); break; - + case MODEM_CMUX_FRAME_TYPE_DM: + modem_cmux_on_dlci_frame_dm(dlci); + break; default: - LOG_WRN("Unknown %s frame type", "DLCI"); + LOG_WRN("Unknown %s frame type (%d, DLCI %d)", "DLCI", cmux->frame.type, + cmux->frame.dlci_address); break; } } @@ -701,6 +1220,11 @@ static void modem_cmux_on_frame(struct modem_cmux *cmux) modem_cmux_advertise_receive_buf_stats(cmux); #endif + if (is_powersaving(cmux) || is_waking_up(cmux)) { + set_state(cmux, MODEM_CMUX_STATE_CONNECTED); + LOG_DBG("Exit powersave on received frame"); + } + if (cmux->frame.dlci_address == 0) { modem_cmux_on_control_frame(cmux); } else { @@ -720,8 +1244,8 @@ static void modem_cmux_drop_frame(struct modem_cmux *cmux) #if defined(CONFIG_MODEM_CMUX_LOG_LEVEL_DBG) struct modem_cmux_frame *frame = &cmux->frame; - frame->data = cmux->receive_buf; - modem_cmux_log_frame(frame, "dropped", MIN(frame->data_len, cmux->receive_buf_size)); + frame->data = cmux->config.receive_buf; + modem_cmux_log_frame(frame, "dropped", MIN(frame->data_len, cmux->config.receive_buf_size)); #endif } @@ -731,8 +1255,10 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by switch (cmux->receive_state) { case MODEM_CMUX_RECEIVE_STATE_SOF: - if (byte == 0xF9) { + cmux->frame_header_len = 0; + if (byte == MODEM_CMUX_SOF) { cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC; + cmux->frame_header_len++; break; } @@ -743,7 +1269,21 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by * Allow any number of consecutive flags (0xF9). * 0xF9 could also be a valid address field for DLCI 62. */ - if (byte == 0xF9) { + if (byte == MODEM_CMUX_SOF) { + /* Use "header_len" to count SOF bytes, only start transmitting + * flag bytes after receiving more than 3 flags. + * Don't reply flags if we are transitioning between modes or + * if T3 timer is still active (suppress residual flags). + */ + cmux->frame_header_len++; + if ((is_powersaving(cmux) || + (is_connected(cmux) && sys_timepoint_expired(cmux->t3_timepoint))) && + cmux->frame_header_len > 3) { + modem_cmux_tx_bypass(cmux, &(char){MODEM_CMUX_SOF}, 1); + } + if (is_waking_up(cmux)) { + k_work_reschedule(&cmux->transmit_work, K_NO_WAIT); + } break; } @@ -800,7 +1340,7 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by if (cmux->frame.data_len > CONFIG_MODEM_CMUX_MTU) { LOG_ERR("Too large frame"); - cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; + modem_cmux_drop_frame(cmux); break; } @@ -825,15 +1365,15 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by if (cmux->frame.data_len > CONFIG_MODEM_CMUX_MTU) { LOG_ERR("Too large frame"); - cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; + modem_cmux_drop_frame(cmux); break; } - if (cmux->frame.data_len > cmux->receive_buf_size) { + if (cmux->frame.data_len > cmux->config.receive_buf_size) { LOG_ERR("Indicated frame data length %u exceeds receive buffer size %u", - cmux->frame.data_len, cmux->receive_buf_size); + cmux->frame.data_len, cmux->config.receive_buf_size); - cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; + modem_cmux_drop_frame(cmux); break; } @@ -843,8 +1383,8 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by case MODEM_CMUX_RECEIVE_STATE_DATA: /* Copy byte to data */ - if (cmux->receive_buf_len < cmux->receive_buf_size) { - cmux->receive_buf[cmux->receive_buf_len] = byte; + if (cmux->receive_buf_len < cmux->config.receive_buf_size) { + cmux->config.receive_buf[cmux->receive_buf_len] = byte; } cmux->receive_buf_len++; @@ -857,10 +1397,10 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by break; case MODEM_CMUX_RECEIVE_STATE_FCS: - if (cmux->receive_buf_len > cmux->receive_buf_size) { - LOG_WRN("Receive buffer overrun (%u > %u)", - cmux->receive_buf_len, cmux->receive_buf_size); - cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; + if (cmux->receive_buf_len > cmux->config.receive_buf_size) { + LOG_WRN("Receive buffer overrun (%u > %u)", cmux->receive_buf_len, + cmux->config.receive_buf_size); + modem_cmux_drop_frame(cmux); break; } @@ -877,28 +1417,23 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by if (fcs != byte) { LOG_WRN("Frame FCS error"); - /* Drop frame */ - cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; + modem_cmux_drop_frame(cmux); break; } cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_EOF; break; - case MODEM_CMUX_RECEIVE_STATE_DROP: - modem_cmux_drop_frame(cmux); - break; - case MODEM_CMUX_RECEIVE_STATE_EOF: /* Validate byte is EOF */ - if (byte != 0xF9) { + if (byte != MODEM_CMUX_SOF) { /* Unexpected byte */ modem_cmux_drop_frame(cmux); break; } /* Process frame */ - cmux->frame.data = cmux->receive_buf; + cmux->frame.data = cmux->config.receive_buf; modem_cmux_on_frame(cmux); /* Await start of next frame */ @@ -916,22 +1451,18 @@ static void modem_cmux_receive_handler(struct k_work *item) struct modem_cmux *cmux = CONTAINER_OF(dwork, struct modem_cmux, receive_work); int ret; + runtime_pm_keepalive(cmux); + /* Receive data from pipe */ - ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf)); - if (ret < 1) { - if (ret < 0) { - LOG_ERR("Pipe receiving error: %d", ret); + while ((ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf))) > 0) { + /* Process received data */ + for (int i = 0; i < ret; i++) { + modem_cmux_process_received_byte(cmux, cmux->work_buf[i]); } - return; } - - /* Process received data */ - for (int i = 0; i < ret; i++) { - modem_cmux_process_received_byte(cmux, cmux->work_buf[i]); + if (ret < 0) { + LOG_ERR("Pipe receiving error: %d", ret); } - - /* Reschedule received work */ - k_work_schedule(&cmux->receive_work, K_NO_WAIT); } static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux) @@ -941,8 +1472,106 @@ static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux) SYS_SLIST_FOR_EACH_NODE(&cmux->dlcis, node) { dlci = (struct modem_cmux_dlci *)node; - modem_pipe_notify_transmit_idle(&dlci->pipe); + if (!dlci->flow_control) { + modem_pipe_notify_transmit_idle(&dlci->pipe); + } + } +} + +static void modem_cmux_runtime_pm_handler(struct k_work *item) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(item); + struct modem_cmux *cmux = CONTAINER_OF(dwork, struct modem_cmux, runtime_pm_work); + + if (!cmux->config.enable_runtime_power_management) { + return; + } + + bool expired = sys_timepoint_expired(cmux->idle_timepoint); + + if (!expired) { + return; + } + + if (is_connected(cmux) && expired) { + LOG_DBG("Idle timeout, entering power saving mode"); + set_state(cmux, MODEM_CMUX_STATE_ENTER_POWERSAVE); + modem_cmux_send_psc(cmux); + k_work_reschedule(&cmux->runtime_pm_work, MODEM_CMUX_T3_TIMEOUT); + return; + } + if (cmux->state == MODEM_CMUX_STATE_ENTER_POWERSAVE) { + LOG_WRN("PSC timeout, not entering power saving mode"); + set_state(cmux, MODEM_CMUX_STATE_CONNECTED); + runtime_pm_keepalive(cmux); + return; + } +} + +static void runtime_pm_keepalive(struct modem_cmux *cmux) +{ + if (cmux == NULL || !cmux->config.enable_runtime_power_management) { + return; + } + + cmux->idle_timepoint = sys_timepoint_calc(cmux->config.idle_timeout); + k_work_reschedule(&cmux->runtime_pm_work, cmux->config.idle_timeout); +} + +/** Transmit bytes bypassing the CMUX buffers. + * Causes modem_cmux_transmit_handler() to be rescheduled as a result of TRANSMIT_IDLE event. + */ +static void modem_cmux_tx_bypass(struct modem_cmux *cmux, const uint8_t *data, size_t len) +{ + if (cmux == NULL) { + return; + } + + modem_pipe_transmit(cmux->pipe, data, len); +} + +static bool powersave_wait_wakeup(struct modem_cmux *cmux) +{ + static const uint8_t wakeup_pattern[] = {MODEM_CMUX_SOF, MODEM_CMUX_SOF, MODEM_CMUX_SOF, + MODEM_CMUX_SOF, MODEM_CMUX_SOF}; + int ret; + + if (is_powersaving(cmux)) { + LOG_DBG("Power saving mode, wake up first"); + set_state(cmux, MODEM_CMUX_STATE_WAKEUP); + + if (cmux->config.close_pipe_on_power_save) { + ret = modem_pipe_open(cmux->pipe, K_FOREVER); + if (ret < 0) { + LOG_ERR("Failed to open pipe for wake up (%d)", ret); + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED); + modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED); + return true; + } + } + + cmux->t3_timepoint = sys_timepoint_calc(MODEM_CMUX_T3_TIMEOUT); + modem_cmux_tx_bypass(cmux, wakeup_pattern, sizeof(wakeup_pattern)); + return true; } + + if (is_waking_up(cmux)) { + if (sys_timepoint_expired(cmux->t3_timepoint)) { + LOG_ERR("Wake up timed out, link dead"); + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED); + modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED); + return true; + } + if (cmux->receive_state != MODEM_CMUX_RECEIVE_STATE_RESYNC) { + /* Retry single flag, until remote wakes up */ + modem_cmux_tx_bypass(cmux, &(uint8_t){MODEM_CMUX_SOF}, 1); + return true; + } + set_state(cmux, MODEM_CMUX_STATE_CONNECTED); + LOG_DBG("Woke up from power saving mode"); + } + + return false; } static void modem_cmux_transmit_handler(struct k_work *item) @@ -960,9 +1589,18 @@ static void modem_cmux_transmit_handler(struct k_work *item) modem_cmux_advertise_transmit_buf_stats(cmux); #endif + if (!is_transitioning_to_powersave(cmux)) { + runtime_pm_keepalive(cmux); + } + while (true) { transmit_rb_empty = ring_buf_is_empty(&cmux->transmit_rb); + if (powersave_wait_wakeup(cmux)) { + k_mutex_unlock(&cmux->transmit_rb_lock); + return; + } + if (transmit_rb_empty) { break; } @@ -987,11 +1625,17 @@ static void modem_cmux_transmit_handler(struct k_work *item) } } - k_mutex_unlock(&cmux->transmit_rb_lock); - if (transmit_rb_empty) { + if (cmux->state == MODEM_CMUX_STATE_CONFIRM_POWERSAVE) { + set_state(cmux, MODEM_CMUX_STATE_POWERSAVE); + LOG_DBG("Entered power saving mode"); + if (cmux->config.close_pipe_on_power_save) { + modem_pipe_close_async(cmux->pipe); + } + } modem_cmux_dlci_notify_transmit_idle(cmux); } + k_mutex_unlock(&cmux->transmit_rb_lock); } static void modem_cmux_connect_handler(struct k_work *item) @@ -1006,7 +1650,8 @@ static void modem_cmux_connect_handler(struct k_work *item) dwork = k_work_delayable_from_work(item); cmux = CONTAINER_OF(dwork, struct modem_cmux, connect_work); - cmux->state = MODEM_CMUX_STATE_CONNECTING; + set_state(cmux, MODEM_CMUX_STATE_CONNECTING); + cmux->initiator = true; static const struct modem_cmux_frame frame = { .dlci_address = 0, @@ -1018,37 +1663,49 @@ static void modem_cmux_connect_handler(struct k_work *item) }; modem_cmux_transmit_cmd_frame(cmux, &frame); - k_work_schedule(&cmux->connect_work, MODEM_CMUX_T1_TIMEOUT); + modem_work_schedule(&cmux->connect_work, MODEM_CMUX_T1_TIMEOUT); } static void modem_cmux_disconnect_handler(struct k_work *item) { struct k_work_delayable *dwork = k_work_delayable_from_work(item); struct modem_cmux *cmux = CONTAINER_OF(dwork, struct modem_cmux, disconnect_work); - struct modem_cmux_command *command; - uint8_t data[2]; - cmux->state = MODEM_CMUX_STATE_DISCONNECTING; + if (cmux->state == MODEM_CMUX_STATE_DISCONNECTING) { + disconnect(cmux); + } else { + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTING); + k_work_schedule(&cmux->disconnect_work, MODEM_CMUX_T1_TIMEOUT); + } + + struct modem_cmux_command command = { + .type.ea = 1, + .type.cr = 1, + .type.value = MODEM_CMUX_COMMAND_CLD, + .length.ea = 1, + .length.value = 0, + }; + + uint16_t len; + uint8_t *data = modem_cmux_command_encode(&command, &len); + + if (data == NULL) { + return; + } - 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, + .cr = cmux->initiator, .pf = false, .type = MODEM_CMUX_FRAME_TYPE_UIH, .data = data, - .data_len = sizeof(data), + .data_len = len, }; /* Transmit close down command */ modem_cmux_transmit_cmd_frame(cmux, &frame); - k_work_schedule(&cmux->disconnect_work, MODEM_CMUX_T1_TIMEOUT); + modem_work_schedule(&cmux->disconnect_work, MODEM_CMUX_T1_TIMEOUT); } #if CONFIG_MODEM_STATS @@ -1098,7 +1755,7 @@ static int modem_cmux_dlci_pipe_api_open(void *data) K_SPINLOCK_BREAK; } - k_work_schedule(&dlci->open_work, K_NO_WAIT); + modem_work_schedule(&dlci->open_work, K_NO_WAIT); } return ret; @@ -1110,6 +1767,17 @@ static int modem_cmux_dlci_pipe_api_transmit(void *data, const uint8_t *buf, siz struct modem_cmux *cmux = dlci->cmux; int ret = 0; + if (size == 0 || buf == NULL) { + /* Allow empty transmit request to wake up CMUX */ + runtime_pm_keepalive(cmux); + k_work_reschedule(&cmux->transmit_work, K_NO_WAIT); + return 0; + } + + if (dlci->flow_control) { + return 0; + } + K_SPINLOCK(&cmux->work_lock) { if (!cmux->attached) { ret = -EPERM; @@ -1118,7 +1786,7 @@ static int modem_cmux_dlci_pipe_api_transmit(void *data, const uint8_t *buf, siz struct modem_cmux_frame frame = { .dlci_address = dlci->dlci_address, - .cr = true, + .cr = cmux->initiator, .pf = false, .type = MODEM_CMUX_FRAME_TYPE_UIH, .data = buf, @@ -1144,6 +1812,15 @@ static int modem_cmux_dlci_pipe_api_receive(void *data, uint8_t *buf, size_t siz ret = ring_buf_get(&dlci->receive_rb, buf, size); k_mutex_unlock(&dlci->receive_rb_lock); + + /* Release FC if set */ + if (dlci->rx_full && + ring_buf_space_get(&dlci->receive_rb) >= MODEM_CMUX_DATA_FRAME_SIZE_MAX) { + LOG_DBG("DLCI %u receive buffer is no longer full", dlci->dlci_address); + dlci->rx_full = false; + modem_cmux_send_msc(dlci->cmux, dlci); + } + return ret; } @@ -1164,7 +1841,7 @@ static int modem_cmux_dlci_pipe_api_close(void *data) K_SPINLOCK_BREAK; } - k_work_schedule(&dlci->close_work, K_NO_WAIT); + modem_work_schedule(&dlci->close_work, K_NO_WAIT); } return ret; @@ -1190,10 +1867,11 @@ static void modem_cmux_dlci_open_handler(struct k_work *item) dlci = CONTAINER_OF(dwork, struct modem_cmux_dlci, open_work); dlci->state = MODEM_CMUX_DLCI_STATE_OPENING; + dlci->msc_sent = false; struct modem_cmux_frame frame = { .dlci_address = dlci->dlci_address, - .cr = true, + .cr = dlci->cmux->initiator, .pf = true, .type = MODEM_CMUX_FRAME_TYPE_SABM, .data = NULL, @@ -1201,7 +1879,7 @@ static void modem_cmux_dlci_open_handler(struct k_work *item) }; modem_cmux_transmit_cmd_frame(dlci->cmux, &frame); - k_work_schedule(&dlci->open_work, MODEM_CMUX_T1_TIMEOUT); + modem_work_schedule(&dlci->open_work, MODEM_CMUX_T1_TIMEOUT); } static void modem_cmux_dlci_close_handler(struct k_work *item) @@ -1222,7 +1900,7 @@ static void modem_cmux_dlci_close_handler(struct k_work *item) struct modem_cmux_frame frame = { .dlci_address = dlci->dlci_address, - .cr = true, + .cr = dlci->cmux->initiator, .pf = true, .type = MODEM_CMUX_FRAME_TYPE_DISC, .data = NULL, @@ -1230,7 +1908,7 @@ static void modem_cmux_dlci_close_handler(struct k_work *item) }; modem_cmux_transmit_cmd_frame(cmux, &frame); - k_work_schedule(&dlci->close_work, MODEM_CMUX_T1_TIMEOUT); + modem_work_schedule(&dlci->close_work, MODEM_CMUX_T1_TIMEOUT); } static void modem_cmux_dlci_pipes_release(struct modem_cmux *cmux) @@ -1252,28 +1930,25 @@ void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *co __ASSERT_NO_MSG(cmux != NULL); __ASSERT_NO_MSG(config != NULL); __ASSERT_NO_MSG(config->receive_buf != NULL); - __ASSERT_NO_MSG(config->receive_buf_size >= - (CONFIG_MODEM_CMUX_MTU + MODEM_CMUX_FRAME_SIZE_MAX)); + __ASSERT_NO_MSG(config->receive_buf_size >= MODEM_CMUX_DATA_FRAME_SIZE_MAX); __ASSERT_NO_MSG(config->transmit_buf != NULL); - __ASSERT_NO_MSG(config->transmit_buf_size >= - (CONFIG_MODEM_CMUX_MTU + MODEM_CMUX_FRAME_SIZE_MAX)); - - memset(cmux, 0x00, sizeof(*cmux)); - cmux->callback = config->callback; - cmux->user_data = config->user_data; - cmux->receive_buf = config->receive_buf; - cmux->receive_buf_size = config->receive_buf_size; + __ASSERT_NO_MSG(config->transmit_buf_size >= MODEM_CMUX_DATA_FRAME_SIZE_MAX); + + *cmux = (struct modem_cmux){ + .t3_timepoint = sys_timepoint_calc(K_NO_WAIT), + .config = *config, + }; sys_slist_init(&cmux->dlcis); - cmux->state = MODEM_CMUX_STATE_DISCONNECTED; - ring_buf_init(&cmux->transmit_rb, config->transmit_buf_size, config->transmit_buf); + ring_buf_init(&cmux->transmit_rb, cmux->config.transmit_buf_size, + cmux->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_work_init_delayable(&cmux->runtime_pm_work, modem_cmux_runtime_pm_handler); k_event_init(&cmux->event); - k_event_clear(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); - k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED); #if CONFIG_MODEM_STATS modem_cmux_init_buf_stats(cmux); @@ -1335,8 +2010,7 @@ int modem_cmux_connect(struct modem_cmux *cmux) return ret; } - if (k_event_wait(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT, false, - MODEM_CMUX_T2_TIMEOUT) == 0) { + if (!wait_state(cmux, MODEM_CMUX_STATE_CONNECTED, MODEM_CMUX_T2_TIMEOUT)) { return -EAGAIN; } @@ -1347,7 +2021,7 @@ int modem_cmux_connect_async(struct modem_cmux *cmux) { int ret = 0; - if (k_event_test(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT)) { + if (cmux->state != MODEM_CMUX_STATE_DISCONNECTED) { return -EALREADY; } @@ -1358,7 +2032,7 @@ int modem_cmux_connect_async(struct modem_cmux *cmux) } if (k_work_delayable_is_pending(&cmux->connect_work) == false) { - k_work_schedule(&cmux->connect_work, K_NO_WAIT); + modem_work_schedule(&cmux->connect_work, K_NO_WAIT); } } @@ -1374,8 +2048,7 @@ int modem_cmux_disconnect(struct modem_cmux *cmux) return ret; } - if (k_event_wait(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT, false, - MODEM_CMUX_T2_TIMEOUT) == 0) { + if (!wait_state(cmux, MODEM_CMUX_STATE_DISCONNECTED, MODEM_CMUX_T2_TIMEOUT)) { return -EAGAIN; } @@ -1386,7 +2059,7 @@ int modem_cmux_disconnect_async(struct modem_cmux *cmux) { int ret = 0; - if (k_event_test(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT)) { + if (cmux->state == MODEM_CMUX_STATE_DISCONNECTED) { return -EALREADY; } @@ -1397,7 +2070,7 @@ int modem_cmux_disconnect_async(struct modem_cmux *cmux) } if (k_work_delayable_is_pending(&cmux->disconnect_work) == false) { - k_work_schedule(&cmux->disconnect_work, K_NO_WAIT); + modem_work_schedule(&cmux->disconnect_work, K_NO_WAIT); } } @@ -1433,7 +2106,6 @@ void modem_cmux_release(struct modem_cmux *cmux) /* Unreference pipe */ cmux->pipe = NULL; - /* Reset events */ - k_event_clear(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); - k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); + /* Reset state */ + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED); } diff --git a/subsys/modem/modem_pipe.c b/subsys/modem/modem_pipe.c index bf642b3984dc..ef66b9085ea6 100644 --- a/subsys/modem/modem_pipe.c +++ b/subsys/modem/modem_pipe.c @@ -136,7 +136,7 @@ void modem_pipe_attach(struct modem_pipe *pipe, modem_pipe_api_callback callback int modem_pipe_transmit(struct modem_pipe *pipe, const uint8_t *buf, size_t size) { if (!pipe_test_events(pipe, PIPE_EVENT_OPENED_BIT)) { - return -EPERM; + return 0; } pipe_clear_events(pipe, PIPE_EVENT_TRANSMIT_IDLE_BIT); @@ -146,7 +146,7 @@ int modem_pipe_transmit(struct modem_pipe *pipe, const uint8_t *buf, size_t size int modem_pipe_receive(struct modem_pipe *pipe, uint8_t *buf, size_t size) { if (!pipe_test_events(pipe, PIPE_EVENT_OPENED_BIT)) { - return -EPERM; + return 0; } pipe_clear_events(pipe, PIPE_EVENT_RECEIVE_READY_BIT); diff --git a/subsys/modem/modem_ppp.c b/subsys/modem/modem_ppp.c index 537f198251f7..09b62bdaa11e 100644 --- a/subsys/modem/modem_ppp.c +++ b/subsys/modem/modem_ppp.c @@ -7,8 +7,11 @@ #include #include #include +#include #include +#include "modem_workqueue.h" + #include LOG_MODULE_REGISTER(modem_ppp, CONFIG_MODEM_MODULES_LOG_LEVEL); @@ -48,148 +51,134 @@ static uint16_t modem_ppp_ppp_protocol(struct net_pkt *pkt) return 0; } -static uint8_t modem_ppp_wrap_net_pkt_byte(struct modem_ppp *ppp) +static bool modem_ppp_needs_escape(uint8_t byte) { - 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: - (void)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; - } + return (byte == MODEM_PPP_CODE_DELIMITER) || (byte == MODEM_PPP_CODE_ESCAPE) || + (byte < MODEM_PPP_VALUE_ESCAPE); +} - if (net_pkt_remaining_data(ppp->tx_pkt) == 0) { - ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_FCS_LOW; - } +static uint32_t modem_ppp_wrap(struct modem_ppp *ppp, uint8_t *buffer, uint32_t available) +{ + uint32_t offset = 0; + uint32_t remaining; + uint16_t protocol; + uint8_t upper; + uint8_t lower; + uint8_t byte; - return byte; + while (offset < available) { + remaining = available - offset; - 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 { + switch (ppp->transmit_state) { + case MODEM_PPP_TRANSMIT_STATE_SOF: + if (remaining < 4) { + /* Insufficient space for constant header prefix */ + goto end; + } + /* Init cursor for later phases */ + net_pkt_cursor_init(ppp->tx_pkt); + /* 3 byte common header */ + buffer[offset++] = MODEM_PPP_CODE_DELIMITER; + buffer[offset++] = 0xFF; + buffer[offset++] = MODEM_PPP_CODE_ESCAPE; + buffer[offset++] = 0x23; + /* Initialise the FCS. + * This value is always the same at this point, so use the constant value. + * Equivelent to: + * ppp->tx_pkt_fcs = modem_ppp_fcs_init(0xFF); + * ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, 0x03); + */ + ARG_UNUSED(modem_ppp_fcs_init); + ppp->tx_pkt_fcs = 0x3DE3; + /* Next state */ + if (net_pkt_is_ppp(ppp->tx_pkt)) { + ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA; + } else { + ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_PROTOCOL; + } + break; + case MODEM_PPP_TRANSMIT_STATE_PROTOCOL: + /* If both protocol bytes need escaping, it could be 4 bytes */ + if (remaining < 4) { + /* Insufficient space for protocol bytes */ + goto end; + } + /* Extract protocol bytes */ + protocol = modem_ppp_ppp_protocol(ppp->tx_pkt); + upper = (protocol >> 8) & 0xFF; + lower = (protocol >> 0) & 0xFF; + /* FCS is computed without the escape/modification */ + ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, upper); + ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, lower); + /* Push protocol bytes (with required escaping) */ + if (modem_ppp_needs_escape(upper)) { + buffer[offset++] = MODEM_PPP_CODE_ESCAPE; + upper ^= MODEM_PPP_VALUE_ESCAPE; + } + buffer[offset++] = upper; + if (modem_ppp_needs_escape(lower)) { + buffer[offset++] = MODEM_PPP_CODE_ESCAPE; + lower ^= MODEM_PPP_VALUE_ESCAPE; + } + buffer[offset++] = lower; + /* Next state */ ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_DATA; + break; + case MODEM_PPP_TRANSMIT_STATE_DATA: + /* Push all data bytes into the buffer */ + while (net_pkt_remaining_data(ppp->tx_pkt) > 0) { + /* Space available, taking into account possible escapes */ + if (remaining < 2) { + goto end; + } + /* Pull next byte we're sending */ + (void)net_pkt_read_u8(ppp->tx_pkt, &byte); + /* FCS is computed without the escape/modification */ + ppp->tx_pkt_fcs = modem_ppp_fcs_update(ppp->tx_pkt_fcs, byte); + /* Push encoded bytes into buffer */ + if (modem_ppp_needs_escape(byte)) { + buffer[offset++] = MODEM_PPP_CODE_ESCAPE; + byte ^= MODEM_PPP_VALUE_ESCAPE; + remaining--; + } + buffer[offset++] = byte; + remaining--; + } + /* Data phase finished */ + ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_EOF; + break; + case MODEM_PPP_TRANSMIT_STATE_EOF: + /* If both FCS bytes need escaping, it could be 5 bytes */ + if (remaining < 5) { + /* Insufficient space for protocol bytes */ + goto end; + } + /* Push FCS (order is [lower, upper] unlike the protocol) */ + ppp->tx_pkt_fcs = modem_ppp_fcs_final(ppp->tx_pkt_fcs); + lower = (ppp->tx_pkt_fcs >> 0) & 0xFF; + upper = (ppp->tx_pkt_fcs >> 8) & 0xFF; + if (modem_ppp_needs_escape(lower)) { + buffer[offset++] = MODEM_PPP_CODE_ESCAPE; + lower ^= MODEM_PPP_VALUE_ESCAPE; + } + buffer[offset++] = lower; + if (modem_ppp_needs_escape(upper)) { + buffer[offset++] = MODEM_PPP_CODE_ESCAPE; + upper ^= MODEM_PPP_VALUE_ESCAPE; + } + buffer[offset++] = upper; + buffer[offset++] = MODEM_PPP_CODE_DELIMITER; + + /* Packet has finished */ + ppp->transmit_state = MODEM_PPP_TRANSMIT_STATE_IDLE; + goto end; + default: + LOG_DBG("Invalid transmit state (%d)", ppp->transmit_state); + goto end; } - - 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; +end: + return offset; } static bool modem_ppp_is_byte_expected(uint8_t byte, uint8_t expected_byte) @@ -340,12 +329,12 @@ static void modem_ppp_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_eve switch (event) { case MODEM_PIPE_EVENT_RECEIVE_READY: - k_work_submit(&ppp->process_work); + modem_work_submit(&ppp->process_work); break; case MODEM_PIPE_EVENT_OPENED: case MODEM_PIPE_EVENT_TRANSMIT_IDLE: - k_work_submit(&ppp->send_work); + modem_work_submit(&ppp->send_work); break; default: @@ -356,32 +345,36 @@ static void modem_ppp_pipe_callback(struct modem_pipe *pipe, enum modem_pipe_eve 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; + uint32_t pushed; int ret; if (ppp->tx_pkt == NULL) { ppp->tx_pkt = k_fifo_get(&ppp->tx_pkt_fifo, K_NO_WAIT); } + if (ring_buf_is_empty(&ppp->transmit_rb)) { + /* Reset to initial state to maximise contiguous claim */ + ring_buf_reset(&ppp->transmit_rb); + } + 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); + /* Claim as much space as possible */ + reserved_size = ring_buf_put_claim(&ppp->transmit_rb, &reserved, UINT32_MAX); + /* Push wrapped data into claimed buffer */ + pushed = modem_ppp_wrap(ppp, reserved, reserved_size); + /* Limit claimed data to what was actually pushed */ + ring_buf_put_finish(&ppp->transmit_rb, pushed); - 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; - } + 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); } } @@ -424,7 +417,7 @@ static void modem_ppp_process_handler(struct k_work *item) modem_ppp_process_received_byte(ppp, ppp->receive_buf[i]); } - k_work_submit(&ppp->process_work); + modem_work_submit(&ppp->process_work); } static void modem_ppp_ppp_api_init(struct net_if *iface) @@ -445,12 +438,24 @@ static void modem_ppp_ppp_api_init(struct net_if *iface) static int modem_ppp_ppp_api_start(const struct device *dev) { - return 0; + const struct modem_ppp_config *config = (const struct modem_ppp_config *)dev->config; + + if (config == NULL || config->dev == NULL) { + return 0; + } + + return pm_device_runtime_get(config->dev); } static int modem_ppp_ppp_api_stop(const struct device *dev) { - return 0; + const struct modem_ppp_config *config = (const struct modem_ppp_config *)dev->config; + + if (config == NULL || config->dev == NULL) { + return 0; + } + + return pm_device_runtime_put_async(config->dev, K_NO_WAIT); } static int modem_ppp_ppp_api_send(const struct device *dev, struct net_pkt *pkt) @@ -475,7 +480,7 @@ static int modem_ppp_ppp_api_send(const struct device *dev, struct net_pkt *pkt) net_pkt_ref(pkt); k_fifo_put(&ppp->tx_pkt_fifo, pkt); - k_work_submit(&ppp->send_work); + modem_work_submit(&ppp->send_work); return 0; } diff --git a/subsys/modem/modem_ubx.c b/subsys/modem/modem_ubx.c index 22586b8444d0..89d968caf799 100644 --- a/subsys/modem/modem_ubx.c +++ b/subsys/modem/modem_ubx.c @@ -9,6 +9,8 @@ #include #include +#include "modem_workqueue.h" + #include LOG_MODULE_REGISTER(modem_ubx, CONFIG_MODEM_MODULES_LOG_LEVEL); @@ -19,7 +21,7 @@ static void modem_ubx_pipe_callback(struct modem_pipe *pipe, struct modem_ubx *ubx = (struct modem_ubx *)user_data; if (event == MODEM_PIPE_EVENT_RECEIVE_READY) { - k_work_submit(&ubx->process_work); + modem_work_submit(&ubx->process_work); } } diff --git a/subsys/modem/modem_workqueue.c b/subsys/modem/modem_workqueue.c new file mode 100644 index 000000000000..4fb5d716c5c7 --- /dev/null +++ b/subsys/modem/modem_workqueue.c @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2025 Embeint Pty Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +#include "modem_workqueue.h" + +static struct k_work_q modem_work_q; +static K_THREAD_STACK_DEFINE(modem_stack_area, CONFIG_MODEM_DEDICATED_WORKQUEUE_STACK_SIZE); + +int modem_work_submit(struct k_work *work) +{ + return k_work_submit_to_queue(&modem_work_q, work); +} + +int modem_work_schedule(struct k_work_delayable *dwork, k_timeout_t delay) +{ + return k_work_schedule_for_queue(&modem_work_q, dwork, delay); +} + +int modem_work_reschedule(struct k_work_delayable *dwork, k_timeout_t delay) +{ + return k_work_reschedule_for_queue(&modem_work_q, dwork, delay); +} + +static int modem_work_q_init(void) +{ + /* Boot the dedicated workqueue */ + k_work_queue_init(&modem_work_q); + k_work_queue_start(&modem_work_q, modem_stack_area, K_THREAD_STACK_SIZEOF(modem_stack_area), + CONFIG_MODEM_DEDICATED_WORKQUEUE_PRIORITY, NULL); + k_thread_name_set(k_work_queue_thread_get(&modem_work_q), "modem_workq"); + return 0; +} + +SYS_INIT(modem_work_q_init, POST_KERNEL, 0); diff --git a/subsys/modem/modem_workqueue.h b/subsys/modem/modem_workqueue.h new file mode 100644 index 000000000000..d906ed2e00f0 --- /dev/null +++ b/subsys/modem/modem_workqueue.h @@ -0,0 +1,49 @@ +/** @file + * @brief Modem workqueue header file. + */ + +/* + * Copyright (c) 2025 Embeint Pty Ltd + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_MODEM_WORKQUEUE_H_ +#define ZEPHYR_INCLUDE_MODEM_WORKQUEUE_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef CONFIG_MODEM_DEDICATED_WORKQUEUE + +int modem_work_submit(struct k_work *work); +int modem_work_schedule(struct k_work_delayable *dwork, k_timeout_t delay); +int modem_work_reschedule(struct k_work_delayable *dwork, k_timeout_t delay); + +#else + +static inline int modem_work_submit(struct k_work *work) +{ + return k_work_submit(work); +} + +static inline int modem_work_schedule(struct k_work_delayable *dwork, k_timeout_t delay) +{ + return k_work_schedule(dwork, delay); +} + +static inline int modem_work_reschedule(struct k_work_delayable *dwork, k_timeout_t delay) +{ + return k_work_reschedule(dwork, delay); +} + +#endif /* CONFIG_MODEM_DEDICATED_WORKQUEUE */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_MODEM_WORKQUEUE_H_ */ diff --git a/tests/subsys/modem/mock/modem_backend_mock.c b/tests/subsys/modem/mock/modem_backend_mock.c index 5b7b47c0eb49..49ea1927076b 100644 --- a/tests/subsys/modem/mock/modem_backend_mock.c +++ b/tests/subsys/modem/mock/modem_backend_mock.c @@ -52,12 +52,15 @@ static int modem_backend_mock_transmit(void *data, const uint8_t *buf, size_t si return ret; } - ret = ring_buf_put(&mock->tx_rb, buf, size); if (modem_backend_mock_update(mock, buf, size)) { + /* Skip ringbuffer if transaction consumes bytes */ + ret = size; modem_backend_mock_put(mock, mock->transaction->put, mock->transaction->put_size); - mock->transaction = NULL; + modem_backend_mock_prime(mock, mock->transaction->next); + } else { + ret = ring_buf_put(&mock->tx_rb, buf, size); } k_work_submit(&mock->transmit_idle_work); @@ -137,6 +140,10 @@ int modem_backend_mock_get(struct modem_backend_mock *mock, uint8_t *buf, size_t void modem_backend_mock_put(struct modem_backend_mock *mock, const uint8_t *buf, size_t size) { + if (size == 0) { + return; + } + __ASSERT(ring_buf_put(&mock->rx_rb, buf, size) == size, "Mock buffer capacity exceeded"); @@ -155,3 +162,10 @@ void modem_backend_mock_bridge(struct modem_backend_mock *mock_a, struct modem_b mock_a->bridge = mock_b; mock_b->bridge = mock_a; } + +void modem_backend_mock_wait_for_transaction(struct modem_backend_mock *mock) +{ + while (mock->transaction) { + k_msleep(1); + } +} diff --git a/tests/subsys/modem/mock/modem_backend_mock.h b/tests/subsys/modem/mock/modem_backend_mock.h index 56a5b585cb12..34b79aca552f 100644 --- a/tests/subsys/modem/mock/modem_backend_mock.h +++ b/tests/subsys/modem/mock/modem_backend_mock.h @@ -19,6 +19,9 @@ struct modem_backend_mock_transaction { /* Data which will be put in response to get data */ const uint8_t *put; size_t put_size; + + /* Next transaction in chain */ + const struct modem_backend_mock_transaction *next; }; struct modem_backend_mock { @@ -62,4 +65,6 @@ void modem_backend_mock_prime(struct modem_backend_mock *mock, void modem_backend_mock_bridge(struct modem_backend_mock *mock_a, struct modem_backend_mock *mock_b); +void modem_backend_mock_wait_for_transaction(struct modem_backend_mock *mock); + #endif /* ZEPHYR_DRIVERS_MODEM_MODEM_PIPE_MOCK */ diff --git a/tests/subsys/modem/modem_cmux/src/main.c b/tests/subsys/modem/modem_cmux/src/main.c index b1adce37cc46..b40af7e0de89 100644 --- a/tests/subsys/modem/modem_cmux/src/main.c +++ b/tests/subsys/modem/modem_cmux/src/main.c @@ -117,17 +117,19 @@ static uint8_t cmux_frame_control_cld_ack[] = {0xF9, 0x03, 0xEF, 0x05, 0xC1, 0x0 static uint8_t cmux_frame_dlci1_sabm_cmd[] = {0xF9, 0x07, 0x3F, 0x01, 0xDE, 0xF9}; static uint8_t cmux_frame_dlci1_sabm_ack[] = {0xF9, 0x07, 0x73, 0x01, 0x15, 0xF9}; static uint8_t cmux_frame_dlci1_disc_cmd[] = {0xF9, 0x07, 0x53, 0x01, 0x3F, 0xF9}; +static uint8_t cmux_frame_dlci1_msc_cmd[] = {0xF9, 0x03, 0xEF, 0x09, 0xE3, + 0x05, 0x07, 0x8D, 0xFB, 0xF9}; static uint8_t cmux_frame_dlci1_ua_ack[] = {0xF9, 0x07, 0x73, 0x01, 0x15, 0xF9}; static uint8_t cmux_frame_dlci2_sabm_cmd[] = {0xF9, 0x0B, 0x3F, 0x01, 0x59, 0xF9}; static uint8_t cmux_frame_dlci2_sabm_ack[] = {0xF9, 0x0B, 0x73, 0x01, 0x92, 0xF9}; static uint8_t cmux_frame_dlci2_disc_cmd[] = {0xF9, 0x0B, 0x53, 0x01, 0xB8, 0xF9}; +static uint8_t cmux_frame_dlci2_msc_cmd[] = {0xF9, 0x03, 0xEF, 0x09, 0xE3, + 0x05, 0x0B, 0x8D, 0xFB, 0xF9}; static uint8_t cmux_frame_dlci2_ua_ack[] = {0xF9, 0x0B, 0x73, 0x01, 0x92, 0xF9}; -static uint8_t cmux_frame_control_msc_cmd[] = {0xF9, 0x01, 0xFF, 0x0B, 0xE3, - 0x07, 0x0B, 0x09, 0x01, 0x6C, 0xF9}; - -static uint8_t cmux_frame_control_msc_ack[] = {0xF9, 0x01, 0xFF, 0x0B, 0xE1, - 0x07, 0x0B, 0x09, 0x01, 0x6C, 0xF9}; - +static uint8_t cmux_frame_control_msc_cmd[] = {0xF9, 0x01, 0xEF, 0x09, 0xE3, + 0x05, 0x07, 0x01, 0x9A, 0xF9}; +static uint8_t cmux_frame_control_msc_ack[] = {0xF9, 0x01, 0xEF, 0x09, 0xE1, + 0x05, 0x07, 0x01, 0x9A, 0xF9}; static uint8_t cmux_frame_control_fcon_cmd[] = {0xF9, 0x01, 0xFF, 0x05, 0xA3, 0x01, 0x86, 0xF9}; static uint8_t cmux_frame_control_fcon_ack[] = {0xF9, 0x01, 0xFF, 0x05, 0xA1, 0x01, 0x86, 0xF9}; static uint8_t cmux_frame_control_fcoff_cmd[] = {0xF9, 0x01, 0xFF, 0x05, 0x63, 0x01, 0x86, 0xF9}; @@ -227,19 +229,31 @@ const static struct modem_backend_mock_transaction transaction_dlci2_disc = { .put_size = sizeof(cmux_frame_dlci2_ua_ack) }; +const static struct modem_backend_mock_transaction transaction_dlci1_msc = { + .get = cmux_frame_dlci1_msc_cmd, + .get_size = sizeof(cmux_frame_dlci1_msc_cmd), + .put = NULL, + .put_size = 0}; + +const static struct modem_backend_mock_transaction transaction_dlci2_msc = { + .get = cmux_frame_dlci2_msc_cmd, + .get_size = sizeof(cmux_frame_dlci2_msc_cmd), + .put = NULL, + .put_size = 0}; + const static struct modem_backend_mock_transaction transaction_dlci1_sabm = { .get = cmux_frame_dlci1_sabm_cmd, .get_size = sizeof(cmux_frame_dlci1_sabm_cmd), .put = cmux_frame_dlci1_ua_ack, - .put_size = sizeof(cmux_frame_dlci1_ua_ack) -}; + .put_size = sizeof(cmux_frame_dlci1_ua_ack), + .next = &transaction_dlci1_msc}; const static struct modem_backend_mock_transaction transaction_dlci2_sabm = { .get = cmux_frame_dlci2_sabm_cmd, .get_size = sizeof(cmux_frame_dlci2_sabm_cmd), .put = cmux_frame_dlci2_ua_ack, - .put_size = sizeof(cmux_frame_dlci2_ua_ack) -}; + .put_size = sizeof(cmux_frame_dlci2_ua_ack), + .next = &transaction_dlci2_msc}; static void test_modem_cmux_callback(struct modem_cmux *cmux, enum modem_cmux_event event, void *user_data) @@ -317,6 +331,9 @@ static void *test_modem_cmux_setup(void) events = k_event_wait(&cmux_event, EVENT_CMUX_DLCI2_OPEN, false, K_MSEC(100)); __ASSERT_NO_MSG((events & EVENT_CMUX_DLCI2_OPEN)); + /* Consume the MSC command sent after DLCI opening */ + modem_backend_mock_wait_for_transaction(&bus_mock); + return NULL; } @@ -603,8 +620,8 @@ ZTEST(modem_cmux, test_modem_cmux_dlci1_close_open) zassert_true((events & EVENT_CMUX_DLCI1_OPEN), "DLCI1 not opened as expected"); - /* Wait for potential T1 timeout */ - k_msleep(500); + modem_backend_mock_prime(&bus_mock, &transaction_dlci1_msc); + modem_backend_mock_wait_for_transaction(&bus_mock); ret = modem_backend_mock_get(&bus_mock, buffer1, sizeof(buffer1)); zassert_true(ret == 0, "Received unexpected data"); @@ -705,6 +722,9 @@ ZTEST(modem_cmux, test_modem_cmux_disconnect_connect) zassert_true((events & EVENT_CMUX_DLCI1_OPEN), "DLCI1 not opened as expected"); + modem_backend_mock_prime(&bus_mock, &transaction_dlci1_msc); + modem_backend_mock_wait_for_transaction(&bus_mock); + /* Wait for potential T1 timeout */ k_msleep(500); @@ -730,8 +750,10 @@ ZTEST(modem_cmux, test_modem_cmux_disconnect_connect) events = k_event_wait_all(&cmux_event, (EVENT_CMUX_DLCI2_OPEN), false, K_MSEC(100)); - zassert_true((events & EVENT_CMUX_DLCI2_OPEN), - "DLCI1 not opened as expected"); + zassert_true((events & EVENT_CMUX_DLCI2_OPEN), "DLCI2 not opened as expected"); + + modem_backend_mock_prime(&bus_mock, &transaction_dlci2_msc); + modem_backend_mock_wait_for_transaction(&bus_mock); /* Wait for potential T1 timeout */ k_msleep(500); @@ -746,6 +768,10 @@ ZTEST(modem_cmux, test_modem_cmux_disconnect_connect_sync) zassert_true(modem_pipe_close(dlci1_pipe, K_SECONDS(10)) == 0, "Failed to close DLCI1"); modem_backend_mock_prime(&bus_mock, &transaction_dlci2_disc); zassert_true(modem_pipe_close(dlci2_pipe, K_SECONDS(10)) == 0, "Failed to close DLCI2"); + + /* Clear any pending data before CLD transaction */ + modem_backend_mock_reset(&bus_mock); + modem_backend_mock_prime(&bus_mock, &transaction_control_cld); zassert_true(modem_cmux_disconnect(&cmux) == 0, "Failed to disconnect CMUX"); zassert_true(modem_cmux_disconnect(&cmux) == -EALREADY, @@ -759,9 +785,11 @@ ZTEST(modem_cmux, test_modem_cmux_disconnect_connect_sync) modem_backend_mock_prime(&bus_mock, &transaction_dlci1_sabm); zassert_true(modem_pipe_open(dlci1_pipe, K_SECONDS(10)) == 0, "Failed to open DLCI1 pipe"); + modem_backend_mock_wait_for_transaction(&bus_mock); modem_backend_mock_prime(&bus_mock, &transaction_dlci2_sabm); zassert_true(modem_pipe_open(dlci2_pipe, K_SECONDS(10)) == 0, "Failed to open DLCI2 pipe"); + modem_backend_mock_wait_for_transaction(&bus_mock); } ZTEST(modem_cmux, test_modem_cmux_dlci_close_open_sync) @@ -773,9 +801,11 @@ ZTEST(modem_cmux, test_modem_cmux_dlci_close_open_sync) modem_backend_mock_prime(&bus_mock, &transaction_dlci1_sabm); zassert_true(modem_pipe_open(dlci1_pipe, K_SECONDS(10)) == 0, "Failed to open DLCI1 pipe"); + modem_backend_mock_wait_for_transaction(&bus_mock); modem_backend_mock_prime(&bus_mock, &transaction_dlci2_sabm); zassert_true(modem_pipe_open(dlci2_pipe, K_SECONDS(10)) == 0, "Failed to open DLCI2 pipe"); + modem_backend_mock_wait_for_transaction(&bus_mock); } ZTEST(modem_cmux, test_modem_cmux_prevent_work_while_released) @@ -820,10 +850,13 @@ ZTEST(modem_cmux, test_modem_cmux_prevent_work_while_released) zassert_ok(modem_cmux_attach(&cmux, bus_mock_pipe)); modem_backend_mock_prime(&bus_mock, &transaction_control_sabm); zassert_ok(modem_cmux_connect(&cmux)); + modem_backend_mock_wait_for_transaction(&bus_mock); modem_backend_mock_prime(&bus_mock, &transaction_dlci1_sabm); zassert_ok(modem_pipe_open(dlci1_pipe, K_SECONDS(10))); + modem_backend_mock_wait_for_transaction(&bus_mock); modem_backend_mock_prime(&bus_mock, &transaction_dlci2_sabm); zassert_ok(modem_pipe_open(dlci2_pipe, K_SECONDS(10))); + modem_backend_mock_wait_for_transaction(&bus_mock); } ZTEST(modem_cmux, test_modem_drop_frames_with_invalid_length) @@ -886,4 +919,41 @@ ZTEST(modem_cmux, test_modem_cmux_split_large_data) "Incorrect number of bytes transmitted %d", ret); } +ZTEST(modem_cmux, test_modem_cmux_invalid_cr) +{ + uint32_t events; + + /* We are initiator, so any CMD with CR set should be dropped */ + modem_backend_mock_put(&bus_mock, cmux_frame_control_cld_cmd, + sizeof(cmux_frame_control_cld_cmd)); + + modem_backend_mock_put(&bus_mock, cmux_frame_control_sabm_cmd, + sizeof(cmux_frame_control_sabm_cmd)); + + events = k_event_wait_all(&cmux_event, + (MODEM_CMUX_EVENT_CONNECTED | MODEM_CMUX_EVENT_DISCONNECTED), + false, K_MSEC(100)); + + zassert_false(events, "Wrong CMD should have been ignored"); +} + +ZTEST(modem_cmux, test_modem_cmux_invalid_command) +{ + static uint8_t invalid_cmd[] = {0xF9, 0x03, 0xEF, 0x09, 0x00, + 0x00, 0x00, 0x00, 0xFB, 0xF9}; + uint32_t events; + + modem_backend_mock_put(&bus_mock, invalid_cmd, + sizeof(invalid_cmd)); + + events = k_event_wait_all(&cmux_event, + (MODEM_CMUX_EVENT_CONNECTED | MODEM_CMUX_EVENT_DISCONNECTED), + false, K_SECONDS(1)); + + zassert_false(events, "Wrong CMD should have been ignored"); + + /* Invalid command should not cause any response */ + zassert_equal(0, modem_backend_mock_get(&bus_mock, buffer1, sizeof(buffer1))); +} + ZTEST_SUITE(modem_cmux, NULL, test_modem_cmux_setup, test_modem_cmux_before, NULL, NULL); diff --git a/tests/subsys/modem/modem_cmux_pair/src/main.c b/tests/subsys/modem/modem_cmux_pair/src/main.c index 5cc8fed4b458..1988e9d73319 100644 --- a/tests/subsys/modem/modem_cmux_pair/src/main.c +++ b/tests/subsys/modem/modem_cmux_pair/src/main.c @@ -507,9 +507,10 @@ ZTEST(modem_cmux_pair, test_modem_cmux_disconnect_connect) modem_backend_mock_reset(&bus_mock_dte); zassert_true(modem_cmux_disconnect_async(&cmux_dte) == 0, "Failed to disconnect CMUX"); - k_msleep(100); + events = k_event_wait_all(&cmux_event_dte, (EVENT_CMUX_DISCONNECTED), false, K_MSEC(660)); + zassert_true((events & EVENT_CMUX_DISCONNECTED), "Failed to disconnect CMUX"); - events = k_event_wait_all(&cmux_event_dte, (EVENT_CMUX_DISCONNECTED), false, K_MSEC(100)); + events = k_event_wait_all(&cmux_event_dce, (EVENT_CMUX_DISCONNECTED), false, K_MSEC(660)); zassert_true((events & EVENT_CMUX_DISCONNECTED), "Failed to disconnect CMUX"); /* Reconnect CMUX */ @@ -554,6 +555,9 @@ ZTEST(modem_cmux_pair, test_modem_cmux_disconnect_connect_sync) zassert_true(modem_cmux_disconnect(&cmux_dte) == 0, "Failed to disconnect CMUX"); zassert_true(modem_cmux_disconnect(&cmux_dte) == -EALREADY, "Should already be disconnected"); + + events = k_event_wait_all(&cmux_event_dce, (EVENT_CMUX_DISCONNECTED), false, K_MSEC(660)); + zassert_true((events & EVENT_CMUX_DISCONNECTED), "Failed to disconnect CMUX"); zassert_true(modem_cmux_disconnect(&cmux_dce) == -EALREADY, "Should already be disconnected"); diff --git a/tests/subsys/modem/modem_pipe/src/main.c b/tests/subsys/modem/modem_pipe/src/main.c index 3cb330550fd5..7889d111d1b9 100644 --- a/tests/subsys/modem/modem_pipe/src/main.c +++ b/tests/subsys/modem/modem_pipe/src/main.c @@ -327,6 +327,14 @@ static void test_pipe_notify_receive_ready(void) "Unexpected state %u", (uint32_t)atomic_get(&test_state)); } +static void test_pipe_receive_closed(void) +{ + /* Try to receive from a closed pipe - should return 0 */ + zassert_equal(modem_pipe_receive(test_pipe, test_buffer, test_buffer_size), 0, + "Reading from closed pipe should return 0"); + zassert_false(test_backend.receive_called, "receive should not be called on closed pipe"); +} + ZTEST(modem_pipe, test_async_open_close) { test_pipe_open(); @@ -397,5 +405,16 @@ ZTEST(modem_pipe, test_attach) test_pipe_attach_receive_not_ready_transmit_idle(); } +ZTEST(modem_pipe, test_receive_closed) +{ + test_pipe_open(); + test_reset(); + test_pipe_async_transmit(); + test_reset(); + test_pipe_close(); + /* Test reading from a closed pipe should return 0 */ + test_pipe_receive_closed(); +} + ZTEST_SUITE(modem_pipe, NULL, modem_backend_fake_setup, modem_backend_fake_before, modem_backend_fake_after, NULL);