diff --git a/doc/services/modem/images/cmux_frame.svg b/doc/services/modem/images/cmux_frame.svg
new file mode 100644
index 0000000000000..1ddfbaf026c76
--- /dev/null
+++ b/doc/services/modem/images/cmux_frame.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/doc/services/modem/images/cmux_state_machine.svg b/doc/services/modem/images/cmux_state_machine.svg
new file mode 100644
index 0000000000000..a27dee309e5a8
--- /dev/null
+++ b/doc/services/modem/images/cmux_state_machine.svg
@@ -0,0 +1,482 @@
+
+
+
diff --git a/doc/services/modem/images/modem_pipes.svg b/doc/services/modem/images/modem_pipes.svg
new file mode 100644
index 0000000000000..f662ad84caef8
--- /dev/null
+++ b/doc/services/modem/images/modem_pipes.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
diff --git a/doc/services/modem/index.rst b/doc/services/modem/index.rst
index 97ae11ecbc89d..cf1e90d5502e1 100644
--- a/doc/services/modem/index.rst
+++ b/doc/services/modem/index.rst
@@ -24,6 +24,10 @@ A modem backend will internally contain an instance of a modem_pipe
structure, alongside any buffers and additional structures required
to abstract away its underlying mechanism.
+.. image:: images/modem_pipes.svg
+ :alt: Modem pipes
+ :align: center
+
The modem backend will return a pointer to its internal modem_pipe
structure when initialized, which will be used to interact with the
backend through the modem pipe API.
@@ -50,6 +54,17 @@ bi-directional streams of data, called DLCI channels. The module
attaches to a single modem backend, exposing multiple modem backends,
each representing a DLCI channel.
+Protocol defines simple framing for splitting each DLC into small chunks of data.
+
+.. image:: images/cmux_frame.svg
+ :alt: CMUX basic frame
+ :align: center
+
+Zephyr implements the basic frame type, with build-time configurable MTU size.
+
+The module also implements power-saving using CMUX Power Saving Command (PSC) for
+modems that support it. See the :ref:`cmux-power-saving` section below for more details.
+
.. doxygengroup:: modem_cmux
Modem pipelink
@@ -63,3 +78,110 @@ the users of said pipes. See
use the modem pipelink between device driver and application.
.. doxygengroup:: modem_pipelink
+
+.. _cmux-power-saving:
+
+CMUX Power Saving
+*****************
+
+The 3GPP TS 27.010 specifies a power saving mechanism for CMUX which can be used in
+Zephyr when the modem supports it.
+
+The power saving mechanism is covered in the following sections on the specification:
+
+* 5.2.5 Inter-frame Fill
+* 5.4.6.3.2 Power Saving Control (PSC) message
+* 5.4.7 Power Control and Wake-up Mechanisms
+
+The power saving mechanism allows runtime power management for the UART device
+used by the CMUX module. When there is no data to be sent or received on any
+DLCI channel, the CMUX module will enter the idle state after a configurable
+timeout. In the idle state, the CMUX module will send a Power Saving Control message to the
+modem, requesting it to enter a low power state. The CMUX module may then
+close the pipe device, allowing the UART device to be powered down if
+runtime power management is enabled.
+
+When data is to be sent or received on any DLCI channel, the CMUX module
+will exit the idle state and wakes the modem up by sending flag characters
+until it receives a flag character from the modem.
+
+Some modems allow UART to be powered down only when the DTR (Data Terminal Ready)
+signal is de-asserted. In this case, a UART device with DTR support can be used
+with the CMUX module to control the DTR signal based on the power state of the UART.
+
+Waking up on incoming data when UART is powered down requires a modem that supports
+RING signal to wake up the host.
+The RING signal is handled by the modem driver and it opens the pipe device when
+the RING signal is detected, allowing the CMUX module to wake up the modem and
+process incoming data.
+
+The :zephyr_file:`subsys/modem/modem_cmux.c` module implements the power saving mechanism using the following state machine.
+
+.. image:: images/cmux_state_machine.svg
+ :alt: CMUX state machine when using power saving
+ :align: center
+
+Within a connected state, ``modem_cmux_process_received_byte()`` is required to reply repeated flag characters as described in 5.2.5 Inter-frame Fill at the specification.
+Idle timer is kept running and cleared on every sent or received frame. Timer expiry will initiate transitioning to power saving modes.
+
+Within the POWERSAVE state all DLC pipes remain open but the pipe towards UART is blocked or closed, so all data is buffered within CMUX ringbuffers to wait for waking up.
+Within this state, repeated flag characters are also replied to allow remote end proceed with wake-up procedure as described in 5.4.7.
+If pipe is closed, it allows UART device to be powered down if runtime power management is enabled.
+
+When idle timer expires on CONNECTED state, CMUX state machine blocks all DLC pipes and sends PSC command for remote end to initiate transitioning to POWERSAVE state.
+When PSC command is replied, CMUX transitions to POWERSAVE mode.
+
+When within the CONNECTED state, the remote end may send a PSC command to initiate the transitioning to power saving mode. CMUX blocks all DLC pipes and sends a PSC response.
+When TX buffers are emptied, CMUX enters the POWERSAVE state.
+
+When any of DLC pipes try to transmit data during POWERSAVE state, CMUX buffers it and moves to WAKEUP state that initiates wake-up procedure as specified in 5.4.7 by sending repeated stream of flag characters.
+Remote end replies the flag characters to indicate it is ready to receive data. CMUX then stops sending flag characters and moves back to CONNECTED state, resuming normal operation.
+
+The CMUX power saving mechanism can be configured using the following Device Tree properties:
+
+.. code-block:: yaml
+
+ 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
+
+
+Example Device Tree setup for CMUX with power saving:
+
+.. code-block:: devicetree
+
+ &uart1 {
+ status = "okay";
+ zephyr,pm-device-runtime-auto;
+
+ uart_dtr: uart-dtr {
+ compatible = "zephyr,uart-dtr";
+ dtr-gpios = <&interface_to_nrf9160 4 GPIO_ACTIVE_LOW>;
+ status = "okay";
+ zephyr,pm-device-runtime-auto;
+
+ modem: modem {
+ compatible = "nordic,nrf91-slm";
+ status = "okay";
+ mdm-ring-gpios = <&interface_to_nrf9160 5 (GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
+ zephyr,pm-device-runtime-auto;
+ cmux-enable-runtime-power-save;
+ cmux-close-pipe-on-power-save;
+ cmux-idle-timeout-ms = <5000>;
+ };
+ };
+ };
+
+The above example shows a UART device with DTR support being used by a modem that supports CMUX and PSC commands. The DTR signal is used to control the power state of the UART.
+The RING signal from the modem is used to wake up the modem subsystem when it is powered down.
diff --git a/drivers/modem/modem_cellular.c b/drivers/modem/modem_cellular.c
index c09c9a12795ad..09c76a5c8ef9a 100644
--- a/drivers/modem/modem_cellular.c
+++ b/drivers/modem/modem_cellular.c
@@ -17,6 +17,8 @@
#include
#include
#include
+#include
+#include
#include
#include
@@ -95,6 +97,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 {
@@ -166,11 +169,13 @@ struct modem_cellular_data {
/* Event dispatcher */
struct k_work event_dispatch_work;
uint8_t event_buf[8];
- struct ring_buf event_rb;
- struct k_mutex event_rb_lock;
+ struct k_pipe event_pipe;
struct k_mutex api_lock;
struct modem_cellular_event_cb cb;
+
+ /* Ring interrupt */
+ struct gpio_callback ring_gpio_cb;
};
struct modem_cellular_user_pipe {
@@ -187,11 +192,16 @@ struct modem_cellular_config {
struct gpio_dt_spec power_gpio;
struct gpio_dt_spec reset_gpio;
struct gpio_dt_spec wake_gpio;
+ struct gpio_dt_spec ring_gpio;
+ struct gpio_dt_spec dtr_gpio;
uint16_t power_pulse_duration_ms;
uint16_t reset_pulse_duration_ms;
uint16_t startup_time_ms;
uint16_t shutdown_time_ms;
bool autostarts;
+ bool cmux_enable_runtime_power_save;
+ bool cmux_close_pipe_on_power_save;
+ k_timeout_t cmux_idle_timeout;
const struct modem_chat_script *init_chat_script;
const struct modem_chat_script *dial_chat_script;
const struct modem_chat_script *periodic_chat_script;
@@ -284,6 +294,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 "";
@@ -721,26 +733,18 @@ static void modem_cellular_event_dispatch_handler(struct k_work *item)
struct modem_cellular_data *data =
CONTAINER_OF(item, struct modem_cellular_data, event_dispatch_work);
- uint8_t events[sizeof(data->event_buf)];
- uint8_t events_cnt;
-
- k_mutex_lock(&data->event_rb_lock, K_FOREVER);
-
- events_cnt = (uint8_t)ring_buf_get(&data->event_rb, events, sizeof(data->event_buf));
+ enum modem_cellular_event event;
+ const size_t len = sizeof(event);
- 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);
}
@@ -1322,7 +1326,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;
}
@@ -1367,7 +1374,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;
}
@@ -1416,7 +1426,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;
}
@@ -2175,6 +2188,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
@@ -2198,14 +2220,14 @@ static int modem_cellular_init(const struct device *dev)
{
struct modem_cellular_data *data = (struct modem_cellular_data *)dev->data;
struct modem_cellular_config *config = (struct modem_cellular_config *)dev->config;
+ const struct gpio_dt_spec *dtr_gpio = NULL;
data->dev = dev;
k_mutex_init(&data->api_lock);
k_work_init_delayable(&data->timeout_work, modem_cellular_timeout_handler);
-
k_work_init(&data->event_dispatch_work, modem_cellular_event_dispatch_handler);
- ring_buf_init(&data->event_rb, sizeof(data->event_buf), data->event_buf);
+ k_pipe_init(&data->event_pipe, data->event_buf, sizeof(data->event_buf));
k_sem_init(&data->suspended_sem, 0, 1);
@@ -2221,9 +2243,42 @@ static int modem_cellular_init(const struct device *dev)
gpio_pin_configure_dt(&config->reset_gpio, GPIO_OUTPUT_ACTIVE);
}
+ if (modem_cellular_gpio_is_enabled(&config->ring_gpio)) {
+ int ret;
+
+ ret = gpio_pin_configure_dt(&config->ring_gpio, GPIO_INPUT);
+ if (ret < 0) {
+ LOG_ERR("Failed to configure ring GPIO (%d)", ret);
+ return ret;
+ }
+
+ gpio_init_callback(&data->ring_gpio_cb, modem_cellular_ring_gpio_callback,
+ BIT(config->ring_gpio.pin));
+
+ ret = gpio_add_callback(config->ring_gpio.port, &data->ring_gpio_cb);
+ if (ret < 0) {
+ LOG_ERR("Failed to add ring GPIO callback (%d)", ret);
+ return ret;
+ }
+
+ ret = gpio_pin_interrupt_configure_dt(&config->ring_gpio, GPIO_INT_EDGE_TO_ACTIVE);
+ if (ret < 0) {
+ LOG_ERR("Failed to configure ring GPIO interrupt (%d)", ret);
+ return ret;
+ }
+
+ LOG_DBG("Ring GPIO interrupt configured");
+ }
+
+ if (modem_cellular_gpio_is_enabled(&config->dtr_gpio)) {
+ gpio_pin_configure_dt(&config->dtr_gpio, GPIO_OUTPUT_INACTIVE);
+ dtr_gpio = &config->dtr_gpio;
+ }
+
{
const struct modem_backend_uart_config uart_backend_config = {
.uart = config->uart,
+ .dtr_gpio = dtr_gpio,
.receive_buf = data->uart_backend_receive_buf,
.receive_buf_size = ARRAY_SIZE(data->uart_backend_receive_buf),
.transmit_buf = data->uart_backend_transmit_buf,
@@ -2244,6 +2299,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);
@@ -2981,26 +3039,30 @@ MODEM_CHAT_SCRIPT_DEFINE(sqn_gm02s_periodic_chat_script,
/* Helper to define modem instance */
#define MODEM_CELLULAR_DEFINE_INSTANCE(inst, power_ms, reset_ms, startup_ms, shutdown_ms, start, \
- set_baudrate_script, \
- init_script, \
- dial_script, \
- periodic_script, \
- shutdown_script) \
+ set_baudrate_script, init_script, dial_script, \
+ periodic_script, shutdown_script) \
static const struct modem_cellular_config MODEM_CELLULAR_INST_NAME(config, inst) = { \
.uart = DEVICE_DT_GET(DT_INST_BUS(inst)), \
.power_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_power_gpios, {}), \
.reset_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_reset_gpios, {}), \
.wake_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_wake_gpios, {}), \
+ .ring_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_ring_gpios, {}), \
+ .dtr_gpio = GPIO_DT_SPEC_INST_GET_OR(inst, mdm_dtr_gpios, {}), \
.power_pulse_duration_ms = (power_ms), \
.reset_pulse_duration_ms = (reset_ms), \
- .startup_time_ms = (startup_ms), \
+ .startup_time_ms = (startup_ms), \
.shutdown_time_ms = (shutdown_ms), \
.autostarts = DT_INST_PROP_OR(inst, autostarts, (start)), \
- .set_baudrate_chat_script = (set_baudrate_script), \
- .init_chat_script = (init_script), \
- .dial_chat_script = (dial_script), \
+ .cmux_enable_runtime_power_save = \
+ DT_INST_PROP_OR(inst, cmux_enable_runtime_power_save, 0), \
+ .cmux_close_pipe_on_power_save = \
+ DT_INST_PROP_OR(inst, cmux_close_pipe_on_power_save, 0), \
+ .cmux_idle_timeout = K_MSEC(DT_INST_PROP_OR(inst, cmux_idle_timeout_ms, 0)), \
+ .set_baudrate_chat_script = (set_baudrate_script), \
+ .init_chat_script = (init_script), \
+ .dial_chat_script = (dial_script), \
.periodic_chat_script = (periodic_script), \
- .shutdown_chat_script = (shutdown_script), \
+ .shutdown_chat_script = (shutdown_script), \
.user_pipes = MODEM_CELLULAR_GET_USER_PIPES(inst), \
.user_pipes_size = ARRAY_SIZE(MODEM_CELLULAR_GET_USER_PIPES(inst)), \
}; \
diff --git a/dts/bindings/modem/zephyr,cellular-modem-device.yaml b/dts/bindings/modem/zephyr,cellular-modem-device.yaml
index bff39d7f9f613..d2ab0056841b2 100644
--- a/dts/bindings/modem/zephyr,cellular-modem-device.yaml
+++ b/dts/bindings/modem/zephyr,cellular-modem-device.yaml
@@ -17,3 +17,34 @@ properties:
mdm-wake-gpios:
type: phandle-array
description: GPIO for modem wake
+
+ mdm-ring-gpios:
+ type: phandle-array
+ description: GPIO for modem ring indicator
+
+ mdm-dtr-gpios:
+ type: phandle-array
+ description: |
+ GPIO for modem data terminal ready.
+
+ Asserted (logical high) when UART is active and
+ deasserted (logical low) when UART is inactive, powered down or in low power mode.
+
+ cmux-enable-runtime-power-save:
+ type: boolean
+ description: |
+ Enable runtime power saving using CMUX PSC commands.
+ This requires modem to support CMUX and PSC commands while keeping the data
+ connection active.
+
+ cmux-close-pipe-on-power-save:
+ type: boolean
+ description: |
+ Close the modem pipe when entering power save mode.
+ When runtime power management is enabled, this closes the UART.
+ This requires modem to support waking up the UART using RING signal.
+
+ cmux-idle-timeout-ms:
+ type: int
+ description: Time in milliseconds after which CMUX will enter power save mode.
+ default: 10000
diff --git a/include/zephyr/modem/backend/uart.h b/include/zephyr/modem/backend/uart.h
index 73054b792d8e4..6407af8cf6afa 100644
--- a/include/zephyr/modem/backend/uart.h
+++ b/include/zephyr/modem/backend/uart.h
@@ -68,6 +68,7 @@ struct modem_backend_uart_async {
struct modem_backend_uart {
const struct device *uart;
+ const struct gpio_dt_spec *dtr_gpio;
struct modem_pipe pipe;
struct k_work_delayable receive_ready_work;
struct k_work transmit_idle_work;
@@ -85,6 +86,7 @@ struct modem_backend_uart {
struct modem_backend_uart_config {
const struct device *uart;
+ const struct gpio_dt_spec *dtr_gpio;
/* Address must be word-aligned when CONFIG_MODEM_BACKEND_UART_ASYNC_HWFC is enabled. */
uint8_t *receive_buf;
uint32_t receive_buf_size;
diff --git a/include/zephyr/modem/cmux.h b/include/zephyr/modem/cmux.h
index bf349980e2c95..0faa8d5d4b388 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
*/
@@ -72,6 +96,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,
};
@@ -144,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;
@@ -162,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];
@@ -184,39 +204,25 @@ struct modem_cmux {
struct k_work_delayable transmit_work;
struct k_work_delayable connect_work;
struct k_work_delayable disconnect_work;
+ struct k_work_delayable runtime_pm_work;
/* Synchronize actions */
struct k_event event;
+ k_timepoint_t t3_timepoint;
+ k_timepoint_t idle_timepoint;
/* Statistics */
#if CONFIG_MODEM_STATS
struct modem_stats_buffer receive_buf_stats;
struct modem_stats_buffer transmit_buf_stats;
#endif
+ struct modem_cmux_config config;
};
/**
* @endcond
*/
-/**
- * @brief Contains CMUX instance configuration data
- */
-struct modem_cmux_config {
- /** Invoked when event occurs */
- modem_cmux_callback callback;
- /** Free to use pointer passed to event handler when invoked */
- void *user_data;
- /** Receive buffer */
- uint8_t *receive_buf;
- /** Size of receive buffer in bytes [127, ...] */
- uint16_t receive_buf_size;
- /** Transmit buffer */
- uint8_t *transmit_buf;
- /** Size of transmit buffer in bytes [149, ...] */
- uint16_t transmit_buf_size;
-};
-
/**
* @brief Initialize CMUX instance
* @param cmux CMUX instance
diff --git a/subsys/modem/Kconfig b/subsys/modem/Kconfig
index dfcb7ddae9a38..8b4a411c26f65 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/backends/modem_backend_uart.c b/subsys/modem/backends/modem_backend_uart.c
index 82242a76c0196..13a35406f182f 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 9b5edc8965c52..0cfcff9e86edd 100644
--- a/subsys/modem/backends/modem_backend_uart_async.c
+++ b/subsys/modem/backends/modem_backend_uart_async.c
@@ -11,6 +11,8 @@
LOG_MODULE_REGISTER(modem_backend_uart_async, CONFIG_MODEM_MODULES_LOG_LEVEL);
#include
+#include
+#include
#include
enum {
@@ -157,6 +159,15 @@ static int modem_backend_uart_async_open(void *data)
atomic_clear(&backend->async.common.state);
ring_buf_reset(&backend->async.receive_rb);
+ ret = pm_device_runtime_get(backend->uart);
+ if (ret < 0) {
+ LOG_ERR("Failed to power on UART: %d", ret);
+ return ret;
+ }
+ if (backend->dtr_gpio) {
+ gpio_pin_set_dt(backend->dtr_gpio, 1);
+ }
+
atomic_set_bit(&backend->async.common.state,
MODEM_BACKEND_UART_ASYNC_STATE_RX_BUF0_USED_BIT);
atomic_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_RECEIVING_BIT);
@@ -264,10 +275,20 @@ static int modem_backend_uart_async_receive(void *data, uint8_t *buf, size_t siz
static int modem_backend_uart_async_close(void *data)
{
struct modem_backend_uart *backend = (struct modem_backend_uart *)data;
+ int ret;
atomic_clear_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT);
uart_tx_abort(backend->uart);
uart_rx_disable(backend->uart);
+ if (backend->dtr_gpio) {
+ gpio_pin_set_dt(backend->dtr_gpio, 0);
+ }
+ ret = pm_device_runtime_put_async(backend->uart, K_NO_WAIT);
+ if (ret < 0) {
+ LOG_ERR("Failed to power off UART: %d", ret);
+ return ret;
+ }
+ modem_pipe_notify_closed(&backend->pipe);
return 0;
}
diff --git a/subsys/modem/backends/modem_backend_uart_async_hwfc.c b/subsys/modem/backends/modem_backend_uart_async_hwfc.c
index 61d307604e5c5..df69c40b6679b 100644
--- a/subsys/modem/backends/modem_backend_uart_async_hwfc.c
+++ b/subsys/modem/backends/modem_backend_uart_async_hwfc.c
@@ -11,6 +11,8 @@
LOG_MODULE_REGISTER(modem_backend_uart_async_hwfc, CONFIG_MODEM_MODULES_LOG_LEVEL);
#include
+#include
+#include
#include
struct rx_buf_t {
@@ -221,6 +223,15 @@ static int modem_backend_uart_async_hwfc_open(void *data)
return -ENOMEM;
}
+ ret = pm_device_runtime_get(backend->uart);
+ if (ret < 0) {
+ LOG_ERR("Failed to power on UART: %d", ret);
+ return ret;
+ }
+ if (backend->dtr_gpio) {
+ gpio_pin_set_dt(backend->dtr_gpio, 1);
+ }
+
atomic_clear(&backend->async.common.state);
atomic_set_bit(&backend->async.common.state, MODEM_BACKEND_UART_ASYNC_STATE_OPEN_BIT);
@@ -347,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);
@@ -357,6 +369,16 @@ static int modem_backend_uart_async_hwfc_close(void *data)
uart_rx_disable(backend->uart);
}
+ if (backend->dtr_gpio) {
+ gpio_pin_set_dt(backend->dtr_gpio, 0);
+ }
+ ret = pm_device_runtime_put_async(backend->uart, K_NO_WAIT);
+ if (ret < 0) {
+ LOG_ERR("Failed to power off UART: %d", ret);
+ return ret;
+ }
+ modem_pipe_notify_closed(&backend->pipe);
+
return 0;
}
diff --git a/subsys/modem/modem_cmux.c b/subsys/modem/modem_cmux.c
index acb1db3bdddab..194541e867916 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
@@ -30,15 +32,14 @@ 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)
#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,
@@ -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,200 @@ 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 void modem_cmux_tx_bypass(struct modem_cmux *cmux, const uint8_t *data, size_t len);
+static void runtime_pm_keepalive(struct modem_cmux *cmux);
+
+static void set_state(struct modem_cmux *cmux, enum modem_cmux_state state)
+{
+ cmux->state = state;
+ k_event_set(&cmux->event, BIT(state));
+}
+
+static bool wait_state(struct modem_cmux *cmux, enum modem_cmux_state state, k_timeout_t timeout)
+{
+ return k_event_wait(&cmux->event, BIT(state), false, timeout) == BIT(state);
+}
+
+static bool is_connected(struct modem_cmux *cmux)
+{
+ return cmux->state == MODEM_CMUX_STATE_CONNECTED;
+}
+
+static bool is_powersaving(struct modem_cmux *cmux)
+{
+ return cmux->state == MODEM_CMUX_STATE_POWERSAVE;
+}
+
+static bool is_waking_up(struct modem_cmux *cmux)
+{
+ return cmux->state == MODEM_CMUX_STATE_WAKEUP;
+}
+
+static bool is_transitioning_to_powersave(struct modem_cmux *cmux)
+{
+ return (cmux->state == MODEM_CMUX_STATE_ENTER_POWERSAVE ||
+ cmux->state == MODEM_CMUX_STATE_CONFIRM_POWERSAVE);
+}
-static int modem_cmux_wrap_command(struct modem_cmux_command **command, const uint8_t *data,
- uint16_t data_len)
+static bool modem_cmux_command_type_is_valid(const struct modem_cmux_command_type type)
{
- if ((data == NULL) || (data_len < 2)) {
- return -EINVAL;
+ /* 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_wrap(const uint8_t *data)
+static struct modem_cmux_command modem_cmux_command_decode(const uint8_t *data, size_t len)
{
- return (struct modem_cmux_command *)data;
+ 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;
+}
+
+/**
+ * @brief Encode command into a shared buffer
+ *
+ * Not a thread safe, so can only be used within a workqueue context and data
+ * must be copied out to a TX ringbuffer.
+ *
+ * @param command command to encode
+ * @param len encoded length of the command is written here
+ * @return pointer to encoded command buffer on success, NULL on failure
+ */
+static uint8_t *modem_cmux_command_encode(struct modem_cmux_command *command, uint16_t *len)
+{
+ static uint8_t buf[MODEM_CMUX_CMD_DATA_SIZE_MAX];
+
+ __ASSERT_NO_MSG(len != NULL);
+ __ASSERT_NO_MSG(command != NULL);
+ __ASSERT_NO_MSG(modem_cmux_command_is_valid(command));
+
+ buf[0] = modem_cmux_command_type_encode(command->type);
+ buf[1] = modem_cmux_command_length_encode(command->length);
+ if (command->length.value > 0) {
+ memcpy(&buf[2], &command->value[0], command->length.value);
+ }
+ *len = 2 + command->length.value;
+ return buf;
}
static struct modem_cmux_msc_signals modem_cmux_msc_signals_decode(const uint8_t byte)
@@ -252,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)
@@ -335,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,
@@ -352,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->config.close_pipe_on_power_save && is_powersaving(cmux)) {
+ break;
+ }
modem_work_schedule(&cmux->transmit_work, K_NO_WAIT);
break;
-
default:
break;
}
@@ -421,7 +576,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 +588,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);
@@ -450,7 +606,7 @@ static int16_t modem_cmux_transmit_data_frame(struct modem_cmux *cmux,
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
- if (cmux->flow_control_on == false) {
+ if (cmux->flow_control_on == false || is_transitioning_to_powersave(cmux)) {
k_mutex_unlock(&cmux->transmit_rb_lock);
return 0;
}
@@ -477,7 +633,7 @@ static int16_t modem_cmux_transmit_data_frame(struct modem_cmux *cmux,
static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux)
{
- struct modem_cmux_command *command;
+ struct modem_cmux_command_type command;
struct modem_cmux_frame frame;
uint8_t data[MODEM_CMUX_CMD_DATA_SIZE_MAX];
@@ -488,8 +644,9 @@ static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux)
memcpy(&frame, &cmux->frame, sizeof(cmux->frame));
memcpy(data, cmux->frame.data, cmux->frame.data_len);
- modem_cmux_wrap_command(&command, data, cmux->frame.data_len);
- command->type.cr = 0;
+ command = modem_cmux_command_type_decode(data[0]);
+ command.cr = 0;
+ data[0] = modem_cmux_command_type_encode(command);
frame.data = data;
frame.data_len = cmux->frame.data_len;
@@ -498,6 +655,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) {
@@ -516,29 +701,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,60 +845,87 @@ 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_psc_command(struct modem_cmux *cmux)
+{
+ LOG_DBG("Received power saving command");
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+ set_state(cmux, MODEM_CMUX_STATE_CONFIRM_POWERSAVE);
+ modem_cmux_acknowledge_received_frame(cmux);
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+}
+
+static void modem_cmux_on_psc_response(struct modem_cmux *cmux)
+{
+ LOG_DBG("Enter power saving");
+ k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
+ set_state(cmux, MODEM_CMUX_STATE_POWERSAVE);
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+
+ if (cmux->config.close_pipe_on_power_save) {
+ modem_pipe_close_async(cmux->pipe);
+ }
+}
+
static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux)
{
- struct modem_cmux_command *command;
+ struct modem_cmux_command command;
- if ((cmux->state != MODEM_CMUX_STATE_CONNECTED) &&
- (cmux->state != MODEM_CMUX_STATE_DISCONNECTING)) {
+ if (cmux->state < MODEM_CMUX_STATE_CONNECTED) {
LOG_DBG("Unexpected UIH frame");
return;
}
- if (modem_cmux_wrap_command(&command, cmux->frame.data, cmux->frame.data_len) < 0) {
+ command = modem_cmux_command_decode(cmux->frame.data, cmux->frame.data_len);
+ if (!modem_cmux_command_is_valid(&command)) {
LOG_WRN("Invalid command");
return;
}
- modem_cmux_log_received_command(command);
+ modem_cmux_log_received_command(&command);
- 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;
+ case MODEM_CMUX_COMMAND_PSC:
+ modem_cmux_on_psc_response(cmux);
break;
default:
/* Responses to other commands are ignored */
@@ -717,13 +934,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:
@@ -734,6 +951,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);
@@ -999,6 +1220,11 @@ static void modem_cmux_on_frame(struct modem_cmux *cmux)
modem_cmux_advertise_receive_buf_stats(cmux);
#endif
+ if (is_powersaving(cmux) || is_waking_up(cmux)) {
+ set_state(cmux, MODEM_CMUX_STATE_CONNECTED);
+ LOG_DBG("Exit powersave on received frame");
+ }
+
if (cmux->frame.dlci_address == 0) {
modem_cmux_on_control_frame(cmux);
} else {
@@ -1018,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
}
@@ -1029,8 +1255,10 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by
switch (cmux->receive_state) {
case MODEM_CMUX_RECEIVE_STATE_SOF:
+ cmux->frame_header_len = 0;
if (byte == MODEM_CMUX_SOF) {
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC;
+ cmux->frame_header_len++;
break;
}
@@ -1042,6 +1270,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;
}
@@ -1127,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;
@@ -1141,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++;
@@ -1155,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;
}
@@ -1191,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 */
@@ -1209,22 +1451,18 @@ static void modem_cmux_receive_handler(struct k_work *item)
struct modem_cmux *cmux = CONTAINER_OF(dwork, struct modem_cmux, receive_work);
int ret;
+ runtime_pm_keepalive(cmux);
+
/* Receive data from pipe */
- ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf));
- if (ret < 1) {
- if (ret < 0) {
- LOG_ERR("Pipe receiving error: %d", ret);
+ while ((ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf))) > 0) {
+ /* Process received data */
+ for (int i = 0; i < ret; i++) {
+ modem_cmux_process_received_byte(cmux, cmux->work_buf[i]);
}
- return;
}
-
- /* Process received data */
- for (int i = 0; i < ret; i++) {
- modem_cmux_process_received_byte(cmux, cmux->work_buf[i]);
+ if (ret < 0) {
+ LOG_ERR("Pipe receiving error: %d", ret);
}
-
- /* Reschedule received work */
- modem_work_schedule(&cmux->receive_work, K_NO_WAIT);
}
static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux)
@@ -1240,6 +1478,102 @@ 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->config.enable_runtime_power_management) {
+ return;
+ }
+
+ bool expired = sys_timepoint_expired(cmux->idle_timepoint);
+
+ if (!expired) {
+ return;
+ }
+
+ if (is_connected(cmux) && expired) {
+ LOG_DBG("Idle timeout, entering power saving mode");
+ set_state(cmux, MODEM_CMUX_STATE_ENTER_POWERSAVE);
+ modem_cmux_send_psc(cmux);
+ k_work_reschedule(&cmux->runtime_pm_work, MODEM_CMUX_T3_TIMEOUT);
+ return;
+ }
+ if (cmux->state == MODEM_CMUX_STATE_ENTER_POWERSAVE) {
+ LOG_WRN("PSC timeout, not entering power saving mode");
+ set_state(cmux, MODEM_CMUX_STATE_CONNECTED);
+ runtime_pm_keepalive(cmux);
+ return;
+ }
+}
+
+static void runtime_pm_keepalive(struct modem_cmux *cmux)
+{
+ if (cmux == NULL || !cmux->config.enable_runtime_power_management) {
+ return;
+ }
+
+ cmux->idle_timepoint = sys_timepoint_calc(cmux->config.idle_timeout);
+ k_work_reschedule(&cmux->runtime_pm_work, cmux->config.idle_timeout);
+}
+
+/** Transmit bytes bypassing the CMUX buffers.
+ * Causes modem_cmux_transmit_handler() to be rescheduled as a result of TRANSMIT_IDLE event.
+ */
+static void modem_cmux_tx_bypass(struct modem_cmux *cmux, const uint8_t *data, size_t len)
+{
+ if (cmux == NULL) {
+ return;
+ }
+
+ modem_pipe_transmit(cmux->pipe, data, len);
+}
+
+static bool powersave_wait_wakeup(struct modem_cmux *cmux)
+{
+ static const uint8_t wakeup_pattern[] = {MODEM_CMUX_SOF, MODEM_CMUX_SOF, MODEM_CMUX_SOF,
+ MODEM_CMUX_SOF, MODEM_CMUX_SOF};
+ int ret;
+
+ if (is_powersaving(cmux)) {
+ LOG_DBG("Power saving mode, wake up first");
+ set_state(cmux, MODEM_CMUX_STATE_WAKEUP);
+
+ if (cmux->config.close_pipe_on_power_save) {
+ ret = modem_pipe_open(cmux->pipe, K_FOREVER);
+ if (ret < 0) {
+ LOG_ERR("Failed to open pipe for wake up (%d)", ret);
+ set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED);
+ modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED);
+ return true;
+ }
+ }
+
+ cmux->t3_timepoint = sys_timepoint_calc(MODEM_CMUX_T3_TIMEOUT);
+ modem_cmux_tx_bypass(cmux, wakeup_pattern, sizeof(wakeup_pattern));
+ return true;
+ }
+
+ if (is_waking_up(cmux)) {
+ if (sys_timepoint_expired(cmux->t3_timepoint)) {
+ LOG_ERR("Wake up timed out, link dead");
+ set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED);
+ modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED);
+ return true;
+ }
+ if (cmux->receive_state != MODEM_CMUX_RECEIVE_STATE_RESYNC) {
+ /* Retry single flag, until remote wakes up */
+ modem_cmux_tx_bypass(cmux, &(uint8_t){MODEM_CMUX_SOF}, 1);
+ return true;
+ }
+ set_state(cmux, MODEM_CMUX_STATE_CONNECTED);
+ LOG_DBG("Woke up from power saving mode");
+ }
+
+ return false;
+}
+
static void modem_cmux_transmit_handler(struct k_work *item)
{
struct k_work_delayable *dwork = k_work_delayable_from_work(item);
@@ -1255,9 +1589,18 @@ static void modem_cmux_transmit_handler(struct k_work *item)
modem_cmux_advertise_transmit_buf_stats(cmux);
#endif
+ if (!is_transitioning_to_powersave(cmux)) {
+ runtime_pm_keepalive(cmux);
+ }
+
while (true) {
transmit_rb_empty = ring_buf_is_empty(&cmux->transmit_rb);
+ if (powersave_wait_wakeup(cmux)) {
+ k_mutex_unlock(&cmux->transmit_rb_lock);
+ return;
+ }
+
if (transmit_rb_empty) {
break;
}
@@ -1282,11 +1625,17 @@ static void modem_cmux_transmit_handler(struct k_work *item)
}
}
- k_mutex_unlock(&cmux->transmit_rb_lock);
-
if (transmit_rb_empty) {
+ if (cmux->state == MODEM_CMUX_STATE_CONFIRM_POWERSAVE) {
+ set_state(cmux, MODEM_CMUX_STATE_POWERSAVE);
+ LOG_DBG("Entered power saving mode");
+ if (cmux->config.close_pipe_on_power_save) {
+ modem_pipe_close_async(cmux->pipe);
+ }
+ }
modem_cmux_dlci_notify_transmit_idle(cmux);
}
+ k_mutex_unlock(&cmux->transmit_rb_lock);
}
static void modem_cmux_connect_handler(struct k_work *item)
@@ -1321,8 +1670,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 +1678,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 +1700,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 */
@@ -1411,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;
}
@@ -1573,18 +1936,19 @@ 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 = (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);
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);
diff --git a/subsys/modem/modem_pipe.c b/subsys/modem/modem_pipe.c
index bf642b3984dc5..ef66b9085ea61 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_cmux/src/main.c b/tests/subsys/modem/modem_cmux/src/main.c
index 67833baabe76c..b40af7e0de898 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);
diff --git a/tests/subsys/modem/modem_pipe/src/main.c b/tests/subsys/modem/modem_pipe/src/main.c
index 3cb330550fd52..7889d111d1b95 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);