Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
google
GitHub Repository: google/crosvm
Path: blob/main/devices/src/usb/backend/fido_backend/poll_thread.rs
5394 views
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 {
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
81
impl 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
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.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.
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.
256
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.
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.
299
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
316