Skip to content

Commit 55c463e

Browse files
SeppoTakalorlubos
authored andcommitted
[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 <[email protected]>
1 parent 2d81801 commit 55c463e

File tree

3 files changed

+168
-20
lines changed

3 files changed

+168
-20
lines changed

include/zephyr/modem/cmux.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ enum modem_cmux_state {
7272
MODEM_CMUX_STATE_DISCONNECTED = 0,
7373
MODEM_CMUX_STATE_CONNECTING,
7474
MODEM_CMUX_STATE_CONNECTED,
75+
MODEM_CMUX_STATE_ENTER_POWERSAVE,
76+
MODEM_CMUX_STATE_POWERSAVE,
77+
MODEM_CMUX_STATE_CONFIRM_POWERSAVE,
78+
MODEM_CMUX_STATE_WAKEUP,
7579
MODEM_CMUX_STATE_DISCONNECTING,
7680
};
7781

@@ -187,6 +191,7 @@ struct modem_cmux {
187191

188192
/* Synchronize actions */
189193
struct k_event event;
194+
k_timepoint_t t3_timepoint;
190195

191196
/* Statistics */
192197
#if CONFIG_MODEM_STATS

subsys/modem/Kconfig

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ config MODEM_CMUX_WORK_BUFFER_SIZE_EXTRA
6464
Extra bytes to add to the work buffers used by the CMUX module.
6565
The default size of these buffers is MODEM_CMUX_MTU + 7 (CMUX header size).
6666

67+
config MODEM_CMUX_T3_TIMEOUT
68+
int "CMUX T3 timeout in seconds"
69+
range 1 255
70+
default 10
71+
help
72+
Response Timer for wake-up procedure(T3).
73+
Time in seconds before the link is considered dead.
74+
6775
module = MODEM_CMUX
6876
module-str = modem_cmux
6977
source "subsys/logging/Kconfig.template.log_config"

subsys/modem/modem_cmux.c

Lines changed: 155 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,7 @@ LOG_MODULE_REGISTER(modem_cmux, CONFIG_MODEM_CMUX_LOG_LEVEL);
3737

3838
#define MODEM_CMUX_T1_TIMEOUT (K_MSEC(330))
3939
#define MODEM_CMUX_T2_TIMEOUT (K_MSEC(660))
40-
41-
#define MODEM_CMUX_EVENT_CONNECTED_BIT (BIT(0))
42-
#define MODEM_CMUX_EVENT_DISCONNECTED_BIT (BIT(1))
40+
#define MODEM_CMUX_T3_TIMEOUT (K_SECONDS(CONFIG_MODEM_CMUX_T3_TIMEOUT))
4341

4442
enum modem_cmux_frame_types {
4543
MODEM_CMUX_FRAME_TYPE_RR = 0x01,
@@ -101,6 +99,7 @@ struct modem_cmux_msc_addr {
10199

102100
static struct modem_cmux_dlci *modem_cmux_find_dlci(struct modem_cmux *cmux, uint8_t dlci_address);
103101
static void modem_cmux_dlci_notify_transmit_idle(struct modem_cmux *cmux);
102+
static void modem_cmux_tx_bypass(struct modem_cmux *cmux, const uint8_t *data, size_t len);
104103

105104
static void set_state(struct modem_cmux *cmux, enum modem_cmux_state state)
106105
{
@@ -118,6 +117,22 @@ static bool is_connected(struct modem_cmux *cmux)
118117
return cmux->state == MODEM_CMUX_STATE_CONNECTED;
119118
}
120119

120+
static bool is_powersaving(struct modem_cmux *cmux)
121+
{
122+
return cmux->state == MODEM_CMUX_STATE_POWERSAVE;
123+
}
124+
125+
static bool is_waking_up(struct modem_cmux *cmux)
126+
{
127+
return cmux->state == MODEM_CMUX_STATE_WAKEUP;
128+
}
129+
130+
static bool is_transitioning_to_powersave(struct modem_cmux *cmux)
131+
{
132+
return (cmux->state == MODEM_CMUX_STATE_ENTER_POWERSAVE ||
133+
cmux->state == MODEM_CMUX_STATE_CONFIRM_POWERSAVE);
134+
}
135+
121136
static bool modem_cmux_command_type_is_valid(const struct modem_cmux_command_type type)
122137
{
123138
/* 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,
581596

582597
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
583598

584-
if (cmux->flow_control_on == false) {
599+
if (cmux->flow_control_on == false || is_transitioning_to_powersave(cmux)) {
585600
k_mutex_unlock(&cmux->transmit_rb_lock);
586601
return 0;
587602
}
@@ -630,6 +645,34 @@ static void modem_cmux_acknowledge_received_frame(struct modem_cmux *cmux)
630645
}
631646
}
632647

648+
static void modem_cmux_send_psc(struct modem_cmux *cmux)
649+
{
650+
uint16_t len;
651+
uint8_t *data = modem_cmux_command_encode(&(struct modem_cmux_command){
652+
.type.ea = 1,
653+
.type.cr = 1,
654+
.type.value = MODEM_CMUX_COMMAND_PSC,
655+
.length.ea = 1,
656+
.length.value = 0,
657+
}, &len);
658+
659+
if (data == NULL) {
660+
return;
661+
}
662+
663+
struct modem_cmux_frame frame = {
664+
.dlci_address = 0,
665+
.cr = cmux->initiator,
666+
.pf = true,
667+
.type = MODEM_CMUX_FRAME_TYPE_UIH,
668+
.data = data,
669+
.data_len = len,
670+
};
671+
672+
LOG_DBG("Sending PSC command");
673+
modem_cmux_transmit_cmd_frame(cmux, &frame);
674+
}
675+
633676
static void modem_cmux_send_msc(struct modem_cmux *cmux, struct modem_cmux_dlci *dlci)
634677
{
635678
if (cmux == NULL || dlci == NULL) {
@@ -827,12 +870,28 @@ static void modem_cmux_respond_unsupported_cmd(struct modem_cmux *cmux)
827870
modem_cmux_transmit_cmd_frame(cmux, &frame);
828871
}
829872

873+
static void modem_cmux_on_psc_command(struct modem_cmux *cmux)
874+
{
875+
LOG_DBG("Received power saving command");
876+
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
877+
set_state(cmux, MODEM_CMUX_STATE_CONFIRM_POWERSAVE);
878+
modem_cmux_acknowledge_received_frame(cmux);
879+
k_mutex_unlock(&cmux->transmit_rb_lock);
880+
}
881+
882+
static void modem_cmux_on_psc_response(struct modem_cmux *cmux)
883+
{
884+
LOG_DBG("Enter power saving");
885+
k_mutex_lock(&cmux->transmit_rb_lock, K_FOREVER);
886+
set_state(cmux, MODEM_CMUX_STATE_POWERSAVE);
887+
k_mutex_unlock(&cmux->transmit_rb_lock);
888+
}
889+
830890
static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux)
831891
{
832892
struct modem_cmux_command command;
833893

834-
if ((cmux->state != MODEM_CMUX_STATE_CONNECTED) &&
835-
(cmux->state != MODEM_CMUX_STATE_DISCONNECTING)) {
894+
if (cmux->state < MODEM_CMUX_STATE_CONNECTED) {
836895
LOG_DBG("Unexpected UIH frame");
837896
return;
838897
}
@@ -851,6 +910,9 @@ static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux)
851910
case MODEM_CMUX_COMMAND_CLD:
852911
modem_cmux_on_cld_command(cmux, &command);
853912
break;
913+
case MODEM_CMUX_COMMAND_PSC:
914+
modem_cmux_on_psc_response(cmux);
915+
break;
854916
default:
855917
/* Responses to other commands are ignored */
856918
break;
@@ -875,6 +937,10 @@ static void modem_cmux_on_control_frame_uih(struct modem_cmux *cmux)
875937
modem_cmux_on_fcoff_command(cmux);
876938
break;
877939

940+
case MODEM_CMUX_COMMAND_PSC:
941+
modem_cmux_on_psc_command(cmux);
942+
break;
943+
878944
default:
879945
LOG_DBG("Unknown control command");
880946
modem_cmux_respond_unsupported_cmd(cmux);
@@ -1140,6 +1206,11 @@ static void modem_cmux_on_frame(struct modem_cmux *cmux)
11401206
modem_cmux_advertise_receive_buf_stats(cmux);
11411207
#endif
11421208

1209+
if (is_powersaving(cmux) || is_waking_up(cmux)) {
1210+
set_state(cmux, MODEM_CMUX_STATE_CONNECTED);
1211+
LOG_DBG("Exit powersave on received frame");
1212+
}
1213+
11431214
if (cmux->frame.dlci_address == 0) {
11441215
modem_cmux_on_control_frame(cmux);
11451216
} else {
@@ -1170,8 +1241,10 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by
11701241

11711242
switch (cmux->receive_state) {
11721243
case MODEM_CMUX_RECEIVE_STATE_SOF:
1244+
cmux->frame_header_len = 0;
11731245
if (byte == MODEM_CMUX_SOF) {
11741246
cmux->receive_state = MODEM_CMUX_RECEIVE_STATE_RESYNC;
1247+
cmux->frame_header_len++;
11751248
break;
11761249
}
11771250

@@ -1183,6 +1256,20 @@ static void modem_cmux_process_received_byte(struct modem_cmux *cmux, uint8_t by
11831256
* 0xF9 could also be a valid address field for DLCI 62.
11841257
*/
11851258
if (byte == MODEM_CMUX_SOF) {
1259+
/* Use "header_len" to count SOF bytes, only start transmitting
1260+
* flag bytes after receiving more than 3 flags.
1261+
* Don't reply flags if we are transitioning between modes or
1262+
* if T3 timer is still active (suppress residual flags).
1263+
*/
1264+
cmux->frame_header_len++;
1265+
if ((is_powersaving(cmux) ||
1266+
(is_connected(cmux) && sys_timepoint_expired(cmux->t3_timepoint))) &&
1267+
cmux->frame_header_len > 3) {
1268+
modem_cmux_tx_bypass(cmux, &(char){MODEM_CMUX_SOF}, 1);
1269+
}
1270+
if (is_waking_up(cmux)) {
1271+
k_work_reschedule(&cmux->transmit_work, K_NO_WAIT);
1272+
}
11861273
break;
11871274
}
11881275

@@ -1351,21 +1438,15 @@ static void modem_cmux_receive_handler(struct k_work *item)
13511438
int ret;
13521439

13531440
/* Receive data from pipe */
1354-
ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf));
1355-
if (ret < 1) {
1356-
if (ret < 0) {
1357-
LOG_ERR("Pipe receiving error: %d", ret);
1441+
while ((ret = modem_pipe_receive(cmux->pipe, cmux->work_buf, sizeof(cmux->work_buf))) > 0) {
1442+
/* Process received data */
1443+
for (int i = 0; i < ret; i++) {
1444+
modem_cmux_process_received_byte(cmux, cmux->work_buf[i]);
13581445
}
1359-
return;
13601446
}
1361-
1362-
/* Process received data */
1363-
for (int i = 0; i < ret; i++) {
1364-
modem_cmux_process_received_byte(cmux, cmux->work_buf[i]);
1447+
if (ret < 0) {
1448+
LOG_ERR("Pipe receiving error: %d", ret);
13651449
}
1366-
1367-
/* Reschedule received work */
1368-
modem_work_schedule(&cmux->receive_work, K_NO_WAIT);
13691450
}
13701451

13711452
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)
13811462
}
13821463
}
13831464

1465+
/** Transmit bytes bypassing the CMUX buffers.
1466+
* Causes modem_cmux_transmit_handler() to be rescheduled as a result of TRANSMIT_IDLE event.
1467+
*/
1468+
static void modem_cmux_tx_bypass(struct modem_cmux *cmux, const uint8_t *data, size_t len)
1469+
{
1470+
if (cmux == NULL) {
1471+
return;
1472+
}
1473+
1474+
modem_pipe_transmit(cmux->pipe, data, len);
1475+
}
1476+
1477+
static bool powersave_wait_wakeup(struct modem_cmux *cmux)
1478+
{
1479+
static const uint8_t wakeup_pattern[] = {MODEM_CMUX_SOF, MODEM_CMUX_SOF, MODEM_CMUX_SOF,
1480+
MODEM_CMUX_SOF, MODEM_CMUX_SOF};
1481+
int ret;
1482+
1483+
if (is_powersaving(cmux)) {
1484+
LOG_DBG("Power saving mode, wake up first");
1485+
set_state(cmux, MODEM_CMUX_STATE_WAKEUP);
1486+
cmux->t3_timepoint = sys_timepoint_calc(MODEM_CMUX_T3_TIMEOUT);
1487+
modem_cmux_tx_bypass(cmux, wakeup_pattern, sizeof(wakeup_pattern));
1488+
return true;
1489+
}
1490+
1491+
if (is_waking_up(cmux)) {
1492+
if (sys_timepoint_expired(cmux->t3_timepoint)) {
1493+
LOG_ERR("Wake up timed out, link dead");
1494+
set_state(cmux, MODEM_CMUX_STATE_DISCONNECTED);
1495+
modem_cmux_raise_event(cmux, MODEM_CMUX_EVENT_DISCONNECTED);
1496+
return true;
1497+
}
1498+
if (cmux->receive_state != MODEM_CMUX_RECEIVE_STATE_RESYNC) {
1499+
/* Retry single flag, until remote wakes up */
1500+
modem_cmux_tx_bypass(cmux, &(uint8_t){MODEM_CMUX_SOF}, 1);
1501+
return true;
1502+
}
1503+
set_state(cmux, MODEM_CMUX_STATE_CONNECTED);
1504+
LOG_DBG("Woke up from power saving mode");
1505+
}
1506+
1507+
return false;
1508+
}
1509+
13841510
static void modem_cmux_transmit_handler(struct k_work *item)
13851511
{
13861512
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)
14031529
break;
14041530
}
14051531

1532+
if (powersave_wait_wakeup(cmux)) {
1533+
k_mutex_unlock(&cmux->transmit_rb_lock);
1534+
return;
1535+
}
1536+
14061537
reserved_size = ring_buf_get_claim(&cmux->transmit_rb, &reserved, UINT32_MAX);
14071538

14081539
ret = modem_pipe_transmit(cmux->pipe, reserved, reserved_size);
@@ -1423,11 +1554,14 @@ static void modem_cmux_transmit_handler(struct k_work *item)
14231554
}
14241555
}
14251556

1426-
k_mutex_unlock(&cmux->transmit_rb_lock);
1427-
14281557
if (transmit_rb_empty) {
1558+
if (cmux->state == MODEM_CMUX_STATE_CONFIRM_POWERSAVE) {
1559+
set_state(cmux, MODEM_CMUX_STATE_POWERSAVE);
1560+
LOG_DBG("Entered power saving mode");
1561+
}
14291562
modem_cmux_dlci_notify_transmit_idle(cmux);
14301563
}
1564+
k_mutex_unlock(&cmux->transmit_rb_lock);
14311565
}
14321566

14331567
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
17241858
cmux->user_data = config->user_data;
17251859
cmux->receive_buf = config->receive_buf;
17261860
cmux->receive_buf_size = config->receive_buf_size;
1861+
cmux->t3_timepoint = sys_timepoint_calc(K_NO_WAIT);
17271862
sys_slist_init(&cmux->dlcis);
17281863
ring_buf_init(&cmux->transmit_rb, config->transmit_buf_size, config->transmit_buf);
17291864
k_mutex_init(&cmux->transmit_rb_lock);

0 commit comments

Comments
 (0)