xref: /aosp_15_r20/external/crosvm/devices/src/usb/backend/fido_backend/poll_thread.rs (revision bb4ee6a4ae7042d18b07a98463b9c8b875e44b39)
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 //! This file contains all functions and structs used to handle polling operations for the fido
6 //! backend device.
7 
8 use std::collections::VecDeque;
9 use std::sync::Arc;
10 use std::time::Duration;
11 
12 use anyhow::Context;
13 use base::debug;
14 use base::error;
15 use base::AsRawDescriptor;
16 use base::Event;
17 use base::EventToken;
18 use base::RawDescriptor;
19 use base::Timer;
20 use base::TimerTrait;
21 use base::WaitContext;
22 use sync::Mutex;
23 use usb_util::TransferStatus;
24 
25 use crate::usb::backend::fido_backend::error::Error;
26 use crate::usb::backend::fido_backend::error::Result;
27 use crate::usb::backend::fido_backend::fido_device::FidoDevice;
28 use crate::usb::backend::fido_backend::transfer::FidoTransfer;
29 use crate::usb::backend::fido_backend::transfer::FidoTransferHandle;
30 use crate::usb::backend::transfer::BackendTransfer;
31 use crate::usb::backend::transfer::GenericTransferHandle;
32 
33 #[derive(EventToken)]
34 enum Token {
35     TransactionPollTimer,
36     TransferPollTimer,
37     PacketPollTimer,
38     Kill,
39 }
40 
41 /// PollTimer is a wrapper around the crosvm-provided `Timer` struct with a focus on maintaining a
42 /// regular interval with easy `arm()` and `clear()` methods to start and stop the timer
43 /// transparently from the interval.
44 pub struct PollTimer {
45     name: String,
46     timer: Timer,
47     interval: Duration,
48 }
49 
50 impl PollTimer {
new(name: String, interval: Duration) -> Result<Self>51     pub fn new(name: String, interval: Duration) -> Result<Self> {
52         let timer = Timer::new().map_err(Error::CannotCreatePollTimer)?;
53         Ok(PollTimer {
54             name,
55             timer,
56             interval,
57         })
58     }
59 
60     /// Arms the timer with its initialized interval.
arm(&mut self) -> Result<()>61     pub fn arm(&mut self) -> Result<()> {
62         self.timer
63             .reset_oneshot(self.interval)
64             .map_err(|error| Error::CannotArmPollTimer {
65                 name: self.name.clone(),
66                 error,
67             })
68     }
69 
70     /// Clears the timer, disarming it.
clear(&mut self) -> Result<()>71     pub fn clear(&mut self) -> Result<()> {
72         self.timer
73             .clear()
74             .map_err(|error| Error::CannotClearPollTimer {
75                 name: self.name.clone(),
76                 error,
77             })
78     }
79 }
80 
81 impl AsRawDescriptor for PollTimer {
as_raw_descriptor(&self) -> RawDescriptor82     fn as_raw_descriptor(&self) -> RawDescriptor {
83         self.timer.as_raw_descriptor()
84     }
85 }
86 
87 /// This function is the main poll thread. It periodically wakes up to emulate a USB interrupt
88 /// (poll) device behavior. It takes care of three different poll timers:
89 /// - `PacketPollTimer`: periodically polls for available USB transfers waiting for data
90 /// - `TransferPollTimer`: times out USB transfers that stay pending for too long without data
91 /// - `TransactionPollTimer`: puts the security key device to sleep when transactions time out
poll_for_pending_packets( device: Arc<Mutex<FidoDevice>>, pending_in_transfers: Arc< Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>, >, kill_evt: Event, ) -> Result<()>92 pub fn poll_for_pending_packets(
93     device: Arc<Mutex<FidoDevice>>,
94     pending_in_transfers: Arc<
95         Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,
96     >,
97     kill_evt: Event,
98 ) -> Result<()> {
99     let device_lock = device.lock();
100     let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[
101         (&device_lock.guest_key.lock().timer, Token::PacketPollTimer),
102         (&device_lock.transfer_timer, Token::TransferPollTimer),
103         (
104             &device_lock.transaction_manager.lock().transaction_timer,
105             Token::TransactionPollTimer,
106         ),
107         (&kill_evt, Token::Kill),
108     ])
109     .context("poll worker context failed")
110     .map_err(Error::WaitContextFailed)?;
111     drop(device_lock);
112 
113     loop {
114         let events = wait_ctx
115             .wait()
116             .context("wait failed")
117             .map_err(Error::WaitContextFailed)?;
118         for event in events.iter().filter(|e| e.is_readable) {
119             match event.token {
120                 // This timer checks that we have u2f host packets pending, waiting to be sent to
121                 // the guest, and that we have a valid USB transfer from the guest waiting for
122                 // data.
123                 Token::PacketPollTimer => {
124                     handle_packet_poll(&device, &pending_in_transfers)?;
125                     // If there are still transfers waiting in the queue we continue polling.
126                     if packet_timer_needs_rearm(&device, &pending_in_transfers) {
127                         device.lock().guest_key.lock().timer.arm()?;
128                     }
129                 }
130                 // This timer takes care of expiring USB transfers from the guest as they time out
131                 // waiting for data from the host. It is the equivalent of a USB interrupt poll
132                 // thread.
133                 Token::TransferPollTimer => {
134                     let mut transfers_lock = pending_in_transfers.lock();
135 
136                     transfers_lock.retain(process_pending_transfer);
137 
138                     // If the device has died, we need to tell the first pending transfer
139                     // that the device has been lost at the xhci level, so we can safely detach the
140                     // device from the guest.
141                     if device.lock().is_device_lost {
142                         let (_, transfer_opt) = match transfers_lock.pop_front() {
143                             Some(tuple) => tuple,
144                             None => {
145                                 // No pending transfers waiting for data, so we do nothing.
146                                 continue;
147                             }
148                         };
149                         signal_device_lost(transfer_opt.lock().take());
150                         return Ok(());
151                     }
152 
153                     // If we still have pending transfers waiting, we keep polling, otherwise we
154                     // stop.
155                     if transfers_lock.len() > 0 {
156                         device.lock().transfer_timer.arm()?;
157                     } else {
158                         device.lock().transfer_timer.clear()?;
159                     }
160                 }
161                 // This timer takes care of timing out u2f transactions that haven't seen any
162                 // activity from either guest or host for a long-enough time.
163                 Token::TransactionPollTimer => {
164                     // If transactions aren't expired, re-arm
165                     if !device
166                         .lock()
167                         .transaction_manager
168                         .lock()
169                         .expire_transactions()
170                     {
171                         device
172                             .lock()
173                             .transaction_manager
174                             .lock()
175                             .transaction_timer
176                             .arm()?;
177                     }
178                 }
179                 Token::Kill => {
180                     debug!("Fido poll thread exited succesfully.");
181                     return Ok(());
182                 }
183             }
184         }
185     }
186 }
187 
188 /// Handles polling for available data to send back to the guest.
handle_packet_poll( device: &Arc<Mutex<FidoDevice>>, pending_in_transfers: &Arc< Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>, >, ) -> Result<()>189 fn handle_packet_poll(
190     device: &Arc<Mutex<FidoDevice>>,
191     pending_in_transfers: &Arc<
192         Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,
193     >,
194 ) -> Result<()> {
195     if device.lock().is_device_lost {
196         // Rather than erroring here, we just return Ok as the case of a device being lost is
197         // handled by the transfer timer.
198         return Ok(());
199     }
200     let mut transfers_lock = pending_in_transfers.lock();
201 
202     // Process and remove expired or cancelled transfers
203     transfers_lock.retain(process_pending_transfer);
204 
205     if transfers_lock.is_empty() {
206         // We cannot do anything, the active transfers got pruned.
207         // Return Ok() and let the poll thread handle the missing packets.
208         return Ok(());
209     }
210 
211     // Fetch first available transfer from the pending list and its fail handle.
212     let (_, transfer_opt) = match transfers_lock.pop_front() {
213         Some(tuple) => tuple,
214         None => {
215             // No pending transfers waiting for data, so we do nothing.
216             return Ok(());
217         }
218     };
219     drop(transfers_lock);
220 
221     let mut transfer_lock = transfer_opt.lock();
222     let transfer = transfer_lock.take();
223 
224     // Obtain the next packet from the guest key and send it to the guest
225     match device
226         .lock()
227         .guest_key
228         .lock()
229         .return_data_to_guest(transfer)?
230     {
231         None => {
232             // The transfer was successful, nothing to do.
233             Ok(())
234         }
235         transfer => {
236             // We received our transfer back, it means there's no data available to return to the
237             // guest.
238             *transfer_lock = transfer;
239             drop(transfer_lock);
240             let cancel_handle = FidoTransferHandle {
241                 weak_transfer: Arc::downgrade(&transfer_opt),
242             };
243 
244             // Put the transfer back into the pending queue, we can try again later.
245             pending_in_transfers
246                 .lock()
247                 .push_front((cancel_handle, transfer_opt));
248             Ok(())
249         }
250     }
251 }
252 
253 /// Filter functions used to check for expired or canceled transfers. It is called over each
254 /// USB transfer waiting in the pending queue. Returns true if the given transfer is still valid,
255 /// otherwise false.
process_pending_transfer( transfer_handle_pair: &(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>), ) -> bool256 fn process_pending_transfer(
257     transfer_handle_pair: &(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>),
258 ) -> bool {
259     let mut lock = transfer_handle_pair.1.lock();
260     let transfer = match lock.take() {
261         Some(t) => {
262             // The transfer has already been cancelled. We report back to the xhci level and remove
263             // it.
264             if t.status() == TransferStatus::Cancelled {
265                 t.complete_transfer();
266                 return false;
267             }
268             // The transfer has expired, we cancel it and report back to the xhci level.
269             if t.timeout_expired() {
270                 if let Err(e) = transfer_handle_pair.0.cancel() {
271                     error!("Failed to properly cancel IN transfer, dropping the request: {e:#}");
272                     return false;
273                 }
274                 t.complete_transfer();
275                 return false;
276             }
277             Some(t)
278         }
279         None => {
280             // Transfer has already been removed so we can skip it.
281             return false;
282         }
283     };
284     *lock = transfer;
285 
286     true
287 }
288 
289 /// Signals to the current transfer that the underlying device has been lost and the xhci layer
290 /// should recover by detaching the FIDO backend.
signal_device_lost(transfer_opt: Option<FidoTransfer>)291 fn signal_device_lost(transfer_opt: Option<FidoTransfer>) {
292     if let Some(mut transfer) = transfer_opt {
293         transfer.signal_device_lost();
294         transfer.complete_transfer();
295     }
296 }
297 
298 /// Checks whether we should re-arm the packet poll timer or not.
packet_timer_needs_rearm( device: &Arc<Mutex<FidoDevice>>, pending_in_transfers: &Arc< Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>, >, ) -> bool299 fn packet_timer_needs_rearm(
300     device: &Arc<Mutex<FidoDevice>>,
301     pending_in_transfers: &Arc<
302         Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,
303     >,
304 ) -> bool {
305     let transfers_lock = pending_in_transfers.lock();
306     if transfers_lock.is_empty() {
307         // If there are no transfers pending, it means that some packet got stuck or lost,
308         // so we just reset the entire device state since no one is waiting for a
309         // response from the xhci level anyway.
310         device.lock().guest_key.lock().reset();
311         return false;
312     }
313     true
314 }
315