aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorBrian Cully <bjc@kublai.com>2019-08-11 12:37:21 -0400
committerBrian Cully <bjc@kublai.com>2019-08-11 12:48:06 -0400
commite8739872b482872adf9b18ef2b5419098f8fadb3 (patch)
tree3956b680f26c495cdddf7b71ad656e2ed14c2cc0
parentb0be48424e81384de3280b5a5ac521d694b82e15 (diff)
downloadusb-host-e8739872b482872adf9b18ef2b5419098f8fadb3.tar.gz
usb-host-e8739872b482872adf9b18ef2b5419098f8fadb3.zip
Add documentation and tests.
-rw-r--r--README.md12
-rw-r--r--TODO.org1
-rw-r--r--src/descriptor.rs152
-rw-r--r--src/lib.rs86
-rw-r--r--src/setup.rs49
5 files changed, 284 insertions, 16 deletions
diff --git a/README.md b/README.md
index a39968e..50262c8 100644
--- a/README.md
+++ b/README.md
@@ -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.
diff --git a/TODO.org b/TODO.org
index 8d22b40..7636304 100644
--- a/TODO.org
+++ b/TODO.org
@@ -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);
}
}
diff --git a/src/lib.rs b/src/lib.rs
index 742e02c..7a9fb01 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -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);
+ }
+}