@@ -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
4442enum modem_cmux_frame_types {
4543 MODEM_CMUX_FRAME_TYPE_RR = 0x01 ,
@@ -101,6 +99,7 @@ struct modem_cmux_msc_addr {
10199
102100static struct modem_cmux_dlci * modem_cmux_find_dlci (struct modem_cmux * cmux , uint8_t dlci_address );
103101static 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
105104static 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+
121136static 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+
633676static 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+
830890static 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
13711452static 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+
13841510static 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
14331567static 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