From 50116d4428b1d55dfec37fe00009527f73610e79 Mon Sep 17 00:00:00 2001 From: Ian Rees Date: Fri, 12 Mar 2021 12:04:13 +1300 Subject: [PATCH 1/6] Add enumerations and allocator for isochronous endpoints --- src/bus.rs | 38 +++++++++++++++++++++++++++- src/descriptor.rs | 2 +- src/endpoint.rs | 64 +++++++++++++++++++++++++++++++++++++++++------ 3 files changed, 94 insertions(+), 10 deletions(-) diff --git a/src/bus.rs b/src/bus.rs index 9461412..a4123e1 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,38 @@ 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 +298,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..0568be1 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -29,21 +29,69 @@ 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)] +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)] +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. + Isochronous((IsochronousSynchronizationType, 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((sync_type, usage_type)) => { + let sync_bits = match sync_type { + IsochronousSynchronizationType::NoSynchronization => 0b00, + IsochronousSynchronizationType::Asynchronous => 0b01, + IsochronousSynchronizationType::Adaptive => 0b10, + IsochronousSynchronizationType::Synchronous => 0b11, + }; + let usage_bits = match usage_type { + 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 From 3959ede228c55694e61ac6f7a6fb7ff5524ff91c Mon Sep 17 00:00:00 2001 From: Ian Rees Date: Fri, 5 Aug 2022 09:52:46 +1200 Subject: [PATCH 2/6] Derive defmt for new enums --- src/endpoint.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/endpoint.rs b/src/endpoint.rs index 0568be1..9a262c5 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -31,6 +31,7 @@ pub type EndpointIn<'a, B> = Endpoint<'a, B, In>; /// 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, @@ -45,6 +46,7 @@ pub enum IsochronousSynchronizationType { /// 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, From 0ca9bcf0311f4b9790f0d1aa18ff8d07173f41de Mon Sep 17 00:00:00 2001 From: Ian Rees Date: Fri, 5 Aug 2022 10:59:26 +1200 Subject: [PATCH 3/6] Isochronous endpoint enum from tuple to struct --- src/bus.rs | 5 ++++- src/endpoint.rs | 18 ++++++++++++++---- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/bus.rs b/src/bus.rs index a4123e1..dd0f0ba 100644 --- a/src/bus.rs +++ b/src/bus.rs @@ -272,7 +272,10 @@ impl UsbBusAllocator { ) -> Endpoint<'_, B, D> { self.alloc( None, - EndpointType::Isochronous((synchronization, usage)), + EndpointType::Isochronous { + synchronization, + usage, + }, payload_size, interval, ) diff --git a/src/endpoint.rs b/src/endpoint.rs index 9a262c5..de7c75f 100644 --- a/src/endpoint.rs +++ b/src/endpoint.rs @@ -64,7 +64,14 @@ pub enum EndpointType { /// used only endpoint 0. Control, /// Isochronous endpoint. Used for time-critical unreliable data. - Isochronous((IsochronousSynchronizationType, IsochronousUsageType)), + /// + /// 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, /// Interrupt endpoint. Used for small amounts of time-critical reliable data. @@ -76,14 +83,17 @@ impl EndpointType { pub fn to_bm_attributes(&self) -> u8 { match self { EndpointType::Control => 0b00, - EndpointType::Isochronous((sync_type, usage_type)) => { - let sync_bits = match sync_type { + 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_type { + let usage_bits = match usage { IsochronousUsageType::Data => 0b00, IsochronousUsageType::Feedback => 0b01, IsochronousUsageType::ImplicitFeedbackData => 0b10, From f4582c038adab71c4a7e0090358e34806e43bb3c Mon Sep 17 00:00:00 2001 From: Ian Rees Date: Sat, 6 Aug 2022 18:17:40 +1200 Subject: [PATCH 4/6] Add new isochronous ep enums to class_prelude --- src/lib.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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; } From eadbbc9bdfbcc9bf813597bb3d14238a5e44cb05 Mon Sep 17 00:00:00 2001 From: Ian Rees Date: Sat, 6 Aug 2022 18:19:21 +1200 Subject: [PATCH 5/6] Add tests for isochronous endpoint descriptors --- src/test_class.rs | 9 ++++++++- tests/test_class_host/tests.rs | 36 +++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 2 deletions(-) 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() { From ddfa11646f99a8296de5e9226b42070d3476d190 Mon Sep 17 00:00:00 2001 From: Ian Rees Date: Sat, 6 Aug 2022 19:08:08 +1200 Subject: [PATCH 6/6] Update changelog --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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