1 // Copyright 2024 The ChromiumOS Authors 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 use std::sync::Weak; 6 use std::time::Instant; 7 8 use base::error; 9 use base::Clock; 10 use sync::Mutex; 11 use usb_util::TransferBuffer; 12 use usb_util::TransferStatus; 13 14 use crate::usb::backend::error::Error as BackendError; 15 use crate::usb::backend::error::Result as BackendResult; 16 use crate::usb::backend::fido_backend::constants::USB_TRANSFER_TIMEOUT_MILLIS; 17 use crate::usb::backend::transfer::BackendTransfer; 18 use crate::usb::backend::transfer::BackendTransferType; 19 use crate::usb::backend::transfer::GenericTransferHandle; 20 21 /// Implementation of a generic USB transfer for the FIDO backend. It implements common USB 22 /// transfer functionality since it cannot rely on the transfer structures provided by the 23 /// usb_utils crate as the FIDO backend does not use usbdevfs to communicate with the host. 24 pub struct FidoTransfer { 25 /// TransferBuffer structure with either a request or response data from the guest/host. 26 pub buffer: TransferBuffer, 27 /// Status of the transfer, used by the xhci layer for a successful completion. 28 status: TransferStatus, 29 /// Actual length of the transfer, as per USB specs. 30 pub actual_length: usize, 31 /// USB endpoint associated with this transfer. 32 pub endpoint: u8, 33 /// Timestamp of the transfer submission time. 34 submission_time: Instant, 35 /// Callback to be executed once the transfer has completed, to signal the xhci layer. 36 pub callback: Option<Box<dyn Fn(FidoTransfer) + Send + Sync>>, 37 } 38 39 impl FidoTransfer { new(endpoint: u8, buffer: TransferBuffer) -> FidoTransfer40 pub fn new(endpoint: u8, buffer: TransferBuffer) -> FidoTransfer { 41 let clock = Clock::new(); 42 FidoTransfer { 43 buffer, 44 status: TransferStatus::Error, // Default to error 45 actual_length: 0, 46 endpoint, 47 submission_time: clock.now(), 48 callback: None, 49 } 50 } 51 52 /// Called when the device is lost and we need to signal to the xhci layer that the transfer 53 /// cannot continue and the device should be detached. signal_device_lost(&mut self)54 pub fn signal_device_lost(&mut self) { 55 self.status = TransferStatus::NoDevice; 56 } 57 58 /// Checks if the current transfer should time out or not timeout_expired(&self) -> bool59 pub fn timeout_expired(&self) -> bool { 60 self.submission_time.elapsed().as_millis() >= USB_TRANSFER_TIMEOUT_MILLIS.into() 61 } 62 63 /// Finalizes the transfer by setting the right status and then calling the callback to signal 64 /// the xhci layer. complete_transfer(mut self)65 pub fn complete_transfer(mut self) { 66 // The default status is "Error". Unless it was explicitly set to Cancel or NoDevice, 67 // we can just transition it to Completed instead. 68 if self.status == TransferStatus::Error { 69 self.status = TransferStatus::Completed; 70 } 71 72 if let Some(cb) = self.callback.take() { 73 cb(self); 74 } 75 } 76 } 77 78 impl BackendTransfer for FidoTransfer { status(&self) -> TransferStatus79 fn status(&self) -> TransferStatus { 80 self.status 81 } 82 actual_length(&self) -> usize83 fn actual_length(&self) -> usize { 84 self.actual_length 85 } 86 buffer(&self) -> &TransferBuffer87 fn buffer(&self) -> &TransferBuffer { 88 &self.buffer 89 } 90 set_callback<C: 'static + Fn(BackendTransferType) + Send + Sync>(&mut self, cb: C)91 fn set_callback<C: 'static + Fn(BackendTransferType) + Send + Sync>(&mut self, cb: C) { 92 let callback = move |t: FidoTransfer| cb(BackendTransferType::FidoDevice(t)); 93 self.callback = Some(Box::new(callback)); 94 } 95 } 96 97 /// Implementation of a cancel handler for `FidoTransfer` 98 pub struct FidoTransferHandle { 99 pub weak_transfer: Weak<Mutex<Option<FidoTransfer>>>, 100 } 101 102 impl GenericTransferHandle for FidoTransferHandle { cancel(&self) -> BackendResult<()>103 fn cancel(&self) -> BackendResult<()> { 104 let rc_transfer = match self.weak_transfer.upgrade() { 105 None => { 106 return Err(BackendError::TransferHandleAlreadyComplete); 107 } 108 Some(rc_transfer) => rc_transfer, 109 }; 110 111 let mut lock = rc_transfer.lock(); 112 113 let mut transfer = match lock.take() { 114 Some(t) => t, 115 None => { 116 error!("Transfer has already been lost while being cancelled. Ignore"); 117 return Err(BackendError::TransferHandleAlreadyComplete); 118 } 119 }; 120 transfer.status = TransferStatus::Cancelled; 121 *lock = Some(transfer); 122 Ok(()) 123 } 124 } 125