diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d7f026..00935c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +* New enums and allocators for Isochronous endpoints -... +### Changed +* `EndpointType` enum now has fields for isochronous synchronization and usage. ## [0.2.9] - 2022-08-02 diff --git a/src/bus.rs b/src/bus.rs index 9461412..dd0f0ba 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -1,4 +1,7 @@ -use crate::endpoint::{Endpoint, EndpointAddress, EndpointDirection, EndpointType}; +use crate::endpoint::{ + Endpoint, EndpointAddress, EndpointDirection, EndpointType, IsochronousSynchronizationType, + IsochronousUsageType, +}; use crate::{Result, UsbDirection, UsbError}; use core::cell::RefCell; use core::mem; @@ -244,6 +247,41 @@ impl UsbBusAllocator { .expect("alloc_ep failed") } + /// Allocates an isochronous endpoint. + /// + /// # Arguments + /// + /// * `synchronization` - Type of synchronization used by the endpoint + /// * `usage` - Whether the endpoint is data, explicit feedback, or data+implicit feedback + /// * `payload_size` - Payload size in bytes. + /// * `interval` - Interval for polling, expressed in frames/microframes. + /// + /// See USB 2.0 section 9.6.6. + /// + /// # Panics + /// + /// Panics if endpoint allocation fails, because running out of endpoints or memory is not + /// feasibly recoverable. + #[inline] + pub fn isochronous( + &self, + synchronization: IsochronousSynchronizationType, + usage: IsochronousUsageType, + payload_size: u16, + interval: u8, + ) -> Endpoint<'_, B, D> { + self.alloc( + None, + EndpointType::Isochronous { + synchronization, + usage, + }, + payload_size, + interval, + ) + .expect("alloc_ep failed") + } + /// Allocates a bulk endpoint. /// /// # Arguments @@ -263,6 +301,7 @@ impl UsbBusAllocator { /// Allocates an interrupt endpoint. /// /// * `max_packet_size` - Maximum packet size in bytes. Cannot exceed 64 bytes. + /// * `interval` - Polling interval. /// /// # Panics /// diff --git a/src/descriptor.rs b/src/descriptor.rs index 9bb7361..e946f71 100644 --- a/src/descriptor.rs +++ b/src/descriptor.rs @@ -324,7 +324,7 @@ impl DescriptorWriter<'_> { let mps = endpoint.max_packet_size(); buf[0] = endpoint.address().into(); - buf[1] = endpoint.ep_type() as u8; + buf[1] = endpoint.ep_type().to_bm_attributes(); buf[2] = mps as u8; buf[3] = (mps >> 8) as u8; buf[4] = endpoint.interval(); diff --git a/src/endpoint.rs b/src/endpoint.rs index d761ed1..de7c75f 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -29,21 +29,81 @@ pub type EndpointOut<'a, B> = Endpoint<'a, B, Out>; /// A device-to-host (IN) endpoint. pub type EndpointIn<'a, B> = Endpoint<'a, B, In>; -/// 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)] +/// Isochronous transfers employ one of three synchronization schemes. See USB 2.0 spec 5.12.4.1. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum IsochronousSynchronizationType { + /// Synchronization is not implemented for this endpoint. + NoSynchronization, + /// Source and Sink sample clocks are free running. + Asynchronous, + /// Source sample clock is locked to Sink, Sink sample clock is locked to data flow. + Adaptive, + /// Source and Sink sample clocks are locked to USB SOF. + Synchronous, +} + +/// Intended use of an isochronous endpoint, see USB 2.0 spec sections 5.12 and 9.6.6. +/// Associations between data and feedback endpoints are described in section 9.6.6. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum IsochronousUsageType { + /// Endpoint is used for isochronous data. + Data, + /// Feedback for synchronization. + Feedback, + /// Endpoint is data and provides implicit feedback for synchronization. + ImplicitFeedbackData, +} + +/// USB endpoint transfer type. #[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, + Control, + /// Isochronous endpoint. Used for time-critical unreliable data. + /// + /// See USB 2.0 spec section 5.12 "Special Considerations for Isochronous Transfers" + Isochronous { + /// Synchronization model used for the data stream that this endpoint relates to. + synchronization: IsochronousSynchronizationType, + /// Endpoint's role in the synchronization model selected by [Self::Isochronous::synchronization]. + usage: IsochronousUsageType, + }, /// Bulk endpoint. Used for large amounts of best-effort reliable data. - Bulk = 0b10, + Bulk, /// Interrupt endpoint. Used for small amounts of time-critical reliable data. - Interrupt = 0b11, + Interrupt, +} + +impl EndpointType { + /// Format EndpointType for use in bmAttributes transfer type field USB 2.0 spec section 9.6.6 + pub fn to_bm_attributes(&self) -> u8 { + match self { + EndpointType::Control => 0b00, + EndpointType::Isochronous { + synchronization, + usage, + } => { + let sync_bits = match synchronization { + IsochronousSynchronizationType::NoSynchronization => 0b00, + IsochronousSynchronizationType::Asynchronous => 0b01, + IsochronousSynchronizationType::Adaptive => 0b10, + IsochronousSynchronizationType::Synchronous => 0b11, + }; + let usage_bits = match usage { + IsochronousUsageType::Data => 0b00, + IsochronousUsageType::Feedback => 0b01, + IsochronousUsageType::ImplicitFeedbackData => 0b10, + }; + (usage_bits << 4) | (sync_bits << 2) | 0b01 + } + EndpointType::Bulk => 0b10, + EndpointType::Interrupt => 0b11, + } + } } /// Handle for a USB endpoint. The endpoint direction is constrained by the `D` type argument, which diff --git a/src/lib.rs b/src/lib.rs index c2d2204..b3befbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -190,7 +190,10 @@ pub mod class_prelude { pub use crate::class::{ControlIn, ControlOut, UsbClass}; pub use crate::control; pub use crate::descriptor::{BosWriter, DescriptorWriter}; - pub use crate::endpoint::{EndpointAddress, EndpointIn, EndpointOut, EndpointType}; + pub use crate::endpoint::{ + EndpointAddress, EndpointIn, EndpointOut, EndpointType, IsochronousSynchronizationType, + IsochronousUsageType, + }; pub use crate::UsbError; } diff --git a/src/test_class.rs b/src/test_class.rs index 3713856..b16be62 100644 --- a/src/test_class.rs +++ b/src/test_class.rs @@ -32,6 +32,7 @@ pub struct TestClass<'a, B: UsbBus> { ep_bulk_out: EndpointOut<'a, B>, ep_interrupt_in: EndpointIn<'a, B>, ep_interrupt_out: EndpointOut<'a, B>, + ep_iso_in: EndpointIn<'a, B>, control_buf: [u8; sizes::BUFFER], bulk_buf: [u8; sizes::BUFFER], interrupt_buf: [u8; sizes::BUFFER], @@ -72,6 +73,12 @@ impl TestClass<'_, B> { ep_bulk_out: alloc.bulk(sizes::BULK_ENDPOINT), ep_interrupt_in: alloc.interrupt(sizes::INTERRUPT_ENDPOINT, 1), ep_interrupt_out: alloc.interrupt(sizes::INTERRUPT_ENDPOINT, 1), + ep_iso_in: alloc.isochronous( + IsochronousSynchronizationType::Asynchronous, + IsochronousUsageType::ImplicitFeedbackData, + 500, // These last two args are arbitrary in this usage, they + 1, // let the host know how much bandwidth to reserve. + ), control_buf: [0; sizes::BUFFER], bulk_buf: [0; sizes::BUFFER], interrupt_buf: [0; sizes::BUFFER], @@ -218,7 +225,7 @@ impl UsbClass for TestClass<'_, B> { writer.endpoint(&self.ep_interrupt_in)?; writer.endpoint(&self.ep_interrupt_out)?; writer.interface_alt(self.iface, 1, 0xff, 0x01, 0x00, Some(self.interface_string))?; - + writer.endpoint(&self.ep_iso_in)?; Ok(()) } diff --git a/tests/test_class_host/tests.rs b/tests/test_class_host/tests.rs index f72fbd0..f745a1d 100644 --- a/tests/test_class_host/tests.rs +++ b/tests/test_class_host/tests.rs @@ -1,6 +1,6 @@ use crate::device::*; use rand::prelude::*; -use rusb::{request_type, Direction, Recipient, RequestType}; +use rusb::{request_type, Direction, Recipient, RequestType, TransferType}; use std::cmp::max; use std::fmt::Write; use std::time::{Duration, Instant}; @@ -163,6 +163,40 @@ fn interface_descriptor(dev, _out) { test_class::INTERFACE_STRING); } +fn iso_endpoint_descriptors(dev, _out) { + // Tests that an isochronous endpoint descriptor is present in the first + // alternate setting, but not in the default setting. + let iface = dev.config_descriptor + .interfaces() + .find(|i| i.number() == 0) + .expect("interface not found"); + + let mut iso_ep_count = 0; + for iface_descriptor in iface.descriptors() { + if iface_descriptor.setting_number() == 0 { + // Default setting - no isochronous endpoints allowed. Per USB 2.0 + // spec rev 2.0, 5.6.3 Isochronous Transfer Packet Size Constraints: + // + // All device default interface settings must not include any + // isochronous endpoints with non-zero data payload sizes (specified + // via wMaxPacketSize in the endpoint descriptor) + let issue = iface_descriptor + .endpoint_descriptors() + .find(|ep| ep.transfer_type() == TransferType::Isochronous + && ep.max_packet_size() != 0); + if let Some(ep) = issue { + panic!("Endpoint {} is isochronous and in the default setting", + ep.number()); + } + } else { + iso_ep_count += iface_descriptor.endpoint_descriptors() + .filter(|ep| ep.transfer_type() == TransferType::Isochronous) + .count(); + } + } + assert!(iso_ep_count > 0, "At least one isochronous endpoint is expected"); +} + fn bulk_loopback(dev, _out) { let mut lens = vec![0, 1, 2, 32, 63, 64, 65, 127, 128, 129]; if dev.is_high_speed() {