usb: docs
This commit is contained in:
parent
4c19464548
commit
ca10fe7135
17 changed files with 267 additions and 97 deletions
|
@ -235,7 +235,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
packet_size: u16,
|
packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
|
) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
|
||||||
let index = self.alloc_in.allocate(ep_type)?;
|
let index = self.alloc_in.allocate(ep_type)?;
|
||||||
let ep_addr = EndpointAddress::from_parts(index, Direction::In);
|
let ep_addr = EndpointAddress::from_parts(index, Direction::In);
|
||||||
|
@ -243,7 +243,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
|
||||||
addr: ep_addr,
|
addr: ep_addr,
|
||||||
ep_type,
|
ep_type,
|
||||||
max_packet_size: packet_size,
|
max_packet_size: packet_size,
|
||||||
interval,
|
interval_ms,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -251,7 +251,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
packet_size: u16,
|
packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
|
) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
|
||||||
let index = self.alloc_out.allocate(ep_type)?;
|
let index = self.alloc_out.allocate(ep_type)?;
|
||||||
let ep_addr = EndpointAddress::from_parts(index, Direction::Out);
|
let ep_addr = EndpointAddress::from_parts(index, Direction::Out);
|
||||||
|
@ -259,7 +259,7 @@ impl<'d, T: Instance, P: UsbSupply + 'd> driver::Driver<'d> for Driver<'d, T, P>
|
||||||
addr: ep_addr,
|
addr: ep_addr,
|
||||||
ep_type,
|
ep_type,
|
||||||
max_packet_size: packet_size,
|
max_packet_size: packet_size,
|
||||||
interval,
|
interval_ms,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -194,13 +194,13 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> {
|
) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> {
|
||||||
trace!(
|
trace!(
|
||||||
"allocating type={:?} mps={:?} interval={}, dir={:?}",
|
"allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
|
||||||
ep_type,
|
ep_type,
|
||||||
max_packet_size,
|
max_packet_size,
|
||||||
interval,
|
interval_ms,
|
||||||
D::dir()
|
D::dir()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -281,7 +281,7 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||||
addr: EndpointAddress::from_parts(index, D::dir()),
|
addr: EndpointAddress::from_parts(index, D::dir()),
|
||||||
ep_type,
|
ep_type,
|
||||||
max_packet_size,
|
max_packet_size,
|
||||||
interval,
|
interval_ms,
|
||||||
},
|
},
|
||||||
buf,
|
buf,
|
||||||
})
|
})
|
||||||
|
@ -298,18 +298,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
|
) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
|
||||||
self.alloc_endpoint(ep_type, max_packet_size, interval)
|
self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_endpoint_out(
|
fn alloc_endpoint_out(
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
|
) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
|
||||||
self.alloc_endpoint(ep_type, max_packet_size, interval)
|
self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
|
fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
|
||||||
|
|
|
@ -268,13 +268,13 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> {
|
) -> Result<Endpoint<'d, T, D>, driver::EndpointAllocError> {
|
||||||
trace!(
|
trace!(
|
||||||
"allocating type={:?} mps={:?} interval={}, dir={:?}",
|
"allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
|
||||||
ep_type,
|
ep_type,
|
||||||
max_packet_size,
|
max_packet_size,
|
||||||
interval,
|
interval_ms,
|
||||||
D::dir()
|
D::dir()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -345,7 +345,7 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||||
addr: EndpointAddress::from_parts(index, D::dir()),
|
addr: EndpointAddress::from_parts(index, D::dir()),
|
||||||
ep_type,
|
ep_type,
|
||||||
max_packet_size,
|
max_packet_size,
|
||||||
interval,
|
interval_ms,
|
||||||
},
|
},
|
||||||
buf,
|
buf,
|
||||||
})
|
})
|
||||||
|
@ -362,18 +362,18 @@ impl<'d, T: Instance> driver::Driver<'d> for Driver<'d, T> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
|
) -> Result<Self::EndpointIn, driver::EndpointAllocError> {
|
||||||
self.alloc_endpoint(ep_type, max_packet_size, interval)
|
self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_endpoint_out(
|
fn alloc_endpoint_out(
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
|
) -> Result<Self::EndpointOut, driver::EndpointAllocError> {
|
||||||
self.alloc_endpoint(ep_type, max_packet_size, interval)
|
self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
|
fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
|
||||||
|
|
|
@ -217,13 +217,13 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Endpoint<'d, T, D>, EndpointAllocError> {
|
) -> Result<Endpoint<'d, T, D>, EndpointAllocError> {
|
||||||
trace!(
|
trace!(
|
||||||
"allocating type={:?} mps={:?} interval={}, dir={:?}",
|
"allocating type={:?} mps={:?} interval_ms={}, dir={:?}",
|
||||||
ep_type,
|
ep_type,
|
||||||
max_packet_size,
|
max_packet_size,
|
||||||
interval,
|
interval_ms,
|
||||||
D::dir()
|
D::dir()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -292,7 +292,7 @@ impl<'d, T: Instance> Driver<'d, T> {
|
||||||
addr: EndpointAddress::from_parts(index, D::dir()),
|
addr: EndpointAddress::from_parts(index, D::dir()),
|
||||||
ep_type,
|
ep_type,
|
||||||
max_packet_size,
|
max_packet_size,
|
||||||
interval,
|
interval_ms,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -308,18 +308,18 @@ impl<'d, T: Instance> embassy_usb_driver::Driver<'d> for Driver<'d, T> {
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointIn, EndpointAllocError> {
|
) -> Result<Self::EndpointIn, EndpointAllocError> {
|
||||||
self.alloc_endpoint(ep_type, max_packet_size, interval)
|
self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn alloc_endpoint_out(
|
fn alloc_endpoint_out(
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointOut, EndpointAllocError> {
|
) -> Result<Self::EndpointOut, EndpointAllocError> {
|
||||||
self.alloc_endpoint(ep_type, max_packet_size, interval)
|
self.alloc_endpoint(ep_type, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
|
fn start(mut self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe) {
|
||||||
|
|
32
embassy-usb-driver/README.md
Normal file
32
embassy-usb-driver/README.md
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
# embassy-usb-driver
|
||||||
|
|
||||||
|
This crate contains the driver traits for [`embassy-usb`]. HAL/BSP crates can implement these
|
||||||
|
traits to add support for using `embassy-usb` for a given chip/platform.
|
||||||
|
|
||||||
|
The traits are kept in a separate crate so that breaking changes in the higher-level [`embassy-usb`]
|
||||||
|
APIs don't cause a semver-major bump of thsi crate. This allows existing HALs/BSPs to be used
|
||||||
|
with the newer `embassy-usb` without needing updates.
|
||||||
|
|
||||||
|
If you're writing an application using USB, you should depend on the main [`embassy-usb`] crate
|
||||||
|
instead of this one.
|
||||||
|
|
||||||
|
[`embassy-usb`]: https://crates.io/crates/embassy-usb
|
||||||
|
|
||||||
|
## Interoperability
|
||||||
|
|
||||||
|
This crate can run on any executor.
|
||||||
|
|
||||||
|
## Minimum supported Rust version (MSRV)
|
||||||
|
|
||||||
|
This crate requires nightly Rust, due to using "async fn in trait" support.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This work is licensed under either of
|
||||||
|
|
||||||
|
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
<http://www.apache.org/licenses/LICENSE-2.0>)
|
||||||
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(async_fn_in_trait)]
|
#![feature(async_fn_in_trait)]
|
||||||
#![allow(incomplete_features)]
|
#![allow(incomplete_features)]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
/// Direction of USB traffic. Note that in the USB standard the direction is always indicated from
|
/// 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
|
/// the perspective of the host, which is backward for devices, but the standard directions are used
|
||||||
|
@ -95,46 +97,65 @@ impl EndpointAddress {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Infomation for an endpoint.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub struct EndpointInfo {
|
pub struct EndpointInfo {
|
||||||
|
/// Endpoint's address.
|
||||||
pub addr: EndpointAddress,
|
pub addr: EndpointAddress,
|
||||||
|
/// Endpoint's type.
|
||||||
pub ep_type: EndpointType,
|
pub ep_type: EndpointType,
|
||||||
|
/// Max packet size, in bytes.
|
||||||
pub max_packet_size: u16,
|
pub max_packet_size: u16,
|
||||||
pub interval: u8,
|
/// Polling interval, in milliseconds.
|
||||||
|
pub interval_ms: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Driver for a specific USB peripheral. Implement this to add support for a new hardware
|
/// Main USB driver trait.
|
||||||
/// platform.
|
///
|
||||||
|
/// Implement this to add support for a new hardware platform.
|
||||||
pub trait Driver<'a> {
|
pub trait Driver<'a> {
|
||||||
|
/// Type of the OUT endpoints for this driver.
|
||||||
type EndpointOut: EndpointOut + 'a;
|
type EndpointOut: EndpointOut + 'a;
|
||||||
|
/// Type of the IN endpoints for this driver.
|
||||||
type EndpointIn: EndpointIn + 'a;
|
type EndpointIn: EndpointIn + 'a;
|
||||||
|
/// Type of the control pipe for this driver.
|
||||||
type ControlPipe: ControlPipe + 'a;
|
type ControlPipe: ControlPipe + 'a;
|
||||||
|
/// Type for bus control for this driver.
|
||||||
type Bus: Bus + 'a;
|
type Bus: Bus + 'a;
|
||||||
|
|
||||||
/// Allocates an endpoint and specified endpoint parameters. This method is called by the device
|
/// Allocates an OUT endpoint.
|
||||||
/// and class implementations to allocate endpoints, and can only be called before
|
///
|
||||||
/// [`start`](Self::start) is called.
|
/// This method is called by the USB stack to allocate endpoints.
|
||||||
|
/// It can only be called before [`start`](Self::start) is called.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
///
|
///
|
||||||
/// * `ep_addr` - A static endpoint address to allocate. If Some, the implementation should
|
/// * `ep_type` - the endpoint's type.
|
||||||
/// 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.
|
/// * `max_packet_size` - Maximum packet size in bytes.
|
||||||
/// * `interval` - Polling interval parameter for interrupt endpoints.
|
/// * `interval_ms` - Polling interval parameter for interrupt endpoints.
|
||||||
fn alloc_endpoint_out(
|
fn alloc_endpoint_out(
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointOut, EndpointAllocError>;
|
) -> Result<Self::EndpointOut, EndpointAllocError>;
|
||||||
|
|
||||||
|
/// Allocates an IN endpoint.
|
||||||
|
///
|
||||||
|
/// This method is called by the USB stack to allocate endpoints.
|
||||||
|
/// It can only be called before [`start`](Self::start) is called.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `ep_type` - the endpoint's type.
|
||||||
|
/// * `max_packet_size` - Maximum packet size in bytes.
|
||||||
|
/// * `interval_ms` - Polling interval parameter for interrupt endpoints.
|
||||||
fn alloc_endpoint_in(
|
fn alloc_endpoint_in(
|
||||||
&mut self,
|
&mut self,
|
||||||
ep_type: EndpointType,
|
ep_type: EndpointType,
|
||||||
max_packet_size: u16,
|
max_packet_size: u16,
|
||||||
interval: u8,
|
interval_ms: u8,
|
||||||
) -> Result<Self::EndpointIn, EndpointAllocError>;
|
) -> Result<Self::EndpointIn, EndpointAllocError>;
|
||||||
|
|
||||||
/// Start operation of the USB device.
|
/// Start operation of the USB device.
|
||||||
|
@ -146,35 +167,37 @@ pub trait Driver<'a> {
|
||||||
/// This consumes the `Driver` instance, so it's no longer possible to allocate more
|
/// This consumes the `Driver` instance, so it's no longer possible to allocate more
|
||||||
/// endpoints.
|
/// endpoints.
|
||||||
fn start(self, control_max_packet_size: u16) -> (Self::Bus, Self::ControlPipe);
|
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// USB bus trait.
|
||||||
|
///
|
||||||
|
/// This trait provides methods that act on the whole bus. It is kept owned by
|
||||||
|
/// the main USB task, and used to manage the bus.
|
||||||
pub trait Bus {
|
pub trait Bus {
|
||||||
/// Enables the USB peripheral. Soon after enabling the device will be reset, so
|
/// Enable the USB peripheral.
|
||||||
/// there is no need to perform a USB reset in this method.
|
|
||||||
async fn enable(&mut self);
|
async fn enable(&mut self);
|
||||||
|
|
||||||
/// Disables and powers down the USB peripheral.
|
/// Disable and powers down the USB peripheral.
|
||||||
async fn disable(&mut self);
|
async fn disable(&mut self);
|
||||||
|
|
||||||
|
/// Wait for a bus-related event.
|
||||||
|
///
|
||||||
|
/// This method should asynchronously wait for an event to happen, then
|
||||||
|
/// return it. See [`Event`] for the list of events this method should return.
|
||||||
async fn poll(&mut self) -> Event;
|
async fn poll(&mut self) -> Event;
|
||||||
|
|
||||||
/// Enables or disables an endpoint.
|
/// Enable or disable an endpoint.
|
||||||
fn endpoint_set_enabled(&mut self, ep_addr: EndpointAddress, enabled: bool);
|
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
|
/// Set or clear the STALL condition for an endpoint.
|
||||||
/// should be prepared to receive data again. Only used during control transfers.
|
///
|
||||||
|
/// If the endpoint is an OUT endpoint, it should be prepared to receive data again.
|
||||||
fn endpoint_set_stalled(&mut self, ep_addr: EndpointAddress, stalled: bool);
|
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.
|
/// Get whether the STALL condition is set for an endpoint.
|
||||||
fn endpoint_is_stalled(&mut self, ep_addr: EndpointAddress) -> bool;
|
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
|
/// Simulate a disconnect from the USB bus, causing the host to reset and re-enumerate the
|
||||||
/// device.
|
/// device.
|
||||||
///
|
///
|
||||||
/// The default implementation just returns `Unsupported`.
|
/// The default implementation just returns `Unsupported`.
|
||||||
|
@ -187,7 +210,7 @@ pub trait Bus {
|
||||||
Err(Unsupported)
|
Err(Unsupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initiates a remote wakeup of the host by the device.
|
/// Initiate a remote wakeup of the host by the device.
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
|
@ -196,25 +219,27 @@ pub trait Bus {
|
||||||
async fn remote_wakeup(&mut self) -> Result<(), Unsupported>;
|
async fn remote_wakeup(&mut self) -> Result<(), Unsupported>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Endpoint trait, common for OUT and IN.
|
||||||
pub trait Endpoint {
|
pub trait Endpoint {
|
||||||
/// Get the endpoint address
|
/// Get the endpoint address
|
||||||
fn info(&self) -> &EndpointInfo;
|
fn info(&self) -> &EndpointInfo;
|
||||||
|
|
||||||
/// Waits for the endpoint to be enabled.
|
/// Wait for the endpoint to be enabled.
|
||||||
async fn wait_enabled(&mut self);
|
async fn wait_enabled(&mut self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// OUT Endpoint trait.
|
||||||
pub trait EndpointOut: Endpoint {
|
pub trait EndpointOut: Endpoint {
|
||||||
/// Reads a single packet of data from the endpoint, and returns the actual length of
|
/// Read a single packet of data from the endpoint, and return the actual length of
|
||||||
/// the packet.
|
/// the packet.
|
||||||
///
|
///
|
||||||
/// This should also clear any NAK flags and prepare the endpoint to receive the next packet.
|
/// This should also clear any NAK flags and prepare the endpoint to receive the next packet.
|
||||||
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError>;
|
async fn read(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Trait for USB control pipe.
|
/// USB control pipe trait.
|
||||||
///
|
///
|
||||||
/// The USB control pipe owns both OUT ep 0 and IN ep 0 in a single
|
/// The USB control pipe owns both OUT endpoint 0 and IN endpoint 0 in a single
|
||||||
/// unit, and manages them together to implement the control pipe state machine.
|
/// unit, and manages them together to implement the control pipe state machine.
|
||||||
///
|
///
|
||||||
/// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that
|
/// The reason this is a separate trait instead of using EndpointOut/EndpointIn is that
|
||||||
|
@ -232,6 +257,14 @@ pub trait EndpointOut: Endpoint {
|
||||||
/// accept() or reject()
|
/// accept() or reject()
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
|
/// - control out for setting the device address:
|
||||||
|
///
|
||||||
|
/// ```not_rust
|
||||||
|
/// setup()
|
||||||
|
/// (...processing...)
|
||||||
|
/// accept_set_address(addr) or reject()
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
/// - control out with len != 0:
|
/// - control out with len != 0:
|
||||||
///
|
///
|
||||||
/// ```not_rust
|
/// ```not_rust
|
||||||
|
@ -280,26 +313,26 @@ pub trait ControlPipe {
|
||||||
/// Maximum packet size for the control pipe
|
/// Maximum packet size for the control pipe
|
||||||
fn max_packet_size(&self) -> usize;
|
fn max_packet_size(&self) -> usize;
|
||||||
|
|
||||||
/// Reads a single setup packet from the endpoint.
|
/// Read a single setup packet from the endpoint.
|
||||||
async fn setup(&mut self) -> [u8; 8];
|
async fn setup(&mut self) -> [u8; 8];
|
||||||
|
|
||||||
/// Reads a DATA OUT packet into `buf` in response to a control write request.
|
/// Read a DATA OUT packet into `buf` in response to a control write request.
|
||||||
///
|
///
|
||||||
/// Must be called after `setup()` for requests with `direction` of `Out`
|
/// Must be called after `setup()` for requests with `direction` of `Out`
|
||||||
/// and `length` greater than zero.
|
/// and `length` greater than zero.
|
||||||
async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result<usize, EndpointError>;
|
async fn data_out(&mut self, buf: &mut [u8], first: bool, last: bool) -> Result<usize, EndpointError>;
|
||||||
|
|
||||||
/// Sends a DATA IN packet with `data` in response to a control read request.
|
/// Send 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`.
|
/// If `last_packet` is true, the STATUS packet will be ACKed following the transfer of `data`.
|
||||||
async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>;
|
async fn data_in(&mut self, data: &[u8], first: bool, last: bool) -> Result<(), EndpointError>;
|
||||||
|
|
||||||
/// Accepts a control request.
|
/// Accept a control request.
|
||||||
///
|
///
|
||||||
/// Causes the STATUS packet for the current request to be ACKed.
|
/// Causes the STATUS packet for the current request to be ACKed.
|
||||||
async fn accept(&mut self);
|
async fn accept(&mut self);
|
||||||
|
|
||||||
/// Rejects a control request.
|
/// Reject a control request.
|
||||||
///
|
///
|
||||||
/// Sets a STALL condition on the pipe to indicate an error.
|
/// Sets a STALL condition on the pipe to indicate an error.
|
||||||
async fn reject(&mut self);
|
async fn reject(&mut self);
|
||||||
|
@ -311,8 +344,9 @@ pub trait ControlPipe {
|
||||||
async fn accept_set_address(&mut self, addr: u8);
|
async fn accept_set_address(&mut self, addr: u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// IN Endpoint trait.
|
||||||
pub trait EndpointIn: Endpoint {
|
pub trait EndpointIn: Endpoint {
|
||||||
/// Writes a single packet of data to the endpoint.
|
/// Write a single packet of data to the endpoint.
|
||||||
async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>;
|
async fn write(&mut self, buf: &[u8]) -> Result<(), EndpointError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,18 +372,22 @@ pub enum Event {
|
||||||
PowerRemoved,
|
PowerRemoved,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allocating an endpoint failed.
|
||||||
|
///
|
||||||
|
/// This can be due to running out of endpoints, or out of endpoint memory,
|
||||||
|
/// or because the hardware doesn't support the requested combination of features.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub struct EndpointAllocError;
|
pub struct EndpointAllocError;
|
||||||
|
|
||||||
|
/// Operation is unsupported by the driver.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
/// Operation is unsupported by the driver.
|
|
||||||
pub struct Unsupported;
|
pub struct Unsupported;
|
||||||
|
|
||||||
|
/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`]
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
/// Errors returned by [`EndpointIn::write`] and [`EndpointOut::read`]
|
|
||||||
pub enum EndpointError {
|
pub enum EndpointError {
|
||||||
/// Either the packet to be written is too long to fit in the transmission
|
/// 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`.
|
/// buffer or the received packet is too long to fit in `buf`.
|
||||||
|
|
22
embassy-usb/README.md
Normal file
22
embassy-usb/README.md
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
# embassy-usb
|
||||||
|
|
||||||
|
TODO crate description/
|
||||||
|
|
||||||
|
## Interoperability
|
||||||
|
|
||||||
|
This crate can run on any executor.
|
||||||
|
|
||||||
|
## Minimum supported Rust version (MSRV)
|
||||||
|
|
||||||
|
This crate requires nightly Rust, due to using "async fn in trait" support.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This work is licensed under either of
|
||||||
|
|
||||||
|
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or
|
||||||
|
<http://www.apache.org/licenses/LICENSE-2.0>)
|
||||||
|
- MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
|
||||||
|
|
||||||
|
at your option.
|
||||||
|
|
|
@ -349,11 +349,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
|
||||||
self.builder.config_descriptor.write(descriptor_type, descriptor)
|
self.builder.config_descriptor.write(descriptor_type, descriptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointIn {
|
fn endpoint_in(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
|
||||||
let ep = self
|
let ep = self
|
||||||
.builder
|
.builder
|
||||||
.driver
|
.driver
|
||||||
.alloc_endpoint_in(ep_type, max_packet_size, interval)
|
.alloc_endpoint_in(ep_type, max_packet_size, interval_ms)
|
||||||
.expect("alloc_endpoint_in failed");
|
.expect("alloc_endpoint_in failed");
|
||||||
|
|
||||||
self.builder.config_descriptor.endpoint(ep.info());
|
self.builder.config_descriptor.endpoint(ep.info());
|
||||||
|
@ -361,11 +361,11 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
|
||||||
ep
|
ep
|
||||||
}
|
}
|
||||||
|
|
||||||
fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval: u8) -> D::EndpointOut {
|
fn endpoint_out(&mut self, ep_type: EndpointType, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
|
||||||
let ep = self
|
let ep = self
|
||||||
.builder
|
.builder
|
||||||
.driver
|
.driver
|
||||||
.alloc_endpoint_out(ep_type, max_packet_size, interval)
|
.alloc_endpoint_out(ep_type, max_packet_size, interval_ms)
|
||||||
.expect("alloc_endpoint_out failed");
|
.expect("alloc_endpoint_out failed");
|
||||||
|
|
||||||
self.builder.config_descriptor.endpoint(ep.info());
|
self.builder.config_descriptor.endpoint(ep.info());
|
||||||
|
@ -393,25 +393,25 @@ impl<'a, 'd, D: Driver<'d>> InterfaceAltBuilder<'a, 'd, D> {
|
||||||
///
|
///
|
||||||
/// Descriptors are written in the order builder functions are called. Note that some
|
/// Descriptors are written in the order builder functions are called. Note that some
|
||||||
/// classes care about the order.
|
/// classes care about the order.
|
||||||
pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointIn {
|
pub fn endpoint_interrupt_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
|
||||||
self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval)
|
self.endpoint_in(EndpointType::Interrupt, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate a INTERRUPT OUT endpoint and write its descriptor.
|
/// Allocate a INTERRUPT OUT endpoint and write its descriptor.
|
||||||
pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut {
|
pub fn endpoint_interrupt_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
|
||||||
self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval)
|
self.endpoint_out(EndpointType::Interrupt, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate a ISOCHRONOUS IN endpoint and write its descriptor.
|
/// Allocate a ISOCHRONOUS IN endpoint and write its descriptor.
|
||||||
///
|
///
|
||||||
/// Descriptors are written in the order builder functions are called. Note that some
|
/// Descriptors are written in the order builder functions are called. Note that some
|
||||||
/// classes care about the order.
|
/// classes care about the order.
|
||||||
pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointIn {
|
pub fn endpoint_isochronous_in(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointIn {
|
||||||
self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval)
|
self.endpoint_in(EndpointType::Isochronous, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor.
|
/// Allocate a ISOCHRONOUS OUT endpoint and write its descriptor.
|
||||||
pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval: u8) -> D::EndpointOut {
|
pub fn endpoint_isochronous_out(&mut self, max_packet_size: u16, interval_ms: u8) -> D::EndpointOut {
|
||||||
self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval)
|
self.endpoint_out(EndpointType::Isochronous, max_packet_size, interval_ms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! CDC-ACM class implementation, aka Serial over USB.
|
||||||
|
|
||||||
use core::cell::Cell;
|
use core::cell::Cell;
|
||||||
use core::mem::{self, MaybeUninit};
|
use core::mem::{self, MaybeUninit};
|
||||||
use core::sync::atomic::{AtomicBool, Ordering};
|
use core::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -28,12 +30,14 @@ const REQ_SET_LINE_CODING: u8 = 0x20;
|
||||||
const REQ_GET_LINE_CODING: u8 = 0x21;
|
const REQ_GET_LINE_CODING: u8 = 0x21;
|
||||||
const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
|
const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
|
||||||
|
|
||||||
|
/// Internal state for CDC-ACM
|
||||||
pub struct State<'a> {
|
pub struct State<'a> {
|
||||||
control: MaybeUninit<Control<'a>>,
|
control: MaybeUninit<Control<'a>>,
|
||||||
shared: ControlShared,
|
shared: ControlShared,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> State<'a> {
|
impl<'a> State<'a> {
|
||||||
|
/// Create a new `State`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
control: MaybeUninit::uninit(),
|
control: MaybeUninit::uninit(),
|
||||||
|
@ -284,10 +288,15 @@ impl From<u8> for StopBits {
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum ParityType {
|
pub enum ParityType {
|
||||||
|
/// No parity bit.
|
||||||
None = 0,
|
None = 0,
|
||||||
|
/// Parity bit is 1 if the amount of `1` bits in the data byte is odd.
|
||||||
Odd = 1,
|
Odd = 1,
|
||||||
|
/// Parity bit is 1 if the amount of `1` bits in the data byte is even.
|
||||||
Even = 2,
|
Even = 2,
|
||||||
|
/// Parity bit is always 1
|
||||||
Mark = 3,
|
Mark = 3,
|
||||||
|
/// Parity bit is always 0
|
||||||
Space = 4,
|
Space = 4,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! [`embassy-net`](crates.io/crates/embassy-net) driver for the CDC-NCM class.
|
||||||
use embassy_futures::select::{select, Either};
|
use embassy_futures::select::{select, Either};
|
||||||
use embassy_net_driver_channel as ch;
|
use embassy_net_driver_channel as ch;
|
||||||
use embassy_net_driver_channel::driver::LinkState;
|
use embassy_net_driver_channel::driver::LinkState;
|
||||||
|
@ -5,11 +6,13 @@ use embassy_usb_driver::Driver;
|
||||||
|
|
||||||
use super::{CdcNcmClass, Receiver, Sender};
|
use super::{CdcNcmClass, Receiver, Sender};
|
||||||
|
|
||||||
|
/// Internal state for the embassy-net integration.
|
||||||
pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> {
|
pub struct State<const MTU: usize, const N_RX: usize, const N_TX: usize> {
|
||||||
ch_state: ch::State<MTU, N_RX, N_TX>,
|
ch_state: ch::State<MTU, N_RX, N_TX>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_TX> {
|
impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_TX> {
|
||||||
|
/// Create a new `State`.
|
||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ch_state: ch::State::new(),
|
ch_state: ch::State::new(),
|
||||||
|
@ -17,6 +20,9 @@ impl<const MTU: usize, const N_RX: usize, const N_TX: usize> State<MTU, N_RX, N_
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Background runner for the CDC-NCM class.
|
||||||
|
///
|
||||||
|
/// You must call `.run()` in a background task for the class to operate.
|
||||||
pub struct Runner<'d, D: Driver<'d>, const MTU: usize> {
|
pub struct Runner<'d, D: Driver<'d>, const MTU: usize> {
|
||||||
tx_usb: Sender<'d, D>,
|
tx_usb: Sender<'d, D>,
|
||||||
rx_usb: Receiver<'d, D>,
|
rx_usb: Receiver<'d, D>,
|
||||||
|
@ -24,6 +30,9 @@ pub struct Runner<'d, D: Driver<'d>, const MTU: usize> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> {
|
impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> {
|
||||||
|
/// Run the CDC-NCM class.
|
||||||
|
///
|
||||||
|
/// You must call this in a background task for the class to operate.
|
||||||
pub async fn run(mut self) -> ! {
|
pub async fn run(mut self) -> ! {
|
||||||
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
|
let (state_chan, mut rx_chan, mut tx_chan) = self.ch.split();
|
||||||
let rx_fut = async move {
|
let rx_fut = async move {
|
||||||
|
@ -66,9 +75,11 @@ impl<'d, D: Driver<'d>, const MTU: usize> Runner<'d, D, MTU> {
|
||||||
|
|
||||||
// would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug?
|
// would be cool to use a TAIT here, but it gives a "may not live long enough". rustc bug?
|
||||||
//pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd;
|
//pub type Device<'d, const MTU: usize> = impl embassy_net_driver_channel::driver::Driver + 'd;
|
||||||
|
/// Type alias for the embassy-net driver for CDC-NCM.
|
||||||
pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>;
|
pub type Device<'d, const MTU: usize> = embassy_net_driver_channel::Device<'d, MTU>;
|
||||||
|
|
||||||
impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||||
|
/// Obtain a driver for using the CDC-NCM class with [`embassy-net`](crates.io/crates/embassy-net).
|
||||||
pub fn into_embassy_net_device<const MTU: usize, const N_RX: usize, const N_TX: usize>(
|
pub fn into_embassy_net_device<const MTU: usize, const N_RX: usize, const N_TX: usize>(
|
||||||
self,
|
self,
|
||||||
state: &'d mut State<MTU, N_RX, N_TX>,
|
state: &'d mut State<MTU, N_RX, N_TX>,
|
||||||
|
|
|
@ -1,18 +1,19 @@
|
||||||
/// CDC-NCM, aka Ethernet over USB.
|
//! CDC-NCM class implementation, aka Ethernet over USB.
|
||||||
///
|
//!
|
||||||
/// # Compatibility
|
//! # Compatibility
|
||||||
///
|
//!
|
||||||
/// Windows: NOT supported in Windows 10. Supported in Windows 11.
|
//! Windows: NOT supported in Windows 10 (though there's apparently a driver you can install?). Supported out of the box in Windows 11.
|
||||||
///
|
//!
|
||||||
/// Linux: Well-supported since forever.
|
//! Linux: Well-supported since forever.
|
||||||
///
|
//!
|
||||||
/// Android: Support for CDC-NCM is spotty and varies across manufacturers.
|
//! Android: Support for CDC-NCM is spotty and varies across manufacturers.
|
||||||
///
|
//!
|
||||||
/// - On Pixel 4a, it refused to work on Android 11, worked on Android 12.
|
//! - On Pixel 4a, it refused to work on Android 11, worked on Android 12.
|
||||||
/// - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte),
|
//! - if the host's MAC address has the "locally-administered" bit set (bit 1 of first byte),
|
||||||
/// it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled.
|
//! it doesn't work! The "Ethernet tethering" option in settings doesn't get enabled.
|
||||||
/// This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417
|
//! This is due to regex spaghetti: https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-mainline-12.0.0_r84/core/res/res/values/config.xml#417
|
||||||
/// and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757
|
//! and this nonsense in the linux kernel: https://github.com/torvalds/linux/blob/c00c5e1d157bec0ef0b0b59aa5482eb8dc7e8e49/drivers/net/usb/usbnet.c#L1751-L1757
|
||||||
|
|
||||||
use core::intrinsics::copy_nonoverlapping;
|
use core::intrinsics::copy_nonoverlapping;
|
||||||
use core::mem::{size_of, MaybeUninit};
|
use core::mem::{size_of, MaybeUninit};
|
||||||
|
|
||||||
|
@ -114,6 +115,7 @@ fn byteify<T>(buf: &mut [u8], data: T) -> &[u8] {
|
||||||
&buf[..len]
|
&buf[..len]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal state for the CDC-NCM class.
|
||||||
pub struct State<'a> {
|
pub struct State<'a> {
|
||||||
comm_control: MaybeUninit<CommControl<'a>>,
|
comm_control: MaybeUninit<CommControl<'a>>,
|
||||||
data_control: MaybeUninit<DataControl>,
|
data_control: MaybeUninit<DataControl>,
|
||||||
|
@ -121,6 +123,7 @@ pub struct State<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> State<'a> {
|
impl<'a> State<'a> {
|
||||||
|
/// Create a new `State`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
comm_control: MaybeUninit::uninit(),
|
comm_control: MaybeUninit::uninit(),
|
||||||
|
@ -223,6 +226,7 @@ impl ControlHandler for DataControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CDC-NCM class
|
||||||
pub struct CdcNcmClass<'d, D: Driver<'d>> {
|
pub struct CdcNcmClass<'d, D: Driver<'d>> {
|
||||||
_comm_if: InterfaceNumber,
|
_comm_if: InterfaceNumber,
|
||||||
comm_ep: D::EndpointIn,
|
comm_ep: D::EndpointIn,
|
||||||
|
@ -235,6 +239,7 @@ pub struct CdcNcmClass<'d, D: Driver<'d>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||||
|
/// Create a new CDC NCM class.
|
||||||
pub fn new(
|
pub fn new(
|
||||||
builder: &mut Builder<'d, D>,
|
builder: &mut Builder<'d, D>,
|
||||||
state: &'d mut State<'d>,
|
state: &'d mut State<'d>,
|
||||||
|
@ -319,6 +324,9 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Split the class into a sender and receiver.
|
||||||
|
///
|
||||||
|
/// This allows concurrently sending and receiving packets from separate tasks.
|
||||||
pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) {
|
pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) {
|
||||||
(
|
(
|
||||||
Sender {
|
Sender {
|
||||||
|
@ -334,12 +342,18 @@ impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CDC NCM class packet sender.
|
||||||
|
///
|
||||||
|
/// You can obtain a `Sender` with [`CdcNcmClass::split`]
|
||||||
pub struct Sender<'d, D: Driver<'d>> {
|
pub struct Sender<'d, D: Driver<'d>> {
|
||||||
write_ep: D::EndpointIn,
|
write_ep: D::EndpointIn,
|
||||||
seq: u16,
|
seq: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, D: Driver<'d>> Sender<'d, D> {
|
impl<'d, D: Driver<'d>> Sender<'d, D> {
|
||||||
|
/// Write a packet.
|
||||||
|
///
|
||||||
|
/// This waits until the packet is succesfully stored in the CDC-NCM endpoint buffers.
|
||||||
pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
|
pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> {
|
||||||
let seq = self.seq;
|
let seq = self.seq;
|
||||||
self.seq = self.seq.wrapping_add(1);
|
self.seq = self.seq.wrapping_add(1);
|
||||||
|
@ -393,6 +407,9 @@ impl<'d, D: Driver<'d>> Sender<'d, D> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// CDC NCM class packet receiver.
|
||||||
|
///
|
||||||
|
/// You can obtain a `Receiver` with [`CdcNcmClass::split`]
|
||||||
pub struct Receiver<'d, D: Driver<'d>> {
|
pub struct Receiver<'d, D: Driver<'d>> {
|
||||||
data_if: InterfaceNumber,
|
data_if: InterfaceNumber,
|
||||||
comm_ep: D::EndpointIn,
|
comm_ep: D::EndpointIn,
|
||||||
|
@ -400,7 +417,9 @@ pub struct Receiver<'d, D: Driver<'d>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d, D: Driver<'d>> Receiver<'d, D> {
|
impl<'d, D: Driver<'d>> Receiver<'d, D> {
|
||||||
/// Reads a single packet from the OUT endpoint.
|
/// Write a network packet.
|
||||||
|
///
|
||||||
|
/// This waits until a packet is succesfully received from the endpoint buffers.
|
||||||
pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
|
pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> {
|
||||||
// Retry loop
|
// Retry loop
|
||||||
loop {
|
loop {
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! USB HID (Human Interface Device) class implementation.
|
||||||
|
|
||||||
use core::mem::MaybeUninit;
|
use core::mem::MaybeUninit;
|
||||||
use core::ops::Range;
|
use core::ops::Range;
|
||||||
use core::sync::atomic::{AtomicUsize, Ordering};
|
use core::sync::atomic::{AtomicUsize, Ordering};
|
||||||
|
@ -28,6 +30,7 @@ const HID_REQ_SET_REPORT: u8 = 0x09;
|
||||||
const HID_REQ_GET_PROTOCOL: u8 = 0x03;
|
const HID_REQ_GET_PROTOCOL: u8 = 0x03;
|
||||||
const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
|
const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
|
||||||
|
|
||||||
|
/// Configuration for the HID class.
|
||||||
pub struct Config<'d> {
|
pub struct Config<'d> {
|
||||||
/// HID report descriptor.
|
/// HID report descriptor.
|
||||||
pub report_descriptor: &'d [u8],
|
pub report_descriptor: &'d [u8],
|
||||||
|
@ -46,11 +49,15 @@ pub struct Config<'d> {
|
||||||
pub max_packet_size: u16,
|
pub max_packet_size: u16,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Report ID
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum ReportId {
|
pub enum ReportId {
|
||||||
|
/// IN report
|
||||||
In(u8),
|
In(u8),
|
||||||
|
/// OUT report
|
||||||
Out(u8),
|
Out(u8),
|
||||||
|
/// Feature report
|
||||||
Feature(u8),
|
Feature(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,12 +72,14 @@ impl ReportId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal state for USB HID.
|
||||||
pub struct State<'d> {
|
pub struct State<'d> {
|
||||||
control: MaybeUninit<Control<'d>>,
|
control: MaybeUninit<Control<'d>>,
|
||||||
out_report_offset: AtomicUsize,
|
out_report_offset: AtomicUsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'d> State<'d> {
|
impl<'d> State<'d> {
|
||||||
|
/// Create a new `State`.
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
State {
|
State {
|
||||||
control: MaybeUninit::uninit(),
|
control: MaybeUninit::uninit(),
|
||||||
|
@ -79,6 +88,7 @@ impl<'d> State<'d> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// USB HID reader/writer.
|
||||||
pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> {
|
pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> {
|
||||||
reader: HidReader<'d, D, READ_N>,
|
reader: HidReader<'d, D, READ_N>,
|
||||||
writer: HidWriter<'d, D, WRITE_N>,
|
writer: HidWriter<'d, D, WRITE_N>,
|
||||||
|
@ -180,20 +190,30 @@ impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWrit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// USB HID writer.
|
||||||
|
///
|
||||||
|
/// You can obtain a `HidWriter` using [`HidReaderWriter::split`].
|
||||||
pub struct HidWriter<'d, D: Driver<'d>, const N: usize> {
|
pub struct HidWriter<'d, D: Driver<'d>, const N: usize> {
|
||||||
ep_in: D::EndpointIn,
|
ep_in: D::EndpointIn,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// USB HID reader.
|
||||||
|
///
|
||||||
|
/// You can obtain a `HidReader` using [`HidReaderWriter::split`].
|
||||||
pub struct HidReader<'d, D: Driver<'d>, const N: usize> {
|
pub struct HidReader<'d, D: Driver<'d>, const N: usize> {
|
||||||
ep_out: D::EndpointOut,
|
ep_out: D::EndpointOut,
|
||||||
offset: &'d AtomicUsize,
|
offset: &'d AtomicUsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error when reading a HID report.
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum ReadError {
|
pub enum ReadError {
|
||||||
|
/// The given buffer was too small to read the received report.
|
||||||
BufferOverflow,
|
BufferOverflow,
|
||||||
|
/// The endpoint is disabled.
|
||||||
Disabled,
|
Disabled,
|
||||||
|
/// The report was only partially read. See [`HidReader::read`] for details.
|
||||||
Sync(Range<usize>),
|
Sync(Range<usize>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,6 +364,7 @@ impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handler for HID-related control requests.
|
||||||
pub trait RequestHandler {
|
pub trait RequestHandler {
|
||||||
/// Reads the value of report `id` into `buf` returning the size.
|
/// Reads the value of report `id` into `buf` returning the size.
|
||||||
///
|
///
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Implementations of well-known USB classes.
|
||||||
pub mod cdc_acm;
|
pub mod cdc_acm;
|
||||||
pub mod cdc_ncm;
|
pub mod cdc_ncm;
|
||||||
pub mod hid;
|
pub mod hid;
|
||||||
|
|
|
@ -126,17 +126,23 @@ impl Request {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Response for a CONTROL OUT request.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum OutResponse {
|
pub enum OutResponse {
|
||||||
|
/// The request was accepted.
|
||||||
Accepted,
|
Accepted,
|
||||||
|
/// The request was rejected.
|
||||||
Rejected,
|
Rejected,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Response for a CONTROL IN request.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum InResponse<'a> {
|
pub enum InResponse<'a> {
|
||||||
|
/// The request was accepted. The buffer contains the response data.
|
||||||
Accepted(&'a [u8]),
|
Accepted(&'a [u8]),
|
||||||
|
/// The request was rejected.
|
||||||
Rejected,
|
Rejected,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -148,6 +154,7 @@ pub trait ControlHandler {
|
||||||
/// Called after a USB reset after the bus reset sequence is complete.
|
/// Called after a USB reset after the bus reset sequence is complete.
|
||||||
fn reset(&mut self) {}
|
fn reset(&mut self) {}
|
||||||
|
|
||||||
|
/// Called when a "set alternate setting" control request is done on the interface.
|
||||||
fn set_alternate_setting(&mut self, alternate_setting: u8) {
|
fn set_alternate_setting(&mut self, alternate_setting: u8) {
|
||||||
let _ = alternate_setting;
|
let _ = alternate_setting;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! Utilities for writing USB descriptors.
|
||||||
|
|
||||||
use crate::builder::Config;
|
use crate::builder::Config;
|
||||||
use crate::driver::EndpointInfo;
|
use crate::driver::EndpointInfo;
|
||||||
use crate::types::*;
|
use crate::types::*;
|
||||||
|
@ -236,7 +238,7 @@ impl<'a> DescriptorWriter<'a> {
|
||||||
endpoint.ep_type as u8, // bmAttributes
|
endpoint.ep_type as u8, // bmAttributes
|
||||||
endpoint.max_packet_size as u8,
|
endpoint.max_packet_size as u8,
|
||||||
(endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize
|
(endpoint.max_packet_size >> 8) as u8, // wMaxPacketSize
|
||||||
endpoint.interval, // bInterval
|
endpoint.interval_ms, // bInterval
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
#![no_std]
|
#![no_std]
|
||||||
#![feature(type_alias_impl_trait)]
|
#![feature(type_alias_impl_trait)]
|
||||||
|
#![doc = include_str!("../README.md")]
|
||||||
|
#![warn(missing_docs)]
|
||||||
|
|
||||||
// This mod MUST go first, so that the others see its macros.
|
// This mod MUST go first, so that the others see its macros.
|
||||||
pub(crate) mod fmt;
|
pub(crate) mod fmt;
|
||||||
|
@ -46,10 +48,13 @@ pub enum UsbDeviceState {
|
||||||
Configured,
|
Configured,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Error returned by [`UsbDevice::remote_wakeup`].
|
||||||
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
pub enum RemoteWakeupError {
|
pub enum RemoteWakeupError {
|
||||||
|
/// The USB device is not suspended, or remote wakeup was not enabled.
|
||||||
InvalidState,
|
InvalidState,
|
||||||
|
/// The underlying driver doesn't support remote wakeup.
|
||||||
Unsupported,
|
Unsupported,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,6 +70,7 @@ pub const CONFIGURATION_NONE: u8 = 0;
|
||||||
/// The bConfiguration value for the single configuration supported by this device.
|
/// The bConfiguration value for the single configuration supported by this device.
|
||||||
pub const CONFIGURATION_VALUE: u8 = 1;
|
pub const CONFIGURATION_VALUE: u8 = 1;
|
||||||
|
|
||||||
|
/// Maximum interface count, configured at compile time.
|
||||||
pub const MAX_INTERFACE_COUNT: usize = 4;
|
pub const MAX_INTERFACE_COUNT: usize = 4;
|
||||||
|
|
||||||
const STRING_INDEX_MANUFACTURER: u8 = 1;
|
const STRING_INDEX_MANUFACTURER: u8 = 1;
|
||||||
|
@ -100,6 +106,7 @@ struct Interface<'d> {
|
||||||
num_strings: u8,
|
num_strings: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Main struct for the USB device stack.
|
||||||
pub struct UsbDevice<'d, D: Driver<'d>> {
|
pub struct UsbDevice<'d, D: Driver<'d>> {
|
||||||
control_buf: &'d mut [u8],
|
control_buf: &'d mut [u8],
|
||||||
control: D::ControlPipe,
|
control: D::ControlPipe,
|
||||||
|
@ -489,7 +496,6 @@ impl<'d, D: Driver<'d>> Inner<'d, D> {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
// TODO check it is valid (not out of range)
|
// TODO check it is valid (not out of range)
|
||||||
// TODO actually enable/disable endpoints.
|
|
||||||
|
|
||||||
if let Some(handler) = &mut iface.handler {
|
if let Some(handler) = &mut iface.handler {
|
||||||
handler.set_alternate_setting(new_altsetting);
|
handler.set_alternate_setting(new_altsetting);
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
//! USB types.
|
||||||
|
|
||||||
/// A handle for a USB interface that contains its number.
|
/// A handle for a USB interface that contains its number.
|
||||||
#[derive(Copy, Clone, Eq, PartialEq)]
|
#[derive(Copy, Clone, Eq, PartialEq)]
|
||||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||||
|
|
Loading…
Reference in a new issue