devices/usb/backend/fido_backend/
transfer.rs

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
5use std::sync::Weak;
6use std::time::Instant;
7
8use base::error;
9use base::Clock;
10use sync::Mutex;
11use usb_util::TransferBuffer;
12use usb_util::TransferStatus;
13
14use crate::usb::backend::error::Error as BackendError;
15use crate::usb::backend::error::Result as BackendResult;
16use crate::usb::backend::fido_backend::constants::USB_TRANSFER_TIMEOUT_MILLIS;
17use crate::usb::backend::transfer::BackendTransfer;
18use crate::usb::backend::transfer::BackendTransferType;
19use 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.
24pub 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
39impl FidoTransfer {
40    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.
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
59    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.
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
78impl BackendTransfer for FidoTransfer {
79    fn status(&self) -> TransferStatus {
80        self.status
81    }
82
83    fn actual_length(&self) -> usize {
84        self.actual_length
85    }
86
87    fn buffer(&self) -> &TransferBuffer {
88        &self.buffer
89    }
90
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`
98pub struct FidoTransferHandle {
99    pub weak_transfer: Weak<Mutex<Option<FidoTransfer>>>,
100}
101
102impl GenericTransferHandle for FidoTransferHandle {
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}