diff options
author | Brian Cully <bjc@kublai.com> | 2019-08-11 12:37:21 -0400 |
---|---|---|
committer | Brian Cully <bjc@kublai.com> | 2019-08-11 12:48:06 -0400 |
commit | e8739872b482872adf9b18ef2b5419098f8fadb3 (patch) | |
tree | 3956b680f26c495cdddf7b71ad656e2ed14c2cc0 | |
parent | b0be48424e81384de3280b5a5ac521d694b82e15 (diff) | |
download | usb-host-e8739872b482872adf9b18ef2b5419098f8fadb3.tar.gz usb-host-e8739872b482872adf9b18ef2b5419098f8fadb3.zip |
Add documentation and tests.
-rw-r--r-- | README.md | 12 | ||||
-rw-r--r-- | TODO.org | 1 | ||||
-rw-r--r-- | src/descriptor.rs | 152 | ||||
-rw-r--r-- | src/lib.rs | 86 | ||||
-rw-r--r-- | src/setup.rs | 49 |
5 files changed, 284 insertions, 16 deletions
@@ -3,11 +3,23 @@ This is a collection of traits that mediate between drivers for USB peripherals and USB Host. +# Warning + This is still very early days, yet, and everything should be considered experimental. However, it has been used to write a [driver for a boot protocol keyboard](https://github.com/bjc/bootkbd), and can presumably be used for other drivers as well. +The traits and structures defined in this crate are probably not +sufficient for general use. They are being kept purposefully minimal +to ease maintenance burden for implementors of these traits. If you +think there need to be further definitions, please open an issue (or, +even better, a pull request) on github describing the need. + + +A (partial) list of things that need to be changed, or at least looked +at, is maintained in `TODO.org`. + # Device driver crates. * [bootkbd](https://github.com/bjc/bootkbd) - A very simple boot-protocol keyboard driver. @@ -1,6 +1,5 @@ * Documentation - README.md - - Literally every trait and method. * Do not require `core::fmt::Debug` on drivers. This is a temporary measure so I can show which driver had an issue when something goes wrong, but it's much more expansive than needed. diff --git a/src/descriptor.rs b/src/descriptor.rs index f49259f..04461e7 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -1,3 +1,12 @@ +//! A collection of structures defining descriptors in the USB. +//! +//! These types are defined in §9.5 and 9.6 of the USB 2.0 +//! specification. +//! +//! The structures defined herein are `repr(C)` and `repr(packed)` +//! when necessary to ensure that they are able to be directly +//! marshalled to the bus. + use core::convert::TryFrom; #[derive(Clone, Copy, Debug, PartialEq)] @@ -92,24 +101,159 @@ mod test { use super::*; use core::mem; + use core::slice; #[test] fn device_descriptor_layout() { - assert_eq!(mem::size_of::<DeviceDescriptor>(), 18); + let len = mem::size_of::<DeviceDescriptor>(); + assert_eq!(len, 18); + let desc = DeviceDescriptor { + b_length: len as u8, + b_descriptor_type: DescriptorType::Device, + bcd_usb: 0x1001, + b_device_class: 0xaa, + b_device_sub_class: 0xbb, + b_device_protocol: 0xcc, + b_max_packet_size: 0xdd, + id_vendor: 0xdead, + id_product: 0xbeef, + bcd_device: 0xf00d, + i_manufacturer: 0x11, + i_product: 0x22, + i_serial_number: 0x33, + b_num_configurations: 0x44, + }; + let base = &desc as *const _ as usize; + assert_offset("b_length", &desc.b_length, base, 0x00); + assert_offset("b_descriptor_type", &desc.b_descriptor_type, base, 0x01); + assert_offset("bcd_usb", &desc.bcd_usb, base, 0x02); + assert_offset("b_device_class", &desc.b_device_class, base, 0x04); + assert_offset("b_device_sub_class", &desc.b_device_sub_class, base, 0x05); + assert_offset("b_device_protocol", &desc.b_device_protocol, base, 0x06); + assert_offset("b_max_packet_size", &desc.b_max_packet_size, base, 0x07); + assert_offset("id_vendor", &desc.id_vendor, base, 0x08); + assert_offset("id_product", &desc.id_product, base, 0x0a); + assert_offset("bcd_device", &desc.bcd_device, base, 0x0c); + assert_offset("i_manufacturer", &desc.i_manufacturer, base, 0x0e); + assert_offset("i_product", &desc.i_product, base, 0x0f); + assert_offset("i_serial_number", &desc.i_serial_number, base, 0x10); + assert_offset( + "b_num_configurations", + &desc.b_num_configurations, + base, + 0x011, + ); + + let got = unsafe { slice::from_raw_parts(&desc as *const _ as *const u8, len) }; + let want = &[ + 0x12, 0x01, 0x01, 0x10, 0xaa, 0xbb, 0xcc, 0xdd, 0xad, 0xde, 0xef, 0xbe, 0x0d, 0xf0, + 0x11, 0x22, 0x33, 0x44, + ]; + assert_eq!(got, want); } #[test] fn configuration_descriptor_layout() { - assert_eq!(mem::size_of::<ConfigurationDescriptor>(), 9); + let len = mem::size_of::<ConfigurationDescriptor>(); + assert_eq!(len, 9); + let desc = ConfigurationDescriptor { + b_length: len as u8, + b_descriptor_type: DescriptorType::Configuration, + w_total_length: 0xdead, + b_num_interfaces: 0x22, + b_configuration_value: 0x33, + i_configuration: 0x44, + bm_attributes: 0x55, + b_max_power: 0x66, + }; + let base = &desc as *const _ as usize; + assert_offset("b_length", &desc.b_length, base, 0x00); + assert_offset("b_descriptor_type", &desc.b_descriptor_type, base, 0x01); + assert_offset("w_total_length", &desc.w_total_length, base, 0x02); + assert_offset("b_num_interfaces", &desc.b_num_interfaces, base, 0x04); + assert_offset( + "b_configuration_value", + &desc.b_configuration_value, + base, + 0x05, + ); + assert_offset("i_configuration", &desc.i_configuration, base, 0x06); + assert_offset("bm_attributes", &desc.bm_attributes, base, 0x07); + assert_offset("b_max_power", &desc.b_max_power, base, 0x08); + + let got = unsafe { slice::from_raw_parts(&desc as *const _ as *const u8, len) }; + let want = &[0x09, 0x02, 0xad, 0xde, 0x22, 0x33, 0x44, 0x55, 0x66]; + assert_eq!(got, want); } #[test] fn interface_descriptor_layout() { - assert_eq!(mem::size_of::<InterfaceDescriptor>(), 9); + let len = mem::size_of::<InterfaceDescriptor>(); + assert_eq!(len, 9); + let desc = InterfaceDescriptor { + b_length: len as u8, + b_descriptor_type: DescriptorType::Interface, + b_interface_number: 0xee, + b_alternate_setting: 0xaa, + b_num_endpoints: 0xf7, + b_interface_class: 0x11, + b_interface_sub_class: 0x22, + b_interface_protocol: 0x33, + i_interface: 0x44, + }; + let base = &desc as *const _ as usize; + assert_offset("b_length", &desc.b_length, base, 0x00); + assert_offset("b_descriptor_type", &desc.b_descriptor_type, base, 0x01); + assert_offset("b_interface_number", &desc.b_interface_number, base, 0x02); + assert_offset("b_alternate_setting", &desc.b_alternate_setting, base, 0x03); + assert_offset("b_num_endpoints", &desc.b_num_endpoints, base, 0x04); + assert_offset("b_interface_class", &desc.b_interface_class, base, 0x05); + assert_offset( + "b_interface_sub_class", + &desc.b_interface_sub_class, + base, + 0x06, + ); + assert_offset( + "b_interface_protocol", + &desc.b_interface_protocol, + base, + 0x07, + ); + assert_offset("i_interface", &desc.i_interface, base, 0x08); + + let got = unsafe { slice::from_raw_parts(&desc as *const _ as *const u8, len) }; + let want = &[0x09, 0x04, 0xee, 0xaa, 0xf7, 0x11, 0x22, 0x33, 0x44]; + assert_eq!(got, want); } #[test] fn endpoint_descriptor_layout() { - assert_eq!(mem::size_of::<EndpointDescriptor>(), 7); + let len = mem::size_of::<EndpointDescriptor>(); + assert_eq!(len, 7); + let desc = EndpointDescriptor { + b_length: len as u8, + b_descriptor_type: DescriptorType::Endpoint, + b_endpoint_address: 2, + bm_attributes: 0xae, + w_max_packet_size: 0xdead, + b_interval: 0x7a, + }; + let base = &desc as *const _ as usize; + assert_offset("b_length", &desc.b_length, base, 0x00); + assert_offset("b_descriptor_type", &desc.b_descriptor_type, base, 0x01); + assert_offset("b_endpoint_address", &desc.b_endpoint_address, base, 0x02); + assert_offset("bm_attributes", &desc.bm_attributes, base, 0x03); + assert_offset("w_max_packet_size", &desc.w_max_packet_size, base, 0x04); + assert_offset("b_interval", &desc.b_interval, base, 0x06); + + let got = unsafe { slice::from_raw_parts(&desc as *const _ as *const u8, len) }; + let want = &[0x07, 0x05, 0x02, 0xae, 0xad, 0xde, 0x7a]; + assert_eq!(got, want); + } + + fn assert_offset<T>(name: &str, field: &T, base: usize, offset: usize) { + let ptr = field as *const _ as usize; + assert_eq!(ptr - base, offset, "{} register offset.", name); } } @@ -1,20 +1,38 @@ -//! Traits for a hardware-agnostic USB host interface. +//! This crate defines a set of traits for use on the host side of the +//! USB. +//! +//! The `USBHost` defines the Host Controller Interface that can be +//! used by the `Driver` interface. +//! +//! The `Driver` interface defines the set of functions necessary to +//! use devices plugged into the host. #![no_std] -mod descriptor; -mod setup; +pub mod descriptor; +pub mod setup; pub use descriptor::*; pub use setup::*; +/// Errors that can be generated when attempting to do a USB transfer. #[derive(Debug)] pub enum TransferError { + /// An error that may be retried. Retry(&'static str), + + /// A permanent error. Permanent(&'static str), } +/// Trait for host controller interface. pub trait USBHost { + /// Issue a control transfer with an optional data stage to + /// `ep`. The data stage direction is determined by the direction + /// of `bm_request_type`. + /// + /// On success, the amount of data transferred into `buf` is + /// returned. fn control_transfer( &mut self, ep: &mut dyn Endpoint, @@ -25,16 +43,26 @@ pub trait USBHost { buf: Option<&mut [u8]>, ) -> Result<usize, TransferError>; + /// Issue a transfer from `ep` to the host. + /// + /// On success, the amount of data transferred into `buf` is + /// returned. fn in_transfer( &mut self, ep: &mut dyn Endpoint, buf: &mut [u8], ) -> Result<usize, TransferError>; + /// Issue a transfer from the host to `ep`. + /// + /// On success, the amount of data transferred from `buf` is + /// returned. This should always be equal to `buf.len()`. fn out_transfer(&mut self, ep: &mut dyn Endpoint, buf: &[u8]) -> Result<usize, TransferError>; } -// cf §9.6.6 of USB 2.0 +/// The type of transfer to use when talking to USB devices. +/// +/// cf §9.6.6 of USB 2.0 #[derive(Copy, Clone, Debug, PartialEq)] pub enum TransferType { Control = 0, @@ -43,50 +71,86 @@ pub enum TransferType { Interrupt = 3, } -// ibid +/// The direction of the transfer with the USB device. +/// +/// cf §9.6.6 of USB 2.0 #[derive(Copy, Clone, Debug, PartialEq)] pub enum Direction { Out, In, } +/// `Endpoint` defines the USB endpoint for various transfers. pub trait Endpoint { + /// Address of the device owning this endpoint. Must be between 0 + /// and 127. fn address(&self) -> u8; + /// Endpoint number, irrespective of direction. (e.g., for both + /// endpoint addresses, `0x81` and `0x01`, this function would + /// return `0x01`). fn endpoint_num(&self) -> u8; + /// The type of transfer this endpoint uses. fn transfer_type(&self) -> TransferType; + /// The direction of transfer this endpoint accepts. fn direction(&self) -> Direction; + /// The maximum packet size for this endpoint. fn max_packet_size(&self) -> u16; + /// The data toggle sequence bit for the next transfer from the + /// device to the host. fn in_toggle(&self) -> bool; + /// The `USBHost` will, when required, update the data toggle + /// sequence bit for the next device to host transfer. fn set_in_toggle(&mut self, toggle: bool); + /// The data toggle sequence bit for the next transfer from the + /// host to the device. fn out_toggle(&self) -> bool; + /// The `USBHost` will, when required, update the data toggle + /// sequence bit for the next host to device transfer. fn set_out_toggle(&mut self, toggle: bool); } +/// Types of errors that can be returned from a `Driver`. #[derive(Copy, Clone, Debug)] pub enum DriverError { + /// An error that may be retried. Retry(u8, &'static str), + + /// A permanent error. Permanent(u8, &'static str), } + +/// Trait for drivers on the USB host. pub trait Driver: core::fmt::Debug { + /// Does this driver want `device`? + /// + /// Answering `true` to this not necessarily mean the driver will + /// get `device`. fn want_device(&self, device: &DeviceDescriptor) -> bool; + /// Add `device` with address `address` to the driver's registry, + /// if necessary. fn add_device(&mut self, device: DeviceDescriptor, address: u8) -> Result<(), DriverError>; + /// Remove the device at address `address` from the driver's + /// registry, if necessary. fn remove_device(&mut self, address: u8); + /// Called regularly by the USB host to allow the driver to do any + /// work necessary on its registered devices. + /// + /// `millis` is the current time, in milliseconds from some + /// arbitrary starting point. It should be expected that after a + /// long enough run-time, this value will wrap. + /// + /// `usbhost` may be used for communication with the USB when + /// required. fn tick(&mut self, millis: usize, usbhost: &mut dyn USBHost) -> Result<(), DriverError>; } - -// TODO: There needs to be a per-interface/function driver trait, as -// well, since that's how most drivers will actually work. -// -// As a result, the host driver has to at least get the full -// configuration. diff --git a/src/setup.rs b/src/setup.rs index 8fa6414..1451cb6 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,3 +1,12 @@ +//! A collection of structures for use in setting up devices during +//! enumeration. +//! +//! These types are all defined in §9.3 of the USB 2.0 specification. +//! +//! The structures defined herein are `repr(C)` and `repr(packed)` +//! when necessary to ensure that they are able to be directly +//! marshalled to the bus. + use core::convert::{TryFrom, TryInto}; #[derive(Clone, Copy, Debug, PartialEq)] @@ -194,3 +203,43 @@ pub struct SetupPacket { pub w_index: u16, pub w_length: u16, } + +#[cfg(test)] +mod test { + use super::*; + + use core::mem; + use core::slice; + + #[test] + fn setup_packet_layout() { + let len = mem::size_of::<SetupPacket>(); + assert_eq!(len, 8); + let sp = SetupPacket { + bm_request_type: RequestType::from(( + RequestDirection::HostToDevice, + RequestKind::Class, + RequestRecipient::Endpoint, + )), + b_request: RequestCode::GetInterface, + w_value: WValue::from((0xf0, 0x0d)), + w_index: 0xadde, + w_length: 0xefbe, + }; + let base = &sp as *const _ as usize; + assert_offset("bm_request_type", &sp.bm_request_type, base, 0x00); + assert_offset("b_request", &sp.b_request, base, 0x01); + assert_offset("w_value", &sp.w_value, base, 0x02); + assert_offset("w_index", &sp.w_index, base, 0x04); + assert_offset("w_length", &sp.w_length, base, 0x06); + + let got = unsafe { slice::from_raw_parts(&sp as *const _ as *const u8, len) }; + let want = &[0x22, 0x0a, 0xf0, 0x0d, 0xde, 0xad, 0xbe, 0xef]; + assert_eq!(got, want); + } + + fn assert_offset<T>(name: &str, field: &T, base: usize, offset: usize) { + let ptr = field as *const _ as usize; + assert_eq!(ptr - base, offset, "{} register offset.", name); + } +} |