@@ -22,9 +22,16 @@ use crate::serial::{BufferedSerial, serial_bridge};
2222
2323#[ cfg( feature = "can" ) ]
2424use crate :: can:: can_bridge;
25+ #[ cfg( feature = "i2c" ) ]
26+ use crate :: i2c:: i2c_bridge;
2527
26- #[ cfg( feature = "can" ) ]
28+ #[ cfg( all(
29+ any( feature = "can" , feature = "i2c" ) ,
30+ not( all( feature = "can" , feature = "i2c" ) )
31+ ) ) ]
2732use embassy_futures:: select:: { Either , select} ;
33+ #[ cfg( all( feature = "can" , feature = "i2c" ) ) ]
34+ use embassy_futures:: select:: { Either3 , select3} ;
2835use embassy_sync:: blocking_mutex:: raw:: NoopRawMutex ;
2936use embassy_sync:: channel:: Channel ;
3037
@@ -155,22 +162,50 @@ pub enum SessionType {
155162 Sftp ( ChanHandle ) ,
156163}
157164
165+ /// Hand-off channels from the connection loop to the per-subsystem bridge
166+ /// futures in [`ssh_client`]. One slot per optional bridge subsystem;
167+ /// empty when no bridge feature is enabled.
168+ pub struct SessionQueues {
169+ #[ cfg( feature = "can" ) ]
170+ pub can : Channel < NoopRawMutex , ChanHandle , 1 > ,
171+ #[ cfg( feature = "i2c" ) ]
172+ pub i2c : Channel < NoopRawMutex , ChanHandle , 1 > ,
173+ }
174+
175+ impl SessionQueues {
176+ #[ must_use]
177+ pub fn new ( ) -> Self {
178+ SessionQueues {
179+ #[ cfg( feature = "can" ) ]
180+ can : Channel :: new ( ) ,
181+ #[ cfg( feature = "i2c" ) ]
182+ i2c : Channel :: new ( ) ,
183+ }
184+ }
185+ }
186+
187+ impl Default for SessionQueues {
188+ fn default ( ) -> Self {
189+ Self :: new ( )
190+ }
191+ }
192+
158193pub struct EventContext < ' a > {
159194 pub session : & ' a mut Option < ChanHandle > ,
160195 pub auth_checked : & ' a mut bool ,
161196 pub config_changed : & ' a mut bool ,
162197 pub needs_reset : & ' a mut bool ,
163- /// Hands accepted `can` subsystem channels to the CAN bridge, which
164- /// runs concurrently with the shell (UART) session.
165- # [ cfg ( feature = "can" ) ]
166- pub can_queue : & ' a Channel < NoopRawMutex , ChanHandle , 1 > ,
167- /// Set once a CAN session is dispatched on this connection. SFTP (OTA)
168- /// needs the connection's full bandwidth, so it is refused afterwards.
169- #[ cfg( all( feature = "sftp-ota" , feature = "can" ) ) ]
170- pub can_dispatched : & ' a mut bool ,
198+ /// Hands accepted bridge subsystem channels ( CAN, I2C) to their
199+ /// bridges, which run concurrently with the shell (UART) session.
200+ pub queues : & ' a SessionQueues ,
201+ /// Set once a bridge session is dispatched on this connection. SFTP
202+ /// (OTA) needs the connection's full bandwidth, so it is refused
203+ /// afterwards.
204+ #[ cfg( all( feature = "sftp-ota" , any ( feature = "can" , feature = "i2c" ) ) ) ]
205+ pub bridge_dispatched : & ' a mut bool ,
171206}
172207
173- /// Handles SSH session subsystem requests (e.g., SFTP, CAN).
208+ /// Handles SSH session subsystem requests (e.g., SFTP, CAN, I2C ).
174209///
175210/// # Errors
176211///
@@ -190,13 +225,13 @@ pub fn session_subsystem(
190225 #[ cfg( feature = "sftp-ota" ) ]
191226 {
192227 // SFTP (OTA) is exclusive: it needs the connection's full
193- // bandwidth, so refuse it once a CAN session is active.
194- #[ cfg( feature = "can" ) ]
195- let can_active = * ctx. can_dispatched ;
196- #[ cfg( not( feature = "can" ) ) ]
197- let can_active = false ;
198- if can_active {
199- warn ! ( "SFTP subsystem refused: a CAN session is active on this connection" ) ;
228+ // bandwidth, so refuse it once a bridge session is active.
229+ #[ cfg( any ( feature = "can" , feature = "i2c" ) ) ]
230+ let bridge_active = * ctx. bridge_dispatched ;
231+ #[ cfg( not( any ( feature = "can" , feature = "i2c" ) ) ) ]
232+ let bridge_active = false ;
233+ if bridge_active {
234+ warn ! ( "SFTP subsystem refused: a bridge session is active on this connection" ) ;
200235 a. fail ( ) ?;
201236 } else if let Some ( ch) = ctx. session . take ( ) {
202237 debug_assert_eq ! ( ch. num( ) , a. channel( ) ) ;
@@ -224,12 +259,12 @@ pub fn session_subsystem(
224259 // auth_checked is deliberately left untouched so the same
225260 // (already authenticated) connection can still request a
226261 // shell session and bridge UART concurrently with CAN.
227- if let Err ( e) = ctx. can_queue . try_send ( ch) {
262+ if let Err ( e) = ctx. queues . can . try_send ( ch) {
228263 log:: error!( "Could not send the CAN channel: {e:?}" ) ;
229264 }
230265 #[ cfg( feature = "sftp-ota" ) ]
231266 {
232- * ctx. can_dispatched = true ;
267+ * ctx. bridge_dispatched = true ;
233268 }
234269 } else {
235270 a. fail ( ) ?;
@@ -239,6 +274,29 @@ pub fn session_subsystem(
239274 warn ! ( "CAN subsystem requested but not supported in this build" ) ;
240275 a. fail ( ) ?;
241276 }
277+ } else if a. command ( ) ?. to_lowercase ( ) . as_str ( ) == "i2c" {
278+ #[ cfg( feature = "i2c" ) ]
279+ if let Some ( ch) = ctx. session . take ( ) {
280+ debug_assert_eq ! ( ch. num( ) , a. channel( ) ) ;
281+ a. succeed ( ) ?;
282+ debug ! ( "We got I2C subsystem" ) ;
283+ // As with CAN, auth_checked is left untouched so shell and
284+ // other bridge sessions can still be requested afterwards.
285+ if let Err ( e) = ctx. queues . i2c . try_send ( ch) {
286+ log:: error!( "Could not send the I2C channel: {e:?}" ) ;
287+ }
288+ #[ cfg( feature = "sftp-ota" ) ]
289+ {
290+ * ctx. bridge_dispatched = true ;
291+ }
292+ } else {
293+ a. fail ( ) ?;
294+ }
295+ #[ cfg( not( feature = "i2c" ) ) ]
296+ {
297+ warn ! ( "I2C subsystem requested but not supported in this build" ) ;
298+ a. fail ( ) ?;
299+ }
242300 } else {
243301 a. fail ( ) ?;
244302 }
@@ -749,10 +807,10 @@ pub fn defunct() -> Result<(), sunset::Error> {
749807/// Handles an SSH client connection, bridging UART and SSH.
750808///
751809#[ cfg_attr(
752- feature = "can" ,
753- doc = "A `can` subsystem channel is bridged concurrently with the shell " ,
754- doc = "(UART) session on the same connection. The whole connection is " ,
755- doc = "torn down when either bridge finishes." ,
810+ any ( feature = "can" , feature = "i2c" ) ,
811+ doc = "Bridge subsystem channels (CAN, I2C) are bridged concurrently " ,
812+ doc = "with the shell (UART) session on the same connection. The whole" ,
813+ doc = "connection is torn down when any bridge finishes." ,
756814 doc = ""
757815) ]
758816/// # Errors
@@ -762,11 +820,12 @@ pub async fn ssh_client<'a, 'b, U, P>(
762820 ssh_server : & ' b SSHServer < ' a > ,
763821 chan_pipe : & ' b Channel < NoopRawMutex , SessionType , 1 > ,
764822 #[ cfg_attr(
765- not( any( feature = "sftp-ota" , feature = "can" ) ) ,
823+ not( any( feature = "sftp-ota" , feature = "can" , feature = "i2c" ) ) ,
766824 allow( unused_variables)
767825 ) ]
768826 platform : & ' b P ,
769- #[ cfg( feature = "can" ) ] can_queue : & ' b Channel < NoopRawMutex , ChanHandle , 1 > ,
827+ #[ cfg_attr( not( any( feature = "can" , feature = "i2c" ) ) , allow( unused_variables) ) ]
828+ queues : & ' b SessionQueues ,
770829) -> Result < ( ) , sunset:: Error >
771830where
772831 U : BufferedSerial ,
@@ -796,20 +855,38 @@ where
796855 } ;
797856
798857 #[ cfg( feature = "can" ) ]
799- let result = {
800- let can_session = async {
801- let ch = can_queue. receive ( ) . await ;
802- info ! ( "Handling CAN session" ) ;
803- let chan_io: ChanInOut < ' _ > = ssh_server. stdio ( ch) . await ?;
804- let ( stdin, stdout) = chan_io. split ( ) ;
805- info ! ( "Starting CAN bridge" ) ;
806- can_bridge ( stdin, stdout, platform. can ( ) ) . await
807- } ;
808- match select ( session, can_session) . await {
809- Either :: First ( r) | Either :: Second ( r) => r,
810- }
858+ let can_session = async {
859+ let ch = queues. can . receive ( ) . await ;
860+ info ! ( "Handling CAN session" ) ;
861+ let chan_io: ChanInOut < ' _ > = ssh_server. stdio ( ch) . await ?;
862+ let ( stdin, stdout) = chan_io. split ( ) ;
863+ info ! ( "Starting CAN bridge" ) ;
864+ can_bridge ( stdin, stdout, platform. can ( ) ) . await
865+ } ;
866+
867+ #[ cfg( feature = "i2c" ) ]
868+ let i2c_session = async {
869+ let ch = queues. i2c . receive ( ) . await ;
870+ info ! ( "Handling I2C session" ) ;
871+ let chan_io: ChanInOut < ' _ > = ssh_server. stdio ( ch) . await ?;
872+ let ( stdin, stdout) = chan_io. split ( ) ;
873+ info ! ( "Starting I2C bridge" ) ;
874+ i2c_bridge ( stdin, stdout, platform. i2c ( ) ) . await
875+ } ;
876+
877+ #[ cfg( all( feature = "can" , feature = "i2c" ) ) ]
878+ let result = match select3 ( session, can_session, i2c_session) . await {
879+ Either3 :: First ( r) | Either3 :: Second ( r) | Either3 :: Third ( r) => r,
880+ } ;
881+ #[ cfg( all( feature = "can" , not( feature = "i2c" ) ) ) ]
882+ let result = match select ( session, can_session) . await {
883+ Either :: First ( r) | Either :: Second ( r) => r,
884+ } ;
885+ #[ cfg( all( feature = "i2c" , not( feature = "can" ) ) ) ]
886+ let result = match select ( session, i2c_session) . await {
887+ Either :: First ( r) | Either :: Second ( r) => r,
811888 } ;
812- #[ cfg( not( feature = "can" ) ) ]
889+ #[ cfg( not( any ( feature = "can" , feature = "i2c" ) ) ) ]
813890 let result = session. await ;
814891 result
815892}
0 commit comments