diff --git a/uefi-test-runner/src/proto/mod.rs b/uefi-test-runner/src/proto/mod.rs index 7dfdb51d9..aa36a49c6 100644 --- a/uefi-test-runner/src/proto/mod.rs +++ b/uefi-test-runner/src/proto/mod.rs @@ -24,6 +24,7 @@ pub fn test() { rng::test(); shell_params::test(); string::test(); + usb::test(); misc::test(); // disable the ATA test on aarch64 for now. The aarch64 UEFI Firmware does not yet seem @@ -96,3 +97,4 @@ mod shell_params; mod shim; mod string; mod tcg; +mod usb; diff --git a/uefi-test-runner/src/proto/usb/io.rs b/uefi-test-runner/src/proto/usb/io.rs new file mode 100644 index 000000000..e80b986ab --- /dev/null +++ b/uefi-test-runner/src/proto/usb/io.rs @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +use core::mem; +use uefi::proto::usb::DeviceDescriptor; +use uefi::proto::usb::io::{ControlTransfer, UsbIo}; +use uefi::{Status, boot}; + +const DEVICE_TO_HOST: u8 = 1 << 7; +const STANDARD_REQUEST: u8 = 0b00 << 5; +const DEVICE_RECIPIENT: u8 = 0b0_0000; +const GET_DESCRIPTOR_REQUEST: u8 = 6; +const DEVICE_DESCRIPTOR: u8 = 1; + +/// This test iterates through all of the exposed active USB interfaces and +/// performs checks on each to validate that descriptor acquisition and control +/// transfers work correctly. +pub fn test() { + info!("Testing USB I/O protocol"); + + let handles = boot::locate_handle_buffer(boot::SearchType::from_proto::()) + .expect("failed to acquire USB I/O handles"); + + for handle in handles.iter().copied() { + let mut io = boot::open_protocol_exclusive::(handle) + .expect("failed to open USB I/O protocol"); + + let device = io + .device_descriptor() + .expect("failed to acquire USB device descriptor"); + io.config_descriptor() + .expect("failed to acquire USB config descriptor"); + io.interface_descriptor() + .expect("failed to acquire USB interface descriptor"); + + for endpoint_index in 0..16 { + let result = io.endpoint_descriptor(endpoint_index); + if result + .as_ref() + .is_err_and(|error| error.status() == Status::NOT_FOUND) + { + continue; + } + + result.expect("failed to acquire USB endpoint descriptor"); + } + + let supported_languages = io + .supported_languages() + .expect("failed to acquire supported language list"); + let test_language = supported_languages[0]; + + for string_index in 0..=u8::MAX { + let result = io.string_descriptor(test_language, string_index); + if result + .as_ref() + .is_err_and(|error| error.status() == Status::NOT_FOUND) + { + continue; + } + + result.expect("failed to acquire string descriptor"); + } + + let mut buffer = [0u8; mem::size_of::()]; + + io.control_transfer( + DEVICE_TO_HOST | STANDARD_REQUEST | DEVICE_RECIPIENT, + GET_DESCRIPTOR_REQUEST, + u16::from(DEVICE_DESCRIPTOR) << 8, + 0, + ControlTransfer::DataIn(&mut buffer[..mem::size_of::()]), + 0, + ) + .expect("failed control transfer"); + unsafe { + assert_eq!( + device, + buffer.as_ptr().cast::().read_unaligned() + ) + } + } +} diff --git a/uefi-test-runner/src/proto/usb/mod.rs b/uefi-test-runner/src/proto/usb/mod.rs new file mode 100644 index 000000000..9e99f7b26 --- /dev/null +++ b/uefi-test-runner/src/proto/usb/mod.rs @@ -0,0 +1,9 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +pub fn test() { + info!("Testing USB protocols"); + + io::test(); +} + +mod io; diff --git a/uefi/CHANGELOG.md b/uefi/CHANGELOG.md index 927f0fef2..75bd767f0 100644 --- a/uefi/CHANGELOG.md +++ b/uefi/CHANGELOG.md @@ -2,6 +2,7 @@ ## Added - Added `ConfigTableEntry::MEMORY_ATTRIBUTES_GUID` and `ConfigTableEntry::IMAGE_SECURITY_DATABASE_GUID`. +- Added `proto::usb::io::UsbIo`. ## Changed - **Breaking:** `boot::stall` now take `core::time::Duration` instead of `usize`. diff --git a/uefi/src/proto/mod.rs b/uefi/src/proto/mod.rs index 4d67080b7..77474248e 100644 --- a/uefi/src/proto/mod.rs +++ b/uefi/src/proto/mod.rs @@ -31,6 +31,7 @@ pub mod shell_params; pub mod shim; pub mod string; pub mod tcg; +pub mod usb; mod boot_policy; diff --git a/uefi/src/proto/usb/io.rs b/uefi/src/proto/usb/io.rs new file mode 100644 index 000000000..b2babe906 --- /dev/null +++ b/uefi/src/proto/usb/io.rs @@ -0,0 +1,296 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! USB I/O protocol. + +use core::ffi; + +use uefi_macros::unsafe_protocol; +use uefi_raw::protocol::usb::io::UsbIoProtocol; +use uefi_raw::protocol::usb::{ + ConfigDescriptor, DataDirection, DeviceDescriptor, DeviceRequest, EndpointDescriptor, + InterfaceDescriptor, UsbTransferStatus, +}; + +use crate::data_types::PoolString; +use crate::{Char16, Result, StatusExt}; + +/// USB I/O protocol. +#[derive(Debug)] +#[repr(transparent)] +#[unsafe_protocol(UsbIoProtocol::GUID)] +pub struct UsbIo(UsbIoProtocol); + +impl UsbIo { + /// Performs a USB Control transfer, allowing the driver to communicate with the USB device. + pub fn control_transfer( + &mut self, + request_type: u8, + request: u8, + value: u16, + index: u16, + transfer: ControlTransfer, + timeout: u32, + ) -> Result<(), UsbTransferStatus> { + let (direction, buffer_ptr, length) = match transfer { + ControlTransfer::None => (DataDirection::NO_DATA, core::ptr::null_mut(), 0), + ControlTransfer::DataIn(buffer) => ( + DataDirection::DATA_IN, + buffer.as_ptr().cast_mut(), + buffer.len(), + ), + ControlTransfer::DataOut(buffer) => ( + DataDirection::DATA_OUT, + buffer.as_ptr().cast_mut(), + buffer.len(), + ), + }; + + let request_type = if direction == DataDirection::DATA_IN { + request_type | 0x80 + } else if direction == DataDirection::DATA_OUT { + request_type & !0x80 + } else { + request_type + }; + + let mut device_request = DeviceRequest { + request_type, + request, + value, + index, + length: length as u16, + }; + let mut status = UsbTransferStatus::default(); + + unsafe { + (self.0.control_transfer)( + &mut self.0, + &mut device_request, + direction, + timeout, + buffer_ptr.cast::(), + length, + &mut status, + ) + } + .to_result_with_err(|_| status) + } + + /// Sends the provided buffer to a USB device over a bulk transfer pipe. + /// + /// Returns the number of bytes that were actually sent to the device. + pub fn sync_bulk_send( + &mut self, + endpoint: u8, + buffer: &[u8], + timeout: usize, + ) -> Result { + let mut status = UsbTransferStatus::default(); + let mut length = buffer.len(); + + unsafe { + (self.0.bulk_transfer)( + &mut self.0, + endpoint & !0x80, + buffer.as_ptr().cast_mut().cast::(), + &mut length, + timeout, + &mut status, + ) + } + .to_result_with_err(|_| status) + .map(|()| length) + } + + /// Fills the provided buffer with data from a USB device over a bulk transfer pipe. + /// + /// Returns the number of bytes that were actually received from the device. + pub fn sync_bulk_receive( + &mut self, + endpoint: u8, + buffer: &mut [u8], + timeout: usize, + ) -> Result { + let mut status = UsbTransferStatus::default(); + let mut length = buffer.len(); + + unsafe { + (self.0.bulk_transfer)( + &mut self.0, + endpoint | 0x80, + buffer.as_ptr().cast_mut().cast::(), + &mut length, + timeout, + &mut status, + ) + } + .to_result_with_err(|_| status) + .map(|()| length) + } + + /// Sends the provided buffer to a USB device through a synchronous interrupt transfer. + pub fn sync_interrupt_send( + &mut self, + endpoint: u8, + buffer: &[u8], + timeout: usize, + ) -> Result { + let mut status = UsbTransferStatus::default(); + let mut length = buffer.len(); + + unsafe { + (self.0.sync_interrupt_transfer)( + &mut self.0, + endpoint & !0x80, + buffer.as_ptr().cast_mut().cast::(), + &mut length, + timeout, + &mut status, + ) + } + .to_result_with_err(|_| status) + .map(|()| length) + } + + /// Fills the provided buffer with data from a USB device through a synchronous interrupt + /// transfer. + pub fn sync_interrupt_receive( + &mut self, + endpoint: u8, + buffer: &mut [u8], + timeout: usize, + ) -> Result { + let mut status = UsbTransferStatus::default(); + let mut length = buffer.len(); + + unsafe { + (self.0.sync_interrupt_transfer)( + &mut self.0, + endpoint | 0x80, + buffer.as_ptr().cast_mut().cast::(), + &mut length, + timeout, + &mut status, + ) + } + .to_result_with_err(|_| status) + .map(|()| length) + } + + /// Sends the provided buffer to a USB device over an isochronous transfer pipe. + pub fn sync_isochronous_send( + &mut self, + endpoint: u8, + buffer: &[u8], + ) -> Result<(), UsbTransferStatus> { + let mut status = UsbTransferStatus::default(); + + unsafe { + (self.0.isochronous_transfer)( + &mut self.0, + endpoint & !0x80, + buffer.as_ptr().cast_mut().cast::(), + buffer.len(), + &mut status, + ) + } + .to_result_with_err(|_| status) + } + + /// Fills the provided buffer with data from a USB device over an isochronous transfer pipe. + pub fn sync_isochronous_receive( + &mut self, + endpoint: u8, + buffer: &mut [u8], + ) -> Result<(), UsbTransferStatus> { + let mut status = UsbTransferStatus::default(); + + unsafe { + (self.0.isochronous_transfer)( + &mut self.0, + endpoint | 0x80, + buffer.as_mut_ptr().cast::(), + buffer.len(), + &mut status, + ) + } + .to_result_with_err(|_| status) + } + + /// Returns information about USB devices, including the device's class, subclass, and number + /// of configurations. + pub fn device_descriptor(&mut self) -> Result { + let mut device_descriptor = unsafe { core::mem::zeroed() }; + + unsafe { (self.0.get_device_descriptor)(&mut self.0, &mut device_descriptor) } + .to_result_with_val(|| device_descriptor) + } + + /// Returns information about the active configuration of the USB device. + pub fn config_descriptor(&mut self) -> Result { + let mut config_descriptor = unsafe { core::mem::zeroed() }; + + unsafe { (self.0.get_config_descriptor)(&mut self.0, &mut config_descriptor) } + .to_result_with_val(|| config_descriptor) + } + + /// Returns information about the interface of the USB device. + pub fn interface_descriptor(&mut self) -> Result { + let mut interface_descriptor = unsafe { core::mem::zeroed() }; + + unsafe { (self.0.get_interface_descriptor)(&mut self.0, &mut interface_descriptor) } + .to_result_with_val(|| interface_descriptor) + } + + /// Returns information about the interface of the USB device. + pub fn endpoint_descriptor(&mut self, endpoint: u8) -> Result { + let mut endpoint_descriptor = unsafe { core::mem::zeroed() }; + + unsafe { (self.0.get_endpoint_descriptor)(&mut self.0, endpoint, &mut endpoint_descriptor) } + .to_result_with_val(|| endpoint_descriptor) + } + + /// Returns the string associated with `string_id` in the language associated with `lang_id`. + pub fn string_descriptor(&mut self, lang_id: u16, string_id: u8) -> Result { + let mut string_ptr = core::ptr::null_mut(); + + unsafe { (self.0.get_string_descriptor)(&mut self.0, lang_id, string_id, &mut string_ptr) } + .to_result()?; + unsafe { PoolString::new(string_ptr.cast::()) } + } + + /// Returns all of the language ID codes that the USB device supports. + pub fn supported_languages(&mut self) -> Result<&[u16]> { + let mut lang_id_table_ptr = core::ptr::null_mut(); + let mut lang_id_table_size = 0; + + unsafe { + (self.0.get_supported_languages)( + &mut self.0, + &mut lang_id_table_ptr, + &mut lang_id_table_size, + ) + } + .to_result_with_val(|| unsafe { + core::slice::from_raw_parts(lang_id_table_ptr, usize::from(lang_id_table_size)) + }) + } + + /// Resets and reconfigures the USB controller. + /// + /// This function should work for all USB devices except USB Hub Controllers. + pub fn port_reset(&mut self) -> Result { + unsafe { (self.0.port_reset)(&mut self.0) }.to_result() + } +} + +/// Controls what type of USB control transfer operation should occur. +#[derive(Debug)] +pub enum ControlTransfer<'buffer> { + /// The USB control transfer has no data phase. + None, + /// The USB control transfer has an input data phase. + DataIn(&'buffer mut [u8]), + /// The USB control transfer has an output data phase. + DataOut(&'buffer [u8]), +} diff --git a/uefi/src/proto/usb/mod.rs b/uefi/src/proto/usb/mod.rs new file mode 100644 index 000000000..24a766084 --- /dev/null +++ b/uefi/src/proto/usb/mod.rs @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 + +//! USB I/O protocols. +//! +//! These protocols can be used to interact with and configure USB devices. + +pub mod io; + +pub use uefi_raw::protocol::usb::{ + AsyncUsbTransferCallback, ConfigDescriptor, DeviceDescriptor, EndpointDescriptor, + InterfaceDescriptor, UsbTransferStatus, +}; diff --git a/xtask/src/qemu.rs b/xtask/src/qemu.rs index 6499f5220..20cc7094d 100644 --- a/xtask/src/qemu.rs +++ b/xtask/src/qemu.rs @@ -438,6 +438,11 @@ pub fn run_qemu(arch: UefiArch, opt: &QemuOpt) -> Result<()> { cmd.args(["-display", "none"]); } + // Configure USB + cmd.args(["-device", "qemu-xhci"]); + + cmd.args(["-device", "usb-net"]); + // Second (FAT) disk let test_disk = tmp_dir.join("test_disk.fat.img"); create_mbr_test_disk(&test_disk)?;