devices/usb/backend/fido_backend/
poll_thread.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
5//! This file contains all functions and structs used to handle polling operations for the fido
6//! backend device.
7
8use std::collections::VecDeque;
9use std::sync::Arc;
10use std::time::Duration;
11
12use anyhow::Context;
13use base::debug;
14use base::error;
15use base::AsRawDescriptor;
16use base::Event;
17use base::EventToken;
18use base::RawDescriptor;
19use base::Timer;
20use base::TimerTrait;
21use base::WaitContext;
22use sync::Mutex;
23use usb_util::TransferStatus;
24
25use crate::usb::backend::fido_backend::error::Error;
26use crate::usb::backend::fido_backend::error::Result;
27use crate::usb::backend::fido_backend::fido_device::FidoDevice;
28use crate::usb::backend::fido_backend::transfer::FidoTransfer;
29use crate::usb::backend::fido_backend::transfer::FidoTransferHandle;
30use crate::usb::backend::transfer::BackendTransfer;
31use crate::usb::backend::transfer::GenericTransferHandle;
32
33#[derive(EventToken)]
34enum 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.
44pub struct PollTimer {
45    name: String,
46    timer: Timer,
47    interval: Duration,
48}
49
50impl PollTimer {
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.
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.
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
81impl AsRawDescriptor for PollTimer {
82    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
92pub 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.is_empty() {
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.
189fn 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.
256fn 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.
291fn 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.
299fn 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}