@@ -191,12 +191,14 @@ impl UsbIpCommand {
191191#[ derive( Debug , Clone , Copy ) ]
192192enum UsbIpCmdType {
193193 CmdSubmit = 0x00000001 ,
194+ CmdUnlink = 0x00000002 ,
194195}
195196
196197impl UsbIpCmdType {
197198 fn name ( self ) -> & ' static str {
198199 match self {
199200 Self :: CmdSubmit => "USBIP_CMD_SUBMIT" ,
201+ Self :: CmdUnlink => "USBIP_CMD_UNLINK" ,
200202 }
201203 }
202204}
@@ -328,6 +330,34 @@ impl UsbCommandSubmit {
328330 }
329331}
330332
333+ #[ derive( Debug , Clone ) ]
334+ pub struct UsbCommandUnlink {
335+ header : UsbCommandHeader ,
336+ seqnum_to_unlink : u32 ,
337+ }
338+
339+ impl UsbCommandUnlink {
340+ pub fn new ( header : UsbCommandHeader , seqnum_to_unlink : u32 ) -> Self {
341+ Self {
342+ header,
343+ seqnum_to_unlink,
344+ }
345+ }
346+
347+ pub fn to_bytes ( & self ) -> BytesMut {
348+ let mut buf = BytesMut :: with_capacity ( USB_COMMAND_HEADER_SIZE + 24 ) ;
349+ buf. put ( & self . header . to_bytes ( ) [ ..] ) ;
350+ buf. put_u32 ( self . seqnum_to_unlink ) ;
351+ // Padding to match USBIP_CMD_UNLINK structure (24 bytes after header)
352+ buf. put_u32 ( 0 ) ; // padding
353+ buf. put_u32 ( 0 ) ; // padding
354+ buf. put_u32 ( 0 ) ; // padding
355+ buf. put_u32 ( 0 ) ; // padding
356+ buf. put_u32 ( 0 ) ; // padding
357+ buf
358+ }
359+ }
360+
331361pub struct UsbReturnSubmit {
332362 header : UsbCommandHeader ,
333363 status : u32 ,
@@ -519,6 +549,58 @@ impl UsbIpClient {
519549 Ok ( response)
520550 }
521551
552+ /// Send an unlink command to cancel a pending transaction
553+ pub async fn cmd_unlink ( & mut self , seqnum_to_unlink : u32 ) -> UsbIpResult < ( ) > {
554+ self . seqnum = self . seqnum . wrapping_add ( 1 ) ;
555+
556+ let device = self . get_imported_device ( ) ?;
557+ let header = UsbCommandHeader :: new (
558+ UsbIpCmdType :: CmdUnlink as u32 ,
559+ self . seqnum ,
560+ ( device. busnum << 16 ) | device. devnum ,
561+ 0 , // direction doesn't matter for unlink
562+ 0 , // endpoint doesn't matter for unlink
563+ ) ;
564+
565+ let cmd = UsbCommandUnlink :: new ( header, seqnum_to_unlink) ;
566+ let stream = self . get_stream_mut ( ) ?;
567+ stream. write_all ( & cmd. to_bytes ( ) ) . await ?;
568+ debug ! (
569+ "Sent {} request for seqnum {}" ,
570+ UsbIpCmdType :: CmdUnlink . name( ) ,
571+ seqnum_to_unlink
572+ ) ;
573+
574+ Ok ( ( ) )
575+ }
576+
577+ /// Unlink all pending transactions - call this before shutdown
578+ pub async fn unlink_all_pending ( & mut self ) -> UsbIpResult < ( ) > {
579+ let pending_seqnums: Vec < u32 > = {
580+ let pending = self . pending . lock ( ) . await ;
581+ pending. keys ( ) . copied ( ) . collect ( )
582+ } ;
583+
584+ if pending_seqnums. is_empty ( ) {
585+ debug ! ( "No pending transactions to unlink" ) ;
586+ return Ok ( ( ) ) ;
587+ }
588+
589+ debug ! (
590+ "Unlinking {} pending transactions: {:?}" ,
591+ pending_seqnums. len( ) ,
592+ pending_seqnums
593+ ) ;
594+
595+ for seqnum in pending_seqnums {
596+ if let Err ( e) = self . cmd_unlink ( seqnum) . await {
597+ debug ! ( "Failed to unlink seqnum {}: {}" , seqnum, e) ;
598+ }
599+ }
600+
601+ Ok ( ( ) )
602+ }
603+
522604 pub async fn poll ( & mut self ) -> UsbIpResult < ( ) > {
523605 let stream = match self . stream . as_mut ( ) {
524606 Some ( s) => s,
@@ -536,6 +618,19 @@ impl UsbIpClient {
536618 let header = UsbCommandHeader :: from_stream ( stream) . await ?;
537619 let seqnum = header. seqnum ( ) ;
538620
621+ // Handle RET_UNLINK (0x04) separately - just consume the response
622+ if header. command == 0x04 {
623+ // RET_UNLINK: read status (4 bytes) + padding (24 bytes)
624+ let status = stream. read_i32 ( ) . await ?;
625+ let mut padding = [ 0u8 ; 24 ] ;
626+ stream. read_exact ( & mut padding) . await ?;
627+ debug ! (
628+ "Received RET_UNLINK for seqnum {}, status: {}" ,
629+ seqnum, status
630+ ) ;
631+ return Ok ( ( ) ) ;
632+ }
633+
539634 let pending_request = self . pending . lock ( ) . await . remove ( & seqnum) ;
540635
541636 if let Some ( ( direction, tx) ) = pending_request {
0 commit comments