From 39bcca3bbeda392589a011624820b6c16a5d37e9 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Thu, 18 Sep 2025 16:35:03 +1000 Subject: [PATCH 01/26] [nrf fromtree] modem: at_shell: extract user pipe handling Extract the user pipe setup and claim/release logic so that it can be re-used by other software modules, if the AT shell is not used. Ideally the chat instance would live within the `modem_at_user_pipe.c` and be handed out by `modem_at_user_pipe_claim`, but the current chat API doesn't make this possible. Signed-off-by: Jordan Yates (cherry picked from commit c0a2928f46e962e05504981e7e4dacaca9be4114) --- doc/releases/migration-guide-4.3.rst | 5 ++ drivers/modem/CMakeLists.txt | 1 + drivers/modem/Kconfig.at_shell | 24 +++-- drivers/modem/modem_at_shell.c | 126 ++++----------------------- drivers/modem/modem_at_user_pipe.c | 124 ++++++++++++++++++++++++++ include/zephyr/modem/at/user_pipe.h | 37 ++++++++ 6 files changed, 200 insertions(+), 117 deletions(-) create mode 100644 drivers/modem/modem_at_user_pipe.c create mode 100644 include/zephyr/modem/at/user_pipe.h diff --git a/doc/releases/migration-guide-4.3.rst b/doc/releases/migration-guide-4.3.rst index 83dc5e771201..97e913c765ed 100644 --- a/doc/releases/migration-guide-4.3.rst +++ b/doc/releases/migration-guide-4.3.rst @@ -153,6 +153,11 @@ Networking .. zephyr-keep-sorted-stop +Modem +***** + +* ``CONFIG_MODEM_AT_SHELL_USER_PIPE`` has been renamed to :kconfig:option:`CONFIG_MODEM_AT_USER_PIPE`. + Display ******* 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/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_ */ From 4165e0dd8ad8a0c6e61ac5c4b481e49fbd40ed11 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Wed, 24 Sep 2025 16:49:54 +0300 Subject: [PATCH 02/26] [nrf fromtree] modem: modem_cellular: Allow PPP interface to wake up the device Allow PPP device to wake up the underlying cellular modem. Signed-off-by: Seppo Takalo (cherry picked from commit b8541c53eeafd1dc9dd17d05c09db39a20bb200a) --- drivers/modem/modem_cellular.c | 26 ++++++++++++------------- include/zephyr/modem/ppp.h | 35 ++++++++++++++++++++++++++++++---- subsys/modem/Kconfig | 1 + subsys/modem/modem_ppp.c | 17 +++++++++++++++-- 4 files changed, 60 insertions(+), 19 deletions(-) diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index 3e7b3a30eff1..23c4795a3549 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -2905,7 +2905,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 +2925,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 +2944,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 +2963,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 +2982,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 +3002,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 +3021,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 +3040,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 +3060,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 +3079,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 +3098,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 +3117,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", \ @@ -3134,7 +3134,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, &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/include/zephyr/modem/ppp.h b/include/zephyr/modem/ppp.h index 2b91e51a35c9..b3dbcfdeb7fb 100644 --- a/include/zephyr/modem/ppp.h +++ b/include/zephyr/modem/ppp.h @@ -123,6 +123,10 @@ struct modem_ppp { #endif }; +struct modem_ppp_config { + const struct device *dev; +}; + /** * @endcond */ @@ -172,13 +176,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 +197,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/Kconfig b/subsys/modem/Kconfig index b7c3f7ba9014..087b4d79be8c 100644 --- a/subsys/modem/Kconfig +++ b/subsys/modem/Kconfig @@ -71,6 +71,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/modem_ppp.c b/subsys/modem/modem_ppp.c index 537f198251f7..9e2e2b5d71b7 100644 --- a/subsys/modem/modem_ppp.c +++ b/subsys/modem/modem_ppp.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -445,12 +446,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) From 4f83b20047b86859e0a9c77f820adb5005cb6619 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Tue, 26 Aug 2025 15:23:51 +0300 Subject: [PATCH 03/26] [nrf fromtree] modem: cmux: Clean debugging a bit Full CMUX frames are way too much to fill the debug log so disable those by default. Sometimes it is enough to see the CMD and response types, without hexdump. Added also debug messages for opening and closing events. Signed-off-by: Seppo Takalo (cherry picked from commit dac7a0fe06b4e4e3c53c9a19163699d6f5455001) --- subsys/modem/Kconfig | 6 ++++++ subsys/modem/modem_cmux.c | 23 ++++++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/subsys/modem/Kconfig b/subsys/modem/Kconfig index 087b4d79be8c..9f6ce441b7d5 100644 --- a/subsys/modem/Kconfig +++ b/subsys/modem/Kconfig @@ -55,6 +55,12 @@ 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 diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index d5547e03da28..3e7a001c82ca 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -101,6 +101,7 @@ static struct modem_cmux_command *modem_cmux_command_wrap(const uint8_t *data) return (struct modem_cmux_command *)data; } +#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 +132,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) { @@ -225,14 +232,12 @@ 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) @@ -438,6 +443,7 @@ static void modem_cmux_on_cld_command(struct modem_cmux *cmux, struct modem_cmux k_work_cancel_delayable(&cmux->disconnect_work); } + LOG_DBG("CMUX disconnected"); cmux->state = MODEM_CMUX_STATE_DISCONNECTED; k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); cmux->flow_control_on = false; @@ -455,6 +461,7 @@ static void modem_cmux_on_control_frame_ua(struct modem_cmux *cmux) return; } + LOG_DBG("CMUX connected"); cmux->state = MODEM_CMUX_STATE_CONNECTED; k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); cmux->flow_control_on = true; @@ -534,6 +541,7 @@ static void modem_cmux_on_control_frame_sabm(struct modem_cmux *cmux) return; } + LOG_DBG("CMUX connection request received"); cmux->state = MODEM_CMUX_STATE_CONNECTED; k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER); cmux->flow_control_on = true; @@ -561,7 +569,7 @@ 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; } } @@ -586,6 +594,7 @@ static void modem_cmux_on_dlci_frame_ua(struct modem_cmux_dlci *dlci) { 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); @@ -595,6 +604,7 @@ static void modem_cmux_on_dlci_frame_ua(struct modem_cmux_dlci *dlci) break; case MODEM_CMUX_DLCI_STATE_CLOSING: + LOG_DBG("DLCI %u closed", dlci->dlci_address); dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED; modem_pipe_notify_closed(&dlci->pipe); k_work_cancel_delayable(&dlci->close_work); @@ -637,6 +647,7 @@ 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; modem_pipe_notify_opened(&dlci->pipe); k_mutex_lock(&dlci->receive_rb_lock, K_FOREVER); @@ -655,6 +666,7 @@ static void modem_cmux_on_dlci_frame_disc(struct modem_cmux_dlci *dlci) return; } + LOG_DBG("DLCI %u disconnected", dlci->dlci_address); dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED; modem_pipe_notify_closed(&dlci->pipe); } @@ -690,7 +702,8 @@ static void modem_cmux_on_dlci_frame(struct modem_cmux *cmux) 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; } } From 6cf31199805e7c4010c17c4da64d67f12f6cd046 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Thu, 28 Aug 2025 16:15:46 +0300 Subject: [PATCH 04/26] [nrf fromtree] modem: cmux: Rework the drop handling Do not read extra byte when we decide to drop a frame. Instead go directly to MODEM_CMUX_RECEIVE_STATE_SOF, so the next flag character will start a new frame. Signed-off-by: Seppo Takalo (cherry picked from commit 0025751414ef69e906d445be7269fe0ed544173e) --- include/zephyr/modem/cmux.h | 1 - subsys/modem/modem_cmux.c | 26 +++++++++++--------------- 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h index 3332c40bd18e..10fe93132819 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -74,7 +74,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, }; diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index 3e7a001c82ca..563f6a0baed6 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -13,6 +13,7 @@ LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL); #include +#define MODEM_CMUX_SOF (0xF9) #define MODEM_CMUX_FCS_POLYNOMIAL (0xE0) #define MODEM_CMUX_FCS_INIT_VALUE (0xFF) #define MODEM_CMUX_EA (0x01) @@ -282,7 +283,7 @@ static uint16_t modem_cmux_transmit_frame(struct modem_cmux *cmux, 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); @@ -318,7 +319,7 @@ 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); return data_len; @@ -744,7 +745,7 @@ 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) { + if (byte == MODEM_CMUX_SOF) { cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC; break; } @@ -756,7 +757,7 @@ 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) { break; } @@ -813,7 +814,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; } @@ -838,7 +839,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; } @@ -846,7 +847,7 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by LOG_ERR("Indicated frame data length %u exceeds receive buffer size %u", cmux->frame.data_len, cmux->receive_buf_size); - cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_DROP; + modem_cmux_drop_frame(cmux); break; } @@ -873,7 +874,7 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by 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; + modem_cmux_drop_frame(cmux); break; } @@ -890,21 +891,16 @@ 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; From 2e7bcc297a6b99e873c400ccef5cdee174b4fb7a Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Thu, 2 Oct 2025 14:52:25 +1000 Subject: [PATCH 05/26] [nrf fromtree] modem: cmux: auto calculate work buffer sizes Automatically size the CMUX work buffers based on `CONFIG_MODEM_CMUX_MTU`. This eliminates a Kconfig variable that would otherwise need to manually be kept in sync. The option to extend the size of these buffers is still provided through `CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE_EXTRA`. Signed-off-by: Jordan Yates (cherry picked from commit f047a412e068ffb403a464e24f47509e057acdae) --- doc/releases/migration-guide-4.3.rst | 2 ++ drivers/modem/modem_cellular.c | 8 ++++---- include/zephyr/modem/cmux.h | 6 +++++- subsys/modem/Kconfig | 12 +++++------- 4 files changed, 16 insertions(+), 12 deletions(-) diff --git a/doc/releases/migration-guide-4.3.rst b/doc/releases/migration-guide-4.3.rst index 97e913c765ed..a3aec0898c8e 100644 --- a/doc/releases/migration-guide-4.3.rst +++ b/doc/releases/migration-guide-4.3.rst @@ -157,6 +157,8 @@ 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/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index 23c4795a3549..277ae390fae4 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -110,8 +110,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 +119,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; diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h index 10fe93132819..913f4187d729 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -57,6 +57,10 @@ typedef void (*modem_cmux_callback)(struct modem_cmux *cmux, enum modem_cmux_eve * @cond INTERNAL_HIDDEN */ +/* Total size of the CMUX work buffers */ +#define MODEM_CMUX_WORK_BUFFER_SIZE (CONFIG_MODEM_CMUX_MTU + 7 + \ + CONFIG_MODEM_CMUX_WORK_BUFFER_SIZE_EXTRA) + enum modem_cmux_state { MODEM_CMUX_STATE_DISCONNECTED = 0, MODEM_CMUX_STATE_CONNECTING, @@ -152,7 +156,7 @@ struct modem_cmux { 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; diff --git a/subsys/modem/Kconfig b/subsys/modem/Kconfig index 9f6ce441b7d5..34114cd3893b 100644 --- a/subsys/modem/Kconfig +++ b/subsys/modem/Kconfig @@ -42,14 +42,12 @@ 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). module = MODEM_CMUX module-str = modem_cmux From effa8e42d11ae61ab345c037db639c86e48d16fd Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Wed, 24 Sep 2025 16:44:18 +0300 Subject: [PATCH 06/26] [nrf fromtree] drivers: modem: cellular: nRF91: Remove periodic chat script Remove periodic chat script as the AT+CEREG=1 notifications are already enabled. Signed-off-by: Seppo Takalo (cherry picked from commit 5fa605af172a18985d7d9ab6636d9c9db45b2a7c) --- drivers/modem/modem_cellular.c | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index 277ae390fae4..c0cc43a5dafa 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -2773,12 +2773,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) @@ -3131,7 +3127,8 @@ 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_DT_INST_PPP_DEFINE(inst, MODEM_CELLULAR_INST_NAME(ppp, inst), NULL, 98, 1500, 64); \ From b4499597c73bdc2d4f58210076b047a29349a8d6 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Tue, 26 Aug 2025 16:31:46 +0300 Subject: [PATCH 07/26] [nrf fromtree] modem: cmux: Implement DM and NSC responses Implement following responses from 3GPP TS 27.010: 5.4.6.3.8 Non Supported Command Response (NSC) 5.3.3 Disconnected Mode (DM) response Close DLC when receiving DM response. Signed-off-by: Seppo Takalo (cherry picked from commit 9de7d617096c7518ae085bb3af384aae42847fae) --- subsys/modem/modem_cmux.c | 79 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 73 insertions(+), 6 deletions(-) diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index 563f6a0baed6..93665525ac7b 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -336,6 +336,7 @@ static bool modem_cmux_transmit_cmd_frame(struct modem_cmux *cmux, if (space < MODEM_CMUX_CMD_FRAME_SIZE_MAX) { k_mutex_unlock(&cmux->transmit_rb_lock); + LOG_WRN("CMD buffer overflow"); return false; } @@ -473,6 +474,41 @@ static void modem_cmux_on_control_frame_ua(struct modem_cmux *cmux) 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; + + if (modem_cmux_wrap_command(&cmd, frame.data, frame.data_len) < 0) { + LOG_WRN("Invalid command"); + return; + } + + struct { + /* 3GPP TS 27.010: 5.4.6.3.8 Non Supported Command Response (NSC) */ + struct modem_cmux_command nsc; + struct modem_cmux_command_type value; + } nsc_cmd = { + .nsc = { + .type = { + .ea = 1, + .cr = 0, + .value = MODEM_CMUX_COMMAND_NSC, + }, + .length = { + .ea = 1, + .value = 1, + }, + }, + .value = cmd->type, + }; + + frame.data = (uint8_t *)&nsc_cmd; + frame.data_len = sizeof(nsc_cmd); + + modem_cmux_transmit_cmd_frame(cmux, &frame); +} + static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) { struct modem_cmux_command *command; @@ -509,6 +545,7 @@ static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) default: LOG_DBG("Unknown control command"); + modem_cmux_respond_unsupported_cmd(cmux); break; } } @@ -532,6 +569,26 @@ static void modem_cmux_connect_response_transmit(struct modem_cmux *cmux) modem_cmux_transmit_cmd_frame(cmux, &frame); } +static void modem_cmux_dm_response_transmit(struct modem_cmux *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) { modem_cmux_connect_response_transmit(cmux); @@ -591,6 +648,13 @@ 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) { switch (dlci->state) { @@ -606,9 +670,7 @@ static void modem_cmux_on_dlci_frame_ua(struct modem_cmux_dlci *dlci) case MODEM_CMUX_DLCI_STATE_CLOSING: LOG_DBG("DLCI %u closed", dlci->dlci_address); - dlci->state = MODEM_CMUX_DLCI_STATE_CLOSED; - modem_pipe_notify_closed(&dlci->pipe); - k_work_cancel_delayable(&dlci->close_work); + modem_cmux_on_dlci_frame_dm(dlci); break; default: @@ -617,6 +679,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; @@ -663,7 +727,7 @@ 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; } @@ -680,8 +744,9 @@ static void modem_cmux_on_dlci_frame(struct modem_cmux *cmux) dlci = modem_cmux_find_dlci(cmux); 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; } @@ -701,7 +766,9 @@ 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 (%d, DLCI %d)", "DLCI", cmux->frame.type, cmux->frame.dlci_address); From 5d9b6910412df1bbceb072419055bed27cf8c21f Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Fri, 10 Oct 2025 10:35:12 +0300 Subject: [PATCH 08/26] [nrf fromtree] modem: cmux: Define macros for header size Define CMUX_HEADER_SIZE macro that is 6 or 7 bytes depending on MTU. Remove the previous CMUX_FRAME_SIZE_MAX that was a bit misleading as it meant only the header size without data and was fixed to 7 bytes. Redefine new macros CMUX_FRAME_SIZE_MAX and CMUX_FRAME_SIZE_MIN which are actually frame sizes with data included. Signed-off-by: Seppo Takalo (cherry picked from commit 9d93594de99fa921086e32ad04041af1b328e873) --- include/zephyr/modem/cmux.h | 9 ++++++++- subsys/modem/modem_cmux.c | 28 +++++++++++++++------------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h index 913f4187d729..fd9481fee035 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -57,8 +57,15 @@ typedef void (*modem_cmux_callback)(struct modem_cmux *cmux, enum modem_cmux_eve * @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 + 7 + \ +#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 { diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index 93665525ac7b..fb0f14a84807 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -19,14 +19,18 @@ LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL); #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 +#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)) @@ -272,13 +276,13 @@ 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); @@ -1328,11 +1332,9 @@ 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)); + __ASSERT_NO_MSG(config->transmit_buf_size >= MODEM_CMUX_DATA_FRAME_SIZE_MAX); memset(cmux, 0x00, sizeof(*cmux)); cmux->callback = config->callback; From 7fb70e6563ca028fd8e6093f12729dc73f964af3 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Tue, 26 Aug 2025 09:45:31 +0300 Subject: [PATCH 09/26] [nrf fromtree] modem: cmux: Handle C/R bit from address field The C/R bit in the address field should be handled as explained in 5.2.1.2 in the spec (3GPP TS 127.010). To detect if the frame is a command or a response, we need to know who was the initiator of the CMUX channel. > Initiator is the station that take the initiative to initialize > the multiplexer (i.e. sends the SABM command at DLCI 0 ) See the table from given section of the specification. Also, on UIH frames 5.4.3.1 says > The frames sent by the initiating station have the C/R bit set to 1 > and those sent by the responding station have the C/R bit set to 0. NOTE: This is different than a C/R bit in the Type field. Signed-off-by: Seppo Takalo (cherry picked from commit b1942ced1a9a94b52c0cd241943972b058d2c5d0) --- include/zephyr/modem/cmux.h | 5 ++-- subsys/modem/modem_cmux.c | 32 ++++++++++++++++++------ tests/subsys/modem/modem_cmux/src/main.c | 18 +++++++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h index fd9481fee035..17b63bdf66af 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -149,10 +149,11 @@ struct modem_cmux { /* 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*/ diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index fb0f14a84807..f304985717ac 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -463,12 +463,13 @@ static void modem_cmux_on_cld_command(struct modem_cmux *cmux, struct modem_cmux 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; } LOG_DBG("CMUX connected"); cmux->state = 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); @@ -595,8 +596,6 @@ static void modem_cmux_dm_response_transmit(struct modem_cmux *cmux) static void modem_cmux_on_control_frame_sabm(struct modem_cmux *cmux) { - modem_cmux_connect_response_transmit(cmux); - if ((cmux->state == MODEM_CMUX_STATE_CONNECTED) || (cmux->state == MODEM_CMUX_STATE_DISCONNECTING)) { LOG_DBG("Connect request not accepted"); @@ -604,7 +603,9 @@ static void modem_cmux_on_control_frame_sabm(struct modem_cmux *cmux) } LOG_DBG("CMUX connection request received"); + cmux->initiator = false; cmux->state = 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); @@ -617,6 +618,11 @@ static void modem_cmux_on_control_frame(struct modem_cmux *cmux) { modem_cmux_log_received_frame(&cmux->frame); + if (cmux->state == MODEM_CMUX_STATE_CONNECTED && cmux->frame.cr == cmux->initiator) { + LOG_DBG("Received a response frame, dropping"); + return; + } + switch (cmux->frame.type) { case MODEM_CMUX_FRAME_TYPE_UA: modem_cmux_on_control_frame_ua(cmux); @@ -661,6 +667,12 @@ static void modem_cmux_on_dlci_frame_dm(struct modem_cmux_dlci *dlci) 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); @@ -746,6 +758,11 @@ static void modem_cmux_on_dlci_frame(struct modem_cmux *cmux) modem_cmux_log_received_frame(&cmux->frame); + if (cmux->state != MODEM_CMUX_STATE_CONNECTED) { + LOG_DBG("Unexpected DLCI frame in state %d", cmux->state); + return; + } + dlci = modem_cmux_find_dlci(cmux); if (dlci == NULL) { LOG_WRN("Frame intended for unconfigured DLCI %u.", @@ -1087,6 +1104,7 @@ static void modem_cmux_connect_handler(struct k_work *item) cmux = CONTAINER_OF(dwork, struct modem_cmux, connect_work); cmux->state = MODEM_CMUX_STATE_CONNECTING; + cmux->initiator = true; static const struct modem_cmux_frame frame = { .dlci_address = 0, @@ -1119,7 +1137,7 @@ static void modem_cmux_disconnect_handler(struct k_work *item) struct modem_cmux_frame frame = { .dlci_address = 0, - .cr = true, + .cr = cmux->initiator, .pf = false, .type = MODEM_CMUX_FRAME_TYPE_UIH, .data = data, @@ -1198,7 +1216,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, @@ -1273,7 +1291,7 @@ static void modem_cmux_dlci_open_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_SABM, .data = NULL, @@ -1302,7 +1320,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, diff --git a/tests/subsys/modem/modem_cmux/src/main.c b/tests/subsys/modem/modem_cmux/src/main.c index b1adce37cc46..30c055bc8e74 100644 --- a/tests/subsys/modem/modem_cmux/src/main.c +++ b/tests/subsys/modem/modem_cmux/src/main.c @@ -886,4 +886,22 @@ 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_SUITE(modem_cmux, NULL, test_modem_cmux_setup, test_modem_cmux_before, NULL, NULL); From 9191c783c79bf1a04aa771dae4956e1fe5ff4187 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Sat, 4 Oct 2025 13:52:25 +1000 Subject: [PATCH 10/26] [nrf fromtree] modem: modem_ppp: optimise frame wrapping Optimise the PPP frame wrapping process by performing all work inside a single function into a contiguous buffer, instead of operating on the ring buffer one byte at a time. On a nRF54L15 (M33 @ 128 MHz) before the change: ``` Wrapping 1062 byte packet: ~4.1 ms Wrapping 1355 byte packet: ~5.0 ms ``` After the change: ``` Wrapping 1026 byte packet: ~2.4 ms Wrapping 1341 byte packet: ~3.1 ms ``` Signed-off-by: Jordan Yates (cherry picked from commit 9f1e166abd02e6e8f547b06687bdebdf058439e5) --- include/zephyr/modem/ppp.h | 21 +-- subsys/modem/modem_ppp.c | 282 ++++++++++++++++++------------------- 2 files changed, 137 insertions(+), 166 deletions(-) diff --git a/include/zephyr/modem/ppp.h b/include/zephyr/modem/ppp.h index b3dbcfdeb7fb..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 */ diff --git a/subsys/modem/modem_ppp.c b/subsys/modem/modem_ppp.c index 9e2e2b5d71b7..cd13d4ac8b1a 100644 --- a/subsys/modem/modem_ppp.c +++ b/subsys/modem/modem_ppp.c @@ -49,148 +49,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) @@ -357,32 +343,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); } } From e09b724ab533cc7626b8d4a21584cb77263e300d Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Fri, 10 Oct 2025 10:35:37 +0300 Subject: [PATCH 11/26] [nrf fromtree] modem: cmux: Combine state and event Refactor internal event bits to use state enum values and define set_state() and wait_state() so we don't need two set of variables to maintain. Signed-off-by: Seppo Takalo (cherry picked from commit 2cfd92410ead8af098ca8f772990f59a4675a999) --- subsys/modem/modem_cmux.c | 53 +++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 24 deletions(-) diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index f304985717ac..4573c1b8bdc5 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -101,6 +101,22 @@ static int modem_cmux_wrap_command(struct modem_cmux_command **command, const ui return 0; } +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 struct modem_cmux_command *modem_cmux_command_wrap(const uint8_t *data) { return (struct modem_cmux_command *)data; @@ -450,14 +466,12 @@ static void modem_cmux_on_cld_command(struct modem_cmux *cmux, struct modem_cmux } LOG_DBG("CMUX disconnected"); - cmux->state = MODEM_CMUX_STATE_DISCONNECTED; + 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); - 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) @@ -468,15 +482,13 @@ static void modem_cmux_on_control_frame_ua(struct modem_cmux *cmux) } LOG_DBG("CMUX connected"); - cmux->state = MODEM_CMUX_STATE_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) @@ -604,21 +616,19 @@ static void modem_cmux_on_control_frame_sabm(struct modem_cmux *cmux) LOG_DBG("CMUX connection request received"); cmux->initiator = false; - cmux->state = MODEM_CMUX_STATE_CONNECTED; + 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 (cmux->state == MODEM_CMUX_STATE_CONNECTED && cmux->frame.cr == cmux->initiator) { + if (is_connected(cmux) && cmux->frame.cr == cmux->initiator) { LOG_DBG("Received a response frame, dropping"); return; } @@ -1103,7 +1113,7 @@ 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 = { @@ -1126,7 +1136,7 @@ static void modem_cmux_disconnect_handler(struct k_work *item) struct modem_cmux_command *command; uint8_t data[2]; - cmux->state = MODEM_CMUX_STATE_DISCONNECTING; + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTING); command = modem_cmux_command_wrap(data); command->type.ea = 1; @@ -1360,7 +1370,6 @@ void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *co cmux->receive_buf = config->receive_buf; cmux->receive_buf_size = config->receive_buf_size; sys_slist_init(&cmux->dlcis); - cmux->state = MODEM_CMUX_STATE_DISCONNECTED; ring_buf_init(&cmux->transmit_rb, config->transmit_buf_size, config->transmit_buf); k_mutex_init(&cmux->transmit_rb_lock); k_work_init_delayable(&cmux->receive_work, modem_cmux_receive_handler); @@ -1368,8 +1377,7 @@ void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *co k_work_init_delayable(&cmux->connect_work, modem_cmux_connect_handler); k_work_init_delayable(&cmux->disconnect_work, modem_cmux_disconnect_handler); k_event_init(&cmux->event); - k_event_clear(&cmux->event, MODEM_CMUX_EVENT_CONNECTED_BIT); - k_event_post(&cmux->event, MODEM_CMUX_EVENT_DISCONNECTED_BIT); + set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED); #if CONFIG_MODEM_STATS modem_cmux_init_buf_stats(cmux); @@ -1431,8 +1439,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; } @@ -1443,7 +1450,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; } @@ -1470,8 +1477,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; } @@ -1482,7 +1488,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; } @@ -1529,7 +1535,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); } From 3bb9abd47d6ad2f28ec16ce1122b585f84af7a7c Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Tue, 14 Oct 2025 21:04:25 +0000 Subject: [PATCH 12/26] [nrf fromtree] modem: cmux: Handle CLD response Instead of dropping all responses, handle the CLD. Signed-off-by: Seppo Takalo (cherry picked from commit 822a501828b48a89fa5da4149f8a85901206a2d6) --- subsys/modem/modem_cmux.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index 4573c1b8bdc5..e668052ffb5d 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -543,6 +543,19 @@ static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) modem_cmux_log_received_command(command); + 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; + 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); @@ -629,8 +642,7 @@ 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, dropping"); - return; + LOG_DBG("Received a response frame"); } switch (cmux->frame.type) { From 68760cec73c03bb5f981fd464755b6a9da7d1122 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Thu, 25 Sep 2025 17:03:05 +0300 Subject: [PATCH 13/26] [nrf fromtree] modem: cmux: Send disconnect event only after responding to CLD If we immediately send disconnected event when CLD is received, we might close the UART pipe before the response is actually send. Also, shutdown_handler should not retry indefinitely. Signed-off-by: Seppo Takalo (cherry picked from commit a9bc72a22a79b80e644792c8149cc67894b24ecb) --- subsys/modem/modem_cmux.c | 31 +++++++++++++------ tests/subsys/modem/modem_cmux_pair/src/main.c | 8 +++-- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index e668052ffb5d..914f3348f401 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -449,6 +449,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) { @@ -462,16 +473,11 @@ 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); } - - LOG_DBG("CMUX disconnected"); - 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_control_frame_ua(struct modem_cmux *cmux) @@ -1148,7 +1154,12 @@ static void modem_cmux_disconnect_handler(struct k_work *item) struct modem_cmux_command *command; uint8_t data[2]; - set_state(cmux, 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); + } command = modem_cmux_command_wrap(data); command->type.ea = 1; 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"); From 90fa7e66b778cdee8a44ad6140f246861aeaaea1 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Thu, 25 Sep 2025 16:57:27 +0300 Subject: [PATCH 14/26] [nrf fromtree] drivers: modem: cellular: Close down CMUX before shut down Properly close down the CMUX channel before shutting down the modem. The CMUX Close-Down command should indicate the remote end to clean up, even if we don't have shutdown script or power-key GPIO. Signed-off-by: Seppo Takalo (cherry picked from commit 72701683be80073c40b6ecd6afbf7039fb2bc6c0) --- drivers/modem/modem_cellular.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index c0cc43a5dafa..64e3614b51eb 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -83,6 +83,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, @@ -257,6 +258,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: @@ -1377,6 +1380,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 +1392,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 +1782,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; } From f5213951c4785ece934bbb919317e0bb46ebfc0d Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Tue, 26 Aug 2025 15:19:54 +0300 Subject: [PATCH 15/26] [nrf fromtree] modem: cmux: Implement Modem Status Command Implement Modem Status Command(MSC) and a per DLC flow control by using it. Send flow control signals when our input buffer don't fit full frame anymore. Stop TX if we have received from controls on MSC. Signed-off-by: Seppo Takalo (cherry picked from commit 1f996d07b8047bbd20c1c519f60beb076fb80216) --- include/zephyr/modem/cmux.h | 4 + subsys/modem/modem_cmux.c | 194 ++++++++++++++++++- tests/subsys/modem/mock/modem_backend_mock.c | 18 +- tests/subsys/modem/mock/modem_backend_mock.h | 5 + tests/subsys/modem/modem_cmux/src/main.c | 61 ++++-- 5 files changed, 260 insertions(+), 22 deletions(-) diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h index 17b63bdf66af..bf349980e2c9 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -120,6 +120,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 { diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index 914f3348f401..b311ab7865d9 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -81,6 +81,29 @@ struct modem_cmux_command { uint8_t value[]; }; +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 */ +}; + +struct modem_cmux_command_msc { + struct modem_cmux_command command; + uint8_t value[2]; +}; + +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 int modem_cmux_wrap_command(struct modem_cmux_command **command, const uint8_t *data, uint16_t data_len) { @@ -122,6 +145,53 @@ static struct modem_cmux_command *modem_cmux_command_wrap(const uint8_t *data) return (struct modem_cmux_command *)data; } +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 (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) { @@ -426,10 +496,92 @@ static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux) } } +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_msc cmd = { + .command = { + .type = { + .ea = 1, + .cr = 1, + .value = MODEM_CMUX_COMMAND_MSC, + }, + .length = { + .ea = 1, + .value = sizeof(cmd.value), + }, + }, + .value[0] = modem_cmux_msc_addr_encode(addr), + .value[1] = modem_cmux_msc_signals_encode(signals), + }; + + struct modem_cmux_frame frame = { + .dlci_address = 0, + .cr = cmux->initiator, + .pf = false, + .type = MODEM_CMUX_FRAME_TYPE_UIH, + .data = (void *)&cmd, + .data_len = sizeof(cmd), + }; + + 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); + } } } @@ -439,6 +591,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) @@ -670,7 +823,7 @@ static void modem_cmux_on_control_frame(struct modem_cmux *cmux) } } -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; @@ -678,7 +831,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; } } @@ -710,6 +863,12 @@ static void modem_cmux_on_dlci_frame_ua(struct modem_cmux_dlci *dlci) 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: @@ -742,6 +901,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); } @@ -758,6 +923,7 @@ static void modem_cmux_on_dlci_frame_sabm(struct modem_cmux_dlci *dlci) 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); @@ -791,7 +957,7 @@ static void modem_cmux_on_dlci_frame(struct modem_cmux *cmux) return; } - dlci = modem_cmux_find_dlci(cmux); + dlci = modem_cmux_find_dlci(cmux, cmux->frame.dlci_address); if (dlci == NULL) { LOG_WRN("Frame intended for unconfigured DLCI %u.", cmux->frame.dlci_address); @@ -1066,7 +1232,9 @@ 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); + } } } @@ -1241,6 +1409,10 @@ 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 (dlci->flow_control) { + return 0; + } + K_SPINLOCK(&cmux->work_lock) { if (!cmux->attached) { ret = -EPERM; @@ -1275,6 +1447,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; } @@ -1321,6 +1502,7 @@ 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, 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 30c055bc8e74..67833baabe76 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) From 61dcdb3d876fc678e9fd31c0b63ba6fd284ca64a Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Wed, 24 Sep 2025 13:31:38 +0300 Subject: [PATCH 16/26] [nrf fromtree] drivers: modem: Extract common dts bindings Extract common DTS bindings to zephyr,cellular-modem-device.yaml as these are referred in the modem_cellular.c in the MODEM_CELLULAR_DEFINE_INSTANCE() macro. Signed-off-by: Seppo Takalo (cherry picked from commit 4040a1e2b2a65c17164b3d09d847e85591e97fe5) --- dts/bindings/modem/nordic,nrf91-slm.yaml | 6 +----- dts/bindings/modem/quectel,bg95.yaml | 2 +- dts/bindings/modem/quectel,bg9x.yaml | 5 +---- dts/bindings/modem/quectel,eg25-g.yaml | 2 +- dts/bindings/modem/quectel,eg800q.yaml | 2 +- dts/bindings/modem/simcom,a76xx.yaml | 2 +- dts/bindings/modem/simcom,sim7080.yaml | 2 +- dts/bindings/modem/sqn,gm02s.yaml | 5 +---- dts/bindings/modem/swir,hl7800.yaml | 2 +- dts/bindings/modem/telit,me310g1.yaml | 2 +- dts/bindings/modem/telit,me910g1.yaml | 2 +- dts/bindings/modem/u-blox,lara-r6.yaml | 5 +---- dts/bindings/modem/u-blox,sara-r4.yaml | 5 +---- dts/bindings/modem/u-blox,sara-r5.yaml | 9 +-------- dts/bindings/modem/wnc,m14a2a.yaml | 2 +- .../modem/zephyr,cellular-modem-device.yaml | 19 +++++++++++++++++++ 16 files changed, 34 insertions(+), 38 deletions(-) create mode 100644 dts/bindings/modem/zephyr,cellular-modem-device.yaml 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..bff39d7f9f61 --- /dev/null +++ b/dts/bindings/modem/zephyr,cellular-modem-device.yaml @@ -0,0 +1,19 @@ +# 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 From b757c1a2f344cea1ebf966c95b864071811f1283 Mon Sep 17 00:00:00 2001 From: Robert Lubos Date: Tue, 16 Sep 2025 14:58:27 +0200 Subject: [PATCH 17/26] [nrf fromtree] doc: release-notes-4.3: Mention the backlog support for listen() Add a note that the backlog parameter of the listen() function is now respected and the backlog support has been implemented for the TCP server. Signed-off-by: Robert Lubos (cherry picked from commit 77c348a9de34986998f062e3a0955a1bf3732e6c) --- doc/releases/release-notes-4.3.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/releases/release-notes-4.3.rst b/doc/releases/release-notes-4.3.rst index 6dfc304b3ef5..43b217ae205c 100644 --- a/doc/releases/release-notes-4.3.rst +++ b/doc/releases/release-notes-4.3.rst @@ -171,6 +171,13 @@ New APIs and options * :kconfig:option:`CONFIG_HAWKBIT_REBOOT_NONE` +* 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` From 6597eea1ed74ae6d6d7ccb10f22361b63ed8b4b4 Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Thu, 2 Oct 2025 20:33:36 +1000 Subject: [PATCH 18/26] [nrf fromtree] modem: optional dedicated workqueue Add the option to use a dedicated workqueue for the modem backend instead of the system workqueue. Signed-off-by: Jordan Yates (cherry picked from commit 761961fa28a536edfb72a1f60eba1078b4a5e3f3) --- doc/releases/release-notes-4.3.rst | 4 ++ subsys/modem/CMakeLists.txt | 1 + subsys/modem/Kconfig | 15 ++++++ .../modem/backends/modem_backend_uart_async.c | 11 +++-- .../backends/modem_backend_uart_async_hwfc.c | 13 ++--- .../modem/backends/modem_backend_uart_isr.c | 9 ++-- subsys/modem/modem_chat.c | 18 ++++--- subsys/modem/modem_cmux.c | 26 +++++----- subsys/modem/modem_ppp.c | 10 ++-- subsys/modem/modem_ubx.c | 4 +- subsys/modem/modem_workqueue.c | 39 +++++++++++++++ subsys/modem/modem_workqueue.h | 49 +++++++++++++++++++ 12 files changed, 159 insertions(+), 40 deletions(-) create mode 100644 subsys/modem/modem_workqueue.c create mode 100644 subsys/modem/modem_workqueue.h diff --git a/doc/releases/release-notes-4.3.rst b/doc/releases/release-notes-4.3.rst index 43b217ae205c..718e4ad3ba33 100644 --- a/doc/releases/release-notes-4.3.rst +++ b/doc/releases/release-notes-4.3.rst @@ -171,6 +171,10 @@ New APIs and options * :kconfig:option:`CONFIG_HAWKBIT_REBOOT_NONE` +* Modem + + * :kconfig:option:`CONFIG_MODEM_DEDICATED_WORKQUEUE` + * Networking * Sockets 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 34114cd3893b..dfcb7ddae9a3 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 diff --git a/subsys/modem/backends/modem_backend_uart_async.c b/subsys/modem/backends/modem_backend_uart_async.c index d96f513d6061..9b5edc8965c5 100644 --- a/subsys/modem/backends/modem_backend_uart_async.c +++ b/subsys/modem/backends/modem_backend_uart_async.c @@ -5,6 +5,7 @@ */ #include "modem_backend_uart_async.h" +#include "../modem_workqueue.h" #include LOG_MODULE_REGISTER(modem_backend_uart_async, CONFIG_MODEM_MODULES_LOG_LEVEL); @@ -58,7 +59,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 +68,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 +128,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 +145,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); } } @@ -254,7 +255,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; diff --git a/subsys/modem/backends/modem_backend_uart_async_hwfc.c b/subsys/modem/backends/modem_backend_uart_async_hwfc.c index 78855daa4eb3..61d307604e5c 100644 --- a/subsys/modem/backends/modem_backend_uart_async_hwfc.c +++ b/subsys/modem/backends/modem_backend_uart_async_hwfc.c @@ -5,6 +5,7 @@ */ #include "modem_backend_uart_async.h" +#include "../modem_workqueue.h" #include LOG_MODULE_REGISTER(modem_backend_uart_async_hwfc, CONFIG_MODEM_MODULES_LOG_LEVEL); @@ -136,7 +137,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 +146,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 +183,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 +192,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 +207,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); } } @@ -335,7 +336,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); 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 b311ab7865d9..fb0b04aa6469 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -13,6 +13,8 @@ LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL); #include +#include "modem_workqueue.h" + #define MODEM_CMUX_SOF (0xF9) #define MODEM_CMUX_FCS_POLYNOMIAL (0xE0) #define MODEM_CMUX_FCS_INIT_VALUE (0xFF) @@ -347,11 +349,11 @@ 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_TRANSMIT_IDLE: - k_work_schedule(&cmux->transmit_work, K_NO_WAIT); + modem_work_schedule(&cmux->transmit_work, K_NO_WAIT); break; default: @@ -411,7 +413,7 @@ static uint16_t modem_cmux_transmit_frame(struct modem_cmux *cmux, buf[0] = fcs; 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; } @@ -1222,7 +1224,7 @@ static void modem_cmux_receive_handler(struct k_work *item) } /* Reschedule received work */ - k_work_schedule(&cmux->receive_work, K_NO_WAIT); + modem_work_schedule(&cmux->receive_work, K_NO_WAIT); } static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux) @@ -1312,7 +1314,7 @@ 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) @@ -1347,7 +1349,7 @@ static void modem_cmux_disconnect_handler(struct k_work *item) /* 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 @@ -1397,7 +1399,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; @@ -1476,7 +1478,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; @@ -1514,7 +1516,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) @@ -1543,7 +1545,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) @@ -1666,7 +1668,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); } } @@ -1704,7 +1706,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); } } diff --git a/subsys/modem/modem_ppp.c b/subsys/modem/modem_ppp.c index cd13d4ac8b1a..09b62bdaa11e 100644 --- a/subsys/modem/modem_ppp.c +++ b/subsys/modem/modem_ppp.c @@ -10,6 +10,8 @@ #include #include +#include "modem_workqueue.h" + #include LOG_MODULE_REGISTER(modem_ppp, CONFIG_MODEM_MODULES_LOG_LEVEL); @@ -327,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: @@ -415,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) @@ -478,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_ */ From 79c20086ce66c0e19397056b05ee5f66afdd0639 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Tue, 21 Oct 2025 16:55:43 +0300 Subject: [PATCH 19/26] [nrf fromlist] modem: cmux: Define encoding and decoding functions for commands Instead of relying non-standard compiler behavior, define encode and decode functions for all CMUX command structures. Final command is encoded into a shared buffer, because it is always copied directly to TX ringbuffer. Added also functions to validate commands. Upstream PR #: 98009 Signed-off-by: Seppo Takalo --- subsys/modem/modem_cmux.c | 314 +++++++++++++++++------ tests/subsys/modem/modem_cmux/src/main.c | 19 ++ 2 files changed, 250 insertions(+), 83 deletions(-) diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index fb0b04aa6469..3bebde19d462 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -30,7 +30,8 @@ LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL); * * PN would be 10 bytes, but that is not implemented */ -#define MODEM_CMUX_CMD_DATA_SIZE_MAX 5 +#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) @@ -80,7 +81,7 @@ 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 { @@ -98,53 +99,182 @@ struct modem_cmux_msc_addr { uint8_t dlci_address: 6; /**< DLCI channel address */ }; -struct modem_cmux_command_msc { - struct modem_cmux_command command; - uint8_t value[2]; -}; - 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 int modem_cmux_wrap_command(struct modem_cmux_command **command, const uint8_t *data, - uint16_t data_len) +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) { - if ((data == NULL) || (data_len < 2)) { - return -EINVAL; + return cmux->state == MODEM_CMUX_STATE_CONNECTED; +} + +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; } +} - (*command) = (struct modem_cmux_command *)data; +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; +} - if (((*command)->length.ea == 0) || ((*command)->type.ea == 0)) { - return -EINVAL; +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; } +} - if ((*command)->length.value != (data_len - 2)) { - return -EINVAL; +static struct modem_cmux_command_type modem_cmux_command_type_decode(const uint8_t byte) +{ + 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}; } - return 0; + return type; } -static void set_state(struct modem_cmux *cmux, enum modem_cmux_state state) +static uint8_t modem_cmux_command_type_encode(const struct modem_cmux_command_type type) { - cmux->state = state; - k_event_set(&cmux->event, BIT(state)); + return (type.ea ? MODEM_CMUX_EA : 0) | + (type.cr ? MODEM_CMUX_CR : 0) | + ((type.value & 0x3F) << 2); } -static bool wait_state(struct modem_cmux *cmux, enum modem_cmux_state state, k_timeout_t timeout) +static struct modem_cmux_command_length modem_cmux_command_length_decode(const uint8_t byte) { - return k_event_wait(&cmux->event, BIT(state), false, timeout) == BIT(state); + struct modem_cmux_command_length length = { + .ea = (byte & MODEM_CMUX_EA) ? 1 : 0, + .value = (byte >> 1) & 0x7F, + }; + + if (length.ea == 0) { + return (struct modem_cmux_command_length){0}; + } + + return length; } -static bool is_connected(struct modem_cmux *cmux) +static uint8_t modem_cmux_command_length_encode(const struct modem_cmux_command_length length) { - return cmux->state == MODEM_CMUX_STATE_CONNECTED; + 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}; + } + + 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) { - return (struct modem_cmux_command *)data; + 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) @@ -421,7 +551,7 @@ 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); @@ -433,8 +563,9 @@ static bool modem_cmux_transmit_cmd_frame(struct modem_cmux *cmux, } 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); @@ -477,7 +608,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]; @@ -488,8 +619,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; @@ -516,29 +648,34 @@ static void modem_cmux_send_msc(struct modem_cmux *cmux, struct modem_cmux_dlci .rtr = dlci->state == MODEM_CMUX_DLCI_STATE_OPEN ? 1 : 0, .dv = 1, }; - struct modem_cmux_command_msc cmd = { - .command = { - .type = { - .ea = 1, - .cr = 1, - .value = MODEM_CMUX_COMMAND_MSC, - }, - .length = { - .ea = 1, - .value = sizeof(cmd.value), - }, + 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 = (void *)&cmd, - .data_len = sizeof(cmd), + .data = data, + .data_len = len, }; LOG_DBG("Sending MSC command for DLCI %u, FC:%d RTR: %d DV: %d", addr.dlci_address, @@ -655,41 +792,44 @@ static void modem_cmux_on_control_frame_ua(struct modem_cmux *cmux) static void modem_cmux_respond_unsupported_cmd(struct modem_cmux *cmux) { struct modem_cmux_frame frame = cmux->frame; - struct modem_cmux_command *cmd; + struct modem_cmux_command cmd = modem_cmux_command_decode(frame.data, frame.data_len); - if (modem_cmux_wrap_command(&cmd, frame.data, frame.data_len) < 0) { + if (!modem_cmux_command_is_valid(&cmd)) { LOG_WRN("Invalid command"); return; } - struct { - /* 3GPP TS 27.010: 5.4.6.3.8 Non Supported Command Response (NSC) */ - struct modem_cmux_command nsc; - struct modem_cmux_command_type value; - } nsc_cmd = { - .nsc = { - .type = { - .ea = 1, - .cr = 0, - .value = MODEM_CMUX_COMMAND_NSC, - }, - .length = { - .ea = 1, - .value = 1, - }, + + /* 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 = cmd->type, + .value[0] = modem_cmux_command_type_encode(cmd.type), }; - frame.data = (uint8_t *)&nsc_cmd; - frame.data_len = sizeof(nsc_cmd); + 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_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)) { @@ -697,18 +837,19 @@ static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) 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); - if (!command->type.cr) { + if (!command.type.cr) { LOG_DBG("Received response command"); - switch (command->type.value) { + switch (command.type.value) { case MODEM_CMUX_COMMAND_CLD: - modem_cmux_on_cld_command(cmux, command); + modem_cmux_on_cld_command(cmux, &command); break; default: /* Responses to other commands are ignored */ @@ -717,13 +858,13 @@ static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) return; } - switch (command->type.value) { + 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: @@ -1321,8 +1462,6 @@ 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]; if (cmux->state == MODEM_CMUX_STATE_DISCONNECTING) { disconnect(cmux); @@ -1331,12 +1470,21 @@ static void modem_cmux_disconnect_handler(struct k_work *item) k_work_schedule(&cmux->disconnect_work, MODEM_CMUX_T1_TIMEOUT); } - 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_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; + } + struct modem_cmux_frame frame = { .dlci_address = 0, @@ -1344,7 +1492,7 @@ static void modem_cmux_disconnect_handler(struct k_work *item) .pf = false, .type = MODEM_CMUX_FRAME_TYPE_UIH, .data = data, - .data_len = sizeof(data), + .data_len = len, }; /* Transmit close down command */ diff --git a/tests/subsys/modem/modem_cmux/src/main.c b/tests/subsys/modem/modem_cmux/src/main.c index 67833baabe76..b40af7e0de89 100644 --- a/tests/subsys/modem/modem_cmux/src/main.c +++ b/tests/subsys/modem/modem_cmux/src/main.c @@ -937,4 +937,23 @@ ZTEST(modem_cmux, test_modem_cmux_invalid_cr) 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); From e2c075bb1554a0697c29c0b0a306b51273ba0bc6 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Thu, 23 Oct 2025 16:39:48 +0300 Subject: [PATCH 20/26] [nrf fromlist] drivers: modem: Implement support for DTR signal DTR signal on UART extends the power saving by allowing host to indicate the remote end that the UART is not in active state. Upstream PR #: 98145 Signed-off-by: Seppo Takalo --- drivers/modem/modem_cellular.c | 9 +++++++++ dts/bindings/modem/zephyr,cellular-modem-device.yaml | 8 ++++++++ include/zephyr/modem/backend/uart.h | 2 ++ subsys/modem/backends/modem_backend_uart.c | 1 + subsys/modem/backends/modem_backend_uart_async.c | 8 ++++++++ subsys/modem/backends/modem_backend_uart_async_hwfc.c | 8 ++++++++ 6 files changed, 36 insertions(+) diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index 64e3614b51eb..0079a07b10d6 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -185,6 +185,7 @@ 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 dtr_gpio; uint16_t power_pulse_duration_ms; uint16_t reset_pulse_duration_ms; uint16_t startup_time_ms; @@ -2095,6 +2096,7 @@ 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; @@ -2118,9 +2120,15 @@ 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->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, @@ -2888,6 +2896,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, .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, {}), \ + .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), \ diff --git a/dts/bindings/modem/zephyr,cellular-modem-device.yaml b/dts/bindings/modem/zephyr,cellular-modem-device.yaml index bff39d7f9f61..66c7fd91d092 100644 --- a/dts/bindings/modem/zephyr,cellular-modem-device.yaml +++ b/dts/bindings/modem/zephyr,cellular-modem-device.yaml @@ -17,3 +17,11 @@ properties: mdm-wake-gpios: type: phandle-array description: GPIO for modem wake + + 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. 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/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 9b5edc8965c5..1615120a4d5e 100644 --- a/subsys/modem/backends/modem_backend_uart_async.c +++ b/subsys/modem/backends/modem_backend_uart_async.c @@ -11,6 +11,7 @@ LOG_MODULE_REGISTER(modem_backend_uart_async, CONFIG_MODEM_MODULES_LOG_LEVEL); #include +#include #include enum { @@ -157,6 +158,10 @@ static int modem_backend_uart_async_open(void *data) atomic_clear(&backend->async.common.state); ring_buf_reset(&backend->async.receive_rb); + 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); @@ -268,6 +273,9 @@ static int modem_backend_uart_async_close(void *data) 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); + } 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 61d307604e5c..3aaaad2e196c 100644 --- a/subsys/modem/backends/modem_backend_uart_async_hwfc.c +++ b/subsys/modem/backends/modem_backend_uart_async_hwfc.c @@ -11,6 +11,7 @@ LOG_MODULE_REGISTER(modem_backend_uart_async_hwfc, CONFIG_MODEM_MODULES_LOG_LEVEL); #include +#include #include struct rx_buf_t { @@ -221,6 +222,10 @@ static int modem_backend_uart_async_hwfc_open(void *data) return -ENOMEM; } + 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); @@ -357,6 +362,9 @@ 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); + } return 0; } From d82765f8e01ff65bbb3d7fa78532f7512ceeaf01 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Tue, 14 Oct 2025 21:04:46 +0000 Subject: [PATCH 21/26] [nrf fromlist] modem: cmux: Implement Power Saving Control message Signal powersaving mode for the remote end using PSC command. Wakes up the remote end from powersaving mode by sending flag characters. This method is defined in 3GPP TS 27.010. Sections 5.4.6.3.2 Power Saving Control (PSC) and 5.4.7 Power Control and Wake-up Mechanisms. Essentially it is one PSC command to indicate a sleep state, and then repeated flag characters to wake up the remote end or indicate that we have been woken up. Upstream PR #: 97362 Signed-off-by: Seppo Takalo --- include/zephyr/modem/cmux.h | 5 ++ subsys/modem/Kconfig | 8 ++ subsys/modem/modem_cmux.c | 175 +++++++++++++++++++++++++++++++----- 3 files changed, 168 insertions(+), 20 deletions(-) diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h index bf349980e2c9..912edb1b79ef 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -72,6 +72,10 @@ 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, }; @@ -187,6 +191,7 @@ struct modem_cmux { /* Synchronize actions */ struct k_event event; + k_timepoint_t t3_timepoint; /* Statistics */ #if CONFIG_MODEM_STATS diff --git a/subsys/modem/Kconfig b/subsys/modem/Kconfig index dfcb7ddae9a3..8b4a411c26f6 100644 --- a/subsys/modem/Kconfig +++ b/subsys/modem/Kconfig @@ -64,6 +64,14 @@ config MODEM_CMUX_WORK_BUFFER_SIZE_EXTRA 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" diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index 3bebde19d462..a491195b78e9 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -37,9 +37,7 @@ LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL); #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, @@ -101,6 +99,7 @@ struct modem_cmux_msc_addr { 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 set_state(struct modem_cmux *cmux, enum modem_cmux_state state) { @@ -118,6 +117,22 @@ 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 */ @@ -581,7 +596,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; } @@ -630,6 +645,34 @@ 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) { @@ -827,12 +870,28 @@ static void modem_cmux_respond_unsupported_cmd(struct modem_cmux *cmux) 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); +} + static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) { struct modem_cmux_command command; - if ((cmux->state != MODEM_CMUX_STATE_CONNECTED) && - (cmux->state != MODEM_CMUX_STATE_DISCONNECTING)) { + if (cmux->state < MODEM_CMUX_STATE_CONNECTED) { LOG_DBG("Unexpected UIH frame"); return; } @@ -851,6 +910,9 @@ static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) 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; @@ -875,6 +937,10 @@ 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); @@ -1140,6 +1206,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 { @@ -1170,8 +1241,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: + cmux->frame_header_len = 0; if (byte == MODEM_CMUX_SOF) { cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC; + cmux->frame_header_len++; break; } @@ -1183,6 +1256,20 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by * 0xF9 could also be a valid address field for DLCI 62. */ 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; } @@ -1351,21 +1438,15 @@ static void modem_cmux_receive_handler(struct k_work *item) int ret; /* Receive data from pipe */ - ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf)); - if (ret < 1) { - if (ret < 0) { - LOG_ERR("Pipe receiving error: %d", ret); + 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 */ - modem_work_schedule(&cmux->receive_work, K_NO_WAIT); } static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux) @@ -1381,6 +1462,51 @@ static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux) } } +/** 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); + 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) { struct k_work_delayable *dwork = k_work_delayable_from_work(item); @@ -1403,6 +1529,11 @@ static void modem_cmux_transmit_handler(struct k_work *item) break; } + if (powersave_wait_wakeup(cmux)) { + k_mutex_unlock(&cmux->transmit_rb_lock); + return; + } + reserved_size = ring_buf_get_claim(&cmux->transmit_rb, &reserved, UINT32_MAX); ret = modem_pipe_transmit(cmux->pipe, reserved, reserved_size); @@ -1423,11 +1554,14 @@ 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"); + } modem_cmux_dlci_notify_transmit_idle(cmux); } + k_mutex_unlock(&cmux->transmit_rb_lock); } static void modem_cmux_connect_handler(struct k_work *item) @@ -1724,6 +1858,7 @@ void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *co cmux->user_data = config->user_data; cmux->receive_buf = config->receive_buf; cmux->receive_buf_size = config->receive_buf_size; + cmux->t3_timepoint = sys_timepoint_calc(K_NO_WAIT); sys_slist_init(&cmux->dlcis); ring_buf_init(&cmux->transmit_rb, config->transmit_buf_size, config->transmit_buf); k_mutex_init(&cmux->transmit_rb_lock); From acfc210fba272e2234b2031a90db1653a0e2d108 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Fri, 10 Oct 2025 11:14:53 +0300 Subject: [PATCH 22/26] [nrf fromlist] drivers: modem: Implement runtime power management for CMUX CMUX driver can enable the support for idle-timer in devicetree and can be requested to shut down the pipe during sleep. Then UART backend put the actual device into sleep when pipe is closed. Waking up is requested by sending data to DLC pipe or by manually opening the uart_pipe. Modem may request similar wake-up by a RING interrupt which would open the same pipe. When UART is powered and pipe is not closed, CMUX wake-up procedure is automatic. Either end may initiate the wake-up. Upstream PR #: 97362 Signed-off-by: Seppo Takalo --- drivers/modem/modem_cellular.c | 31 +++--- .../modem/zephyr,cellular-modem-device.yaml | 19 ++++ include/zephyr/modem/cmux.h | 14 +++ .../modem/backends/modem_backend_uart_async.c | 13 +++ .../backends/modem_backend_uart_async_hwfc.c | 14 +++ subsys/modem/modem_cmux.c | 96 ++++++++++++++++++- 6 files changed, 171 insertions(+), 16 deletions(-) diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index 0079a07b10d6..5d381af2a821 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include #include @@ -191,6 +193,9 @@ struct modem_cellular_config { 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; @@ -2102,7 +2107,6 @@ static int modem_cellular_init(const struct device *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); @@ -2149,6 +2153,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); @@ -2886,11 +2893,8 @@ 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, {}), \ @@ -2899,14 +2903,19 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, .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)), \ }; \ diff --git a/dts/bindings/modem/zephyr,cellular-modem-device.yaml b/dts/bindings/modem/zephyr,cellular-modem-device.yaml index 66c7fd91d092..c2d2e9e00a88 100644 --- a/dts/bindings/modem/zephyr,cellular-modem-device.yaml +++ b/dts/bindings/modem/zephyr,cellular-modem-device.yaml @@ -25,3 +25,22 @@ properties: 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/cmux.h b/include/zephyr/modem/cmux.h index 912edb1b79ef..18bb6fff980d 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -159,6 +159,12 @@ struct modem_cmux { enum modem_cmux_state state; bool flow_control_on : 1; bool initiator : 1; + /** 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; /* Work lock */ bool attached : 1; @@ -188,10 +194,12 @@ 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 @@ -220,6 +228,12 @@ struct modem_cmux_config { 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; }; /** diff --git a/subsys/modem/backends/modem_backend_uart_async.c b/subsys/modem/backends/modem_backend_uart_async.c index 1615120a4d5e..0cfcff9e86ed 100644 --- a/subsys/modem/backends/modem_backend_uart_async.c +++ b/subsys/modem/backends/modem_backend_uart_async.c @@ -11,6 +11,7 @@ LOG_MODULE_REGISTER(modem_backend_uart_async, CONFIG_MODEM_MODULES_LOG_LEVEL); #include +#include #include #include @@ -158,6 +159,11 @@ 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); } @@ -269,6 +275,7 @@ 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); @@ -276,6 +283,12 @@ static int modem_backend_uart_async_close(void *data) 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 3aaaad2e196c..df69c40b6679 100644 --- a/subsys/modem/backends/modem_backend_uart_async_hwfc.c +++ b/subsys/modem/backends/modem_backend_uart_async_hwfc.c @@ -11,6 +11,7 @@ LOG_MODULE_REGISTER(modem_backend_uart_async_hwfc, CONFIG_MODEM_MODULES_LOG_LEVEL); #include +#include #include #include @@ -222,6 +223,11 @@ 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); } @@ -352,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); @@ -365,6 +372,13 @@ static int modem_backend_uart_async_hwfc_close(void *data) 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/modem_cmux.c b/subsys/modem/modem_cmux.c index a491195b78e9..94c249aaa938 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -10,6 +10,8 @@ LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL); #include #include #include +#include +#include #include @@ -100,6 +102,7 @@ struct modem_cmux_msc_addr { 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) { @@ -497,10 +500,17 @@ static void modem_cmux_bus_callback(struct modem_pipe *pipe, enum modem_pipe_eve 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: + /* If we keep UART open in power-save, we should avoid waking up on RX idle */ + if (!cmux->close_pipe_on_power_save && is_powersaving(cmux)) { + break; + } modem_work_schedule(&cmux->transmit_work, K_NO_WAIT); break; - default: break; } @@ -885,6 +895,10 @@ static void modem_cmux_on_psc_response(struct modem_cmux *cmux) 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->close_pipe_on_power_save) { + modem_pipe_close_async(cmux->pipe); + } } static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux) @@ -1437,6 +1451,8 @@ 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 */ while ((ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf))) > 0) { /* Process received data */ @@ -1462,6 +1478,46 @@ static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux) } } +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->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->enable_runtime_power_management) { + return; + } + + cmux->idle_timepoint = sys_timepoint_calc(cmux->idle_timeout); + k_work_reschedule(&cmux->runtime_pm_work, cmux->idle_timeout); +} + /** Transmit bytes bypassing the CMUX buffers. * Causes modem_cmux_transmit_handler() to be rescheduled as a result of TRANSMIT_IDLE event. */ @@ -1483,6 +1539,17 @@ static bool powersave_wait_wakeup(struct modem_cmux *cmux) if (is_powersaving(cmux)) { LOG_DBG("Power saving mode, wake up first"); set_state(cmux, MODEM_CMUX_STATE_WAKEUP); + + if (cmux->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; @@ -1522,18 +1589,22 @@ 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 (transmit_rb_empty) { - break; - } - if (powersave_wait_wakeup(cmux)) { k_mutex_unlock(&cmux->transmit_rb_lock); return; } + if (transmit_rb_empty) { + break; + } + reserved_size = ring_buf_get_claim(&cmux->transmit_rb, &reserved, UINT32_MAX); ret = modem_pipe_transmit(cmux->pipe, reserved, reserved_size); @@ -1558,6 +1629,9 @@ static void modem_cmux_transmit_handler(struct k_work *item) if (cmux->state == MODEM_CMUX_STATE_CONFIRM_POWERSAVE) { set_state(cmux, MODEM_CMUX_STATE_POWERSAVE); LOG_DBG("Entered power saving mode"); + if (cmux->close_pipe_on_power_save) { + modem_pipe_close_async(cmux->pipe); + } } modem_cmux_dlci_notify_transmit_idle(cmux); } @@ -1693,6 +1767,13 @@ 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; } @@ -1859,6 +1940,10 @@ void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *co cmux->receive_buf = config->receive_buf; cmux->receive_buf_size = config->receive_buf_size; cmux->t3_timepoint = sys_timepoint_calc(K_NO_WAIT); + cmux->enable_runtime_power_management = config->enable_runtime_power_management; + cmux->close_pipe_on_power_save = config->close_pipe_on_power_save; + cmux->idle_timeout = + cmux->enable_runtime_power_management ? config->idle_timeout : K_FOREVER; sys_slist_init(&cmux->dlcis); ring_buf_init(&cmux->transmit_rb, config->transmit_buf_size, config->transmit_buf); k_mutex_init(&cmux->transmit_rb_lock); @@ -1866,6 +1951,7 @@ void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *co 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); set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED); From 988e7d5be4882a04163ee5c8ccffdf318bc31c62 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Thu, 9 Oct 2025 15:47:56 +0300 Subject: [PATCH 23/26] [nrf fromlist] drivers: modem: cellular: Use k_pipe instead of ringbuffer Ringbuffer is not safe in ISR but k_pipe without waiting is. So use pipe for events, so that possible GPIO callbacks from ISR content can post events. Upstream PR #: 97362 Signed-off-by: Seppo Takalo --- drivers/modem/modem_cellular.c | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index 5d381af2a821..f42622840a2d 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -166,8 +166,7 @@ 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; @@ -711,26 +710,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; + enum modem_cellular_event event; + const size_t len = sizeof(event); - k_mutex_lock(&data->event_rb_lock, K_FOREVER); - - 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); } @@ -2108,7 +2099,7 @@ static int modem_cellular_init(const struct device *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); From 5b8dd840bc3e2bc6eb683c5e9805a338a7188e97 Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Wed, 10 Sep 2025 16:17:12 +0300 Subject: [PATCH 24/26] [nrf fromlist] drivers: modem: Implement support for RING indicator Use ring indicator to wake up the CMUX device from sleep. Only used for runtime power management, but same event could be used for initiating idle -> connected as well. Upstream PR #: 97362 Signed-off-by: Seppo Takalo --- drivers/modem/modem_cellular.c | 59 ++++++++++++++++++- .../modem/zephyr,cellular-modem-device.yaml | 4 ++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c index f42622840a2d..dfa2c719a8af 100644 --- a/drivers/modem/modem_cellular.c +++ b/drivers/modem/modem_cellular.c @@ -96,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 { @@ -170,6 +171,9 @@ struct modem_cellular_data { struct k_mutex api_lock; struct modem_cellular_event_cb cb; + + /* Ring interrupt */ + struct gpio_callback ring_gpio_cb; }; struct modem_cellular_user_pipe { @@ -186,6 +190,7 @@ 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; @@ -285,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 ""; @@ -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; } @@ -2069,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 @@ -2115,6 +2140,33 @@ 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; @@ -2891,6 +2943,7 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script, .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), \ diff --git a/dts/bindings/modem/zephyr,cellular-modem-device.yaml b/dts/bindings/modem/zephyr,cellular-modem-device.yaml index c2d2e9e00a88..d2ab0056841b 100644 --- a/dts/bindings/modem/zephyr,cellular-modem-device.yaml +++ b/dts/bindings/modem/zephyr,cellular-modem-device.yaml @@ -18,6 +18,10 @@ properties: 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: | From d2a6268a946312a78c53e78d49f4e5b81ef4a94a Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Mon, 13 Oct 2025 12:00:33 +0300 Subject: [PATCH 25/26] [nrf fromlist] modem: pipe: Don't return EPERM on closed pipe When working on CMUX power saving, it is typical that we end up closing the pipe before the last RX_READY event is handled from workqueue, so we end up receiving -EPERM which is not really a fatal error. Pipes recover when they are re-opened. So drop this error and return zero instead, like modem_pipe_open() and close() does. Upstream PR #: 97362 Signed-off-by: Seppo Takalo --- subsys/modem/modem_pipe.c | 4 ++-- tests/subsys/modem/modem_pipe/src/main.c | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) 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/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); From 4982b013d5dd9222f3c90bb573e43027c7e7689d Mon Sep 17 00:00:00 2001 From: Seppo Takalo Date: Mon, 13 Oct 2025 11:36:49 +0300 Subject: [PATCH 26/26] [nrf fromlist] modem: cmux: Add struct cmux_config into struct cmux Instead of copying all fields from cmux_config into run-time struct cmux, just have the configuration structure as a member. Upstream PR #: 97362 Signed-off-by: Seppo Takalo --- include/zephyr/modem/cmux.h | 63 +++++++++++++++---------------------- subsys/modem/modem_cmux.c | 59 ++++++++++++++++------------------ 2 files changed, 52 insertions(+), 70 deletions(-) diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h index 18bb6fff980d..0faa8d5d4b38 100644 --- a/include/zephyr/modem/cmux.h +++ b/include/zephyr/modem/cmux.h @@ -53,6 +53,30 @@ 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 */ @@ -148,10 +172,6 @@ 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; @@ -159,12 +179,6 @@ struct modem_cmux { enum modem_cmux_state state; bool flow_control_on : 1; bool initiator : 1; - /** 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; /* Work lock */ bool attached : 1; @@ -172,10 +186,6 @@ struct modem_cmux { /* 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[MODEM_CMUX_WORK_BUFFER_SIZE]; @@ -206,36 +216,13 @@ struct modem_cmux { 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; - /** 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; -}; - /** * @brief Initialize CMUX instance * @param cmux CMUX instance diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c index 94c249aaa938..1edf3feaff41 100644 --- a/subsys/modem/modem_cmux.c +++ b/subsys/modem/modem_cmux.c @@ -400,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) @@ -483,11 +483,11 @@ static void modem_cmux_log_received_command(const struct modem_cmux_command *com 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, @@ -506,7 +506,7 @@ static void modem_cmux_bus_callback(struct modem_pipe *pipe, enum modem_pipe_eve break; case MODEM_PIPE_EVENT_TRANSMIT_IDLE: /* If we keep UART open in power-save, we should avoid waking up on RX idle */ - if (!cmux->close_pipe_on_power_save && is_powersaving(cmux)) { + if (!cmux->config.close_pipe_on_power_save && is_powersaving(cmux)) { break; } modem_work_schedule(&cmux->transmit_work, K_NO_WAIT); @@ -896,7 +896,7 @@ static void modem_cmux_on_psc_response(struct modem_cmux *cmux) set_state(cmux, MODEM_CMUX_STATE_POWERSAVE); k_mutex_unlock(&cmux->transmit_rb_lock); - if (cmux->close_pipe_on_power_save) { + if (cmux->config.close_pipe_on_power_save) { modem_pipe_close_async(cmux->pipe); } } @@ -1244,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 } @@ -1369,9 +1369,9 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by 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); modem_cmux_drop_frame(cmux); break; @@ -1383,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++; @@ -1397,9 +1397,9 @@ 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); + 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; } @@ -1433,7 +1433,7 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by } /* 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 */ @@ -1483,7 +1483,7 @@ 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->enable_runtime_power_management) { + if (!cmux->config.enable_runtime_power_management) { return; } @@ -1510,12 +1510,12 @@ static void modem_cmux_runtime_pm_handler(struct k_work *item) static void runtime_pm_keepalive(struct modem_cmux *cmux) { - if (cmux == NULL || !cmux->enable_runtime_power_management) { + if (cmux == NULL || !cmux->config.enable_runtime_power_management) { return; } - cmux->idle_timepoint = sys_timepoint_calc(cmux->idle_timeout); - k_work_reschedule(&cmux->runtime_pm_work, cmux->idle_timeout); + 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. @@ -1540,7 +1540,7 @@ static bool powersave_wait_wakeup(struct modem_cmux *cmux) LOG_DBG("Power saving mode, wake up first"); set_state(cmux, MODEM_CMUX_STATE_WAKEUP); - if (cmux->close_pipe_on_power_save) { + 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); @@ -1629,7 +1629,7 @@ static void modem_cmux_transmit_handler(struct k_work *item) if (cmux->state == MODEM_CMUX_STATE_CONFIRM_POWERSAVE) { set_state(cmux, MODEM_CMUX_STATE_POWERSAVE); LOG_DBG("Entered power saving mode"); - if (cmux->close_pipe_on_power_save) { + if (cmux->config.close_pipe_on_power_save) { modem_pipe_close_async(cmux->pipe); } } @@ -1934,18 +1934,13 @@ void modem_cmux_init(struct modem_cmux *cmux, const struct modem_cmux_config *co __ASSERT_NO_MSG(config->transmit_buf != NULL); __ASSERT_NO_MSG(config->transmit_buf_size >= MODEM_CMUX_DATA_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; - cmux->t3_timepoint = sys_timepoint_calc(K_NO_WAIT); - cmux->enable_runtime_power_management = config->enable_runtime_power_management; - cmux->close_pipe_on_power_save = config->close_pipe_on_power_save; - cmux->idle_timeout = - cmux->enable_runtime_power_management ? config->idle_timeout : K_FOREVER; + *cmux = (struct modem_cmux){ + .t3_timepoint = sys_timepoint_calc(K_NO_WAIT), + .config = *config, + }; sys_slist_init(&cmux->dlcis); - 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);