Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add enumerations and allocator for isochronous endpoints #60

Merged
merged 6 commits into from
Mar 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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,
ryan-summers marked this conversation as resolved.
Show resolved Hide resolved
) -> Endpoint<'_, B, D> {
self.alloc(
None,
EndpointType::Isochronous {
synchronization,
usage,
},
payload_size,
interval,
)
.expect("alloc_ep failed")
ryan-summers marked this conversation as resolved.
Show resolved Hide resolved
}

/// 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