blob: 8691eef92849d77c24ab6929b7c7f61a5c0cea1b [file]
// Licensed under the Apache-2.0 license
// SPDX-License-Identifier: Apache-2.0
//! Generic USB protocol stack.
//!
//! This module provides the core logic for a USB device stack, including
//! Endpoint 0 control request handling, descriptor management, and
//! multi-packet transfer accumulation.
#![no_std]
use aligned::Aligned;
use aligned::A4;
use core::mem::size_of;
use hal_usb::driver::UsbDriver;
use hal_usb::driver::UsbEvent;
use hal_usb::driver::UsbPacket;
use hal_usb::DescriptorInfo;
use hal_usb::DescriptorType;
use hal_usb::Request;
use hal_usb::SetupPacket;
use hal_usb::StringDescriptorRef;
use hal_usb::StringHandle;
use zerocopy::IntoBytes;
use pw_status::Error;
pub const CONFIG_0: Aligned<A4, [u8; 1]> = Aligned([0]);
pub const CONFIG_1: Aligned<A4, [u8; 1]> = Aligned([1]);
pub const STATUS_OK: Aligned<A4, [u8; 2]> = Aligned([0, 0]);
pub const STATUS_HALTED: Aligned<A4, [u8; 2]> = Aligned([1, 0]);
#[inline(always)]
const fn endpoint_number(endpoint_addr: u8) -> u8 {
endpoint_addr & 0x0f
}
#[inline(always)]
const fn endpoint_is_in(endpoint_addr: u8) -> bool {
(endpoint_addr & 0x80) != 0
}
#[inline(always)]
const fn endpoint_bit_index(endpoint_addr: u8) -> u32 {
let num = endpoint_number(endpoint_addr) as u32;
if endpoint_is_in(endpoint_addr) {
16 + num
} else {
num
}
}
/// A trait for providing USB descriptors to the stack.
///
/// Applications must implement this trait to define the device's identity
/// and capabilities.
pub trait DescriptorSource {
/// Device descriptor bytes.
const DEVICE_DESC_BYTES: &'static Aligned<A4, [u8]>;
/// Configuration descriptor bytes (including interfaces and endpoints).
const CONFIG_DESC_BYTES: &'static Aligned<A4, [u8]>;
/// String descriptor 0 bytes (supported languages).
const STRING_DESC_0_BYTES: &'static Aligned<A4, [u8]>;
/// Device status bytes (2 bytes, usually [0, 0]).
const DEVICE_STATUS: Aligned<A4, [u8; 2]>;
/// Returns a string descriptor by handle and language ID.
fn get_string(&self, handle: StringHandle, lang: u16) -> Option<StringDescriptorRef<'_>>;
/// Returns the device status bytes.
fn get_device_status(&self) -> &Aligned<A4, [u8]> {
&Self::DEVICE_STATUS
}
}
/// An empty aligned buffer.
pub const EMPTY: &Aligned<A4, [u8]> = &Aligned([]);
/// A simple implementation of USB Endpoint 0 (control endpoint).
pub struct SimpleEp0 {
new_address: Option<u8>,
configuration: u8,
halted_endpoints: u32,
pending_halt: Option<(u8, bool)>,
}
/// A trait for modular USB class implementations.
pub trait UsbClass {
/// Attempt to handle a USB event.
///
/// If the event is handled by this class, it returns `Ok(UsbAction)`.
/// Otherwise, it returns the original event in `Err`.
fn handle_event<'a, P: UsbPacket>(
&'a mut self,
event: UsbEvent<P>,
) -> Result<UsbAction<'a>, UsbEvent<P>>;
}
/// Indicates the result of running a USB action.
#[derive(Copy, Clone, PartialEq, Eq)]
pub enum UsbActionRun {
/// No operation was performed.
NoOp,
/// The action has more data to transfer.
HasMoreData,
/// The action is complete.
Done,
}
/// Actions to be performed on a USB driver.
pub enum UsbAction<'a> {
/// No action.
None,
/// Perform an IN transfer on the specified endpoint.
TransferIn {
/// The endpoint index.
endpoint: u8,
/// The data to transfer.
data: &'a Aligned<A4, [u8]>,
/// If true, send a zero-length packet (ZLP) if the data length
/// is a multiple of the maximum packet size.
zlp: bool,
},
/// Perform an IN transfer on the specified endpoint using unaligned data.
TransferInUnaligned {
/// The endpoint index.
endpoint: u8,
/// The data to transfer.
data: &'a [u8],
/// If true, send a zero-length packet (ZLP) if the data length
/// is a multiple of the maximum packet size.
zlp: bool,
},
/// Stall both IN and OUT directions on the specified endpoint.
StallInAndOut {
/// The endpoint index.
endpoint: u8,
},
/// Set the device address.
SetAddress {
/// The new device address.
new_address: u8,
},
/// Set the stall status of an endpoint.
EndpointHalt {
/// The endpoint address.
endpoint_addr: u8,
/// Whether to stall or unstall the endpoint.
halted: bool,
},
}
impl PartialEq for UsbAction<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::None, Self::None) => true,
(
Self::TransferIn {
endpoint: e1,
data: d1,
zlp: z1,
},
Self::TransferIn {
endpoint: e2,
data: d2,
zlp: z2,
},
) => e1 == e2 && core::ptr::eq(*d1, *d2) && z1 == z2,
(
Self::TransferInUnaligned {
endpoint: e1,
data: d1,
zlp: z1,
},
Self::TransferInUnaligned {
endpoint: e2,
data: d2,
zlp: z2,
},
) => e1 == e2 && core::ptr::eq(*d1, *d2) && z1 == z2,
(Self::StallInAndOut { endpoint: e1 }, Self::StallInAndOut { endpoint: e2 }) => {
e1 == e2
}
(Self::SetAddress { new_address: a1 }, Self::SetAddress { new_address: a2 }) => {
a1 == a2
}
(
Self::EndpointHalt {
endpoint_addr: a1,
halted: h1,
},
Self::EndpointHalt {
endpoint_addr: a2,
halted: h2,
},
) => a1 == a2 && h1 == h2,
_ => false,
}
}
}
impl Eq for UsbAction<'_> {}
impl<'a> UsbAction<'a> {
/// Helper to create a TransferIn action for a control transfer,
/// or a StallInAndOut if the requested length is too small.
#[inline(always)]
#[track_caller]
pub fn control_transfer_in_or_stall(
endpoint: u8,
pkt: &SetupPacket,
data: &'a Aligned<A4, [u8]>,
) -> Self {
if data.len() > pkt.length().into() {
Self::StallInAndOut { endpoint }
} else {
Self::TransferIn {
endpoint,
data,
// Per USB Specs 5.5.3, we need to send ZLP for control transfers
// if the response is less than requested.
zlp: data.is_empty() || data.len() < pkt.length().into(),
}
}
}
/// Merges another action into this one.
pub fn merge(&mut self, new_action: UsbAction<'a>) {
match new_action {
UsbAction::None => {}
_ => *self = new_action,
}
}
/// Executes the action on the provided driver.
pub fn run<TDriver: UsbDriver>(&mut self, driver: &mut TDriver) -> UsbActionRun {
match self {
Self::None => return UsbActionRun::NoOp,
Self::TransferIn {
endpoint,
data,
zlp,
} => {
let bytes_transferred = driver.transfer_in(*endpoint, data, *zlp);
// Note: bytes_transferred is guaranteed to be a multiple of
// UsbDriver::MAX_PACKET_SIZE, which is guaranteed to be a
// multiple of 4.
if bytes_transferred < data.len() && (bytes_transferred & 3) == 0 {
// We're not done yet...
*data = &data[bytes_transferred..];
return UsbActionRun::HasMoreData;
}
}
Self::TransferInUnaligned {
endpoint,
data,
zlp,
} => {
let bytes_transferred = driver.transfer_in_unaligned(*endpoint, data, *zlp);
if bytes_transferred < data.len() {
// We're not done yet...
*data = &data[bytes_transferred..];
return UsbActionRun::HasMoreData;
}
}
Self::SetAddress { new_address } => driver.set_address(*new_address),
Self::StallInAndOut { endpoint } => {
driver.stall((*endpoint) & 0x7f, true);
driver.stall((*endpoint) | 0x80, true);
}
Self::EndpointHalt {
endpoint_addr,
halted,
} => {
driver.stall(*endpoint_addr, *halted);
}
}
*self = UsbAction::None;
UsbActionRun::Done
}
}
impl SimpleEp0 {
/// Creates a new `SimpleEp0` handler.
pub fn new() -> Self {
Self {
new_address: None,
configuration: 0,
halted_endpoints: 0,
pending_halt: None,
}
}
pub fn is_endpoint_halted(&self, endpoint_addr: u8) -> bool {
let idx = endpoint_bit_index(endpoint_addr);
(self.halted_endpoints & (1 << idx)) != 0
}
pub fn set_endpoint_halted(&mut self, endpoint_addr: u8, halted: bool) {
let idx = endpoint_bit_index(endpoint_addr);
if halted {
self.halted_endpoints |= 1 << idx;
} else {
self.halted_endpoints &= !(1 << idx);
}
}
/// A helper function to process a driver UsbEvent.
///
/// This function returns the action that should be performed on the driver.
pub fn handle_event<'a, P: UsbPacket>(
&mut self,
ev: UsbEvent<P>,
descriptor_source: &'a impl DescriptorSource,
) -> Result<UsbAction<'a>, UsbEvent<P>> {
match ev {
UsbEvent::SetupPacket { endpoint: 0, pkt } => {
use hal_usb::RequestType;
if pkt.request().request_type() == RequestType::Standard {
Ok(self.handle_setup(pkt, descriptor_source))
} else {
Err(ev)
}
}
UsbEvent::PacketSent { endpoint: 0 } => Ok(self.handle_packet_sent()),
UsbEvent::UsbReset => {
self.configuration = 0;
self.new_address = None;
self.halted_endpoints = 0;
self.pending_halt = None;
Ok(UsbAction::None)
}
_ => Err(ev),
}
}
/// Process a SETUP transfer and return the resulting action.
fn handle_setup<'a, TDescriptorSource: DescriptorSource>(
&mut self,
setup_pkt: SetupPacket,
descriptor_source: &'a TDescriptorSource,
) -> UsbAction<'a> {
match setup_pkt.request() {
Request::DEVICE_GET_DESCRIPTOR => {
let descriptor = DescriptorInfo::from(&setup_pkt);
#[rustfmt::skip]
let mut response: Option<&Aligned<A4, [u8]>> = match descriptor {
DescriptorInfo { ty: DescriptorType::DEVICE, index: 0, .. } => {
Some(TDescriptorSource::DEVICE_DESC_BYTES)
}
DescriptorInfo { ty: DescriptorType::CONFIGURATION, index: 0, .. } => {
Some(TDescriptorSource::CONFIG_DESC_BYTES)
}
DescriptorInfo { ty: DescriptorType::STRING, index: 0, .. } => {
Some(TDescriptorSource::STRING_DESC_0_BYTES)
}
DescriptorInfo { ty: DescriptorType::STRING, index, .. } => {
descriptor_source
.get_string(StringHandle(index), setup_pkt.index())
.map(|desc| desc.as_bytes())
}
_ => None,
};
if let Some(response) = &mut response {
if response.len() > setup_pkt.length().into() {
*response = &(*response)[..setup_pkt.length().into()];
}
UsbAction::control_transfer_in_or_stall(0, &setup_pkt, response)
} else {
UsbAction::StallInAndOut { endpoint: 0 }
}
}
Request::DEVICE_GET_STATUS => UsbAction::control_transfer_in_or_stall(
0,
&setup_pkt,
descriptor_source.get_device_status(),
),
Request::INTERFACE_GET_STATUS => {
UsbAction::control_transfer_in_or_stall(0, &setup_pkt, &STATUS_OK)
}
Request::ENDPOINT_GET_STATUS => {
let ep_addr = u8::try_from(setup_pkt.index() & 0xff).unwrap();
let data = if self.is_endpoint_halted(ep_addr) {
&STATUS_HALTED
} else {
&STATUS_OK
};
UsbAction::control_transfer_in_or_stall(0, &setup_pkt, data)
}
Request::ENDPOINT_SET_FEATURE => {
match setup_pkt.value() {
0 => {
// ENDPOINT_HALT
let ep_addr = u8::try_from(setup_pkt.index() & 0xff).unwrap();
if endpoint_number(ep_addr) == 0 {
// Endpoint 0 cannot be halted. Stall the control pipe to reject the request.
UsbAction::StallInAndOut { endpoint: 0 }
} else {
self.set_endpoint_halted(ep_addr, true);
self.pending_halt = Some((ep_addr, true));
UsbAction::TransferIn {
endpoint: 0,
data: EMPTY,
zlp: true,
}
}
}
_ => {
// Feature not supported.
UsbAction::StallInAndOut { endpoint: 0 }
}
}
}
Request::ENDPOINT_CLEAR_FEATURE => {
match setup_pkt.value() {
0 => {
// ENDPOINT_HALT
let ep_addr = u8::try_from(setup_pkt.index() & 0xff).unwrap();
if endpoint_number(ep_addr) == 0 {
// Endpoint 0 cannot be cleared. Stall the control pipe to reject the request.
UsbAction::StallInAndOut { endpoint: 0 }
} else {
self.set_endpoint_halted(ep_addr, false);
self.pending_halt = Some((ep_addr, false));
UsbAction::TransferIn {
endpoint: 0,
data: EMPTY,
zlp: true,
}
}
}
_ => {
// Feature not supported.
UsbAction::StallInAndOut { endpoint: 0 }
}
}
}
Request::DEVICE_SET_ADDRESS => {
self.new_address = Some(setup_pkt.value() as u8);
UsbAction::TransferIn {
endpoint: 0,
data: EMPTY,
zlp: true,
}
}
Request::DEVICE_SET_CONFIGURATION => {
let val = setup_pkt.value();
if val == 0 || val == 1 {
self.configuration = val as u8;
UsbAction::TransferIn {
endpoint: 0,
data: EMPTY,
zlp: true,
}
} else {
UsbAction::StallInAndOut { endpoint: 0 }
}
}
Request::DEVICE_GET_CONFIGURATION => {
let data = match self.configuration {
1 => &CONFIG_1,
_ => &CONFIG_0,
};
UsbAction::control_transfer_in_or_stall(0, &setup_pkt, data)
}
Request::INTERFACE_GET_INTERFACE => {
UsbAction::control_transfer_in_or_stall(0, &setup_pkt, &CONFIG_0)
}
Request::INTERFACE_SET_INTERFACE => {
if setup_pkt.value() == 0 {
UsbAction::TransferIn {
endpoint: 0,
data: EMPTY,
zlp: true,
}
} else {
UsbAction::StallInAndOut { endpoint: 0 }
}
}
_ => UsbAction::StallInAndOut { endpoint: 0 },
}
}
fn handle_packet_sent(&mut self) -> UsbAction<'static> {
if let Some(new_address) = self.new_address.take() {
// Now that the transfer is complete it's safe to change the address..
return UsbAction::SetAddress { new_address };
}
if let Some((endpoint_addr, halted)) = self.pending_halt.take() {
return UsbAction::EndpointHalt {
endpoint_addr,
halted,
};
}
UsbAction::None
}
}
impl Default for SimpleEp0 {
fn default() -> Self {
Self::new()
}
}
/// A helper struct to handle multi-packet USB transfers.
///
/// It accumulates incoming USB packets into an internal buffer until a short
/// packet or a zero-length packet (ZLP) is received, indicating the end of a transfer.
///
/// `N` is the number of **words** (`u32`s) in the internal buffer and NOT bytes.
#[derive(Debug, PartialEq, Eq)]
pub struct Transfer<const N: usize> {
buffer: [u32; N],
word_offset: usize,
}
impl<const N: usize> Transfer<N> {
/// Maximum packet size supported (fixed at 64 bytes).
pub const MAX_PACKET_SIZE: usize = 64;
/// Creates a new `Transfer` buffer.
pub fn new() -> Self {
Self {
buffer: [0; N],
word_offset: 0,
}
}
/// Splices a USB packet into the buffer.
///
/// Returns `Ok(Some(slice))` if the transfer is complete, `Ok(None)` otherwise.
pub fn splice(&mut self, packet: impl UsbPacket) -> Result<Option<&Aligned<A4, [u8]>>, Error> {
const {
assert!(Self::MAX_PACKET_SIZE % size_of::<u32>() == 0);
}
let packet_len = packet.len();
let dest = {
let start = self.word_offset;
let end = start + packet_len.div_ceil(size_of::<u32>());
self.buffer.get_mut(start..end).ok_or(Error::OutOfRange)?
};
packet.copy_to(dest);
if packet_len < Self::MAX_PACKET_SIZE {
let result = &self
.buffer
.as_bytes()
.get(..self.word_offset * size_of::<u32>() + packet_len)
.ok_or(Error::OutOfRange)?;
self.word_offset = 0;
Ok(Some(
// nosemgrep
unsafe {
// SAFETY: `self.buffer` is `[u32]` which has alignment of 4.
core::mem::transmute::<&[u8], &Aligned<A4, [u8]>>(result)
},
))
} else {
self.word_offset += Self::MAX_PACKET_SIZE / size_of::<u32>();
Ok(None)
}
}
}
impl<const N: usize> Default for Transfer<N> {
fn default() -> Self {
Self::new()
}
}
pub mod testing {
use aligned::Aligned;
use aligned::A4;
use hal_usb::driver::UsbPacket;
use zerocopy::IntoBytes;
#[derive(Debug)]
pub struct FakeUsbPacket<'a> {
pub data: &'a [u8],
pub ep: usize,
}
impl UsbPacket for FakeUsbPacket<'_> {
fn endpoint_index(&self) -> usize {
self.ep
}
fn len(&self) -> usize {
self.data.len()
}
fn copy_to_uninit(self, _dest: &mut [core::mem::MaybeUninit<u32>]) -> &[u8] {
unimplemented!()
}
fn copy_to(self, dest: &mut [u32]) -> &[u8] {
let dest_bytes = dest.as_mut_bytes();
let copy_len = self.data.len().min(dest_bytes.len());
dest_bytes[..copy_len].copy_from_slice(&self.data[..copy_len]);
// nosemgrep
unsafe {
// SAFETY: `dest` is a `&mut [u32]`, which is guaranteed to be 4-byte
// aligned. `dest_bytes` is a byte slice view of the same memory, so it's also
// 4-byte aligned. The subslice `&dest_bytes[..copy_len]` maintains this alignment.
core::mem::transmute::<&[u8], &Aligned<A4, [u8]>>(&dest_bytes[..copy_len])
}
}
fn copy_to_unaligned(self, dest: &mut [u8]) -> &[u8] {
let copy_len = self.data.len().min(dest.len());
dest[..copy_len].copy_from_slice(&self.data[..copy_len]);
&dest[..copy_len]
}
}
}
#[cfg(test)]
mod splice_tests {
use super::testing::FakeUsbPacket;
use super::*;
const MAX_PACKET_SIZE: usize = Transfer::<0>::MAX_PACKET_SIZE;
#[test]
fn test_splice_single_short_packet() {
let packet_data = [1, 2, 3, 4];
let packet = FakeUsbPacket {
data: &packet_data,
ep: 0,
};
let mut transfer = Transfer::<32>::new();
let result = transfer.splice(packet).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().as_ref(), &packet_data[..]);
}
#[test]
fn test_splice_single_full_packet_then_zlp() {
let packet_data = [42; MAX_PACKET_SIZE];
let packet = FakeUsbPacket {
data: &packet_data,
ep: 0,
};
let mut transfer = Transfer::<32>::new();
let result = transfer.splice(packet).unwrap();
assert!(result.is_none());
let zlp = FakeUsbPacket { data: &[], ep: 0 };
let result = transfer.splice(zlp).unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().as_ref(), &packet_data[..]);
}
#[test]
fn test_splice_multiple_packets() {
let packet1_data = [1; MAX_PACKET_SIZE];
let packet2_data = [2; MAX_PACKET_SIZE];
let packet3_data = [3; 32];
// Packet 1
let packet1 = FakeUsbPacket {
data: &packet1_data,
ep: 0,
};
let mut transfer = Transfer::<64>::new();
let result = transfer.splice(packet1).unwrap();
assert!(result.is_none());
// Packet 2
let packet2 = FakeUsbPacket {
data: &packet2_data,
ep: 0,
};
let result = transfer.splice(packet2).unwrap();
assert!(result.is_none());
// Packet 3 (short packet)
let packet3 = FakeUsbPacket {
data: &packet3_data,
ep: 0,
};
let result = transfer.splice(packet3).unwrap();
assert!(result.is_some());
let mut expected_data = [0u8; 2 * MAX_PACKET_SIZE + 32];
expected_data[..MAX_PACKET_SIZE].copy_from_slice(&packet1_data);
expected_data[MAX_PACKET_SIZE..2 * MAX_PACKET_SIZE].copy_from_slice(&packet2_data);
expected_data[2 * MAX_PACKET_SIZE..].copy_from_slice(&packet3_data);
assert_eq!(result.unwrap().as_ref(), &expected_data[..]);
}
#[test]
fn test_splice_buffer_overflow() {
let packet_data = [1; 1];
let packet = FakeUsbPacket {
data: &packet_data,
ep: 0,
};
let mut transfer = Transfer::<16>::new();
transfer
.splice(FakeUsbPacket {
data: &[0; MAX_PACKET_SIZE],
ep: 0,
})
.unwrap();
let result = transfer.splice(packet);
assert_eq!(result.err(), Some(Error::OutOfRange));
}
#[test]
fn test_full_capacity_with_full_packets_then_partial_packet() {
const PARTIAL_SIZE: usize = 16;
const FULL1_DATA: &[u8] = &[0xaa; MAX_PACKET_SIZE];
const FULL2_DATA: &[u8] = &[0xbb; MAX_PACKET_SIZE];
const PARTIAL_DATA: &[u8] = &[0xcc; PARTIAL_SIZE];
const RECEIVE_BUFFER_WORDS: usize =
(FULL1_DATA.len() + FULL2_DATA.len() + PARTIAL_DATA.len()) / size_of::<u32>();
let full1 = FakeUsbPacket {
data: FULL1_DATA,
ep: 0,
};
let full2 = FakeUsbPacket {
data: FULL2_DATA,
ep: 0,
};
let partial = FakeUsbPacket {
data: PARTIAL_DATA,
ep: 0,
};
let mut transfer = Transfer::<RECEIVE_BUFFER_WORDS>::new();
assert!(transfer.splice(full1).unwrap().is_none());
assert!(transfer.splice(full2).unwrap().is_none());
let buffer = transfer.splice(partial).unwrap().unwrap().as_ref();
assert_eq!(&buffer[..MAX_PACKET_SIZE], FULL1_DATA);
assert_eq!(&buffer[MAX_PACKET_SIZE..2 * MAX_PACKET_SIZE], FULL2_DATA);
}
}
#[cfg(test)]
mod simple_ep0_tests {
use super::*;
use aligned::Aligned;
use aligned::A4;
use hal_usb::Request;
use hal_usb::SetupPacket;
use hal_usb::StringDescriptorRef;
use hal_usb::StringHandle;
struct DummyDescriptors;
impl DescriptorSource for DummyDescriptors {
const DEVICE_DESC_BYTES: &'static Aligned<A4, [u8]> = &Aligned([]);
const CONFIG_DESC_BYTES: &'static Aligned<A4, [u8]> = &Aligned([]);
const STRING_DESC_0_BYTES: &'static Aligned<A4, [u8]> = &Aligned([]);
const DEVICE_STATUS: Aligned<A4, [u8; 2]> = Aligned([0, 0]);
fn get_string(&self, _handle: StringHandle, _lang: u16) -> Option<StringDescriptorRef<'_>> {
None
}
}
fn unwrap_action<'a, P: UsbPacket>(res: Result<UsbAction<'a>, UsbEvent<P>>) -> UsbAction<'a> {
match res {
Ok(a) => a,
Err(_) => panic!("Expected Ok action"),
}
}
#[test]
fn test_set_configuration() {
let mut ep0 = SimpleEp0::new();
let descriptors = DummyDescriptors;
// Value = 0 (de-configure/address state) should be accepted
let req = Request::DEVICE_SET_CONFIGURATION;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0]);
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
assert!(matches!(
action,
UsbAction::TransferIn {
endpoint: 0,
zlp: true,
..
}
));
// Value = 1 (active configuration) should be accepted
let buf0 = (u16::from(req) as u32) | (1u32 << 16);
let setup_pkt = SetupPacket::new([buf0, 0]);
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
assert!(matches!(
action,
UsbAction::TransferIn {
endpoint: 0,
zlp: true,
..
}
));
// Value = 2 (invalid configuration) should be stalled
let buf0 = (u16::from(req) as u32) | (2u32 << 16);
let setup_pkt = SetupPacket::new([buf0, 0]);
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
assert!(matches!(action, UsbAction::StallInAndOut { endpoint: 0 }));
}
#[test]
fn test_get_configuration() {
let mut ep0 = SimpleEp0::new();
let descriptors = DummyDescriptors;
// Initial state should be unconfigured (0)
let req = Request::DEVICE_GET_CONFIGURATION;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0001_0000]); // Length = 1
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[0]);
} else {
panic!("Expected TransferIn action");
}
// Set configuration to 1
let req_set = Request::DEVICE_SET_CONFIGURATION;
let buf0 = (u16::from(req_set) as u32) | (1u32 << 16);
let setup_pkt = SetupPacket::new([buf0, 0]);
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let _ = ep0.handle_event(ev, &descriptors);
// Now GET_CONFIGURATION should return 1
let req_get = Request::DEVICE_GET_CONFIGURATION;
let buf0 = u16::from(req_get) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0001_0000]); // Length = 1
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[1]);
} else {
panic!("Expected TransferIn action");
}
// USB reset should reset configuration back to 0
let ev_reset: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::UsbReset;
let _ = ep0.handle_event(ev_reset, &descriptors);
// Now GET_CONFIGURATION should return 0
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[0]);
} else {
panic!("Expected TransferIn action");
}
}
#[test]
fn test_get_status() {
let mut ep0 = SimpleEp0::new();
let descriptors = DummyDescriptors;
// Device recipient
let req = Request::DEVICE_GET_STATUS;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0002_0000]); // Length = 2
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[0, 0]);
} else {
panic!("Expected TransferIn action");
}
// Interface recipient
let req = Request::INTERFACE_GET_STATUS;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0002_0000]); // Length = 2
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[0, 0]);
} else {
panic!("Expected TransferIn action");
}
// Endpoint recipient
let req = Request::ENDPOINT_GET_STATUS;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0002_0000]); // Length = 2
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[0, 0]);
} else {
panic!("Expected TransferIn action");
}
}
#[test]
fn test_fallback_interface_requests() {
let mut ep0 = SimpleEp0::new();
let descriptors = DummyDescriptors;
// GET_INTERFACE should return alternate setting 0
let req = Request::INTERFACE_GET_INTERFACE;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0001_0000]); // Length = 1
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[0]);
} else {
panic!("Expected TransferIn action");
}
// SET_INTERFACE with alternate setting 0 should succeed
let req = Request::INTERFACE_SET_INTERFACE;
let buf0 = u16::from(req) as u32; // Value = 0
let setup_pkt = SetupPacket::new([buf0, 0]);
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
assert!(matches!(
action,
UsbAction::TransferIn {
endpoint: 0,
zlp: true,
..
}
));
// SET_INTERFACE with alternate setting 1 should be stalled (only 0 supported as fallback)
let buf0 = (u16::from(req) as u32) | (1u32 << 16); // Value = 1
let setup_pkt = SetupPacket::new([buf0, 0]);
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
assert!(matches!(action, UsbAction::StallInAndOut { endpoint: 0 }));
}
#[test]
fn test_endpoint_halt_requests() {
let mut ep0 = SimpleEp0::new();
let descriptors = DummyDescriptors;
// 1. Check Endpoint 1 IN status (initially not halted)
let req = Request::ENDPOINT_GET_STATUS;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0002_0081]); // Length = 2, Endpoint = 0x81 (1 IN)
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[0, 0]);
} else {
panic!("Expected TransferIn action");
}
// 2. SET_FEATURE(ENDPOINT_HALT) to Endpoint 1 IN
let req = Request::ENDPOINT_SET_FEATURE;
let buf0 = u16::from(req) as u32; // Value = 0 (ENDPOINT_HALT)
let setup_pkt = SetupPacket::new([buf0, 0x0000_0081]); // Endpoint = 0x81
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
assert!(matches!(
action,
UsbAction::TransferIn {
endpoint: 0,
zlp: true,
..
}
));
// Verify that handle_packet_sent returns EndpointHalt action
let action_sent = ep0.handle_packet_sent();
assert!(matches!(
action_sent,
UsbAction::EndpointHalt {
endpoint_addr: 0x81,
halted: true
}
));
// 3. Check Endpoint 1 IN status again (should be halted)
let req = Request::ENDPOINT_GET_STATUS;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0002_0081]); // Length = 2, Endpoint = 0x81
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[1, 0]); // Halt bit set
} else {
panic!("Expected TransferIn action");
}
// 4. CLEAR_FEATURE(ENDPOINT_HALT) to Endpoint 1 IN
let req = Request::ENDPOINT_CLEAR_FEATURE;
let buf0 = u16::from(req) as u32; // Value = 0 (ENDPOINT_HALT)
let setup_pkt = SetupPacket::new([buf0, 0x0000_0081]); // Endpoint = 0x81
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
assert!(matches!(
action,
UsbAction::TransferIn {
endpoint: 0,
zlp: true,
..
}
));
// Verify that handle_packet_sent returns EndpointHalt action (halted = false)
let action_sent = ep0.handle_packet_sent();
assert!(matches!(
action_sent,
UsbAction::EndpointHalt {
endpoint_addr: 0x81,
halted: false
}
));
// 5. Check Endpoint 1 IN status again (should be not halted)
let req = Request::ENDPOINT_GET_STATUS;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0002_0081]); // Length = 2, Endpoint = 0x81
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
if let UsbAction::TransferIn {
endpoint: 0, data, ..
} = action
{
assert_eq!(data.as_ref(), &[0, 0]);
} else {
panic!("Expected TransferIn action");
}
// 6. Trying to halt Endpoint 0 should fail/stall
let req = Request::ENDPOINT_SET_FEATURE;
let buf0 = u16::from(req) as u32;
let setup_pkt = SetupPacket::new([buf0, 0x0000_0000]); // Endpoint = 0
let ev: UsbEvent<testing::FakeUsbPacket<'static>> = UsbEvent::SetupPacket {
endpoint: 0,
pkt: setup_pkt,
};
let action = unwrap_action(ep0.handle_event(ev, &descriptors));
assert!(matches!(action, UsbAction::StallInAndOut { endpoint: 0 }));
}
}