diff options
author | Dario Nieuwenhuis <dirbaio@dirbaio.net> | 2022-09-26 12:29:27 +0200 |
---|---|---|
committer | Dario Nieuwenhuis <dirbaio@dirbaio.net> | 2022-09-26 12:29:27 +0200 |
commit | 7f7c14b7bce5b84eb27c8122535a96a6f0e5dd77 (patch) | |
tree | c7481fd07b616128718301de7aafea553cc8dd6f /embassy-usb-driver | |
parent | a9efbf18c62186de0581ec72cca90a69340a02a3 (diff) | |
download | embassy-7f7c14b7bce5b84eb27c8122535a96a6f0e5dd77.zip |
usb: split driver trait to separate crate.
Diffstat (limited to 'embassy-usb-driver')
-rw-r--r-- | embassy-usb-driver/Cargo.toml | 16 | ||||
-rw-r--r-- | embassy-usb-driver/src/lib.rs | 337 |
2 files changed, 353 insertions, 0 deletions
diff --git a/embassy-usb-driver/Cargo.toml b/embassy-usb-driver/Cargo.toml new file mode 100644 index 00000000..b525df33 --- /dev/null +++ b/embassy-usb-driver/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "embassy-usb-driver" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[package.metadata.embassy_docs] +src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-driver-v$VERSION/embassy-usb/src/" +src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-driver/src/" +features = ["defmt"] +target = "thumbv7em-none-eabi" + +[dependencies] +defmt = { version = "0.3", optional = true } +log = { version = "0.4.14", optional = true }
\ No newline at end of file diff --git a/embassy-usb-driver/src/lib.rs b/embassy-usb-driver/src/lib.rs new file mode 100644 index 00000000..051190a4 --- /dev/null +++ b/embassy-usb-driver/src/lib.rs @@ -0,0 +1,337 @@ +#![no_std] + +use core::future::Future; + +/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from +/// the perspective of the host, which is backward for devices, but the standard directions are used +/// for consistency. +/// +/// The values of the enum also match the direction bit used in endpoint addresses and control +/// request types. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum Direction { + /// Host to device (OUT) + Out = 0x00, + /// Device to host (IN) + In = 0x80, +} + +impl From<u8> for Direction { + fn from(value: u8) -> Self { + unsafe { core::mem::transmute(value & 0x80) } + } +} + +/// USB endpoint transfer type. The values of this enum can be directly cast into `u8` to get the +/// transfer bmAttributes transfer type bits. +#[repr(u8)] +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum EndpointType { + /// Control endpoint. Used for device management. Only the host can initiate requests. Usually + /// used only endpoint 0. + Control = 0b00, + /// Isochronous endpoint. Used for time-critical unreliable data. Not implemented yet. + Isochronous = 0b01, + /// Bulk endpoint. Used for large amounts of best-effort reliable data. + Bulk = 0b10, + /// Interrupt endpoint. Used for small amounts of time-critical reliable data. + Interrupt = 0b11, +} + +/// Type-safe endpoint address. +#[derive(Debug, Clone, Copy, Eq, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAddress(u8); + +impl From<u8> for EndpointAddress { + #[inline] + fn from(addr: u8) -> EndpointAddress { + EndpointAddress(addr) + } +} + +impl From<EndpointAddress> for u8 { + #[inline] + fn from(addr: EndpointAddress) -> u8 { + addr.0 + } +} + +impl EndpointAddress { + const INBITS: u8 = Direction::In as u8; + + /// Constructs a new EndpointAddress with the given index and direction. + #[inline] + pub fn from_parts(index: usize, dir: Direction) -> Self { + EndpointAddress(index as u8 | dir as u8) + } + + /// Gets the direction part of the address. + #[inline] + pub fn direction(&self) -> Direction { + if (self.0 & Self::INBITS) != 0 { + Direction::In + } else { + Direction::Out + } + } + + /// Returns true if the direction is IN, otherwise false. + #[inline] + pub fn is_in(&self) -> bool { + (self.0 & Self::INBITS) != 0 + } + + /// Returns true if the direction is OUT, otherwise false. + #[inline] + pub fn is_out(&self) -> bool { + (self.0 & Self::INBITS) == 0 + } + + /// Gets the index part of the endpoint address. + #[inline] + pub fn index(&self) -> usize { + (self.0 & !Self::INBITS) as usize + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointInfo { + pub addr: EndpointAddress, + pub ep_type: EndpointType, + pub max_packet_size: u16, + pub interval: u8, +} + +/// Driver for a specific USB peripheral. Implement this to add support for a new hardware +/// platform. +pub trait Driver<'a> { + type EndpointOut: EndpointOut + 'a; + type EndpointIn: EndpointIn + 'a; + type ControlPipe: ControlPipe + 'a; + type Bus: Bus + 'a; + + /// Allocates an endpoint and specified endpoint parameters. This method is called by the device + /// and class implementations to allocate endpoints, and can only be called before + /// [`start`](Self::start) is called. + /// + /// # Arguments + /// + /// * `ep_addr` - A static endpoint address to allocate. If Some, the implementation should + /// attempt to return an endpoint with the specified address. If None, the implementation + /// should return the next available one. + /// * `max_packet_size` - Maximum packet size in bytes. + /// * `interval` - Polling interval parameter for interrupt endpoints. + fn alloc_endpoint_out( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result<Self::EndpointOut, EndpointAllocError>; + + fn alloc_endpoint_in( + &mut self, + ep_type: EndpointType, + max_packet_size: u16, + interval: u8, + ) -> Result<Self::EndpointIn, EndpointAllocError>; + + /// Start operation of the USB device. + /// + /// This returns the `Bus` and `ControlPipe` instances that are used to operate + /// the USB device. Additionally, this makes all the previously allocated endpoints + /// start operating. + /// + /// This consumes the `Driver` instance, so it's no longer possible to allocate more + /// endpoints. + fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe); + + /// Indicates that `set_device_address` must be called before accepting the corresponding + /// control transfer, not after. + /// + /// The default value for this constant is `false`, which corresponds to the USB 2.0 spec, 9.4.6 + const QUIRK_SET_ADDRESS_BEFORE_STATUS: bool = false; +} + +pub trait Bus { + type EnableFuture<'a>: Future<Output = ()> + 'a + where + Self: 'a; + type DisableFuture<'a>: Future<Output = ()> + 'a + where + Self: 'a; + type PollFuture<'a>: Future<Output = Event> + 'a + where + Self: 'a; + type RemoteWakeupFuture<'a>: Future<Output = Result<(), Unsupported>> + 'a + where + Self: 'a; + + /// Enables the USB peripheral. Soon after enabling the device will be reset, so + /// there is no need to perform a USB reset in this method. + fn enable(&mut self) -> Self::EnableFuture<'_>; + + /// Disables and powers down the USB peripheral. + fn disable(&mut self) -> Self::DisableFuture<'_>; + + fn poll<'a>(&'a mut self) -> Self::PollFuture<'a>; + + /// Sets the device USB address to `addr`. + fn set_address(&mut self, addr: u8); + + /// Enables or disables an endpoint. + fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool); + + /// Sets or clears the STALL condition for an endpoint. If the endpoint is an OUT endpoint, it + /// should be prepared to receive data again. Only used during control transfers. + fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool); + + /// Gets whether the STALL condition is set for an endpoint. Only used during control transfers. + fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool; + + /// Simulates a disconnect from the USB bus, causing the host to reset and re-enumerate the + /// device. + /// + /// The default implementation just returns `Unsupported`. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::driver::Unsupported) - This UsbBus implementation doesn't support + /// simulating a disconnect or it has not been enabled at creation time. + fn force_reset(&mut self) -> Result<(), Unsupported> { + Err(Unsupported) + } + + /// Initiates a remote wakeup of the host by the device. + /// + /// # Errors + /// + /// * [`Unsupported`](crate::driver::Unsupported) - This UsbBus implementation doesn't support + /// remote wakeup or it has not been enabled at creation time. + fn remote_wakeup(&mut self) -> Self::RemoteWakeupFuture<'_>; +} + +pub trait Endpoint { + type WaitEnabledFuture<'a>: Future<Output = ()> + 'a + where + Self: 'a; + + /// Get the endpoint address + fn info(&self) -> &EndpointInfo; + + /// Waits for the endpoint to be enabled. + fn wait_enabled(&mut self) -> Self::WaitEnabledFuture<'_>; +} + +pub trait EndpointOut: Endpoint { + type ReadFuture<'a>: Future<Output = Result<usize, EndpointError>> + 'a + where + Self: 'a; + + /// Reads a single packet of data from the endpoint, and returns the actual length of + /// the packet. + /// + /// This should also clear any NAK flags and prepare the endpoint to receive the next packet. + fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Self::ReadFuture<'a>; +} + +pub trait ControlPipe { + type SetupFuture<'a>: Future<Output = [u8; 8]> + 'a + where + Self: 'a; + type DataOutFuture<'a>: Future<Output = Result<usize, EndpointError>> + 'a + where + Self: 'a; + type DataInFuture<'a>: Future<Output = Result<(), EndpointError>> + 'a + where + Self: 'a; + type AcceptFuture<'a>: Future<Output = ()> + 'a + where + Self: 'a; + type RejectFuture<'a>: Future<Output = ()> + 'a + where + Self: 'a; + + /// Maximum packet size for the control pipe + fn max_packet_size(&self) -> usize; + + /// Reads a single setup packet from the endpoint. + fn setup<'a>(&'a mut self) -> Self::SetupFuture<'a>; + + /// Reads a DATA OUT packet into `buf` in response to a control write request. + /// + /// Must be called after `setup()` for requests with `direction` of `Out` + /// and `length` greater than zero. + fn data_out<'a>(&'a mut self, buf: &'a mut [u8], first: bool, last: bool) -> Self::DataOutFuture<'a>; + + /// Sends a DATA IN packet with `data` in response to a control read request. + /// + /// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`. + fn data_in<'a>(&'a mut self, data: &'a [u8], first: bool, last: bool) -> Self::DataInFuture<'a>; + + /// Accepts a control request. + /// + /// Causes the STATUS packet for the current request to be ACKed. + fn accept<'a>(&'a mut self) -> Self::AcceptFuture<'a>; + + /// Rejects a control request. + /// + /// Sets a STALL condition on the pipe to indicate an error. + fn reject<'a>(&'a mut self) -> Self::RejectFuture<'a>; +} + +pub trait EndpointIn: Endpoint { + type WriteFuture<'a>: Future<Output = Result<(), EndpointError>> + 'a + where + Self: 'a; + + /// Writes a single packet of data to the endpoint. + fn write<'a>(&'a mut self, buf: &'a [u8]) -> Self::WriteFuture<'a>; +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Event returned by [`Bus::poll`]. +pub enum Event { + /// The USB reset condition has been detected. + Reset, + + /// A USB suspend request has been detected or, in the case of self-powered devices, the device + /// has been disconnected from the USB bus. + Suspend, + + /// A USB resume request has been detected after being suspended or, in the case of self-powered + /// devices, the device has been connected to the USB bus. + Resume, + + /// The USB power has been detected. + PowerDetected, + + /// The USB power has been removed. Not supported by all devices. + PowerRemoved, +} + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub struct EndpointAllocError; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Operation is unsupported by the driver. +pub struct Unsupported; + +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`] +pub enum EndpointError { + /// Either the packet to be written is too long to fit in the transmission + /// buffer or the received packet is too long to fit in `buf`. + BufferOverflow, + + /// The endpoint is disabled. + Disabled, +} |