Skip to content

Commit

Permalink
Merge pull request #60 from ianrrees/isochronous
Browse files Browse the repository at this point in the history
Add enumerations and allocator for isochronous endpoints
  • Loading branch information
ryan-summers authored Mar 30, 2023
2 parents d0b201e + ddfa116 commit 46a354c
Show file tree
Hide file tree
Showing 7 changed files with 160 additions and 14 deletions.
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
41 changes: 40 additions & 1 deletion src/bus.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -244,6 +247,41 @@ impl<B: UsbBus> UsbBusAllocator<B> {
.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<D: EndpointDirection>(
&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
Expand All @@ -263,6 +301,7 @@ impl<B: UsbBus> UsbBusAllocator<B> {
/// Allocates an interrupt endpoint.
///
/// * `max_packet_size` - Maximum packet size in bytes. Cannot exceed 64 bytes.
/// * `interval` - Polling interval.
///
/// # Panics
///
Expand Down
2 changes: 1 addition & 1 deletion src/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down
76 changes: 68 additions & 8 deletions src/endpoint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
9 changes: 8 additions & 1 deletion src/test_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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],
Expand Down Expand Up @@ -72,6 +73,12 @@ impl<B: UsbBus> 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],
Expand Down Expand Up @@ -218,7 +225,7 @@ impl<B: UsbBus> UsbClass<B> 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(())
}

Expand Down
36 changes: 35 additions & 1 deletion tests/test_class_host/tests.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand Down Expand Up @@ -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() {
Expand Down

0 comments on commit 46a354c

Please sign in to comment.