Path: blob/main/devices/src/usb/backend/fido_backend/poll_thread.rs
5394 views
// Copyright 2024 The ChromiumOS Authors1// Use of this source code is governed by a BSD-style license that can be2// found in the LICENSE file.34//! This file contains all functions and structs used to handle polling operations for the fido5//! backend device.67use std::collections::VecDeque;8use std::sync::Arc;9use std::time::Duration;1011use anyhow::Context;12use base::debug;13use base::error;14use base::AsRawDescriptor;15use base::Event;16use base::EventToken;17use base::RawDescriptor;18use base::Timer;19use base::TimerTrait;20use base::WaitContext;21use sync::Mutex;22use usb_util::TransferStatus;2324use crate::usb::backend::fido_backend::error::Error;25use crate::usb::backend::fido_backend::error::Result;26use crate::usb::backend::fido_backend::fido_device::FidoDevice;27use crate::usb::backend::fido_backend::transfer::FidoTransfer;28use crate::usb::backend::fido_backend::transfer::FidoTransferHandle;29use crate::usb::backend::transfer::BackendTransfer;30use crate::usb::backend::transfer::GenericTransferHandle;3132#[derive(EventToken)]33enum Token {34TransactionPollTimer,35TransferPollTimer,36PacketPollTimer,37Kill,38}3940/// PollTimer is a wrapper around the crosvm-provided `Timer` struct with a focus on maintaining a41/// regular interval with easy `arm()` and `clear()` methods to start and stop the timer42/// transparently from the interval.43pub struct PollTimer {44name: String,45timer: Timer,46interval: Duration,47}4849impl PollTimer {50pub fn new(name: String, interval: Duration) -> Result<Self> {51let timer = Timer::new().map_err(Error::CannotCreatePollTimer)?;52Ok(PollTimer {53name,54timer,55interval,56})57}5859/// Arms the timer with its initialized interval.60pub fn arm(&mut self) -> Result<()> {61self.timer62.reset_oneshot(self.interval)63.map_err(|error| Error::CannotArmPollTimer {64name: self.name.clone(),65error,66})67}6869/// Clears the timer, disarming it.70pub fn clear(&mut self) -> Result<()> {71self.timer72.clear()73.map_err(|error| Error::CannotClearPollTimer {74name: self.name.clone(),75error,76})77}78}7980impl AsRawDescriptor for PollTimer {81fn as_raw_descriptor(&self) -> RawDescriptor {82self.timer.as_raw_descriptor()83}84}8586/// This function is the main poll thread. It periodically wakes up to emulate a USB interrupt87/// (poll) device behavior. It takes care of three different poll timers:88/// - `PacketPollTimer`: periodically polls for available USB transfers waiting for data89/// - `TransferPollTimer`: times out USB transfers that stay pending for too long without data90/// - `TransactionPollTimer`: puts the security key device to sleep when transactions time out91pub fn poll_for_pending_packets(92device: Arc<Mutex<FidoDevice>>,93pending_in_transfers: Arc<94Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,95>,96kill_evt: Event,97) -> Result<()> {98let device_lock = device.lock();99let wait_ctx: WaitContext<Token> = WaitContext::build_with(&[100(&device_lock.guest_key.lock().timer, Token::PacketPollTimer),101(&device_lock.transfer_timer, Token::TransferPollTimer),102(103&device_lock.transaction_manager.lock().transaction_timer,104Token::TransactionPollTimer,105),106(&kill_evt, Token::Kill),107])108.context("poll worker context failed")109.map_err(Error::WaitContextFailed)?;110drop(device_lock);111112loop {113let events = wait_ctx114.wait()115.context("wait failed")116.map_err(Error::WaitContextFailed)?;117for event in events.iter().filter(|e| e.is_readable) {118match event.token {119// This timer checks that we have u2f host packets pending, waiting to be sent to120// the guest, and that we have a valid USB transfer from the guest waiting for121// data.122Token::PacketPollTimer => {123handle_packet_poll(&device, &pending_in_transfers)?;124// If there are still transfers waiting in the queue we continue polling.125if packet_timer_needs_rearm(&device, &pending_in_transfers) {126device.lock().guest_key.lock().timer.arm()?;127}128}129// This timer takes care of expiring USB transfers from the guest as they time out130// waiting for data from the host. It is the equivalent of a USB interrupt poll131// thread.132Token::TransferPollTimer => {133let mut transfers_lock = pending_in_transfers.lock();134135transfers_lock.retain(process_pending_transfer);136137// If the device has died, we need to tell the first pending transfer138// that the device has been lost at the xhci level, so we can safely detach the139// device from the guest.140if device.lock().is_device_lost {141let (_, transfer_opt) = match transfers_lock.pop_front() {142Some(tuple) => tuple,143None => {144// No pending transfers waiting for data, so we do nothing.145continue;146}147};148signal_device_lost(transfer_opt.lock().take());149return Ok(());150}151152// If we still have pending transfers waiting, we keep polling, otherwise we153// stop.154if !transfers_lock.is_empty() {155device.lock().transfer_timer.arm()?;156} else {157device.lock().transfer_timer.clear()?;158}159}160// This timer takes care of timing out u2f transactions that haven't seen any161// activity from either guest or host for a long-enough time.162Token::TransactionPollTimer => {163// If transactions aren't expired, re-arm164if !device165.lock()166.transaction_manager167.lock()168.expire_transactions()169{170device171.lock()172.transaction_manager173.lock()174.transaction_timer175.arm()?;176}177}178Token::Kill => {179debug!("Fido poll thread exited succesfully.");180return Ok(());181}182}183}184}185}186187/// Handles polling for available data to send back to the guest.188fn handle_packet_poll(189device: &Arc<Mutex<FidoDevice>>,190pending_in_transfers: &Arc<191Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,192>,193) -> Result<()> {194if device.lock().is_device_lost {195// Rather than erroring here, we just return Ok as the case of a device being lost is196// handled by the transfer timer.197return Ok(());198}199let mut transfers_lock = pending_in_transfers.lock();200201// Process and remove expired or cancelled transfers202transfers_lock.retain(process_pending_transfer);203204if transfers_lock.is_empty() {205// We cannot do anything, the active transfers got pruned.206// Return Ok() and let the poll thread handle the missing packets.207return Ok(());208}209210// Fetch first available transfer from the pending list and its fail handle.211let (_, transfer_opt) = match transfers_lock.pop_front() {212Some(tuple) => tuple,213None => {214// No pending transfers waiting for data, so we do nothing.215return Ok(());216}217};218drop(transfers_lock);219220let mut transfer_lock = transfer_opt.lock();221let transfer = transfer_lock.take();222223// Obtain the next packet from the guest key and send it to the guest224match device225.lock()226.guest_key227.lock()228.return_data_to_guest(transfer)?229{230None => {231// The transfer was successful, nothing to do.232Ok(())233}234transfer => {235// We received our transfer back, it means there's no data available to return to the236// guest.237*transfer_lock = transfer;238drop(transfer_lock);239let cancel_handle = FidoTransferHandle {240weak_transfer: Arc::downgrade(&transfer_opt),241};242243// Put the transfer back into the pending queue, we can try again later.244pending_in_transfers245.lock()246.push_front((cancel_handle, transfer_opt));247Ok(())248}249}250}251252/// Filter functions used to check for expired or canceled transfers. It is called over each253/// USB transfer waiting in the pending queue. Returns true if the given transfer is still valid,254/// otherwise false.255fn process_pending_transfer(256transfer_handle_pair: &(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>),257) -> bool {258let mut lock = transfer_handle_pair.1.lock();259let transfer = match lock.take() {260Some(t) => {261// The transfer has already been cancelled. We report back to the xhci level and remove262// it.263if t.status() == TransferStatus::Cancelled {264t.complete_transfer();265return false;266}267// The transfer has expired, we cancel it and report back to the xhci level.268if t.timeout_expired() {269if let Err(e) = transfer_handle_pair.0.cancel() {270error!("Failed to properly cancel IN transfer, dropping the request: {e:#}");271return false;272}273t.complete_transfer();274return false;275}276Some(t)277}278None => {279// Transfer has already been removed so we can skip it.280return false;281}282};283*lock = transfer;284285true286}287288/// Signals to the current transfer that the underlying device has been lost and the xhci layer289/// should recover by detaching the FIDO backend.290fn signal_device_lost(transfer_opt: Option<FidoTransfer>) {291if let Some(mut transfer) = transfer_opt {292transfer.signal_device_lost();293transfer.complete_transfer();294}295}296297/// Checks whether we should re-arm the packet poll timer or not.298fn packet_timer_needs_rearm(299device: &Arc<Mutex<FidoDevice>>,300pending_in_transfers: &Arc<301Mutex<VecDeque<(FidoTransferHandle, Arc<Mutex<Option<FidoTransfer>>>)>>,302>,303) -> bool {304let transfers_lock = pending_in_transfers.lock();305if transfers_lock.is_empty() {306// If there are no transfers pending, it means that some packet got stuck or lost,307// so we just reset the entire device state since no one is waiting for a308// response from the xhci level anyway.309device.lock().guest_key.lock().reset();310return false;311}312true313}314315316