blob: 88d3decf2316de9dfb1ae322ecd469969992efb4 [file]
// Copyright 2026 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use fidl::endpoints::{SynchronousProxy, create_endpoints};
use fidl_fuchsia_hardware_usb as fhardware_usb;
use fidl_fuchsia_hardware_usb_descriptor as fusb_descriptor;
use fidl_fuchsia_hardware_usb_endpoint as fusb_endpoint;
use fidl_fuchsia_hardware_usb_request as fusb_request;
use starnix_core::mm::{MemoryAccessor, MemoryAccessorExt, TaskMemoryAccessor};
use starnix_core::task::dynamic_thread_spawner::SpawnRequestBuilder;
use starnix_core::task::{
CurrentTask, EventHandler, SignalHandler, SignalHandlerInner, TaskFlags, ThreadLockupDetector,
WaitCanceler, WaitQueue, Waiter, WaiterOptions,
};
use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
use starnix_core::vfs::{FileObject, FileOps, fileops_impl_noop_sync, fileops_impl_seekable};
use starnix_logging::track_stub;
use starnix_sync::{
LockDepMutex, UsbfsControlSlotLock, UsbfsDeviceStateLock, UsbfsEndpointStateLock,
UsbfsEndpointTransferLock, UsbfsEndpointsLock, UsbfsFileUrbsLock, UsbfsOpenFilesLock,
};
use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
use starnix_uapi::errors::{ECONNRESET, EINTR, ENODEV, ENOENT, ESHUTDOWN, ETIMEDOUT, Errno};
use starnix_uapi::signals::SigSet;
use starnix_uapi::user_address::{ArchSpecific, MultiArchUserRef, UserAddress, UserRef};
use starnix_uapi::vfs::FdEvents;
use starnix_uapi::{
_IOC_DIRSHIFT, _IOC_SIZEMASK, _IOC_SIZESHIFT, _IOC_WRITE, USB_CLASS_PRINTER, USB_DIR_IN,
USB_DIR_OUT, USB_DT_CONFIG, USB_DT_ENDPOINT, USB_DT_ENDPOINT_SIZE, USB_DT_INTERFACE,
USB_DT_INTERFACE_ASSOCIATION, USB_DT_SS_ENDPOINT_COMP, USB_ENDPOINT_HALT,
USB_ENDPOINT_MAXP_MASK, USB_ENDPOINT_NUMBER_MASK, USB_ENDPOINT_XFER_BULK,
USB_ENDPOINT_XFER_CONTROL, USB_ENDPOINT_XFER_INT, USB_ENDPOINT_XFER_ISOC,
USB_ENDPOINT_XFERTYPE_MASK, USB_RECIP_ENDPOINT, USB_RECIP_INTERFACE, USB_RECIP_MASK,
USB_REQ_CLEAR_FEATURE, USB_TYPE_CLASS, USB_TYPE_MASK, USB_TYPE_STANDARD, USB_TYPE_VENDOR,
USBDEVFS_BULK, USBDEVFS_CAP_BULK_CONTINUATION, USBDEVFS_CAP_NO_PACKET_SIZE_LIM,
USBDEVFS_CAP_REAP_AFTER_DISCONNECT, USBDEVFS_CAP_ZERO_PACKET, USBDEVFS_CLAIMINTERFACE,
USBDEVFS_CLEAR_HALT, USBDEVFS_CONNECT, USBDEVFS_CONNECTINFO, USBDEVFS_CONTROL,
USBDEVFS_DISCARDURB, USBDEVFS_DISCONNECT, USBDEVFS_DISCONNECT_CLAIM,
USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER, USBDEVFS_DISCONNECT_CLAIM_IF_DRIVER,
USBDEVFS_GET_CAPABILITIES, USBDEVFS_GET_SPEED, USBDEVFS_GETDRIVER, USBDEVFS_IOCTL,
USBDEVFS_REAPURB, USBDEVFS_REAPURBNDELAY, USBDEVFS_RELEASEINTERFACE, USBDEVFS_RESET,
USBDEVFS_SETCONFIGURATION, USBDEVFS_SETINTERFACE, USBDEVFS_SUBMITURB,
USBDEVFS_URB_BULK_CONTINUATION, USBDEVFS_URB_ISO_ASAP, USBDEVFS_URB_NO_FSBR,
USBDEVFS_URB_NO_INTERRUPT, USBDEVFS_URB_SHORT_NOT_OK, USBDEVFS_URB_TYPE_BULK,
USBDEVFS_URB_TYPE_CONTROL, USBDEVFS_URB_TYPE_INTERRUPT, USBDEVFS_URB_TYPE_ISO,
USBDEVFS_URB_ZERO_PACKET, errno, error, uapi, usb_config_descriptor, usb_ctrlrequest,
usb_device_descriptor, usb_device_speed_USB_SPEED_FULL, usb_device_speed_USB_SPEED_HIGH,
usb_device_speed_USB_SPEED_LOW, usb_device_speed_USB_SPEED_SUPER,
usb_device_speed_USB_SPEED_SUPER_PLUS, usb_device_speed_USB_SPEED_UNKNOWN,
usb_device_speed_USB_SPEED_WIRELESS, usb_interface_descriptor, usb_ss_ep_comp_descriptor,
usbdevfs_bulktransfer, usbdevfs_connectinfo, usbdevfs_ctrltransfer, usbdevfs_disconnect_claim,
usbdevfs_getdriver, usbdevfs_ioctl, usbdevfs_setinterface,
};
use std::cmp::min;
use std::collections::{BTreeMap, HashMap, HashSet, VecDeque};
use std::mem::{offset_of, size_of};
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, AtomicUsize, Ordering};
use std::sync::mpsc::sync_channel;
use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout};
type UsbdevfsCtrltransferPtr =
MultiArchUserRef<usbdevfs_ctrltransfer, uapi::arch32::usbdevfs_ctrltransfer>;
type UsbdevfsBulktransferPtr =
MultiArchUserRef<usbdevfs_bulktransfer, uapi::arch32::usbdevfs_bulktransfer>;
type UsbdevfsIoctlPtr = MultiArchUserRef<usbdevfs_ioctl, uapi::arch32::usbdevfs_ioctl>;
/// 32-bit compat USBDEVFS ioctl request numbers and their native equivalents. On architectures
/// without 32-bit support (e.g. x86_64), `uapi::arch32` aliases the native definitions, so each
/// entry maps a request number to itself.
const ARCH32_IOCTL_REQUESTS: [(u32, u32); 7] = [
(uapi::arch32::USBDEVFS_CONTROL, uapi::USBDEVFS_CONTROL),
(uapi::arch32::USBDEVFS_BULK, uapi::USBDEVFS_BULK),
(uapi::arch32::USBDEVFS_DISCSIGNAL, uapi::USBDEVFS_DISCSIGNAL),
(uapi::arch32::USBDEVFS_SUBMITURB, uapi::USBDEVFS_SUBMITURB),
(uapi::arch32::USBDEVFS_REAPURB, uapi::USBDEVFS_REAPURB),
(uapi::arch32::USBDEVFS_REAPURBNDELAY, uapi::USBDEVFS_REAPURBNDELAY),
(uapi::arch32::USBDEVFS_IOCTL, uapi::USBDEVFS_IOCTL),
];
/// Returns the native request number of `request`, which is a 32-bit compat request number if
/// `is_arch32` is set.
pub(crate) fn canonicalize_ioctl_request(is_arch32: bool, request: u32) -> u32 {
if !is_arch32 {
return request;
}
ARCH32_IOCTL_REQUESTS
.iter()
.find(|&&(compat, _)| compat == request)
.map_or(request, |&(_, native)| native)
}
/// Returns whether `request` is the number of a 32-bit compat USBDEVFS ioctl. Like Linux, these
/// are honored for 64-bit tasks too. On architectures without 32-bit support (e.g. x86_64),
/// `uapi::arch32` aliases the native definitions, so no request number is a compat request number
/// there.
pub(crate) fn is_arch32_ioctl_request(request: u32) -> bool {
canonicalize_ioctl_request(true, request) != request
}
/// Header of the 64-bit `struct usbdevfs_urb`, without the trailing isochronous packet
/// descriptors.
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, KnownLayout, Immutable)]
pub(crate) struct UsbdevfsUrbHeader {
pub(crate) type_: u8,
pub(crate) endpoint: u8,
pub(crate) _pad0: [u8; 2],
pub(crate) status: i32,
pub(crate) flags: u32,
pub(crate) _pad1: [u8; 4],
pub(crate) buffer: u64,
pub(crate) buffer_length: i32,
pub(crate) actual_length: i32,
pub(crate) start_frame: i32,
pub(crate) number_of_packets_or_stream_id: u32,
pub(crate) error_count: i32,
pub(crate) signr: u32,
pub(crate) usercontext: u64,
}
/// Header of the 32-bit compat `struct usbdevfs_urb`, without the trailing isochronous packet
/// descriptors.
#[repr(C)]
#[derive(Copy, Clone, Debug, Default, FromBytes, IntoBytes, KnownLayout, Immutable)]
pub(crate) struct UsbdevfsUrb32Header {
pub(crate) type_: u8,
pub(crate) endpoint: u8,
pub(crate) _pad0: [u8; 2],
pub(crate) status: i32,
pub(crate) flags: u32,
pub(crate) buffer: u32,
pub(crate) buffer_length: i32,
pub(crate) actual_length: i32,
pub(crate) start_frame: i32,
pub(crate) number_of_packets_or_stream_id: u32,
pub(crate) error_count: i32,
pub(crate) signr: u32,
pub(crate) usercontext: u32,
}
/// Fields of a `struct usbdevfs_urb` used to submit it, from either layout.
struct UrbHeader {
type_: u8,
endpoint: u8,
flags: u32,
buffer: UserAddress,
buffer_length: i32,
signr: u32,
}
impl From<UsbdevfsUrbHeader> for UrbHeader {
fn from(urb: UsbdevfsUrbHeader) -> Self {
Self {
type_: urb.type_,
endpoint: urb.endpoint,
flags: urb.flags,
buffer: UserAddress::from(urb.buffer),
buffer_length: urb.buffer_length,
signr: urb.signr,
}
}
}
impl From<UsbdevfsUrb32Header> for UrbHeader {
fn from(urb: UsbdevfsUrb32Header) -> Self {
Self {
type_: urb.type_,
endpoint: urb.endpoint,
flags: urb.flags,
buffer: UserAddress::from(u64::from(urb.buffer)),
buffer_length: urb.buffer_length,
signr: urb.signr,
}
}
}
/// Reads the header of the `struct usbdevfs_urb` at `addr`, in the 32-bit layout if `is_32bit`.
fn read_urb_header(
current_task: &CurrentTask,
addr: UserAddress,
is_32bit: bool,
) -> Result<UrbHeader, Errno> {
Ok(if is_32bit {
current_task.read_object::<UsbdevfsUrb32Header>(UserRef::new(addr))?.into()
} else {
current_task.read_object::<UsbdevfsUrbHeader>(UserRef::new(addr))?.into()
})
}
/// Writes the result of a completed URB to its `struct usbdevfs_urb` at `addr`. Like Linux, only
/// `status`, `actual_length` and `error_count` are written, so concurrent changes to the other
/// fields by userspace are preserved.
fn write_urb_result(
current_task: &CurrentTask,
addr: UserAddress,
is_32bit: bool,
status: i32,
actual_length: i32,
) -> Result<(), Errno> {
let (status_offset, actual_length_offset, error_count_offset) = if is_32bit {
(
offset_of!(UsbdevfsUrb32Header, status),
offset_of!(UsbdevfsUrb32Header, actual_length),
offset_of!(UsbdevfsUrb32Header, error_count),
)
} else {
(
offset_of!(UsbdevfsUrbHeader, status),
offset_of!(UsbdevfsUrbHeader, actual_length),
offset_of!(UsbdevfsUrbHeader, error_count),
)
};
let write_field = |offset: usize, value: i32| -> Result<(), Errno> {
current_task.write_object(UserRef::<i32>::new((addr + offset)?), &value)?;
Ok(())
};
write_field(status_offset, status)?;
write_field(actual_length_offset, actual_length)?;
// Isochronous URBs are not supported, so no packet ever fails.
write_field(error_count_offset, 0)
}
/// Returns the negated errno code reported in the `status` of a completed URB.
fn urb_status(errno: &Errno) -> i32 {
-(errno.code.error_code() as i32)
}
pub(crate) const USB_SPEED_UNKNOWN: u32 = usb_device_speed_USB_SPEED_UNKNOWN;
pub(crate) const USB_SPEED_LOW: u32 = usb_device_speed_USB_SPEED_LOW;
pub(crate) const USB_SPEED_FULL: u32 = usb_device_speed_USB_SPEED_FULL;
pub(crate) const USB_SPEED_HIGH: u32 = usb_device_speed_USB_SPEED_HIGH;
pub(crate) const USB_SPEED_WIRELESS: u32 = usb_device_speed_USB_SPEED_WIRELESS;
pub(crate) const USB_SPEED_SUPER: u32 = usb_device_speed_USB_SPEED_SUPER;
pub(crate) const USB_SPEED_SUPER_PLUS: u32 = usb_device_speed_USB_SPEED_SUPER_PLUS;
pub(crate) const MAX_USBDEVFS_CONTROL_SIZE: usize = 4096;
/// Size of the setup packet at the start of the buffer of a control URB.
const USB_SETUP_PACKET_SIZE: usize = size_of::<usb_ctrlrequest>();
/// Approximate size of the Linux `struct urb` on 64-bit architectures (184 bytes without vendor
/// padding). usbfs charges it to the memory used by each transfer, on top of the data buffer.
const URB_SIZE: usize = 184;
/// Memory available to the outstanding transfers of usbfs (the default `usbfs_memory_mb` of
/// 16 MiB).
const USBFS_MEMORY_LIMIT: usize = 16 * 1024 * 1024;
/// Largest data length of a transfer that fits in `USBFS_MEMORY_LIMIT`.
#[cfg(test)]
pub(crate) const MAX_USBDEVFS_BULK_SIZE: usize = USBFS_MEMORY_LIMIT - URB_SIZE;
/// Data lengths of `USBDEVFS_BULK` transfers from this value on are invalid (`EINVAL`), before
/// the memory limit is checked.
pub(crate) const INVALID_USBDEVFS_BULK_SIZE: usize = i32::MAX as usize - URB_SIZE;
/// Buffer lengths of URBs from this value on are invalid (`EINVAL`), before the memory limit is
/// checked.
pub(crate) const USBFS_XFER_MAX: usize = (u32::MAX / 2 - 1_000_000) as usize;
/// URB flags accepted for all URB types. Only `USBDEVFS_URB_SHORT_NOT_OK`,
/// `USBDEVFS_URB_BULK_CONTINUATION` and `USBDEVFS_URB_ZERO_PACKET` change the behavior of usbfs;
/// the others are scheduling hints.
const URB_FLAGS: u32 = USBDEVFS_URB_SHORT_NOT_OK
| USBDEVFS_URB_BULK_CONTINUATION
| USBDEVFS_URB_NO_FSBR
| USBDEVFS_URB_ZERO_PACKET
| USBDEVFS_URB_NO_INTERRUPT;
const ENDPOINT_VMO_SIZE: u64 = 65536;
/// Maximum number of retired VMOs of an endpoint, i.e. VMOs whose cancelled requests did not
/// complete in time. Transfers on the endpoint fail with `EIO` while this many VMOs still wait
/// for completions.
pub(crate) const MAX_RETIRED_VMOS: usize = 4;
/// Maximum number of interfaces supported by the `u64` interface claim bitset.
const MAX_USB_INTERFACES: u32 = u64::BITS;
/// Default upper bound on the time spent waiting for the completions of cancelled requests
/// before the endpoint VMO is retired.
pub(crate) const DEFAULT_CANCEL_DRAIN_TIMEOUT: zx::MonotonicDuration =
zx::MonotonicDuration::from_seconds(1);
/// Timeout of the `CLEAR_FEATURE(ENDPOINT_HALT)` control transfers issued by usbfs.
const CLEAR_HALT_TIMEOUT: zx::MonotonicDuration = zx::MonotonicDuration::from_millis(5000);
/// Driver name reported for interfaces claimed through usbfs. Starnix never binds kernel drivers
/// to USB interfaces, so usbfs is the only possible driver.
const USBFS_DRIVER_NAME: &[u8] = b"usbfs";
/// `bmRequestType` and `bRequest` of the printer class GET_DEVICE_ID request.
const USB_PRINTER_GET_DEVICE_ID_REQUEST_TYPE: u32 =
USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE;
const USB_PRINTER_GET_DEVICE_ID: u8 = 0;
/// Offsets of the fields of a standard endpoint descriptor (USB 2.0 Table 9-13). The uapi
/// `usb_endpoint_descriptor` has the 9-byte audio layout, so the 7-byte descriptor is read field
/// by field.
const ENDPOINT_DESC_ADDRESS: usize = 2;
const ENDPOINT_DESC_ATTRIBUTES: usize = 3;
const ENDPOINT_DESC_MAX_PACKET_SIZE: usize = 4;
const ENDPOINT_DESC_INTERVAL: usize = 6;
/// Validated USB interface number in `0..MAX_USB_INTERFACES` (`0..64`),
/// suitable for indexing into a `u64` interface claim bitset.
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
struct Ifno(u8);
impl Ifno {
fn as_u8(self) -> u8 {
self.0
}
}
impl TryFrom<u32> for Ifno {
type Error = Errno;
fn try_from(value: u32) -> Result<Self, Self::Error> {
if value >= MAX_USB_INTERFACES { error!(EINVAL) } else { Ok(Self(value as u8)) }
}
}
impl From<Ifno> for u32 {
fn from(ifno: Ifno) -> Self {
u32::from(ifno.0)
}
}
/// SuperSpeed endpoint companion of an endpoint without one.
const NO_SS_COMPANION: fusb_descriptor::UsbSsEpCompDescriptor =
fusb_descriptor::UsbSsEpCompDescriptor {
b_length: 0,
b_descriptor_type: 0,
b_max_burst: 0,
bm_attributes: 0,
w_bytes_per_interval: 0,
};
#[derive(Clone, Debug, PartialEq)]
struct ParsedEndpoint {
descriptor: fusb_descriptor::UsbEndpointDescriptor,
ss_companion: fusb_descriptor::UsbSsEpCompDescriptor,
}
impl ParsedEndpoint {
fn address(&self) -> u8 {
self.descriptor.b_endpoint_address
}
fn transfer_type(&self) -> u32 {
u32::from(self.descriptor.bm_attributes) & USB_ENDPOINT_XFERTYPE_MASK
}
fn max_packet_size(&self) -> u32 {
u32::from(self.descriptor.w_max_packet_size) & USB_ENDPOINT_MAXP_MASK
}
}
#[derive(Clone, Debug)]
struct ParsedInterfaceAlt {
interface_number: u8,
alt_setting: u8,
interface_class: u8,
endpoints: Vec<ParsedEndpoint>,
}
#[derive(Clone, Debug)]
struct ParsedConfig {
config_value: u8,
interfaces: Vec<ParsedInterfaceAlt>,
}
/// Returns an iterator over the descriptors in `data`.
///
/// Per USB 2.0 Section 9.5, every USB descriptor begins with a 2-byte `[bLength, bDescriptorType]`
/// header followed by `bLength - 2` descriptor-specific bytes. Iteration stops at the first
/// descriptor that is truncated or has a `bLength` smaller than the 2-byte header.
fn iter_descriptors(data: &[u8]) -> impl Iterator<Item = &[u8]> {
let mut rest = data;
std::iter::from_fn(move || {
let length = usize::from(*rest.first()?);
if length < 2 || length > rest.len() {
return None;
}
let (descriptor, tail) = rest.split_at(length);
rest = tail;
Some(descriptor)
})
}
fn parse_endpoint_descriptor(desc: &[u8]) -> Option<ParsedEndpoint> {
if desc.len() < USB_DT_ENDPOINT_SIZE as usize {
return None;
}
let descriptor = fusb_descriptor::UsbEndpointDescriptor {
b_length: desc[0],
b_descriptor_type: desc[1],
b_endpoint_address: desc[ENDPOINT_DESC_ADDRESS],
bm_attributes: desc[ENDPOINT_DESC_ATTRIBUTES],
w_max_packet_size: u16::from_le_bytes([
desc[ENDPOINT_DESC_MAX_PACKET_SIZE],
desc[ENDPOINT_DESC_MAX_PACKET_SIZE + 1],
]),
b_interval: desc[ENDPOINT_DESC_INTERVAL],
};
Some(ParsedEndpoint { descriptor, ss_companion: NO_SS_COMPANION })
}
fn parse_ss_companion_descriptor(desc: &[u8]) -> Option<fusb_descriptor::UsbSsEpCompDescriptor> {
let (comp, _) = usb_ss_ep_comp_descriptor::read_from_prefix(desc).ok()?;
Some(fusb_descriptor::UsbSsEpCompDescriptor {
b_length: comp.bLength,
b_descriptor_type: comp.bDescriptorType,
b_max_burst: comp.bMaxBurst,
bm_attributes: comp.bmAttributes,
w_bytes_per_interval: u16::from_le(comp.wBytesPerInterval),
})
}
/// Parses the configurations, interfaces, and endpoints of the configuration descriptors in
/// `data`.
///
/// Per USB 2.0 Section 9.4.3 (`GET_DESCRIPTOR` for `CONFIGURATION`) and USB 3.2 Section 9.6.7, a
/// configuration descriptor blob is a flat byte stream of concatenated `[bLength, bDescriptorType,
/// ...]` descriptors ordered hierarchically:
/// - Configuration descriptor (`USB_DT_CONFIG`)
/// - Interface alternate setting descriptors (`USB_DT_INTERFACE`)
/// - Endpoint descriptors (`USB_DT_ENDPOINT`)
/// - Optional class-specific endpoint descriptors
/// - Optional SuperSpeed Endpoint Companion descriptor (`USB_DT_SS_ENDPOINT_COMP`)
///
/// `data` concatenates the blobs of all configurations of the device. Descriptors that are too
/// short are ignored, as are interfaces before the first configuration and endpoints before the
/// first interface.
fn parse_config_descriptors(data: &[u8]) -> Vec<ParsedConfig> {
let mut configs: Vec<ParsedConfig> = Vec::new();
// Whether the last descriptor was an endpoint that may be followed by its SuperSpeed
// endpoint companion descriptor.
let mut in_endpoint_scope = false;
for desc in iter_descriptors(data) {
let descriptor_type = u32::from(desc[1]);
let mut is_endpoint = false;
match descriptor_type {
USB_DT_CONFIG => {
if let Ok((config, _)) = usb_config_descriptor::read_from_prefix(desc) {
configs.push(ParsedConfig {
config_value: config.bConfigurationValue,
interfaces: Vec::new(),
});
}
}
USB_DT_INTERFACE => {
let interface = usb_interface_descriptor::read_from_prefix(desc).ok();
if let (Some((interface, _)), Some(config)) = (interface, configs.last_mut()) {
config.interfaces.push(ParsedInterfaceAlt {
interface_number: interface.bInterfaceNumber,
alt_setting: interface.bAlternateSetting,
interface_class: interface.bInterfaceClass,
endpoints: Vec::new(),
});
}
}
USB_DT_ENDPOINT => {
let interface = configs.last_mut().and_then(|c| c.interfaces.last_mut());
if let (Some(endpoint), Some(interface)) =
(parse_endpoint_descriptor(desc), interface)
{
interface.endpoints.push(endpoint);
is_endpoint = true;
}
}
USB_DT_SS_ENDPOINT_COMP if in_endpoint_scope => {
let endpoint = configs
.last_mut()
.and_then(|c| c.interfaces.last_mut())
.and_then(|i| i.endpoints.last_mut());
if let (Some(companion), Some(endpoint)) =
(parse_ss_companion_descriptor(desc), endpoint)
{
endpoint.ss_companion = companion;
}
}
USB_DT_INTERFACE_ASSOCIATION => {}
// Class-specific descriptors may follow an endpoint before its companion.
_ => is_endpoint = in_endpoint_scope,
}
in_endpoint_scope = is_endpoint;
}
configs
}
fn zx_status_to_errno(status: i32) -> Errno {
match status {
zx::sys::ZX_ERR_IO_REFUSED => errno!(EPIPE),
zx::sys::ZX_ERR_TIMED_OUT => errno!(ETIMEDOUT),
zx::sys::ZX_ERR_NOT_FOUND => errno!(ENOENT),
zx::sys::ZX_ERR_ALREADY_BOUND => errno!(EBUSY),
zx::sys::ZX_ERR_IO_NOT_PRESENT | zx::sys::ZX_ERR_PEER_CLOSED => errno!(ENODEV),
zx::sys::ZX_ERR_INVALID_ARGS => errno!(EINVAL),
zx::sys::ZX_ERR_BAD_STATE => errno!(EHOSTUNREACH),
zx::sys::ZX_ERR_CANCELED => errno!(ECONNRESET),
zx::sys::ZX_ERR_NO_MEMORY => errno!(ENOMEM),
_ => errno!(EIO),
}
}
/// Maps a FIDL transport error to an errno: `ENODEV` if the channel is closed (the device is
/// gone), `EIO` otherwise.
fn fidl_error_to_errno(error: &fidl::Error) -> Errno {
if error.is_closed() { errno!(ENODEV) } else { errno!(EIO) }
}
/// Maps the two-layer result of a FIDL method with an `error zx.Status` clause to an errno.
fn fidl_result_to_errno<T>(result: Result<Result<T, i32>, fidl::Error>) -> Result<T, Errno> {
match result {
Ok(Ok(value)) => Ok(value),
Ok(Err(status)) => Err(zx_status_to_errno(status)),
Err(e) => Err(fidl_error_to_errno(&e)),
}
}
/// Converts an optional timeout (`None` meaning no timeout) into an absolute deadline.
fn deadline_after(timeout: Option<zx::MonotonicDuration>) -> zx::MonotonicInstant {
timeout.map_or(zx::MonotonicInstant::INFINITE, zx::MonotonicInstant::after)
}
/// Converts a usbfs timeout in milliseconds into an optional timeout. A timeout of 0 means no
/// timeout.
fn usbfs_timeout(timeout_ms: u32) -> Option<zx::MonotonicDuration> {
(timeout_ms != 0).then(|| zx::MonotonicDuration::from_millis(i64::from(timeout_ms)))
}
/// Blocks the current task until any of the signals in `waits` is asserted on its handle, or
/// `deadline` passes.
///
/// The wait is killable, like Linux usbfs transfers: only unblockable signals (SIGKILL, SIGSTOP)
/// interrupt it, in which case `EINTR` is returned. Other signals stay pending and are delivered
/// when the syscall returns. Returns `ETIMEDOUT` when the deadline passes.
fn wait_for_signals(
current_task: &CurrentTask,
waits: &[(&dyn zx::AsHandleRef, zx::Signals)],
deadline: zx::MonotonicInstant,
) -> Result<(), Errno> {
// Block all the maskable signals for the duration of the wait, unless a temporary signal mask
// is already in place.
//
// TODO(https://fxbug.dev/573607334): On Linux, an unblocked signal whose default action
// terminates the process (e.g. SIGTERM with SIG_DFL, but not core-dumping signals, and not
// when ptraced) is promoted to SIGKILL on delivery, which ends a killable wait. This mask
// prevents that for single-threaded processes waiting without timeout (multi-threaded ones
// still exit through another thread). Fixing it requires a killable wait primitive in the
// Starnix core, since a core promotion honoring the current mask would not see through this
// temporary one.
let mask_signals = !current_task.flags().contains(TaskFlags::TEMPORARY_SIGNAL_MASK);
if mask_signals {
current_task.write().set_temporary_signal_mask(!SigSet::default());
}
let result = wait_for_signals_unmasked(current_task, waits, deadline);
if mask_signals {
// Signals received during the wait are delivered with the original mask.
current_task.write().restore_signal_mask();
}
result
}
fn wait_for_signals_unmasked(
current_task: &CurrentTask,
waits: &[(&dyn zx::AsHandleRef, zx::Signals)],
deadline: zx::MonotonicInstant,
) -> Result<(), Errno> {
loop {
// `PortWaiter::wait_until` automatically invokes `current_task.trigger_delayed_releaser()`
// unless `WaiterOptions::UNSAFE_CALLSTACK` is set. Callers of `wait_for_signals` hold
// `UsbfsEndpointTransferLock`, and flushing a closed `UsbDeviceFile` queued in the
// thread-local delayed releaser runs `UsbDeviceFile::drop`, which acquires
// `UsbfsDeviceStateLock` (ordered before `UsbfsEndpointTransferLock`).
let waiter = Waiter::with_options(WaiterOptions::UNSAFE_CALLSTACK);
let mut cancelers = Vec::with_capacity(waits.len());
for &(handle, signals) in waits {
let handler = SignalHandler {
inner: SignalHandlerInner::None,
event_handler: EventHandler::None,
err_code: None,
};
cancelers.push(
waiter.wake_on_zircon_signals(handle, signals, handler).map_err(|_| errno!(EIO))?,
);
}
// The wait is edge-triggered, so check for signals asserted before it was registered.
let asserted = waits.iter().any(|&(handle, signals)| {
handle
.as_handle_ref()
.wait_one(signals, zx::MonotonicInstant::INFINITE_PAST)
.to_result()
.is_ok()
});
let result = if asserted { Ok(()) } else { waiter.wait_until(current_task, deadline) };
for canceler in cancelers {
canceler.cancel();
}
match result {
// Spurious wakeup without a pending signal.
Err(e)
if e.code == EINTR
&& !current_task.is_interrupt_pending()
&& !current_task.is_exitted() =>
{
continue;
}
result => return result,
}
}
}
/// Kind of transfer issued by `USBDEVFS_BULK` on an endpoint.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
enum TransferType {
Bulk,
Interrupt,
}
impl TransferType {
fn request_info(self) -> fusb_request::RequestInfo {
match self {
Self::Bulk => fusb_request::RequestInfo::Bulk(fusb_request::BulkRequestInfo::default()),
Self::Interrupt => {
fusb_request::RequestInfo::Interrupt(fusb_request::InterruptRequestInfo::default())
}
}
}
}
/// How `USBDEVFS_BULK` transfers are issued on an endpoint.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
struct EndpointTransferParams {
transfer_type: TransferType,
/// `wMaxPacketSize` of the endpoint.
max_packet_size: usize,
/// Size of the requests a transfer is split into: the largest multiple of the endpoint's
/// `wMaxPacketSize` that fits in the endpoint VMO, so that only the last request of a
/// transfer can end with a short packet.
chunk_size: usize,
}
impl EndpointTransferParams {
/// Returns the parameters of `endpoint`, or `EINVAL` if the endpoint is neither a bulk nor
/// an interrupt endpoint, or has a zero `wMaxPacketSize`.
fn for_endpoint(endpoint: &ParsedEndpoint) -> Result<Self, Errno> {
let max_packet_size = endpoint.max_packet_size() as usize;
if max_packet_size == 0 {
return error!(EINVAL);
}
let transfer_type = match endpoint.transfer_type() {
USB_ENDPOINT_XFER_BULK => TransferType::Bulk,
USB_ENDPOINT_XFER_INT => TransferType::Interrupt,
_ => return error!(EINVAL),
};
let vmo_size = ENDPOINT_VMO_SIZE as usize;
Ok(Self {
transfer_type,
max_packet_size,
chunk_size: vmo_size - vmo_size % max_packet_size,
})
}
}
/// Outcome of a transfer that may have moved some data before failing.
#[derive(Debug)]
struct PartialTransfer<T> {
/// Data or number of bytes transferred, even if the transfer failed.
value: T,
error: Option<Errno>,
}
impl<T> PartialTransfer<T> {
fn ok(value: T) -> Self {
Self { value, error: None }
}
fn failed(value: T, error: Errno) -> Self {
Self { value, error: Some(error) }
}
/// Returns the value of a successful transfer, or the error of a failed one. Like Linux
/// `USBDEVFS_BULK`, the data transferred before a failure is discarded.
fn into_result(self) -> Result<T, Errno> {
match self.error {
None => Ok(self.value),
Some(error) => Err(error),
}
}
}
/// Returns the setup packet of a `CLEAR_FEATURE(ENDPOINT_HALT)` request for endpoint `ep_addr`.
fn clear_halt_setup(ep_addr: u8) -> fusb_descriptor::UsbSetup {
fusb_descriptor::UsbSetup {
bm_request_type: (USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_ENDPOINT) as u8,
b_request: USB_REQ_CLEAR_FEATURE as u8,
w_value: USB_ENDPOINT_HALT as u16,
w_index: u16::from(ep_addr),
w_length: 0,
}
}
/// Returns the ID of the VMO used by the request of `completion`, if known.
fn completion_vmo_id(completion: &fusb_endpoint::Completion) -> Option<u64> {
match completion.request.as_ref()?.data.as_ref()?.first()?.buffer {
Some(fusb_request::Buffer::VmoId(id)) => Some(id),
_ => None,
}
}
/// State of an endpoint protected by its transfer lock.
struct EndpointTransferState {
/// VMO used by the requests, at offset 0. At most one request is in flight at a time.
vmo: zx::Vmo,
vmo_id: u64,
/// Number of requests queued with `vmo_id` whose completion has not been received yet.
in_flight_count: usize,
/// Number of requests whose completion has not been received yet, for each retired VMO.
///
/// When the completion of a cancelled request does not arrive in time, its VMO is retired
/// and replaced by a new one, since the request may still complete and write to it later.
/// The completions of the requests of retired VMOs are discarded, and a retired VMO is
/// unregistered once all of them have been received.
retired_vmos: HashMap<u64, usize>,
next_vmo_id: u64,
}
/// Lifecycle and reset state of an endpoint, protected by `UsbEndpointClient::state`.
///
/// Locking this state around both request submission (`queue_requests`) and endpoint disabling or
/// reset (`cancel_all`) ensures a transfer cannot queue a new request onto an endpoint after
/// `cancel_all` has already run for that disable or reset.
struct EndpointClientState {
/// Whether the endpoint is enabled and new requests may be queued. Cleared before the
/// in-flight requests are cancelled when the endpoint is disabled.
usable: bool,
/// Whether the endpoint is currently being disabled and immediately re-enabled by `clear_halt`
/// while URBs are pending on it. Subsequent transfers wait in `acquire_transfer` until
/// restarting finishes rather than failing with `ESHUTDOWN`.
restarting: bool,
/// Whether the driver closed the channel. The driver disables the endpoint before closing
/// the channel, so a new connection can be made right away.
closed: bool,
/// Replacement client connected when this client's channel was closed while restarting the
/// endpoint. Transfers waiting in `acquire_transfer` switch to this client.
replaced_by: Option<Arc<UsbEndpointClient>>,
/// Generation count incremented on each reset or cancellation of the endpoint.
reset_count: u64,
/// Whether a transfer currently holds the endpoint's transfer slot.
transfer_busy: bool,
/// Number of transfers waiting in `acquire_transfer`.
#[cfg(test)]
transfer_waiters: usize,
/// Optional hook invoked right before `begin_transfer` in tests.
#[cfg(test)]
on_before_begin_transfer: Option<Box<dyn FnOnce() + Send>>,
}
/// Connection to an endpoint of the device.
///
/// The channel stays open for the lifetime of the device: closing it makes the host controller
/// cancel the requests and disable the endpoint asynchronously, which would race with a later
/// enable of the endpoint. Only the driver closes it, after disabling the endpoint.
pub(crate) struct UsbEndpointClient {
proxy: fusb_endpoint::EndpointSynchronousProxy,
/// Protects the endpoint's VMO and completion accounting for the active transfer. Locked only
/// after claiming `EndpointClientState::transfer_busy` via `acquire_transfer`.
transfer: LockDepMutex<EndpointTransferState, UsbfsEndpointTransferLock>,
/// Protects the endpoint's enabled/closed status, reset generation, and transfer slot across
/// request submission and cancellation. Ordered after `UninterruptibleLock`,
/// `UsbfsDeviceStateLock`, `UsbfsEndpointTransferLock`, and `UsbfsEndpointsLock`.
state: LockDepMutex<EndpointClientState, UsbfsEndpointStateLock>,
/// Signaled with `USER_0` when `EndpointClientState::transfer_busy` becomes false.
transfer_released: zx::Event,
/// Signaled with `USER_0` to make a transfer waiting for a completion check whether it
/// should stop, when the endpoint is disabled or reset.
wake: zx::Event,
/// Upper bound on the time spent draining cancelled requests.
cancel_drain_timeout: zx::MonotonicDuration,
}
struct EndpointTransferSlotGuard<'a> {
client: &'a UsbEndpointClient,
}
impl Drop for EndpointTransferSlotGuard<'_> {
fn drop(&mut self) {
let mut state = self.client.state.lock();
state.transfer_busy = false;
let _ = self.client.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
}
enum AcquiredTransfer<'a> {
Slot(EndpointTransferSlotGuard<'a>, u64),
Replaced(Arc<UsbEndpointClient>),
}
#[cfg(test)]
struct TransferWaiterCountGuard<'a>(&'a UsbEndpointClient);
#[cfg(test)]
impl Drop for TransferWaiterCountGuard<'_> {
fn drop(&mut self) {
self.0.state.lock().transfer_waiters -= 1;
}
}
#[cfg(test)]
impl UsbEndpointClient {
fn count_transfer_waiter<'a>(
&'a self,
state: &mut EndpointClientState,
) -> TransferWaiterCountGuard<'a> {
state.transfer_waiters += 1;
TransferWaiterCountGuard(self)
}
}
#[cfg(not(test))]
impl UsbEndpointClient {
#[inline(always)]
fn count_transfer_waiter(&self, _state: &mut EndpointClientState) {}
}
impl UsbEndpointClient {
/// Connects to endpoint `ep_addr` of the device behind `proxy` and registers its VMO.
fn connect(
proxy: &fhardware_usb::UsbSynchronousProxy,
ep_addr: u8,
cancel_drain_timeout: zx::MonotonicDuration,
) -> Result<Arc<Self>, Errno> {
let (ep_client_end, ep_server_end) = create_endpoints::<fusb_endpoint::EndpointMarker>();
fidl_result_to_errno(proxy.connect_to_endpoint(
ep_addr,
ep_server_end,
zx::MonotonicInstant::INFINITE,
))?;
let ep_proxy = fusb_endpoint::EndpointSynchronousProxy::new(ep_client_end.into_channel());
let vmo_id = 1;
let vmo = Self::register_vmo(&ep_proxy, vmo_id)?;
Ok(Arc::new(Self {
proxy: ep_proxy,
transfer: LockDepMutex::new(EndpointTransferState {
vmo,
vmo_id,
in_flight_count: 0,
retired_vmos: HashMap::new(),
next_vmo_id: vmo_id + 1,
}),
state: LockDepMutex::new(EndpointClientState {
usable: false,
restarting: false,
closed: false,
replaced_by: None,
reset_count: 0,
transfer_busy: false,
#[cfg(test)]
transfer_waiters: 0,
#[cfg(test)]
on_before_begin_transfer: None,
}),
transfer_released: zx::Event::create(),
wake: zx::Event::create(),
cancel_drain_timeout,
}))
}
fn register_vmo(
proxy: &fusb_endpoint::EndpointSynchronousProxy,
vmo_id: u64,
) -> Result<zx::Vmo, Errno> {
let vmo_infos = [fusb_endpoint::VmoInfo {
id: Some(vmo_id),
size: Some(ENDPOINT_VMO_SIZE),
..Default::default()
}];
let mut vmos = proxy
.register_vmos(&vmo_infos, zx::MonotonicInstant::INFINITE)
.map_err(|e| fidl_error_to_errno(&e))?;
vmos.pop().and_then(|h| h.vmo).ok_or_else(|| errno!(EIO))
}
fn is_closed(&self) -> bool {
let mut state = self.state.lock();
if !state.closed
&& self
.proxy
.as_channel()
.wait_one(zx::Signals::CHANNEL_PEER_CLOSED, zx::MonotonicInstant::INFINITE_PAST)
.to_result()
.is_ok()
{
state.usable = false;
state.closed = true;
let _ = self.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
state.closed
}
fn enable(&self) {
let mut state = self.state.lock();
state.usable = true;
state.restarting = false;
let _ = self.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
/// Makes a transfer waiting for a completion on this endpoint check whether it should stop.
fn wake_transfer(&self) {
let _ = self.wake.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
/// Marks the endpoint as disabled and cancels its in-flight requests under the state lock, so
/// no concurrent transfer can queue a request after `cancel_all` runs.
fn disable(&self) {
let mut state = self.state.lock();
state.usable = false;
state.restarting = false;
state.reset_count += 1;
let _ = self.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
self.wake_transfer();
if let Err(e) =
self.proxy.cancel_all(zx::MonotonicInstant::after(self.cancel_drain_timeout))
{
if e.is_closed() {
state.closed = true;
}
}
}
/// Cancels in-flight requests and holds new transfers in `acquire_transfer` while the endpoint
/// is disabled and immediately re-enabled on the host controller.
fn begin_restart(&self) {
let mut state = self.state.lock();
state.restarting = true;
state.reset_count += 1;
self.wake_transfer();
if let Err(e) =
self.proxy.cancel_all(zx::MonotonicInstant::after(self.cancel_drain_timeout))
{
if e.is_closed() {
state.usable = false;
state.closed = true;
let _ = self.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
}
}
/// Releases transfers waiting in `acquire_transfer` after the endpoint has been re-enabled on
/// the host controller.
fn finish_restart(&self) {
let mut state = self.state.lock();
state.restarting = false;
let _ = self.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
/// Replaces this closed client with `replacement` and wakes transfers waiting in
/// `acquire_transfer` so they switch to `replacement`.
fn replace_with(&self, replacement: Arc<UsbEndpointClient>) {
let mut state = self.state.lock();
state.usable = false;
state.restarting = false;
state.closed = true;
state.replaced_by = Some(replacement);
state.reset_count += 1;
let _ = self.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
self.wake_transfer();
}
/// Cancels the in-flight requests under the state lock. The transfer that queued them drains
/// their completions.
fn cancel_all(&self) {
let mut state = self.state.lock();
state.reset_count += 1;
self.wake_transfer();
if let Err(e) =
self.proxy.cancel_all(zx::MonotonicInstant::after(self.cancel_drain_timeout))
{
if e.is_closed() {
state.usable = false;
state.closed = true;
let _ = self.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
}
}
/// Returns the error of a transfer stopped before or while waiting, if any: `ENOENT` if
/// `cancel` is set, like a killed URB, or `ESHUTDOWN` if usbfs disabled or reset the
/// endpoint.
fn cancellation_error_locked(
state: &EndpointClientState,
cancel: Option<&UrbCancellation>,
expected_reset: u64,
) -> Option<Errno> {
if cancel.is_some_and(UrbCancellation::is_cancelled) {
Some(errno!(ENOENT))
} else if !state.usable || state.reset_count != expected_reset {
Some(errno!(ESHUTDOWN))
} else {
None
}
}
fn cancellation_error(
&self,
cancel: Option<&UrbCancellation>,
expected_reset: u64,
) -> Option<Errno> {
let state = self.state.lock();
let error = Self::cancellation_error_locked(&state, cancel, expected_reset);
if !state.usable || state.reset_count != expected_reset {
// Re-assert `wake` so any other transfer waiting on this endpoint also observes the
// disable or reset.
self.wake_transfer();
}
error
}
/// Acquires exclusive ownership of the endpoint's transfer slot and records the endpoint's
/// current `reset_count`, waiting until any in-flight transfer or endpoint restart finishes,
/// the deadline expires, the calling task is interrupted by a fatal signal, `cancel` is
/// triggered, or the endpoint is disabled.
fn acquire_transfer(
&self,
current_task: &CurrentTask,
deadline: zx::MonotonicInstant,
cancel: Option<&UrbCancellation>,
) -> Result<AcquiredTransfer<'_>, Errno> {
loop {
let _waiter_guard = {
let mut state = self.state.lock();
if cancel.is_some_and(UrbCancellation::is_cancelled) {
return error!(ENOENT);
}
if !state.restarting {
if let Some(replacement) = state.replaced_by.as_ref().cloned() {
return Ok(AcquiredTransfer::Replaced(replacement));
}
if state.closed {
return error!(ENODEV);
}
if !state.usable {
return error!(ESHUTDOWN);
}
if !state.transfer_busy {
state.transfer_busy = true;
return Ok(AcquiredTransfer::Slot(
EndpointTransferSlotGuard { client: self },
state.reset_count,
));
}
}
let _ = self.transfer_released.signal(zx::Signals::USER_0, zx::Signals::NONE);
self.count_transfer_waiter(&mut state)
};
if cancel.is_some_and(UrbCancellation::is_cancelled) {
return error!(ENOENT);
}
match cancel {
Some(cancel) => wait_for_signals(
current_task,
&[
(&self.transfer_released, zx::Signals::USER_0),
(&cancel.wake, zx::Signals::USER_0),
],
deadline,
)?,
None => wait_for_signals(
current_task,
&[(&self.transfer_released, zx::Signals::USER_0)],
deadline,
)?,
};
}
}
fn mark_closed(&self) {
let mut state = self.state.lock();
state.usable = false;
state.closed = true;
let _ = self.transfer_released.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
/// Maps a channel error to an errno, recording whether the driver closed the channel.
fn channel_error(&self, error: &fidl::Error) -> Errno {
if error.is_closed() {
self.mark_closed();
}
fidl_error_to_errno(error)
}
/// Accounts for `completion`, returning it if it is the completion of the request in flight
/// on the current VMO.
fn account_completion(
&self,
transfer: &mut EndpointTransferState,
completion: fusb_endpoint::Completion,
) -> Option<fusb_endpoint::Completion> {
match completion_vmo_id(&completion) {
Some(id) if id != transfer.vmo_id => {
if let Some(count) = transfer.retired_vmos.get_mut(&id) {
*count = count.saturating_sub(1);
if *count == 0 {
transfer.retired_vmos.remove(&id);
let _ = self.proxy.unregister_vmos(&[id], zx::MonotonicInstant::INFINITE);
}
}
None
}
// Completions received while no request is in flight are stale.
_ if transfer.in_flight_count == 0 => None,
_ => {
transfer.in_flight_count -= 1;
Some(completion)
}
}
}
/// Accounts for the completions of the event `event`, returning the completion of the
/// request in flight on the current VMO, if any.
fn account_event(
&self,
transfer: &mut EndpointTransferState,
event: fusb_endpoint::EndpointEvent,
) -> Option<fusb_endpoint::Completion> {
let fusb_endpoint::EndpointEvent::OnCompletion { completion } = event;
let mut current = None;
for completion in completion {
if let Some(completion) = self.account_completion(transfer, completion) {
current = Some(completion);
}
}
current
}
/// Accounts for the completions already received, without blocking.
fn process_pending_events(&self, transfer: &mut EndpointTransferState) -> Result<(), Errno> {
loop {
match self.proxy.wait_for_event(zx::MonotonicInstant::INFINITE_PAST) {
Ok(event) => {
self.account_event(transfer, event);
}
Err(fidl::Error::ClientEvent(zx::Status::TIMED_OUT)) => return Ok(()),
Err(fidl::Error::UnexpectedSyncResponse) => continue,
Err(e) => return Err(self.channel_error(&e)),
}
}
}
/// Replaces the VMO by a new one if requests are still in flight on it.
fn retire_vmo_if_busy(&self, transfer: &mut EndpointTransferState) -> Result<(), Errno> {
if transfer.in_flight_count == 0 {
return Ok(());
}
if transfer.retired_vmos.len() >= MAX_RETIRED_VMOS {
return error!(EIO);
}
let vmo_id = transfer.next_vmo_id;
let vmo = Self::register_vmo(&self.proxy, vmo_id).inspect_err(|e| {
if e.code == ENODEV {
self.mark_closed();
}
})?;
transfer.next_vmo_id += 1;
transfer.retired_vmos.insert(transfer.vmo_id, transfer.in_flight_count);
transfer.vmo = vmo;
transfer.vmo_id = vmo_id;
transfer.in_flight_count = 0;
Ok(())
}
/// Checks that a transfer can start, and makes sure that no request is in flight on the
/// VMO.
fn begin_transfer(
&self,
transfer: &mut EndpointTransferState,
cancel: Option<&UrbCancellation>,
expected_reset: u64,
) -> Result<(), Errno> {
{
let state = self.state.lock();
if state.closed {
return error!(ENODEV);
}
if let Some(error) = Self::cancellation_error_locked(&state, cancel, expected_reset) {
return Err(error);
}
}
self.process_pending_events(transfer)?;
self.retire_vmo_if_busy(transfer)
}
/// Waits for the completion of the request in flight on the current VMO.
///
/// Returns the error of `cancellation_error` if the transfer is stopped while waiting, in
/// which case the request is still in flight.
fn wait_for_completion(
&self,
current_task: &CurrentTask,
transfer: &mut EndpointTransferState,
deadline: zx::MonotonicInstant,
cancel: Option<&UrbCancellation>,
expected_reset: u64,
) -> Result<fusb_endpoint::Completion, Errno> {
loop {
match self.proxy.wait_for_event(zx::MonotonicInstant::INFINITE_PAST) {
Ok(event) => {
if let Some(completion) = self.account_event(transfer, event) {
return Ok(completion);
}
continue;
}
Err(fidl::Error::ClientEvent(zx::Status::TIMED_OUT)) => {}
Err(fidl::Error::UnexpectedSyncResponse) => continue,
Err(e) => return Err(self.channel_error(&e)),
}
// Clear the wake signal before checking the conditions it reports, so that a later
// change is not missed.
let _ = self.wake.signal(zx::Signals::USER_0, zx::Signals::NONE);
if let Some(error) = self.cancellation_error(cancel, expected_reset) {
return Err(error);
}
let ch_signals = zx::Signals::CHANNEL_READABLE | zx::Signals::CHANNEL_PEER_CLOSED;
match cancel {
Some(cancel) => wait_for_signals(
current_task,
&[
(self.proxy.as_channel(), ch_signals),
(&self.wake, zx::Signals::USER_0),
(&cancel.wake, zx::Signals::USER_0),
],
deadline,
)?,
None => wait_for_signals(
current_task,
&[(self.proxy.as_channel(), ch_signals), (&self.wake, zx::Signals::USER_0)],
deadline,
)?,
};
}
}
/// Cancels the request in flight and waits for its completion, which is returned if it
/// arrives in time. Otherwise, the VMO is retired before the next transfer.
fn cancel_and_drain(
&self,
transfer: &mut EndpointTransferState,
) -> Option<fusb_endpoint::Completion> {
let deadline = zx::MonotonicInstant::after(self.cancel_drain_timeout);
match self.proxy.cancel_all(deadline) {
Ok(Ok(())) => {}
Ok(Err(_)) => return None,
Err(e) => {
let _ = self.channel_error(&e);
return None;
}
}
let mut current = None;
while transfer.in_flight_count > 0 {
match self.proxy.wait_for_event(deadline) {
Ok(event) => {
if let Some(completion) = self.account_event(transfer, event) {
current = Some(completion);
}
}
Err(fidl::Error::UnexpectedSyncResponse) => continue,
Err(e) => {
let _ = self.channel_error(&e);
break;
}
}
}
current
}
/// Queues one request of `len` bytes at offset 0 of the endpoint VMO and waits for its
/// completion. Returns the number of bytes transferred, which never exceeds `len`.
///
/// On timeout, interruption, or cancellation, the request is cancelled. If it nevertheless
/// completed successfully, its result is returned. Otherwise, `ETIMEDOUT`, `EINTR`, or the
/// error of `cancellation_error` is returned along with the number of bytes transferred
/// before the cancellation. If the completion reports more than `len` bytes, `EOVERFLOW` is
/// returned with `len` bytes.
fn transfer_chunk(
&self,
current_task: &CurrentTask,
transfer: &mut EndpointTransferState,
transfer_type: TransferType,
len: usize,
deadline: zx::MonotonicInstant,
cancel: Option<&UrbCancellation>,
expected_reset: u64,
) -> PartialTransfer<usize> {
let request = fusb_request::Request {
data: Some(vec![fusb_request::BufferRegion {
buffer: Some(fusb_request::Buffer::VmoId(transfer.vmo_id)),
offset: Some(0),
size: Some(len as u64),
..Default::default()
}]),
defer_completion: Some(false),
information: Some(transfer_type.request_info()),
..Default::default()
};
// Hold the endpoint state lock while checking for cancellation and queueing the request so
// that `disable` or `cancel_all` cannot run before `queue_requests` and leave the newly
// queued request uncancelled on a disabled or reset endpoint.
{
let state = self.state.lock();
if let Some(error) = Self::cancellation_error_locked(&state, cancel, expected_reset) {
return PartialTransfer::failed(0, error);
}
if let Err(e) = self.proxy.queue_requests(vec![request]) {
drop(state);
return PartialTransfer::failed(0, self.channel_error(&e));
}
}
transfer.in_flight_count += 1;
let (completion, cancel_error) = match self.wait_for_completion(
current_task,
transfer,
deadline,
cancel,
expected_reset,
) {
Ok(completion) => (completion, None),
Err(e)
if e.code == ETIMEDOUT
|| e.code == EINTR
|| e.code == ENOENT
|| e.code == ESHUTDOWN =>
{
match self.cancel_and_drain(transfer) {
// The request completed in hardware before `CancelAll` took effect: keep its
// actual completion status (whether `ZX_OK` or a hardware error like `EPIPE`).
Some(completion) if completion.status != Some(zx::sys::ZX_ERR_CANCELED) => {
(completion, None)
}
Some(completion) => (completion, Some(e)),
None => return PartialTransfer::failed(0, e),
}
}
Err(e) => return PartialTransfer::failed(0, e),
};
let transferred = completion.transfer_size.unwrap_or(0);
let Some(transferred) = usize::try_from(transferred).ok().filter(|&t| t <= len) else {
// Like a babble error on Linux: the device sent more data than requested.
return PartialTransfer::failed(len, errno!(EOVERFLOW));
};
if let Some(e) = cancel_error {
return PartialTransfer::failed(transferred, e);
}
match completion.status.unwrap_or(zx::sys::ZX_ERR_IO) {
zx::sys::ZX_OK => PartialTransfer::ok(transferred),
// The cancelled completion of a request may be received before the transfer notices
// why it was cancelled.
zx::sys::ZX_ERR_CANCELED => PartialTransfer::failed(
transferred,
self.cancellation_error(cancel, expected_reset)
.unwrap_or_else(|| zx_status_to_errno(zx::sys::ZX_ERR_CANCELED)),
),
status => PartialTransfer::failed(transferred, zx_status_to_errno(status)),
}
}
/// Sends `data`, in requests of `params.chunk_size` bytes. If `zero_packet` is set and the
/// length of `data` is a non-zero multiple of the endpoint's `wMaxPacketSize`, the transfer
/// is terminated by a zero-length packet, without letting another transfer use the endpoint
/// in between.
///
/// Returns the number of bytes sent, which is short if the device accepted less than a full
/// request. On failure, the number of bytes sent before the failure is returned with the
/// error.
fn bulk_out(
&self,
current_task: &CurrentTask,
params: EndpointTransferParams,
data: &[u8],
zero_packet: bool,
deadline: zx::MonotonicInstant,
cancel: Option<&UrbCancellation>,
) -> PartialTransfer<usize> {
// All transfers on this endpoint are serialized by `acquire_transfer` + `self.transfer`,
// and `begin_transfer` / `cancel_and_drain` ensure no earlier request is still using
// `transfer.vmo` (replacing it with a fresh VMO if a cancelled request did not drain in
// time).
let (_slot, expected_reset) = match self.acquire_transfer(current_task, deadline, cancel) {
Ok(AcquiredTransfer::Slot(slot, expected_reset)) => (slot, expected_reset),
Ok(AcquiredTransfer::Replaced(replacement)) => {
return replacement.bulk_out(
current_task,
params,
data,
zero_packet,
deadline,
cancel,
);
}
Err(e) => return PartialTransfer::failed(0, e),
};
#[cfg(test)]
{
let hook = self.state.lock().on_before_begin_transfer.take();
if let Some(hook) = hook {
hook();
}
}
let mut transfer = self.transfer.lock();
if let Err(e) = self.begin_transfer(&mut transfer, cancel, expected_reset) {
return PartialTransfer::failed(0, e);
}
let send_chunk = |transfer: &mut EndpointTransferState, len: usize| {
self.transfer_chunk(
current_task,
transfer,
params.transfer_type,
len,
deadline,
cancel,
expected_reset,
)
};
if data.is_empty() {
let result = send_chunk(&mut transfer, 0);
return PartialTransfer { value: 0, error: result.error };
}
let mut offset = 0;
while offset < data.len() {
if let Some(error) = self.cancellation_error(cancel, expected_reset) {
return PartialTransfer::failed(offset, error);
}
let chunk_len = min(data.len() - offset, params.chunk_size);
if transfer.vmo.write(&data[offset..offset + chunk_len], 0).is_err() {
return PartialTransfer::failed(offset, errno!(EIO));
}
let _ = transfer.vmo.op_range(zx::VmoOp::CACHE_CLEAN, 0, chunk_len as u64);
let result = send_chunk(&mut transfer, chunk_len);
offset += result.value;
if let Some(e) = result.error {
return PartialTransfer::failed(offset, e);
}
if result.value < chunk_len {
// The device did not accept the whole transfer, so no zero-length packet is
// needed to terminate it.
return PartialTransfer::ok(offset);
}
}
if zero_packet && offset % params.max_packet_size == 0 {
if let Some(e) = send_chunk(&mut transfer, 0).error {
return PartialTransfer::failed(offset, e);
}
}
PartialTransfer::ok(offset)
}
/// Receives up to `max_len` bytes, in requests of at most `params.chunk_size` bytes.
///
/// The transfer ends early on a short request. On failure, the data received before the
/// failure is returned with the error.
fn bulk_in(
&self,
current_task: &CurrentTask,
params: EndpointTransferParams,
max_len: usize,
deadline: zx::MonotonicInstant,
cancel: Option<&UrbCancellation>,
) -> PartialTransfer<Vec<u8>> {
// All transfers on this endpoint are serialized by `acquire_transfer` + `self.transfer`,
// and `begin_transfer` / `cancel_and_drain` ensure no earlier request is still using
// `transfer.vmo` (replacing it with a fresh VMO if a cancelled request did not drain in
// time).
let (_slot, expected_reset) = match self.acquire_transfer(current_task, deadline, cancel) {
Ok(AcquiredTransfer::Slot(slot, expected_reset)) => (slot, expected_reset),
Ok(AcquiredTransfer::Replaced(replacement)) => {
return replacement.bulk_in(current_task, params, max_len, deadline, cancel);
}
Err(e) => return PartialTransfer::failed(Vec::new(), e),
};
#[cfg(test)]
{
let hook = self.state.lock().on_before_begin_transfer.take();
if let Some(hook) = hook {
hook();
}
}
let mut transfer = self.transfer.lock();
if let Err(e) = self.begin_transfer(&mut transfer, cancel, expected_reset) {
return PartialTransfer::failed(Vec::new(), e);
}
if max_len == 0 {
let result = self.transfer_chunk(
current_task,
&mut transfer,
params.transfer_type,
0,
deadline,
cancel,
expected_reset,
);
return PartialTransfer { value: Vec::new(), error: result.error };
}
let mut data = Vec::new();
while data.len() < max_len {
if let Some(error) = self.cancellation_error(cancel, expected_reset) {
return PartialTransfer::failed(data, error);
}
let requested = min(max_len - data.len(), params.chunk_size);
let _ = transfer.vmo.op_range(zx::VmoOp::CACHE_CLEAN_INVALIDATE, 0, requested as u64);
let result = self.transfer_chunk(
current_task,
&mut transfer,
params.transfer_type,
requested,
deadline,
cancel,
expected_reset,
);
if result.value > 0 {
let _ = transfer.vmo.op_range(
zx::VmoOp::CACHE_CLEAN_INVALIDATE,
0,
result.value as u64,
);
let start = data.len();
data.resize(start + result.value, 0);
if transfer.vmo.read(&mut data[start..], 0).is_err() {
data.truncate(start);
return PartialTransfer::failed(data, errno!(EIO));
}
}
if let Some(e) = result.error {
return PartialTransfer::failed(data, e);
}
if result.value < requested {
// Short packet: the device has no more data for this transfer.
break;
}
}
PartialTransfer::ok(data)
}
}
/// Serializes the synchronous control transfers of a device.
///
/// The `Usb` protocol cannot cancel a control transfer, so a transfer whose caller is killed
/// keeps running on its kernel thread until it completes, which may be never if the caller gave
/// no timeout. Later transfers wait for it rather than piling up blocked kernel threads.
struct ControlTransferSlot {
/// Whether a control transfer is currently in flight. Protected by a mutex rather than an
/// atomic so that clearing/signaling the `released` event and updating `busy` are serialized
/// without lock-free ordering subtleties.
busy: LockDepMutex<bool, UsbfsControlSlotLock>,
/// `USER_0` is asserted when the slot is released.
released: zx::Event,
/// Number of transfers waiting in `acquire`.
#[cfg(test)]
waiters: AtomicUsize,
}
#[cfg(test)]
struct ControlWaiterCountGuard<'a>(&'a ControlTransferSlot);
#[cfg(test)]
impl Drop for ControlWaiterCountGuard<'_> {
fn drop(&mut self) {
self.0.waiters.fetch_sub(1, Ordering::SeqCst);
}
}
impl ControlTransferSlot {
#[cfg(test)]
fn count_control_waiter<'a>(&'a self) -> ControlWaiterCountGuard<'a> {
self.waiters.fetch_add(1, Ordering::SeqCst);
ControlWaiterCountGuard(self)
}
#[cfg(not(test))]
#[inline(always)]
fn count_control_waiter(&self) {}
fn new() -> Self {
Self {
busy: LockDepMutex::new(false),
released: zx::Event::create(),
#[cfg(test)]
waiters: AtomicUsize::new(0),
}
}
/// Takes the slot, waiting for the transfer holding it to complete. The wait is killable, and
/// is also interrupted if `cancel` is signalled.
fn acquire(
&self,
current_task: &CurrentTask,
cancel: Option<&UrbCancellation>,
) -> Result<(), Errno> {
loop {
let _waiter_guard = {
let mut busy = self.busy.lock();
if cancel.is_some_and(UrbCancellation::is_cancelled) {
return error!(ENOENT);
}
if !*busy {
*busy = true;
return Ok(());
}
// Clear the release signal while holding `busy` so a concurrent `release` cannot
// signal `USER_0` between the check and the clear.
let _ = self.released.signal(zx::Signals::USER_0, zx::Signals::NONE);
self.count_control_waiter()
};
if cancel.is_some_and(UrbCancellation::is_cancelled) {
return error!(ENOENT);
}
match cancel {
Some(cancel) => wait_for_signals(
current_task,
&[(&self.released, zx::Signals::USER_0), (&cancel.wake, zx::Signals::USER_0)],
zx::MonotonicInstant::INFINITE,
)?,
None => wait_for_signals(
current_task,
&[(&self.released, zx::Signals::USER_0)],
zx::MonotonicInstant::INFINITE,
)?,
};
}
}
fn release(&self) {
let mut busy = self.busy.lock();
*busy = false;
let _ = self.released.signal(zx::Signals::NONE, zx::Signals::USER_0);
}
}
/// Runs a control transfer on the calling thread, which blocks until it completes. A `timeout`
/// of `None` means no timeout.
fn run_control_transfer(
proxy: &fhardware_usb::UsbSynchronousProxy,
setup: &fusb_descriptor::UsbSetup,
timeout: Option<zx::MonotonicDuration>,
out_data: &[u8],
) -> Result<Vec<u8>, Errno> {
// The Usb protocol interprets a timeout of 0 as its default timeout, so request an infinite
// one explicitly.
let timeout_ns = timeout.unwrap_or(zx::MonotonicDuration::INFINITE).into_nanos();
let _waiting_guard = ThreadLockupDetector::pause_tracking();
fidl_result_to_errno(proxy.control_transfer(
setup,
timeout_ns,
out_data,
zx::MonotonicInstant::INFINITE,
))
}
/// Memory used by the outstanding transfers of usbfs, limited to `USBFS_MEMORY_LIMIT`.
#[derive(Debug, Default)]
pub(crate) struct UsbfsMemoryBudget {
/// Bytes charged to outstanding transfers. Updated with `AcqRel`/`Acquire` orderings so a
/// thread charging memory after another thread drops its `UsbfsMemoryCharge` observes the
/// released budget.
used: AtomicUsize,
}
impl UsbfsMemoryBudget {
/// Charges `amount` bytes to the budget, or returns `ENOMEM` if they do not fit.
fn charge(self: &Arc<Self>, amount: usize) -> Result<UsbfsMemoryCharge, Errno> {
self.used
.try_update(Ordering::AcqRel, Ordering::Acquire, |used| {
used.checked_add(amount).filter(|&total| total <= USBFS_MEMORY_LIMIT)
})
.map_err(|_| errno!(ENOMEM))?;
Ok(UsbfsMemoryCharge { budget: Arc::clone(self), amount })
}
}
/// Memory charged to a transfer, returned to the budget when dropped.
#[derive(Debug)]
struct UsbfsMemoryCharge {
budget: Arc<UsbfsMemoryBudget>,
amount: usize,
}
impl Drop for UsbfsMemoryCharge {
fn drop(&mut self) {
self.budget.used.fetch_sub(self.amount, Ordering::AcqRel);
}
}
/// Configuration state of a device, shared by all its file descriptors.
pub(crate) struct DeviceState {
/// `bConfigurationValue` of the active configuration (0 if unconfigured).
pub(crate) active_config: u8,
/// Active alternate setting of each interface (absent means 0).
alt_settings: HashMap<Ifno, u8>,
/// File descriptor (`UsbDeviceFile::id`) owning each claimed interface.
claims: HashMap<Ifno, u64>,
/// Endpoints enabled on the host controller, with the descriptors they were enabled with.
enabled_endpoints: HashMap<u8, ParsedEndpoint>,
/// Optional hook invoked in `submit_urb` after `prepare_urb` releases the device state lock.
#[cfg(test)]
pub(crate) on_urb_prepared: Option<Box<dyn FnOnce(&CurrentTask) + Send>>,
}
pub(crate) struct UsbDevice {
pub(crate) bus_num: u16,
pub(crate) dev_num: u16,
pub(crate) speed: u32,
pub(crate) device_descriptor: usb_device_descriptor,
parsed_configs: Vec<ParsedConfig>,
pub(crate) raw_descriptors: Vec<u8>,
pub(crate) sync_proxy: Option<Arc<fhardware_usb::UsbSynchronousProxy>>,
/// Serializes the ioctls that read or change the configuration, interface claims, and
/// endpoint lifecycle. Held across synchronous FIDL calls to the USB driver, so it is ordered
/// before `UninterruptibleLock`.
pub(crate) state: LockDepMutex<DeviceState, UsbfsDeviceStateLock>,
pub(crate) endpoints: LockDepMutex<HashMap<u8, Arc<UsbEndpointClient>>, UsbfsEndpointsLock>,
/// Set once the device has been removed from usbfs. Stored with `Release` ordering (while
/// holding `endpoints` in `on_disconnect`) and loaded with `Acquire` ordering so that any
/// thread observing `disconnected == false` under `endpoints` is guaranteed that
/// `on_disconnect` has not yet drained the endpoint map, and any thread observing
/// `disconnected == true` after a transfer error sees the disconnect state established by
/// `on_disconnect`.
disconnected: AtomicBool,
/// Upper bound on the time spent draining cancelled requests before retiring an endpoint
/// VMO.
pub(crate) cancel_drain_timeout: zx::MonotonicDuration,
control_slot: Arc<ControlTransferSlot>,
pub(crate) memory: Arc<UsbfsMemoryBudget>,
/// Monotonically increasing counter assigning a unique ID to each `UsbDeviceFile`. Accessed
/// with `Relaxed` ordering because the atomic `fetch_add` itself guarantees uniqueness and no
/// other memory state is published through the ID.
next_file_id: AtomicU64,
/// URB state of each open file description, by `UsbDeviceFile::id`.
open_files: LockDepMutex<HashMap<u64, Arc<UsbfsFileUrbs>>, UsbfsOpenFilesLock>,
}
impl UsbDevice {
pub(crate) fn new(
bus_num: u16,
dev_num: u16,
speed: u32,
device_descriptor: usb_device_descriptor,
config_descriptors: Vec<u8>,
) -> Self {
let mut raw_descriptors = Vec::new();
let desc_bytes = zerocopy::IntoBytes::as_bytes(&device_descriptor);
raw_descriptors.extend_from_slice(desc_bytes);
raw_descriptors.extend_from_slice(&config_descriptors);
let parsed_configs = parse_config_descriptors(&config_descriptors);
Self {
bus_num,
dev_num,
speed,
device_descriptor,
parsed_configs,
raw_descriptors,
sync_proxy: None,
state: LockDepMutex::new(DeviceState {
active_config: 1,
alt_settings: HashMap::new(),
claims: HashMap::new(),
enabled_endpoints: HashMap::new(),
#[cfg(test)]
on_urb_prepared: None,
}),
endpoints: LockDepMutex::new(HashMap::new()),
disconnected: AtomicBool::new(false),
cancel_drain_timeout: DEFAULT_CANCEL_DRAIN_TIMEOUT,
control_slot: Arc::new(ControlTransferSlot::new()),
memory: Arc::default(),
next_file_id: AtomicU64::new(1),
open_files: LockDepMutex::new(HashMap::new()),
}
}
/// Computes the Linux USB device minor number: `(bus - 1) * 128 + (dev_num - 1)`.
pub(crate) fn minor(&self) -> u32 {
debug_assert!(self.bus_num >= 1 && self.dev_num >= 1);
(u32::from(self.bus_num).saturating_sub(1) * 128)
+ u32::from(self.dev_num).saturating_sub(1)
}
pub(crate) fn speed_string(&self) -> &'static str {
match self.speed {
USB_SPEED_LOW => "1.5\n",
USB_SPEED_FULL => "12\n",
USB_SPEED_HIGH => "480\n",
USB_SPEED_WIRELESS => "480\n",
USB_SPEED_SUPER => "5000\n",
USB_SPEED_SUPER_PLUS => "10000\n",
_ => "unknown\n",
}
}
/// Returns the proxy to the USB device, or `ENODEV` if there is none.
fn proxy(&self) -> Result<&Arc<fhardware_usb::UsbSynchronousProxy>, Errno> {
self.sync_proxy.as_ref().ok_or_else(|| errno!(ENODEV))
}
/// Returns the memory charged to the outstanding transfers of the device.
#[cfg(test)]
pub(crate) fn memory_used(&self) -> usize {
self.memory.used.load(Ordering::Acquire)
}
/// Returns the number of transfers waiting in `acquire_transfer` on `ep_addr`.
#[cfg(test)]
pub(crate) fn endpoint_transfer_waiters(&self, ep_addr: u8) -> usize {
self.endpoints.lock().get(&ep_addr).map_or(0, |client| client.state.lock().transfer_waiters)
}
/// Installs a one-shot hook invoked right before `begin_transfer` on `ep_addr`.
#[cfg(test)]
pub(crate) fn set_on_before_begin_transfer(
&self,
ep_addr: u8,
hook: impl FnOnce() + Send + 'static,
) {
let client = self.endpoints.lock().get(&ep_addr).cloned().expect("endpoint connected");
client.state.lock().on_before_begin_transfer = Some(Box::new(hook));
}
/// Returns the number of control transfers waiting in `ControlTransferSlot::acquire`.
#[cfg(test)]
pub(crate) fn control_transfer_waiters(&self) -> usize {
self.control_slot.waiters.load(Ordering::SeqCst)
}
/// Calls `restart_endpoint` on `ep_addr` on a shallow copy of this device that has no
/// `sync_proxy`, returning its result while sharing the live `endpoints` map, and returns
/// whether the endpoint client was left in `restarting`.
#[cfg(test)]
pub(crate) fn restart_endpoint_without_proxy_for_test(
&self,
ep_addr: u8,
) -> (Result<(), Errno>, bool) {
let mut state = self.state.lock();
let enabled = state.enabled_endpoints.clone();
let unproxied = Self {
bus_num: self.bus_num,
dev_num: self.dev_num,
speed: self.speed,
device_descriptor: self.device_descriptor,
parsed_configs: self.parsed_configs.clone(),
raw_descriptors: self.raw_descriptors.clone(),
sync_proxy: None,
state: LockDepMutex::new(DeviceState {
active_config: state.active_config,
alt_settings: state.alt_settings.clone(),
claims: state.claims.clone(),
enabled_endpoints: enabled,
on_urb_prepared: None,
}),
endpoints: LockDepMutex::new(self.endpoints.lock().clone()),
disconnected: AtomicBool::new(false),
cancel_drain_timeout: self.cancel_drain_timeout,
control_slot: Arc::clone(&self.control_slot),
memory: Arc::clone(&self.memory),
next_file_id: AtomicU64::new(1),
open_files: LockDepMutex::new(HashMap::new()),
};
let result = unproxied.restart_endpoint(&mut state, ep_addr);
let endpoints = unproxied.endpoints.into_inner();
let restarting =
endpoints.get(&ep_addr).is_some_and(|client| client.state.lock().restarting);
*self.endpoints.lock() = endpoints;
(result, restarting)
}
/// Returns whether any open file description has a waiter registered on `UsbfsFileUrbs`.
#[cfg(test)]
pub(crate) fn has_reap_waiter(&self) -> bool {
self.open_files().iter().any(|file| !file.waiters.is_empty())
}
/// Returns the active configuration, or `None` if the device is unconfigured.
fn active_config(&self, state: &DeviceState) -> Option<&ParsedConfig> {
self.parsed_configs.iter().find(|c| c.config_value == state.active_config)
}
fn has_interface(&self, state: &DeviceState, ifno: Ifno) -> bool {
self.active_config(state)
.is_some_and(|c| c.interfaces.iter().any(|i| i.interface_number == ifno.as_u8()))
}
/// Returns the alternate settings of interface `ifno` in the active configuration.
fn interface_alts<'a>(
&'a self,
state: &DeviceState,
ifno: Ifno,
) -> impl Iterator<Item = &'a ParsedInterfaceAlt> {
self.active_config(state)
.into_iter()
.flat_map(|c| c.interfaces.iter())
.filter(move |i| i.interface_number == ifno.as_u8())
}
fn find_interface_alt(
&self,
state: &DeviceState,
interface_number: u8,
alt: u8,
) -> Option<&ParsedInterfaceAlt> {
self.active_config(state)?
.interfaces
.iter()
.find(|i| i.interface_number == interface_number && i.alt_setting == alt)
}
/// Returns the addresses of the endpoints of interface `ifno` in all its alternate settings
/// of the active configuration.
fn interface_endpoint_addresses(&self, state: &DeviceState, ifno: Ifno) -> Vec<u8> {
let mut addresses: Vec<u8> = self
.interface_alts(state, ifno)
.flat_map(|i| i.endpoints.iter().map(ParsedEndpoint::address))
.collect();
addresses.sort_unstable();
addresses.dedup();
addresses
}
/// Finds the interface number owning endpoint `ep` in any alternate setting of the active
/// configuration.
/// Returns `EINVAL` if bits outside `USB_DIR_IN | USB_ENDPOINT_NUMBER_MASK` are set, `ESRCH`
/// if the device is unconfigured, and `ENOENT` if no interface has the endpoint.
fn find_interface_for_endpoint(&self, state: &DeviceState, ep: u32) -> Result<Ifno, Errno> {
if (ep & !(USB_DIR_IN | USB_ENDPOINT_NUMBER_MASK)) != 0 {
return error!(EINVAL);
}
let config = self.active_config(state).ok_or_else(|| errno!(ESRCH))?;
config
.interfaces
.iter()
.find(|i| i.endpoints.iter().any(|e| u32::from(e.address()) == ep))
.map_or_else(|| error!(ENOENT), |i| Ifno::try_from(u32::from(i.interface_number)))
}
/// Returns the descriptors of endpoint `ep_addr` in the active alternate settings, if any.
fn active_endpoint(&self, state: &DeviceState, ep_addr: u8) -> Option<&ParsedEndpoint> {
self.active_config(state)?
.interfaces
.iter()
.filter(|i| {
let ifno = Ifno(i.interface_number);
i.alt_setting == state.alt_settings.get(&ifno).copied().unwrap_or(0)
})
.flat_map(|i| i.endpoints.iter())
.find(|e| e.address() == ep_addr)
}
/// Returns the URB state of the open file descriptions of the device.
fn open_files(&self) -> Vec<Arc<UsbfsFileUrbs>> {
self.open_files.lock().values().cloned().collect()
}
/// Returns whether any open file description has a pending URB on `ep_addr`.
fn has_pending_urbs(&self, ep_addr: u8) -> bool {
self.open_files()
.iter()
.any(|file| file.book.lock().pending.values().any(|urb| urb.endpoint == ep_addr))
}
pub(crate) fn on_disconnect(&self) {
// Set `disconnected` and drain `endpoints` before `start_kill_urbs` locks `book` so any
// concurrent `submit_urb` that locks `book` after `start_kill_urbs` sees
// `disconnected == true` and fails with `ENODEV`, without `on_disconnect` acquiring
// `state` (which may be held by a synchronous FIDL call to the disconnecting driver).
// Mark the URBs before calling `client.disable()` so URBs whose requests are cancelled
// complete with `-ESHUTDOWN` and any concurrent synchronous `USBDEVFS_BULK` holding an
// endpoint transfer slot is woken up before `finish_kill_urbs` waits for URB workers.
let endpoints: Vec<_> = {
let mut endpoints = self.endpoints.lock();
self.disconnected.store(true, Ordering::Release);
endpoints.drain().collect()
};
let files_and_running: Vec<_> = self
.open_files()
.into_iter()
.map(|file| {
let running = file.start_kill_urbs(UrbCancelReason::Shutdown, |_| true);
(file, running)
})
.collect();
for (_ep_addr, client) in endpoints {
client.disable();
}
for (file, running) in files_and_running {
file.finish_kill_urbs(running);
file.waiters.notify_fd_events(FdEvents::POLLHUP | FdEvents::POLLERR);
}
}
/// Returns a client for endpoint `endpoint` of the active alternate settings, enabling the
/// endpoint on the host controller and connecting to it if needed.
fn get_or_connect_endpoint(
&self,
state: &mut DeviceState,
endpoint: &ParsedEndpoint,
) -> Result<Arc<UsbEndpointClient>, Errno> {
let proxy = self.proxy()?;
let ep_addr = endpoint.address();
let existing = {
let mut endpoints = self.endpoints.lock();
if self.disconnected.load(Ordering::Acquire) {
return error!(ENODEV);
}
match endpoints.get(&ep_addr).cloned() {
Some(client) if client.is_closed() => {
// The driver closes the channel only after disabling the endpoint, so the
// endpoint can be enabled and connected again right away.
endpoints.remove(&ep_addr);
state.enabled_endpoints.remove(&ep_addr);
None
}
existing => existing,
}
};
if let Some(client) = &existing {
if state.enabled_endpoints.get(&ep_addr) == Some(endpoint) {
return Ok(Arc::clone(client));
}
}
// If the endpoint is still enabled with other descriptors, disable it first. The
// lifecycle code disables endpoints whose interface changes, so this is a safety net.
self.disable_endpoint(state, ep_addr)?;
fidl_result_to_errno(proxy.enable_endpoint(
&endpoint.descriptor,
&endpoint.ss_companion,
true,
zx::MonotonicInstant::INFINITE,
))?;
self.connect_enabled_endpoint(state, endpoint, existing)
}
/// Connects (or re-enables `existing`) and registers the client for `endpoint`, which has
/// just been enabled on the host controller. Rolls back `enable_endpoint` on failure.
fn connect_enabled_endpoint(
&self,
state: &mut DeviceState,
endpoint: &ParsedEndpoint,
existing: Option<Arc<UsbEndpointClient>>,
) -> Result<Arc<UsbEndpointClient>, Errno> {
let proxy = self.proxy()?;
let ep_addr = endpoint.address();
let client = match existing {
Some(client) => client,
None => match UsbEndpointClient::connect(proxy, ep_addr, self.cancel_drain_timeout) {
Ok(client) => client,
Err(e) => {
// Roll back the enable so a later attempt does not fail on an endpoint that
// is already enabled.
let _ = proxy.enable_endpoint(
&endpoint.descriptor,
&endpoint.ss_companion,
false,
zx::MonotonicInstant::INFINITE,
);
return Err(e);
}
},
};
{
let mut endpoints = self.endpoints.lock();
// `on_disconnect` may have run while `enable_endpoint` or `connect` was in flight
// without the `endpoints` lock held. Re-check `disconnected` under `endpoints` before
// publishing the enabled client so we never leave an active endpoint in `endpoints`
// after disconnect.
if self.disconnected.load(Ordering::Acquire) {
drop(endpoints);
client.disable();
let _ = proxy.enable_endpoint(
&endpoint.descriptor,
&endpoint.ss_companion,
false,
zx::MonotonicInstant::INFINITE,
);
return error!(ENODEV);
}
client.enable();
endpoints.insert(ep_addr, Arc::clone(&client));
}
state.enabled_endpoints.insert(ep_addr, endpoint.clone());
Ok(client)
}
/// Updates the endpoint client of pending URBs on `ep_addr` across all open file
/// descriptions so `kill_locked` wakes the replacement client after a reconnect.
fn replace_pending_urb_clients(&self, ep_addr: u8, new_client: &Arc<UsbEndpointClient>) {
for file in self.open_files() {
let mut book = file.book.lock();
for urb in book.pending.values_mut() {
if urb.endpoint == ep_addr && urb.client.is_some() {
urb.client = Some(Arc::clone(new_client));
}
}
}
}
/// Disables endpoint `ep_addr` on the host controller if it is enabled, after cancelling its
/// in-flight requests.
///
/// This does not wait for the transfer that queued the requests: it is woken up and drains
/// their completions itself, within the cancel drain timeout.
fn disable_endpoint(&self, state: &mut DeviceState, ep_addr: u8) -> Result<(), Errno> {
let Some(endpoint) = state.enabled_endpoints.remove(&ep_addr) else {
return Ok(());
};
let client = self.endpoints.lock().get(&ep_addr).cloned();
if let Some(client) = client {
client.disable();
if client.is_closed() {
// The driver disabled the endpoint before closing the channel.
self.endpoints.lock().remove(&ep_addr);
return Ok(());
}
}
fidl_result_to_errno(self.proxy()?.enable_endpoint(
&endpoint.descriptor,
&endpoint.ss_companion,
false,
zx::MonotonicInstant::INFINITE,
))
}
/// Disables all the enabled endpoints.
fn disable_all_endpoints(&self, state: &mut DeviceState) -> Result<(), Errno> {
let addresses: Vec<u8> = state.enabled_endpoints.keys().copied().collect();
for ep_addr in addresses {
self.disable_endpoint(state, ep_addr)?;
}
Ok(())
}
/// Disables and immediately re-enables `ep_addr` on the host controller while holding back
/// queued transfers in `acquire_transfer` so they run on the restarted endpoint instead of
/// failing with `ESHUTDOWN`.
fn restart_endpoint(&self, state: &mut DeviceState, ep_addr: u8) -> Result<(), Errno> {
let proxy = self.proxy()?;
if let Some(endpoint) = state.enabled_endpoints.get(&ep_addr).cloned() {
let client = self.endpoints.lock().get(&ep_addr).cloned();
if let Some(client) = client {
client.begin_restart();
if !client.is_closed() {
let toggled = fidl_result_to_errno(proxy.enable_endpoint(
&endpoint.descriptor,
&endpoint.ss_companion,
false,
zx::MonotonicInstant::INFINITE,
))
.and_then(|()| {
fidl_result_to_errno(proxy.enable_endpoint(
&endpoint.descriptor,
&endpoint.ss_companion,
true,
zx::MonotonicInstant::INFINITE,
))
});
return match toggled {
Ok(()) if !client.is_closed() => {
client.finish_restart();
Ok(())
}
Ok(()) => {
// The driver closed the endpoint channel when disabling the
// endpoint; connect a new client for the re-enabled endpoint and
// redirect any transfer waiting on the old client.
self.endpoints.lock().remove(&ep_addr);
state.enabled_endpoints.remove(&ep_addr);
match self.connect_enabled_endpoint(state, &endpoint, None) {
Ok(new_client) => {
self.replace_pending_urb_clients(ep_addr, &new_client);
client.replace_with(new_client);
Ok(())
}
Err(e) => {
client.disable();
Err(e)
}
}
}
Err(e) => {
state.enabled_endpoints.remove(&ep_addr);
client.disable();
Err(e)
}
};
}
self.endpoints.lock().remove(&ep_addr);
state.enabled_endpoints.remove(&ep_addr);
return match self.active_endpoint(state, ep_addr).cloned() {
Some(active) => match self.get_or_connect_endpoint(state, &active) {
Ok(new_client) => {
self.replace_pending_urb_clients(ep_addr, &new_client);
client.replace_with(new_client);
Ok(())
}
Err(e) => {
client.disable();
Err(e)
}
},
None => {
client.disable();
Ok(())
}
};
}
}
if let Some(endpoint) = self.active_endpoint(state, ep_addr).cloned() {
let new_client = self.get_or_connect_endpoint(state, &endpoint)?;
self.replace_pending_urb_clients(ep_addr, &new_client);
}
Ok(())
}
/// Cancels the in-flight requests of endpoint `ep_addr`.
pub(crate) fn reset_endpoint_state(&self, ep_addr: u8) {
let client = self.endpoints.lock().get(&ep_addr).cloned();
if let Some(client) = client {
client.cancel_all();
}
}
/// Releases the claim on interface `ifno`, killing the URBs of its owner on the interface's
/// endpoints and cancelling their in-flight requests. If a non-zero alternate setting was
/// active, restores alternate setting 0.
fn drop_claim(&self, state: &mut DeviceState, ifno: Ifno) {
let owner = state.claims.remove(&ifno);
let endpoints = self.interface_endpoint_addresses(state, ifno);
// Mark and wake the owner's URBs before cancelling or disabling the interface's endpoints
// so any concurrent synchronous transfer holding an endpoint slot is also aborted, then
// wait for running URBs before restoring alternate setting 0.
let owner_urbs = owner.and_then(|id| self.open_files.lock().get(&id).cloned());
let running = owner_urbs.as_ref().map(|urbs| {
urbs.start_kill_urbs(UrbCancelReason::Killed, |urb| endpoints.contains(&urb.endpoint))
});
let alt = state.alt_settings.remove(&ifno).unwrap_or(0);
let restore_alt0 = alt != 0 && !self.disconnected.load(Ordering::Acquire);
if restore_alt0 {
for &ep_addr in &endpoints {
let _ = self.disable_endpoint(state, ep_addr);
}
} else {
for &ep_addr in &endpoints {
self.reset_endpoint_state(ep_addr);
}
}
if let (Some(urbs), Some(running)) = (&owner_urbs, running) {
urbs.finish_kill_urbs(running);
}
if restore_alt0 {
if let Ok(proxy) = self.proxy() {
let _ = proxy.set_interface(ifno.as_u8(), 0, zx::MonotonicInstant::INFINITE);
}
}
}
/// Maps the error of a transfer to `ENODEV` if the device was disconnected.
fn transfer_error(&self, error: Errno) -> Errno {
if self.disconnected.load(Ordering::Acquire) { errno!(ENODEV) } else { error }
}
/// Sends `data` to OUT endpoint `client`. See `UsbEndpointClient::bulk_out`.
fn endpoint_bulk_out(
&self,
current_task: &CurrentTask,
client: &UsbEndpointClient,
params: EndpointTransferParams,
data: &[u8],
zero_packet: bool,
deadline: zx::MonotonicInstant,
cancel: Option<&UrbCancellation>,
) -> PartialTransfer<usize> {
if self.disconnected.load(Ordering::Acquire) {
return PartialTransfer::failed(0, errno!(ENODEV));
}
let result = client.bulk_out(current_task, params, data, zero_packet, deadline, cancel);
PartialTransfer { value: result.value, error: result.error.map(|e| self.transfer_error(e)) }
}
/// Receives up to `max_len` bytes from IN endpoint `client`. See
/// `UsbEndpointClient::bulk_in`.
fn endpoint_bulk_in(
&self,
current_task: &CurrentTask,
client: &UsbEndpointClient,
params: EndpointTransferParams,
max_len: usize,
deadline: zx::MonotonicInstant,
cancel: Option<&UrbCancellation>,
) -> PartialTransfer<Vec<u8>> {
if self.disconnected.load(Ordering::Acquire) {
return PartialTransfer::failed(Vec::new(), errno!(ENODEV));
}
let result = client.bulk_in(current_task, params, max_len, deadline, cancel);
PartialTransfer { value: result.value, error: result.error.map(|e| self.transfer_error(e)) }
}
/// Issues a control transfer on endpoint 0 and returns the data read, if any.
///
/// The transfer runs on a kernel thread so that the calling task can be killed while it
/// waits. If it is, `EINTR` is returned and the transfer keeps running in the background
/// until it completes, which never happens if `timeout` is `None` and the device does not
/// answer. Control transfers are serialized by the control slot, so at most one such
/// transfer runs per device.
fn control_transfer(
&self,
current_task: &CurrentTask,
setup: fusb_descriptor::UsbSetup,
timeout: Option<zx::MonotonicDuration>,
out_data: Vec<u8>,
) -> Result<Vec<u8>, Errno> {
let proxy = Arc::clone(self.proxy()?);
self.control_slot.acquire(current_task, None)?;
let done = Arc::new(zx::Event::create());
let (sender, receiver) = sync_channel(1);
let done_for_transfer = Arc::clone(&done);
let slot = Arc::clone(&self.control_slot);
let closure = move |_: &CurrentTask| {
let result = run_control_transfer(&proxy, &setup, timeout, &out_data);
slot.release();
let _ = sender.send(result);
let _ = done_for_transfer.signal(zx::Signals::NONE, zx::Signals::USER_0);
};
let request = SpawnRequestBuilder::new()
.with_debug_name("usbfs-control-transfer")
.with_sync_closure(closure)
.build();
current_task.kernel().kthreads.spawner().spawn_from_request(request);
wait_for_signals(
current_task,
&[(done.as_ref(), zx::Signals::USER_0)],
zx::MonotonicInstant::INFINITE,
)?;
receiver.recv().map_err(|_| errno!(EIO))?
}
}
/// Why a URB was cancelled, which determines its completion status.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[repr(u8)]
enum UrbCancelReason {
/// Killed by `USBDEVFS_DISCARDURB`, `USBDEVFS_RESET`, the release of its interface, or the
/// close of its file description.
Killed = 1,
/// Unlinked because an earlier URB of the same bulk transfer failed.
Unlinked = 2,
/// The device was disconnected.
Shutdown = 3,
}
impl UrbCancelReason {
fn from_u8(value: u8) -> Option<Self> {
match value {
1 => Some(Self::Killed),
2 => Some(Self::Unlinked),
3 => Some(Self::Shutdown),
_ => None,
}
}
/// Returns the `status` of a URB cancelled for this reason.
fn status(self) -> i32 {
match self {
Self::Killed => urb_status(&errno!(ENOENT)),
Self::Unlinked => urb_status(&errno!(ECONNRESET)),
Self::Shutdown => urb_status(&errno!(ESHUTDOWN)),
}
}
}
/// Cancellation state of a URB, shared with the worker processing it.
struct UrbCancellation {
/// Set once the URB is cancelled. Synchronized with `Release`/`Acquire` orderings so a
/// thread observing `cancelled == true` also sees `reason`.
cancelled: AtomicBool,
/// `UrbCancelReason` of the first cancellation, or 0. Updated with `AcqRel`/`Acquire` so the
/// first reason is published to readers observing `cancelled`.
reason: AtomicU8,
/// Signaled with `USER_0` when the URB is cancelled, waking any thread waiting to acquire
/// an endpoint transfer slot or the control transfer slot.
wake: zx::Event,
}
impl Default for UrbCancellation {
fn default() -> Self {
Self {
cancelled: AtomicBool::new(false),
reason: AtomicU8::new(0),
wake: zx::Event::create(),
}
}
}
impl UrbCancellation {
/// Cancels the URB for `reason`. Returns whether it was not cancelled already.
fn cancel(&self, reason: UrbCancelReason) -> bool {
let first = self
.reason
.compare_exchange(0, reason as u8, Ordering::AcqRel, Ordering::Acquire)
.is_ok();
self.cancelled.store(true, Ordering::Release);
let _ = self.wake.signal(zx::Signals::NONE, zx::Signals::USER_0);
first
}
fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Acquire)
}
fn reason(&self) -> Option<UrbCancelReason> {
UrbCancelReason::from_u8(self.reason.load(Ordering::Acquire))
}
}
/// Transfer of a URB.
enum UrbTransfer {
/// Control transfer on endpoint 0.
Control { setup: fusb_descriptor::UsbSetup, out_data: Vec<u8> },
/// Bulk or interrupt transfer on another endpoint.
Endpoint {
client: Arc<UsbEndpointClient>,
params: EndpointTransferParams,
out_data: Vec<u8>,
zero_packet: bool,
},
}
/// A URB waiting in the queue of its endpoint, or being processed by the worker of the queue.
struct QueuedUrb {
id: u64,
transfer: UrbTransfer,
is_in: bool,
/// Length of the data stage, excluding the setup packet of control URBs.
length: usize,
/// Whether a short IN transfer fails with `-EREMOTEIO` (`USBDEVFS_URB_SHORT_NOT_OK`).
short_not_ok: bool,
/// Whether the URB targets a bulk endpoint, whose failures unlink the continuation URBs
/// queued after it.
is_bulk: bool,
/// Whether the URB continues the transfer of the previous URB of its bulk endpoint
/// (`USBDEVFS_URB_BULK_CONTINUATION`).
bulk_continuation: bool,
cancellation: Arc<UrbCancellation>,
/// Shared with `PendingUrb`/`CompletedUrb` so the memory budget stays charged until both the
/// worker thread and reaper have finished with the URB (even when a cancelled control URB
/// completes in the book while its worker thread is still running).
_charge: Arc<UsbfsMemoryCharge>,
}
/// A submitted URB that has not completed yet.
struct PendingUrb {
/// Address of the `struct usbdevfs_urb` of the URB, which identifies it to userspace.
user_addr: UserAddress,
is_32bit: bool,
/// Address of the endpoint of the URB, 0 for control URBs.
endpoint: u8,
/// Address of the data stage buffer, after the setup packet of control URBs.
data_addr: UserAddress,
cancellation: Arc<UrbCancellation>,
/// Client of the endpoint of a bulk or interrupt URB, woken up to cancel it.
client: Option<Arc<UsbEndpointClient>>,
/// Signaled with `USER_0` when the URB completes.
completed: Arc<zx::Event>,
charge: Arc<UsbfsMemoryCharge>,
}
/// Result of a URB.
struct UrbResult {
status: i32,
actual_length: usize,
/// Data received by an IN URB.
in_data: Vec<u8>,
}
impl UrbResult {
fn cancelled(reason: UrbCancelReason) -> Self {
Self { status: reason.status(), actual_length: 0, in_data: Vec::new() }
}
}
/// A completed URB waiting to be reaped.
struct CompletedUrb {
user_addr: UserAddress,
is_32bit: bool,
data_addr: UserAddress,
result: UrbResult,
/// Released when the URB is reaped (and its worker has finished).
_charge: Arc<UsbfsMemoryCharge>,
}
/// URBs of an endpoint, processed in submission order by at most one worker kernel thread, like
/// the endpoint queues of a host controller.
#[derive(Default)]
struct EndpointUrbQueue {
queued: VecDeque<QueuedUrb>,
/// ID of the URB being processed by the worker.
running: Option<u64>,
/// Whether a worker processes the queue.
has_worker: bool,
}
#[derive(Default)]
struct UrbBook {
next_id: u64,
/// Submitted URBs that have not completed, by ID, i.e. in submission order.
pending: BTreeMap<u64, PendingUrb>,
/// Completed URBs waiting to be reaped, in completion order.
completed: VecDeque<CompletedUrb>,
/// URB queue of each endpoint address, 0 for control URBs.
queues: HashMap<u8, EndpointUrbQueue>,
/// Bulk endpoints on which continuation URBs are rejected because a transfer failed.
disabled_bulk_endpoints: HashSet<u8>,
}
/// URBs of a usbfs file description, shared with the workers processing them and with its
/// device.
#[derive(Default)]
struct UsbfsFileUrbs {
book: LockDepMutex<UrbBook, UsbfsFileUrbsLock>,
/// Notified when URBs complete and when the device is disconnected.
waiters: WaitQueue,
}
impl UsbfsFileUrbs {
fn notify_completion(&self) {
self.waiters.notify_fd_events(FdEvents::POLLOUT | FdEvents::POLLWRNORM);
}
/// Moves pending URB `id` to the completed URBs with `result`. Returns false if the URB is
/// not pending, e.g. because it completed when it was cancelled.
fn complete_locked(book: &mut UrbBook, id: u64, result: UrbResult) -> bool {
let Some(urb) = book.pending.remove(&id) else {
return false;
};
let _ = urb.completed.signal(zx::Signals::NONE, zx::Signals::USER_0);
book.completed.push_back(CompletedUrb {
user_addr: urb.user_addr,
is_32bit: urb.is_32bit,
data_addr: urb.data_addr,
result,
_charge: urb.charge,
});
true
}
/// Cancels pending URB `id` for `reason`.
///
/// URBs still queued never reached the device, and control transfers cannot be cancelled,
/// so both complete right away; the late result of a running control transfer is dropped.
/// A running bulk or interrupt URB is woken up, and completes once its request is
/// cancelled: the event signaled on its completion is returned.
fn kill_locked(book: &mut UrbBook, id: u64, reason: UrbCancelReason) -> Option<Arc<zx::Event>> {
let urb = book.pending.get(&id)?;
let completed = Arc::clone(&urb.completed);
if !urb.cancellation.cancel(reason) {
// Another cancellation is completing the URB.
return Some(completed);
}
let client = urb.client.clone();
let queue = book.queues.entry(urb.endpoint).or_default();
if queue.running == Some(id) {
if let Some(client) = client {
client.wake_transfer();
return Some(completed);
}
track_stub!(
TODO("https://fxbug.dev/571954283"),
"usbfs cancel in-flight control transfer"
);
} else {
queue.queued.retain(|queued| queued.id != id);
}
Self::complete_locked(book, id, UrbResult::cancelled(reason));
None
}
/// Marks the pending URBs selected by `filter` as cancelled for `reason`, completing queued
/// and control URBs immediately and waking running endpoint URBs. Returns the completion
/// events of the running endpoint URBs so the caller can wait for them via
/// `finish_kill_urbs`.
fn start_kill_urbs(
&self,
reason: UrbCancelReason,
filter: impl Fn(&PendingUrb) -> bool,
) -> Vec<Arc<zx::Event>> {
let mut book = self.book.lock();
let ids: Vec<u64> =
book.pending.iter().filter(|(_, urb)| filter(urb)).map(|(&id, _)| id).collect();
let mut running = Vec::new();
for id in ids {
if let Some(completed) = Self::kill_locked(&mut book, id, reason) {
running.push(completed);
}
}
running
}
/// Waits for the running URBs returned by `start_kill_urbs` to complete and notifies
/// completion waiters.
fn finish_kill_urbs(&self, running: Vec<Arc<zx::Event>>) {
for completed in running {
let _ = completed.wait_one(zx::Signals::USER_0, zx::MonotonicInstant::INFINITE);
}
self.notify_completion();
}
/// Cancels the pending URBs selected by `filter` for `reason`, waiting for any running
/// ones to complete.
fn kill_urbs(&self, reason: UrbCancelReason, filter: impl Fn(&PendingUrb) -> bool) {
let running = self.start_kill_urbs(reason, filter);
self.finish_kill_urbs(running);
}
/// Unlinks the continuation URBs queued on bulk endpoint `endpoint` after a URB of the
/// endpoint failed. Unless a new transfer is queued, continuation URBs are rejected until
/// one is submitted.
fn unlink_bulk_continuations_locked(book: &mut UrbBook, endpoint: u8) {
let queue = book.queues.entry(endpoint).or_default();
let mut unlinked = Vec::new();
let mut new_transfer_queued = false;
while let Some(urb) = queue.queued.front() {
if !urb.bulk_continuation {
new_transfer_queued = true;
break;
}
if let Some(urb) = queue.queued.pop_front() {
urb.cancellation.cancel(UrbCancelReason::Unlinked);
unlinked.push(urb.id);
}
}
if !new_transfer_queued {
book.disabled_bulk_endpoints.insert(endpoint);
}
for id in unlinked {
Self::complete_locked(book, id, UrbResult::cancelled(UrbCancelReason::Unlinked));
}
}
/// Processes the URBs queued on `endpoint` in order, until the queue is empty.
fn run_worker(&self, device: &UsbDevice, endpoint: u8, current_task: &CurrentTask) {
loop {
let urb = {
let mut book = self.book.lock();
let queue = book.queues.entry(endpoint).or_default();
let Some(urb) = queue.queued.pop_front() else {
queue.has_worker = false;
return;
};
queue.running = Some(urb.id);
urb
};
let urb_id = urb.id;
let is_bulk = urb.is_bulk;
let result = device.run_urb(current_task, &urb);
// Drop `urb` (and its `Arc<UsbfsMemoryCharge>`) before signaling `urb.completed` in
// `complete_locked` so a caller waiting on `discard_urb` sees the memory released as
// soon as the URB is reaped.
drop(urb);
let mut book = self.book.lock();
if let Some(queue) = book.queues.get_mut(&endpoint) {
queue.running = None;
}
if let Some(result) = result {
let status = result.status;
let failed = status < 0
&& status != UrbCancelReason::Killed.status()
&& status != UrbCancelReason::Unlinked.status();
if Self::complete_locked(&mut book, urb_id, result) && is_bulk && failed {
Self::unlink_bulk_continuations_locked(&mut book, endpoint);
}
}
drop(book);
self.notify_completion();
}
}
}
/// Endpoint of a validated URB.
enum UrbTarget {
/// Endpoint 0, with the setup packet of the URB.
Control { setup: fusb_descriptor::UsbSetup },
/// A bulk or interrupt endpoint.
Endpoint { endpoint: ParsedEndpoint, params: EndpointTransferParams, is_bulk: bool },
}
/// Reads and validates the setup packet of control URB `urb`, whose buffer is `buffer_length`
/// bytes long.
fn read_control_urb_setup(
current_task: &CurrentTask,
urb: &UrbHeader,
buffer_length: usize,
) -> Result<fusb_descriptor::UsbSetup, Errno> {
if buffer_length < USB_SETUP_PACKET_SIZE {
return error!(EINVAL);
}
let setup: usb_ctrlrequest = current_task.read_object(UserRef::new(urb.buffer))?;
let w_length = u16::from_le(setup.wLength);
if buffer_length < USB_SETUP_PACKET_SIZE + usize::from(w_length) {
return error!(EINVAL);
}
// Unlike Linux, the Usb protocol limits the length of the data stage.
if u64::from(w_length) > fhardware_usb::MAX_CONTROL_REQUEST_LEN {
return error!(EINVAL);
}
Ok(fusb_descriptor::UsbSetup {
bm_request_type: setup.bRequestType,
b_request: setup.bRequest,
w_value: u16::from_le(setup.wValue),
w_index: u16::from_le(setup.wIndex),
w_length,
})
}
/// Rejects URBs requesting a signal on completion, which is not supported.
fn check_urb_signal(urb: &UrbHeader) -> Result<(), Errno> {
if urb.signr != 0 {
track_stub!(TODO("https://fxbug.dev/571954283"), "usbfs URB completion signal");
return error!(EINVAL);
}
Ok(())
}
impl UsbDevice {
/// Runs `urb` on the calling worker thread and returns its result, or `None` if it already
/// completed when it was cancelled.
fn run_urb(&self, current_task: &CurrentTask, urb: &QueuedUrb) -> Option<UrbResult> {
let cancel = Some(urb.cancellation.as_ref());
let outcome = match &urb.transfer {
UrbTransfer::Control { setup, out_data } => {
self.run_control_urb(current_task, urb, setup, out_data)?
}
UrbTransfer::Endpoint { client, params, out_data, zero_packet } => {
let deadline = zx::MonotonicInstant::INFINITE;
if urb.is_in {
let result = self.endpoint_bulk_in(
current_task,
client,
*params,
urb.length,
deadline,
cancel,
);
PartialTransfer {
value: (result.value.len(), result.value),
error: result.error,
}
} else {
let result = self.endpoint_bulk_out(
current_task,
client,
*params,
out_data,
*zero_packet,
deadline,
cancel,
);
PartialTransfer { value: (result.value, Vec::new()), error: result.error }
}
}
};
let (actual_length, in_data) = outcome.value;
let status = match outcome.error {
// A cancelled URB reports why it was cancelled when its transfer was aborted by
// cancellation, endpoint reset, or disconnect, while genuine hardware or transport
// errors (such as EPIPE or EPROTO) that completed before a late cancellation are
// preserved.
Some(e) => match e.code {
ENOENT | ESHUTDOWN | ECONNRESET | EINTR => {
urb.cancellation.reason().map_or_else(|| urb_status(&e), |r| r.status())
}
ENODEV => urb.cancellation.reason().unwrap_or(UrbCancelReason::Shutdown).status(),
_ => urb_status(&e),
},
None if urb.short_not_ok && urb.is_in && actual_length < urb.length => {
urb_status(&errno!(EREMOTEIO))
}
None => 0,
};
Some(UrbResult { status, actual_length, in_data })
}
/// Runs control URB `urb`, holding the control slot like synchronous control transfers.
/// Returns `None` if the URB was cancelled, in which case it already completed.
fn run_control_urb(
&self,
current_task: &CurrentTask,
urb: &QueuedUrb,
setup: &fusb_descriptor::UsbSetup,
out_data: &[u8],
) -> Option<PartialTransfer<(usize, Vec<u8>)>> {
let failed = |e: Errno| PartialTransfer::failed((0, Vec::new()), e);
let proxy = match self.proxy() {
Ok(proxy) => Arc::clone(proxy),
Err(e) => return Some(failed(e)),
};
if let Err(e) = self.control_slot.acquire(current_task, Some(&urb.cancellation)) {
if urb.cancellation.is_cancelled() {
return None;
}
return Some(failed(e));
}
if urb.cancellation.is_cancelled() {
self.control_slot.release();
return None;
}
// Like Linux, asynchronous URBs have no timeout.
let result = run_control_transfer(&proxy, setup, None, out_data);
self.control_slot.release();
Some(match result {
Ok(mut data) if urb.is_in => {
data.truncate(urb.length);
PartialTransfer::ok((data.len(), data))
}
// The Usb protocol does not report the number of bytes sent by an OUT control
// transfer: on success, the device accepted all of them.
Ok(_) => PartialTransfer::ok((urb.length, Vec::new())),
Err(e) => failed(self.transfer_error(e)),
})
}
}
pub(crate) struct UsbDeviceFile {
device: Arc<UsbDevice>,
/// Identifies this file descriptor in `DeviceState::claims` and `UsbDevice::open_files`.
id: u64,
urbs: Arc<UsbfsFileUrbs>,
}
/// Reads the interface number argument of `USBDEVFS_CLAIMINTERFACE` and
/// `USBDEVFS_RELEASEINTERFACE`.
fn read_ifno(current_task: &CurrentTask, arg: SyscallArg) -> Result<Ifno, Errno> {
let raw_ifno: u32 = current_task.read_object(UserRef::new(UserAddress::from(arg)))?;
Ifno::try_from(raw_ifno)
}
impl UsbDeviceFile {
pub(crate) fn new(device: Arc<UsbDevice>) -> Self {
let id = device.next_file_id.fetch_add(1, Ordering::Relaxed);
let urbs = Arc::new(UsbfsFileUrbs::default());
device.open_files.lock().insert(id, Arc::clone(&urbs));
Self { device, id, urbs }
}
/// Writes completed URB `urb` back to userspace, and its address to the `USBDEVFS_REAPURB`
/// argument at `reap_arg_addr`, as a 32-bit pointer if `is_reap_32`.
fn write_reaped_urb(
&self,
current_task: &CurrentTask,
reap_arg_addr: UserAddress,
urb: CompletedUrb,
is_reap_32: bool,
) -> Result<SyscallResult, Errno> {
let result = &urb.result;
let actual_length = i32::try_from(result.actual_length).map_err(|_| errno!(EOVERFLOW))?;
let ptr_32 = if is_reap_32 {
Some(u32::try_from(urb.user_addr.ptr()).map_err(|_| errno!(EOVERFLOW))?)
} else {
None
};
let copied = min(result.in_data.len(), result.actual_length);
if copied > 0 {
current_task.write_memory(urb.data_addr, &result.in_data[..copied])?;
}
write_urb_result(current_task, urb.user_addr, urb.is_32bit, result.status, actual_length)?;
if let Some(ptr) = ptr_32 {
current_task.write_object(UserRef::new(reap_arg_addr), &ptr)?;
} else {
let ptr = urb.user_addr.ptr() as u64;
current_task.write_object(UserRef::new(reap_arg_addr), &ptr)?;
}
Ok(SUCCESS)
}
/// Validates `urb`, charges its memory, and connects to its endpoint. `control_setup` is the
/// setup packet of a URB on endpoint 0.
fn prepare_urb(
&self,
state: &mut DeviceState,
urb: &UrbHeader,
buffer_length: usize,
control_setup: Option<fusb_descriptor::UsbSetup>,
) -> Result<(UrbTarget, Option<Ifno>, Arc<UsbfsMemoryCharge>), Errno> {
if let Some(setup) = control_setup {
let claimed_ifno = self.check_control_recipient(
state,
setup.bm_request_type,
setup.b_request,
setup.w_index,
)?;
check_urb_signal(urb)?;
let charge = Arc::new(
self.device
.memory
.charge(URB_SIZE + USB_SETUP_PACKET_SIZE + usize::from(setup.w_length))?,
);
return Ok((UrbTarget::Control { setup }, claimed_ifno, charge));
}
let ifno = self.device.find_interface_for_endpoint(state, u32::from(urb.endpoint))?;
self.check_interface(state, ifno.into())?;
let endpoint = self
.device
.active_endpoint(state, urb.endpoint)
.cloned()
.ok_or_else(|| errno!(ENOENT))?;
let endpoint_type = endpoint.transfer_type();
match (u32::from(urb.type_), endpoint_type) {
// Bulk URBs on interrupt endpoints are single-shot interrupt transfers.
(USBDEVFS_URB_TYPE_BULK, USB_ENDPOINT_XFER_BULK | USB_ENDPOINT_XFER_INT)
| (USBDEVFS_URB_TYPE_INTERRUPT, USB_ENDPOINT_XFER_INT) => {}
(USBDEVFS_URB_TYPE_CONTROL, USB_ENDPOINT_XFER_CONTROL) => {
track_stub!(
TODO("https://fxbug.dev/571954283"),
"usbfs control URB on a non-default control endpoint"
);
return error!(EINVAL);
}
(USBDEVFS_URB_TYPE_ISO, USB_ENDPOINT_XFER_ISOC) => {
track_stub!(TODO("https://fxbug.dev/571954283"), "usbfs isochronous URB");
return error!(EINVAL);
}
_ => return error!(EINVAL),
}
let params = EndpointTransferParams::for_endpoint(&endpoint)?;
check_urb_signal(urb)?;
let charge = Arc::new(self.device.memory.charge(URB_SIZE + buffer_length)?);
self.device.get_or_connect_endpoint(state, &endpoint)?;
let is_bulk = endpoint_type == USB_ENDPOINT_XFER_BULK;
Ok((UrbTarget::Endpoint { endpoint, params, is_bulk }, Some(ifno), charge))
}
fn submit_urb(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
is_32bit: bool,
) -> Result<SyscallResult, Errno> {
let user_addr = UserAddress::from(arg);
let urb = read_urb_header(current_task, user_addr, is_32bit)?;
let urb_type = u32::from(urb.type_);
let allowed_flags = if urb_type == USBDEVFS_URB_TYPE_ISO {
URB_FLAGS | USBDEVFS_URB_ISO_ASAP
} else {
URB_FLAGS
};
if urb.flags & !allowed_flags != 0 {
return error!(EINVAL);
}
let buffer_length = usize::try_from(urb.buffer_length)
.ok()
.filter(|&len| len < USBFS_XFER_MAX)
.ok_or_else(|| errno!(EINVAL))?;
if buffer_length > 0 {
if urb.buffer.is_null() {
return error!(EINVAL);
}
let end_addr = (urb.buffer + buffer_length).map_err(|_| errno!(EFAULT))?;
if current_task.maximum_valid_address().is_some_and(|max| end_addr > max) {
return error!(EFAULT);
}
}
let is_default_control =
urb_type == USBDEVFS_URB_TYPE_CONTROL && (u32::from(urb.endpoint) & !USB_DIR_IN) == 0;
// The setup packet is read before taking the state lock, which must not be held while
// accessing user memory.
let control_setup = if is_default_control {
Some(read_control_urb_setup(current_task, &urb, buffer_length)?)
} else {
None
};
#[cfg(test)]
let on_urb_prepared;
let (target, claimed_ifno, charge) = {
let mut state = self.device.state.lock();
#[cfg(test)]
{
on_urb_prepared = state.on_urb_prepared.take();
}
self.prepare_urb(&mut state, &urb, buffer_length, control_setup)?
};
#[cfg(test)]
if let Some(hook) = on_urb_prepared {
hook(current_task);
}
let (endpoint, data_addr, length, is_in) = match &target {
UrbTarget::Control { setup } => (
0,
(urb.buffer + USB_SETUP_PACKET_SIZE)?,
usize::from(setup.w_length),
// Like Linux, a control URB without data stage is an OUT transfer.
(u32::from(setup.bm_request_type) & USB_DIR_IN) != 0 && setup.w_length > 0,
),
UrbTarget::Endpoint { .. } => (
urb.endpoint,
urb.buffer,
buffer_length,
(u32::from(urb.endpoint) & USB_DIR_IN) != 0,
),
};
let out_data = if !is_in && length > 0 {
current_task.read_memory_to_vec(data_addr, length)?
} else {
Vec::new()
};
let cancellation = Arc::new(UrbCancellation::default());
let start_worker = {
// Re-verify under `state` that the device was not disconnected, the interface claim
// was not released, and the endpoint was not disabled or reconfigured while reading
// user memory.
let mut state = self.device.state.lock();
if self.device.disconnected.load(Ordering::Acquire) {
return error!(ENODEV);
}
if let Some(ifno) = claimed_ifno {
if state.claims.get(&ifno) != Some(&self.id) {
return error!(EINVAL);
}
}
let (transfer, client, is_bulk) = match target {
UrbTarget::Control { setup } => {
(UrbTransfer::Control { setup, out_data }, None, false)
}
UrbTarget::Endpoint { endpoint: prepared_endpoint, params, is_bulk } => {
match self.device.active_endpoint(&state, endpoint) {
Some(active) if *active == prepared_endpoint => {}
Some(_) => return error!(EINVAL),
None => return error!(ENOENT),
}
let client =
self.device.get_or_connect_endpoint(&mut state, &prepared_endpoint)?;
let zero_packet = !is_in && (urb.flags & USBDEVFS_URB_ZERO_PACKET) != 0;
let transfer = UrbTransfer::Endpoint {
client: Arc::clone(&client),
params,
out_data,
zero_packet,
};
(transfer, Some(client), is_bulk)
}
};
let bulk_continuation = is_bulk && (urb.flags & USBDEVFS_URB_BULK_CONTINUATION) != 0;
let mut book = self.urbs.book.lock();
if self.device.disconnected.load(Ordering::Acquire) {
return error!(ENODEV);
}
if is_bulk {
if !bulk_continuation {
// A new transfer starts.
book.disabled_bulk_endpoints.remove(&endpoint);
} else if book.disabled_bulk_endpoints.contains(&endpoint) {
// An earlier URB of the transfer failed.
return error!(EREMOTEIO);
}
}
let id = book.next_id;
book.next_id += 1;
book.pending.insert(
id,
PendingUrb {
user_addr,
is_32bit,
endpoint,
data_addr,
cancellation: Arc::clone(&cancellation),
client,
completed: Arc::new(zx::Event::create()),
charge: Arc::clone(&charge),
},
);
let queue = book.queues.entry(endpoint).or_default();
queue.queued.push_back(QueuedUrb {
id,
transfer,
is_in,
length,
short_not_ok: is_in && (urb.flags & USBDEVFS_URB_SHORT_NOT_OK) != 0,
is_bulk,
bulk_continuation,
cancellation,
_charge: charge,
});
!std::mem::replace(&mut queue.has_worker, true)
};
if start_worker {
let urbs = Arc::clone(&self.urbs);
let device = Arc::clone(&self.device);
let closure = move |current_task: &CurrentTask| {
urbs.run_worker(&device, endpoint, current_task);
};
let request = SpawnRequestBuilder::new()
.with_sync_closure(closure)
.with_debug_name("usbfs-urb-worker")
.build();
current_task.kernel().kthreads.spawner().spawn_from_request(request);
}
Ok(SUCCESS)
}
fn discard_urb(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let user_addr = UserAddress::from(arg);
let completed = {
let mut book = self.urbs.book.lock();
// Like Linux, the first pending URB with this address is discarded.
let id = book
.pending
.iter()
.find(|(_, urb)| urb.user_addr == user_addr)
.map(|(&id, _)| id)
.ok_or_else(|| errno!(EINVAL))?;
UsbfsFileUrbs::kill_locked(&mut book, id, UrbCancelReason::Killed)
};
// Wait for the URB to complete. Its cancelled request completes within the cancel drain
// timeout, and only fatal signals interrupt the wait.
if let Some(completed) = completed {
wait_for_signals(
current_task,
&[(completed.as_ref(), zx::Signals::USER_0)],
zx::MonotonicInstant::INFINITE,
)?;
} else {
self.urbs.notify_completion();
}
Ok(SUCCESS)
}
pub(crate) fn reap_urb(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
is_32bit: bool,
blocking: bool,
) -> Result<SyscallResult, Errno> {
let user_addr = UserAddress::from(arg);
loop {
let waiter = Waiter::new();
if blocking {
self.urbs.waiters.wait_async(&waiter);
}
{
let mut book = self.urbs.book.lock();
if let Some(urb) = book.completed.pop_front() {
drop(book);
return self.write_reaped_urb(current_task, user_addr, urb, is_32bit);
}
// After a disconnection, the pending URBs complete shortly with `-ESHUTDOWN`
// and can still be reaped.
if self.device.disconnected.load(Ordering::Acquire) && book.pending.is_empty() {
return error!(ENODEV);
}
}
if !blocking {
return error!(EAGAIN);
}
waiter.wait(current_task)?;
}
}
/// Claims interface `ifno` for this file descriptor:
/// - Already claimed by this FD -> `Ok(())`
/// - Interface does not exist in active configuration -> `ENOENT`
/// - Claimed by another FD -> `EBUSY`
fn claim_interface(&self, state: &mut DeviceState, ifno: Ifno) -> Result<(), Errno> {
match state.claims.get(&ifno) {
Some(&owner) if owner == self.id => return Ok(()),
Some(_) => return error!(EBUSY),
None => {}
}
if !self.device.has_interface(state, ifno) {
return error!(ENOENT);
}
state.claims.insert(ifno, self.id);
Ok(())
}
/// Releases interface `ifno` from this file descriptor:
/// - Interface does not exist in active configuration -> `ENOENT`
/// - Not claimed by this FD -> `EINVAL`
fn release_interface(&self, state: &mut DeviceState, ifno: Ifno) -> Result<(), Errno> {
if !self.device.has_interface(state, ifno) {
return error!(ENOENT);
}
if state.claims.get(&ifno) != Some(&self.id) {
return error!(EINVAL);
}
self.device.drop_claim(state, ifno);
Ok(())
}
/// Ensures interface `ifnum` is claimed by this file descriptor, auto-claiming it if
/// currently unclaimed. Returns `EHOSTUNREACH` if the device is unconfigured, then `EINVAL`
/// if `ifnum` is out of range.
fn check_interface(&self, state: &mut DeviceState, ifnum: u32) -> Result<Ifno, Errno> {
if self.device.active_config(state).is_none() {
return error!(EHOSTUNREACH);
}
let ifno = Ifno::try_from(ifnum)?;
self.claim_interface(state, ifno)?;
Ok(ifno)
}
/// Checks that this file descriptor may issue a control transfer to the recipient selected
/// by `request_type` and `index`, auto-claiming the target interface if needed and returning
/// its interface number when claimed.
fn check_control_recipient(
&self,
state: &mut DeviceState,
request_type: u8,
request: u8,
index: u16,
) -> Result<Option<Ifno>, Errno> {
let request_type = u32::from(request_type);
if (request_type & USB_TYPE_MASK) == USB_TYPE_VENDOR {
return Ok(None);
}
let [index_high, index_low] = index.to_be_bytes();
// The printer class GET_DEVICE_ID request is always allowed: it is used to query
// things like ink levels.
if request_type == USB_PRINTER_GET_DEVICE_ID_REQUEST_TYPE
&& request == USB_PRINTER_GET_DEVICE_ID
&& self
.device
.find_interface_alt(state, index_high, index_low)
.is_some_and(|i| u32::from(i.interface_class) == USB_CLASS_PRINTER)
{
return Ok(None);
}
let index = u32::from(index_low);
match request_type & USB_RECIP_MASK {
USB_RECIP_ENDPOINT => {
if (index & !USB_DIR_IN) == 0 {
return Ok(None);
}
// Some applications pass the endpoint number without the direction bit.
let ifno = self.device.find_interface_for_endpoint(state, index).or_else(|_| {
self.device.find_interface_for_endpoint(state, index ^ USB_DIR_IN)
})?;
self.check_interface(state, ifno.into()).map(Some)
}
USB_RECIP_INTERFACE => self.check_interface(state, index).map(Some),
_ => Ok(None),
}
}
fn control(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
is_arch32: bool,
) -> Result<SyscallResult, Errno> {
let user_addr = UserAddress::from(arg);
let ctrl_ref = if is_arch32 {
UsbdevfsCtrltransferPtr::from_32(UserRef::new(user_addr))
} else {
UsbdevfsCtrltransferPtr::new(current_task, user_addr)
};
let ctrl: usbdevfs_ctrltransfer = current_task.read_multi_arch_object(ctrl_ref)?;
self.check_control_recipient(
&mut self.device.state.lock(),
ctrl.bRequestType,
ctrl.bRequest,
ctrl.wIndex,
)?;
let length = usize::from(ctrl.wLength);
if length > MAX_USBDEVFS_CONTROL_SIZE {
return error!(EINVAL);
}
let data_addr = UserAddress::from(ctrl.data);
if length > 0 && data_addr.is_null() {
return error!(EFAULT);
}
let is_in = (u32::from(ctrl.bRequestType) & USB_DIR_IN) != 0;
let setup = fusb_descriptor::UsbSetup {
bm_request_type: ctrl.bRequestType,
b_request: ctrl.bRequest,
w_value: ctrl.wValue,
w_index: ctrl.wIndex,
w_length: ctrl.wLength,
};
let timeout = usbfs_timeout(ctrl.timeout);
if is_in {
let read_data =
self.device.control_transfer(current_task, setup, timeout, Vec::new())?;
let len = min(read_data.len(), length);
if len > 0 {
current_task.write_memory(data_addr, &read_data[..len])?;
}
Ok(len.into())
} else {
let out_data = if length > 0 {
current_task.read_memory_to_vec(data_addr, length)?
} else {
Vec::new()
};
self.device.control_transfer(current_task, setup, timeout, out_data)?;
// The Usb protocol does not report the number of bytes sent by an OUT control
// transfer: on success, the device accepted all of them.
Ok(length.into())
}
}
fn bulk(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
is_arch32: bool,
) -> Result<SyscallResult, Errno> {
let user_addr = UserAddress::from(arg);
let bulk_ref = if is_arch32 {
UsbdevfsBulktransferPtr::from_32(UserRef::new(user_addr))
} else {
UsbdevfsBulktransferPtr::new(current_task, user_addr)
};
let bulk: usbdevfs_bulktransfer = current_task.read_multi_arch_object(bulk_ref)?;
let len = bulk.len as usize;
let data_addr = UserAddress::from(bulk.data);
let (client, params, _charge) = {
let mut state = self.device.state.lock();
let ifno = self.device.find_interface_for_endpoint(&state, bulk.ep)?;
self.check_interface(&mut state, ifno.into())?;
if len >= INVALID_USBDEVFS_BULK_SIZE {
return error!(EINVAL);
}
let endpoint = self
.device
.active_endpoint(&state, bulk.ep as u8)
.cloned()
.ok_or_else(|| errno!(EINVAL))?;
let params = EndpointTransferParams::for_endpoint(&endpoint)?;
// usbfs limits the memory used by outstanding transfers.
let charge = self.device.memory.charge(URB_SIZE + len)?;
if len > 0 && data_addr.is_null() {
return error!(EFAULT);
}
let client = self.device.get_or_connect_endpoint(&mut state, &endpoint)?;
(client, params, charge)
};
let deadline = deadline_after(usbfs_timeout(bulk.timeout));
if (bulk.ep & USB_DIR_IN) != 0 {
let data = self
.device
.endpoint_bulk_in(current_task, &client, params, len, deadline, None)
.into_result()?;
if !data.is_empty() {
current_task.write_memory(data_addr, &data)?;
}
Ok(data.len().into())
} else {
let out_data =
if len > 0 { current_task.read_memory_to_vec(data_addr, len)? } else { Vec::new() };
let written = self
.device
.endpoint_bulk_out(current_task, &client, params, &out_data, false, deadline, None)
.into_result()?;
Ok(written.into())
}
}
fn set_interface(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let set_iface: usbdevfs_setinterface =
current_task.read_object(UserRef::new(UserAddress::from(arg)))?;
let mut state = self.device.state.lock();
if self.device.active_config(&state).is_none() {
return error!(EHOSTUNREACH);
}
let ifno = Ifno::try_from(set_iface.interface)?;
if !self.device.has_interface(&state, ifno) {
return error!(ENOENT);
}
// Validate the alternate setting before auto-claiming the interface.
let alt = u8::try_from(set_iface.altsetting)
.ok()
.filter(|&alt| self.device.find_interface_alt(&state, ifno.as_u8(), alt).is_some())
.ok_or_else(|| errno!(EINVAL))?;
self.claim_interface(&mut state, ifno)?;
// Kill the pending URBs on the interface's endpoints before changing the alternate
// setting. Only endpoints of the interface being changed are affected; all of them are
// disabled, even those also present in the new alternate setting, because the device
// resets their data toggles. They are enabled again with the new descriptors on next use.
let endpoints = self.device.interface_endpoint_addresses(&state, ifno);
let running = self
.urbs
.start_kill_urbs(UrbCancelReason::Killed, |urb| endpoints.contains(&urb.endpoint));
let mut disable_result = Ok(());
for &ep_addr in &endpoints {
if let Err(e) = self.device.disable_endpoint(&mut state, ep_addr) {
if disable_result.is_ok() {
disable_result = Err(e);
}
}
}
self.urbs.finish_kill_urbs(running);
disable_result?;
let proxy = self.device.proxy()?;
match proxy.set_interface(ifno.as_u8(), alt, zx::MonotonicInstant::INFINITE) {
// Devices may STALL SET_INTERFACE on an interface with a single alternate setting
// (USB 2.0 section 9.4.10). Like Linux, treat it as a success and clear the halt of
// each endpoint of the interface instead, which resets their data toggles.
Ok(Err(zx::sys::ZX_ERR_IO_REFUSED))
if self.device.interface_alts(&state, ifno).count() == 1 =>
{
for ep_addr in endpoints {
let _ = proxy.control_transfer(
&clear_halt_setup(ep_addr),
CLEAR_HALT_TIMEOUT.into_nanos(),
&[],
zx::MonotonicInstant::INFINITE,
);
}
}
result => fidl_result_to_errno(result)?,
}
state.alt_settings.insert(ifno, alt);
Ok(SUCCESS)
}
fn set_configuration(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let raw_config: i32 = current_task.read_object(UserRef::new(UserAddress::from(arg)))?;
let mut state = self.device.state.lock();
// Claims are taken under the same lock, so no interface can be claimed concurrently.
if !state.claims.is_empty() {
return error!(EBUSY);
}
// -1 unconfigures the device, which USB represents as configuration value 0.
let config = match raw_config {
-1 => 0,
value => u8::try_from(value).map_err(|_| errno!(EINVAL))?,
};
// Unknown configurations are rejected before touching the device. Whether the device
// can be unconfigured is up to the USB driver.
if config != 0 && !self.device.parsed_configs.iter().any(|c| c.config_value == config) {
return error!(EINVAL);
}
self.device.disable_all_endpoints(&mut state)?;
fidl_result_to_errno(
self.device.proxy()?.set_configuration(config, zx::MonotonicInstant::INFINITE),
)?;
state.active_config = config;
state.alt_settings.clear();
Ok(SUCCESS)
}
fn clear_halt(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let ep: u32 = current_task.read_object(UserRef::new(UserAddress::from(arg)))?;
let mut state = self.device.state.lock();
let ifno = self.device.find_interface_for_endpoint(&state, ep)?;
self.check_interface(&mut state, ifno.into())?;
let ep_addr = ep as u8;
// The request is not killable: once the device reset its data toggle, the host must reset
// its own too.
let proxy = self.device.proxy()?;
fidl_result_to_errno(proxy.control_transfer(
&clear_halt_setup(ep_addr),
CLEAR_HALT_TIMEOUT.into_nanos(),
&[],
zx::MonotonicInstant::INFINITE,
))?;
match proxy.reset_endpoint(ep_addr, zx::MonotonicInstant::INFINITE) {
Ok(Ok(())) => self.device.reset_endpoint_state(ep_addr),
// The host controller only resets halted endpoints, so it kept the data toggle of
// this endpoint while the device reset its own. Disable the endpoint so that enabling
// it again resets the host data toggle too; if asynchronous URBs are already pending
// on the endpoint, restart it right away so their worker does not fail with
// ESHUTDOWN.
Ok(Err(zx::sys::ZX_ERR_NOT_SUPPORTED)) | Ok(Err(zx::sys::ZX_ERR_INVALID_ARGS)) => {
if self.device.has_pending_urbs(ep_addr) {
self.device.restart_endpoint(&mut state, ep_addr)?;
} else {
self.device.disable_endpoint(&mut state, ep_addr)?;
}
}
result => fidl_result_to_errno(result)?,
}
Ok(SUCCESS)
}
fn get_driver(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let user_ref = UserRef::<usbdevfs_getdriver>::new(UserAddress::from(arg));
let mut getdriver = current_task.read_object(user_ref)?;
let claimed = {
let state = self.device.state.lock();
Ifno::try_from(getdriver.interface).is_ok_and(|ifno| {
self.device.has_interface(&state, ifno) && state.claims.contains_key(&ifno)
})
};
if !claimed {
return error!(ENODATA);
}
getdriver.driver.fill(0);
for (dst, src) in getdriver.driver.iter_mut().zip(USBFS_DRIVER_NAME) {
*dst = *src as _;
}
current_task.write_object(user_ref, &getdriver)?;
Ok(SUCCESS)
}
fn disconnect_claim(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
let disconnect: usbdevfs_disconnect_claim =
current_task.read_object(UserRef::new(UserAddress::from(arg)))?;
let mut state = self.device.state.lock();
let ifno = Ifno::try_from(disconnect.interface)?;
if !self.device.has_interface(&state, ifno) {
return error!(EINVAL);
}
// Interfaces claimed through usbfs are bound to the "usbfs" driver.
if state.claims.contains_key(&ifno) {
let driver: Vec<u8> =
disconnect.driver.iter().map(|&c| c as u8).take_while(|&c| c != 0).collect();
let is_usbfs = driver == USBFS_DRIVER_NAME;
if (disconnect.flags & USBDEVFS_DISCONNECT_CLAIM_IF_DRIVER) != 0 && !is_usbfs {
return error!(EBUSY);
}
if (disconnect.flags & USBDEVFS_DISCONNECT_CLAIM_EXCEPT_DRIVER) != 0 && is_usbfs {
return error!(EBUSY);
}
// Disconnect the current claimant.
self.device.drop_claim(&mut state, ifno);
}
self.claim_interface(&mut state, ifno)?;
Ok(SUCCESS)
}
fn driver_ioctl(
&self,
current_task: &CurrentTask,
arg: SyscallArg,
is_arch32: bool,
) -> Result<SyscallResult, Errno> {
let user_addr = UserAddress::from(arg);
let ctl_ref = if is_arch32 {
UsbdevfsIoctlPtr::from_32(UserRef::new(user_addr))
} else {
UsbdevfsIoctlPtr::new(current_task, user_addr)
};
let ctl: usbdevfs_ioctl = current_task.read_multi_arch_object(ctl_ref)?;
let code = ctl.ioctl_code as u32;
let size = ((code >> _IOC_SIZESHIFT) & _IOC_SIZEMASK) as usize;
if size > 0 && ((code >> _IOC_DIRSHIFT) & _IOC_WRITE) != 0 {
current_task.read_memory_to_vec(UserAddress::from(ctl.data), size)?;
}
let mut state = self.device.state.lock();
if self.device.active_config(&state).is_none() {
return error!(EHOSTUNREACH);
}
let ifno = u32::try_from(ctl.ifno).map_err(|_| errno!(EINVAL)).and_then(Ifno::try_from)?;
if !self.device.has_interface(&state, ifno) {
return error!(EINVAL);
}
match code {
USBDEVFS_DISCONNECT => {
if state.claims.contains_key(&ifno) {
self.device.drop_claim(&mut state, ifno);
Ok(SUCCESS)
} else {
error!(ENODATA)
}
}
USBDEVFS_CONNECT => {
if state.claims.contains_key(&ifno) {
error!(EBUSY)
} else {
// Starnix has no in-kernel USB interface drivers to bind, so return 0 as when
// no driver matches.
Ok(SUCCESS)
}
}
_ => error!(ENOTTY),
}
}
}
impl Drop for UsbDeviceFile {
fn drop(&mut self) {
self.device.open_files.lock().remove(&self.id);
// Kill and wait for the pending URBs of this file description before clearing any
// un-reaped completions.
self.urbs.kill_urbs(UrbCancelReason::Killed, |_| true);
self.urbs.book.lock().completed.clear();
let mut state = self.device.state.lock();
let released: Vec<Ifno> = state
.claims
.iter()
.filter(|&(_, &owner)| owner == self.id)
.map(|(&ifno, _)| ifno)
.collect();
for ifno in released {
self.device.drop_claim(&mut state, ifno);
}
}
}
impl FileOps for UsbDeviceFile {
fileops_impl_seekable!();
fileops_impl_noop_sync!();
fn wait_async(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
waiter: &Waiter,
events: FdEvents,
handler: EventHandler,
) -> Option<WaitCanceler> {
Some(self.urbs.waiters.wait_async_fd_events(waiter, events, handler))
}
fn query_events(
&self,
file: &FileObject,
_current_task: &CurrentTask,
) -> Result<FdEvents, Errno> {
let mut events = FdEvents::empty();
let has_completed = !self.urbs.book.lock().completed.is_empty();
if file.can_write() && has_completed {
events |= FdEvents::POLLOUT | FdEvents::POLLWRNORM;
}
if self.device.disconnected.load(Ordering::Acquire) {
events |= FdEvents::POLLHUP | FdEvents::POLLERR;
}
Ok(events)
}
fn read(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
offset: usize,
data: &mut dyn OutputBuffer,
) -> Result<usize, Errno> {
if self.device.disconnected.load(Ordering::Acquire) {
return error!(ENODEV);
}
data.write_tail(&self.device.raw_descriptors, offset)
}
fn write(
&self,
_file: &FileObject,
_current_task: &CurrentTask,
_offset: usize,
_data: &mut dyn InputBuffer,
) -> Result<usize, Errno> {
error!(EINVAL)
}
fn ioctl(
&self,
file: &FileObject,
current_task: &CurrentTask,
request: u32,
arg: SyscallArg,
) -> Result<SyscallResult, Errno> {
// All USBDEVFS_* ioctls require write permission on the open file description.
if !file.can_write() {
return error!(EPERM);
}
let is_arch32_call = current_task.is_arch32() || is_arch32_ioctl_request(request);
let request = canonicalize_ioctl_request(is_arch32_call, request);
// Completed URBs can still be reaped after the device is disconnected.
if self.device.disconnected.load(Ordering::Acquire)
&& request != USBDEVFS_REAPURB
&& request != USBDEVFS_REAPURBNDELAY
{
return error!(ENODEV);
}
match request {
USBDEVFS_CONTROL => self.control(current_task, arg, is_arch32_call),
USBDEVFS_BULK => self.bulk(current_task, arg, is_arch32_call),
USBDEVFS_CLAIMINTERFACE => {
let ifno = read_ifno(current_task, arg)?;
self.claim_interface(&mut self.device.state.lock(), ifno)?;
Ok(SUCCESS)
}
USBDEVFS_RELEASEINTERFACE => {
let ifno = read_ifno(current_task, arg)?;
self.release_interface(&mut self.device.state.lock(), ifno)?;
Ok(SUCCESS)
}
USBDEVFS_SETINTERFACE => self.set_interface(current_task, arg),
USBDEVFS_SETCONFIGURATION => self.set_configuration(current_task, arg),
USBDEVFS_CLEAR_HALT => self.clear_halt(current_task, arg),
USBDEVFS_RESET => {
// The Usb protocol has no method to reset the physical device. On real Linux, a
// device reset force-unbinds usbfs interfaces and drops claims because usbfs lacks
// reset callbacks. Since Fuchsia does not perform a hardware reset here, Starnix
// preserves configuration and claims as a software stub, cancelling in-flight
// requests.
track_stub!(
TODO("https://fxbug.dev/571954283"),
"usbfs USBDEVFS_RESET device reset"
);
// Like a device reset, abort the URBs of all the file descriptions under the
// state lock so no concurrent submission slips in, cancel in-flight endpoint
// requests so both async URB workers and synchronous USBDEVFS_BULK callers wake
// up, and wait for running URBs after releasing the state lock.
let files_and_running: Vec<_> = {
let _state = self.device.state.lock();
let files_and_running: Vec<_> = self
.device
.open_files()
.into_iter()
.map(|urbs| {
let running = urbs.start_kill_urbs(UrbCancelReason::Killed, |_| true);
(urbs, running)
})
.collect();
let addresses: Vec<u8> = self.device.endpoints.lock().keys().copied().collect();
for ep_addr in addresses {
self.device.reset_endpoint_state(ep_addr);
}
files_and_running
};
for (urbs, running) in files_and_running {
urbs.finish_kill_urbs(running);
}
Ok(SUCCESS)
}
USBDEVFS_GETDRIVER => self.get_driver(current_task, arg),
USBDEVFS_GET_SPEED => Ok((self.device.speed as usize).into()),
USBDEVFS_GET_CAPABILITIES => {
let caps: u32 = USBDEVFS_CAP_ZERO_PACKET
| USBDEVFS_CAP_BULK_CONTINUATION
| USBDEVFS_CAP_NO_PACKET_SIZE_LIM
| USBDEVFS_CAP_REAP_AFTER_DISCONNECT;
current_task.write_object(UserRef::new(UserAddress::from(arg)), &caps)?;
Ok(SUCCESS)
}
USBDEVFS_CONNECTINFO => {
let info = usbdevfs_connectinfo {
devnum: u32::from(self.device.dev_num),
slow: u8::from(self.device.speed == USB_SPEED_LOW),
..Default::default()
};
current_task.write_object(UserRef::new(UserAddress::from(arg)), &info)?;
Ok(SUCCESS)
}
USBDEVFS_DISCONNECT_CLAIM => self.disconnect_claim(current_task, arg),
USBDEVFS_IOCTL => self.driver_ioctl(current_task, arg, is_arch32_call),
USBDEVFS_SUBMITURB => self.submit_urb(current_task, arg, is_arch32_call),
USBDEVFS_DISCARDURB => self.discard_urb(current_task, arg),
USBDEVFS_REAPURBNDELAY => self.reap_urb(current_task, arg, is_arch32_call, false),
USBDEVFS_REAPURB => self.reap_urb(current_task, arg, is_arch32_call, true),
_ => {
track_stub!(TODO("https://fxbug.dev/571954283"), "usbfs ioctl", request);
error!(ENOTTY)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use starnix_uapi::{
USB_DT_CONFIG_SIZE, USB_DT_INTERFACE_ASSOCIATION_SIZE, USB_DT_INTERFACE_SIZE,
USB_DT_SS_EP_COMP_SIZE,
};
const CONFIG: [u8; USB_DT_CONFIG_SIZE as usize] =
[USB_DT_CONFIG_SIZE as u8, USB_DT_CONFIG as u8, 0, 0, 1, 7, 0, 0x80, 50];
fn interface(number: u8, alt: u8) -> [u8; USB_DT_INTERFACE_SIZE as usize] {
[USB_DT_INTERFACE_SIZE as u8, USB_DT_INTERFACE as u8, number, alt, 1, 0xff, 0, 0, 0]
}
fn endpoint(address: u8) -> [u8; USB_DT_ENDPOINT_SIZE as usize] {
[
USB_DT_ENDPOINT_SIZE as u8,
USB_DT_ENDPOINT as u8,
address,
USB_ENDPOINT_XFER_BULK as u8,
0x00,
0x04,
0,
]
}
fn ss_companion(max_burst: u8) -> [u8; USB_DT_SS_EP_COMP_SIZE as usize] {
[USB_DT_SS_EP_COMP_SIZE as u8, USB_DT_SS_ENDPOINT_COMP as u8, max_burst, 0, 0, 0]
}
fn concat(parts: &[&[u8]]) -> Vec<u8> {
parts.concat()
}
#[::fuchsia::test]
fn test_parse_configuration_interfaces_and_endpoints() {
let data = concat(&[&CONFIG, &interface(2, 1), &endpoint(0x81)]);
let configs = parse_config_descriptors(&data);
assert_eq!(configs.len(), 1);
assert_eq!(configs[0].config_value, 7);
let interface = &configs[0].interfaces[0];
assert_eq!((interface.interface_number, interface.alt_setting), (2, 1));
assert_eq!(interface.interface_class, 0xff);
let endpoint = &interface.endpoints[0];
assert_eq!(endpoint.address(), 0x81);
assert_eq!(endpoint.max_packet_size(), 1024);
assert_eq!(endpoint.ss_companion, NO_SS_COMPANION);
}
#[::fuchsia::test]
fn test_parse_ss_companion_attaches_to_preceding_endpoint() {
// A class-specific descriptor between an endpoint and its companion is skipped.
let class_specific = [3, 0x25, 0];
let data = concat(&[
&CONFIG,
&interface(0, 0),
&endpoint(0x81),
&class_specific,
&ss_companion(3),
&endpoint(0x02),
&ss_companion(5),
]);
let configs = parse_config_descriptors(&data);
let endpoints = &configs[0].interfaces[0].endpoints;
assert_eq!(endpoints[0].ss_companion.b_max_burst, 3);
assert_eq!(endpoints[0].ss_companion.b_length, USB_DT_SS_EP_COMP_SIZE as u8);
assert_eq!(endpoints[1].ss_companion.b_max_burst, 5);
}
#[::fuchsia::test]
fn test_parse_ss_companion_ignored_outside_endpoint_scope() {
let iad = [USB_DT_INTERFACE_ASSOCIATION_SIZE as u8, USB_DT_INTERFACE_ASSOCIATION as u8];
let iad = concat(&[&iad, &[0; USB_DT_INTERFACE_ASSOCIATION_SIZE as usize - 2]]);
// A companion after an interface association descriptor, or after an interface
// descriptor, does not belong to the previous endpoint.
let data = concat(&[
&CONFIG,
&interface(0, 0),
&endpoint(0x81),
&iad,
&ss_companion(3),
&interface(1, 0),
&ss_companion(4),
]);
let configs = parse_config_descriptors(&data);
assert_eq!(configs[0].interfaces.len(), 2);
let endpoint = &configs[0].interfaces[0].endpoints[0];
assert_eq!(endpoint.ss_companion, NO_SS_COMPANION);
}
#[::fuchsia::test]
fn test_parse_ignores_descriptors_without_parent() {
// An interface before any configuration and an endpoint before any interface.
let data = concat(&[&interface(0, 0), &endpoint(0x81), &CONFIG, &endpoint(0x82)]);
let configs = parse_config_descriptors(&data);
assert_eq!(configs.len(), 1);
assert!(configs[0].interfaces.is_empty());
}
#[::fuchsia::test]
fn test_parse_stops_at_malformed_descriptors() {
// A descriptor with a bLength smaller than its header ends the parsing.
let data = concat(&[&CONFIG, &[1, USB_DT_INTERFACE as u8], &interface(0, 0)]);
assert!(parse_config_descriptors(&data)[0].interfaces.is_empty());
// So does a truncated descriptor.
let data = concat(&[&CONFIG, &interface(0, 0)[..5]]);
assert!(parse_config_descriptors(&data)[0].interfaces.is_empty());
assert!(parse_config_descriptors(&CONFIG[..1]).is_empty());
// Descriptors shorter than their standard size are skipped.
let short_interface = [5, USB_DT_INTERFACE as u8, 0, 0, 0];
let short_endpoint = [5, USB_DT_ENDPOINT as u8, 0x81, 2, 0];
let data = concat(&[&CONFIG, &short_interface, &interface(1, 0), &short_endpoint]);
let configs = parse_config_descriptors(&data);
assert_eq!(configs[0].interfaces.len(), 1);
assert_eq!(configs[0].interfaces[0].interface_number, 1);
assert!(configs[0].interfaces[0].endpoints.is_empty());
}
#[::fuchsia::test]
fn test_memory_budget_limits_outstanding_charges() {
let budget = Arc::new(UsbfsMemoryBudget::default());
let first = budget.charge(USBFS_MEMORY_LIMIT - 1).unwrap();
assert_eq!(budget.charge(2).unwrap_err(), errno!(ENOMEM));
let second = budget.charge(1).unwrap();
assert_eq!(budget.used.load(Ordering::Acquire), USBFS_MEMORY_LIMIT);
drop(first);
drop(second);
assert_eq!(budget.used.load(Ordering::Acquire), 0);
assert_eq!(budget.charge(USBFS_MEMORY_LIMIT + 1).unwrap_err(), errno!(ENOMEM));
}
/// Adds a pending URB queued on endpoint `EP` to `book`.
fn queue_test_urb(
book: &mut UrbBook,
budget: &Arc<UsbfsMemoryBudget>,
continuation: bool,
) -> u64 {
const EP: u8 = 0x81;
let id = book.next_id;
book.next_id += 1;
let cancellation = Arc::new(UrbCancellation::default());
let charge = Arc::new(budget.charge(URB_SIZE).unwrap());
book.pending.insert(
id,
PendingUrb {
user_addr: UserAddress::from(0x1000 + id * 0x100),
is_32bit: false,
endpoint: EP,
data_addr: UserAddress::NULL,
cancellation: Arc::clone(&cancellation),
client: None,
completed: Arc::new(zx::Event::create()),
charge: Arc::clone(&charge),
},
);
book.queues.entry(EP).or_default().queued.push_back(QueuedUrb {
id,
transfer: UrbTransfer::Control {
setup: fusb_descriptor::UsbSetup {
bm_request_type: 0,
b_request: 0,
w_value: 0,
w_index: 0,
w_length: 0,
},
out_data: Vec::new(),
},
is_in: true,
length: 0,
short_not_ok: false,
is_bulk: true,
bulk_continuation: continuation,
cancellation,
_charge: charge,
});
id
}
#[::fuchsia::test]
fn test_bulk_failure_unlinks_queued_continuation_urbs() {
const EP: u8 = 0x81;
let budget = Arc::new(UsbfsMemoryBudget::default());
let unlinked_status = UrbCancelReason::Unlinked.status();
// The continuations of the failed transfer are unlinked, up to the next transfer.
let mut book = UrbBook::default();
let continuations =
[queue_test_urb(&mut book, &budget, true), queue_test_urb(&mut book, &budget, true)];
let next_transfer = queue_test_urb(&mut book, &budget, false);
let next_continuation = queue_test_urb(&mut book, &budget, true);
UsbfsFileUrbs::unlink_bulk_continuations_locked(&mut book, EP);
let completed: Vec<(UserAddress, i32)> =
book.completed.iter().map(|urb| (urb.user_addr, urb.result.status)).collect();
assert_eq!(
completed,
continuations
.iter()
.map(|&id| (UserAddress::from(0x1000 + id * 0x100), unlinked_status))
.collect::<Vec<_>>()
);
let queued: Vec<u64> = book.queues[&EP].queued.iter().map(|urb| urb.id).collect();
assert_eq!(queued, vec![next_transfer, next_continuation]);
assert!(!book.disabled_bulk_endpoints.contains(&EP));
// Without a new transfer, later continuations are rejected.
let mut book = UrbBook::default();
queue_test_urb(&mut book, &budget, true);
UsbfsFileUrbs::unlink_bulk_continuations_locked(&mut book, EP);
assert_eq!(book.completed.len(), 1);
assert!(book.queues[&EP].queued.is_empty());
assert!(book.disabled_bulk_endpoints.contains(&EP));
}
}