From ac6618a162031ffd23d9467ad10efa565451e79a Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Tue, 21 Apr 2026 00:45:03 -0700 Subject: [PATCH] more work on descriptor building, almost there also added the very early beginnings of an example to document how to get speed --- examples/lpc55s28/.gitignore | 1 + examples/lpc55s28/Cargo.lock | 471 ++++++++++++++++++++++++++++++++++ examples/lpc55s28/Cargo.toml | 9 + examples/lpc55s28/src/main.rs | 20 ++ src/descriptors.rs | 121 ++++++++- src/lib.rs | 364 +++++++++++++++++--------- 6 files changed, 855 insertions(+), 131 deletions(-) create mode 100644 examples/lpc55s28/.gitignore create mode 100644 examples/lpc55s28/Cargo.lock create mode 100644 examples/lpc55s28/Cargo.toml create mode 100644 examples/lpc55s28/src/main.rs diff --git a/examples/lpc55s28/.gitignore b/examples/lpc55s28/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/examples/lpc55s28/.gitignore @@ -0,0 +1 @@ +/target diff --git a/examples/lpc55s28/Cargo.lock b/examples/lpc55s28/Cargo.lock new file mode 100644 index 0000000..24cce86 --- /dev/null +++ b/examples/lpc55s28/Cargo.lock @@ -0,0 +1,471 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "bare-metal" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" +dependencies = [ + "rustc_version", +] + +[[package]] +name = "bitfield" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "byteorder-embedded-io" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed6bb9472871706c9b1f648ca527031e33d647a95706d6ab5659f22ca28d419" +dependencies = [ + "byteorder", + "embedded-io", +] + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "cortex-m" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" +dependencies = [ + "bare-metal", + "bitfield", + "embedded-hal", + "volatile-register", +] + +[[package]] +name = "cortex-m-rt" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d4dec46b34c299ccf6b036717ae0fce602faa4f4fe816d9013b9a7c9f5ba6" +dependencies = [ + "cortex-m-rt-macros", +] + +[[package]] +name = "cortex-m-rt-macros" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e37549a379a9e0e6e576fd208ee60394ccb8be963889eebba3ffe0980364f472" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array 0.14.7", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-io" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9eb1aa714776b75c7e67e1da744b81a129b3ff919c8712b5e1b32252c1f07cc7" + +[[package]] +name = "embedded-time" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a4b4d10ac48d08bfe3db7688c402baadb244721f30a77ce360bd24c3dffe58" +dependencies = [ + "num", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "generic-array" +version = "1.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaf57c49a95fd1fe24b90b3033bee6dc7e8f1288d51494cb44e627c295e38542" +dependencies = [ + "rustversion", + "typenum", +] + +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "stable_deref_trait", +] + +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array 0.14.7", +] + +[[package]] +name = "lpc55-hal" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41e86e4e491dd3f8e07e7ba6de4aace716459ddefebd05743b2cb67b6a566d3c" +dependencies = [ + "block-buffer", + "cipher", + "cortex-m", + "digest", + "embedded-hal", + "embedded-time", + "generic-array 1.3.5", + "lpc55-pac", + "nb 1.1.0", + "rand_core", + "usb-device 0.2.9", + "vcell", + "void", +] + +[[package]] +name = "lpc55-pac" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4952baed9d9e7e82a6bbc87333f939b90eb41df3e6c0be5e35d0ec61005f91" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "vcell", +] + +[[package]] +name = "modular-bitfield" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2956e537fc68236d2aa048f55704f231cc93f1c4de42fe1ecb5bd7938061fc4a" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b43b4fd69e3437618106f7754f34021b831a514f9e1a98ae863cabcd8d8dad" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "usb-device" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f6cc3adc849b5292b4075fc0d5fdcf2f24866e88e336dd27a8943090a520508" + +[[package]] +name = "usb-device" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" +dependencies = [ + "heapless", + "portable-atomic", +] + +[[package]] +name = "usbd-uac2" +version = "0.1.0" +dependencies = [ + "byteorder-embedded-io", + "embedded-io", + "modular-bitfield", + "usb-device 0.3.2", +] + +[[package]] +name = "usbd_uac2_demo" +version = "0.1.0" +dependencies = [ + "lpc55-hal", + "lpc55-pac", + "usbd-uac2", +] + +[[package]] +name = "vcell" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "volatile-register" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de437e2a6208b014ab52972a27e59b33fa2920d3e00fe05026167a1c509d19cc" +dependencies = [ + "vcell", +] diff --git a/examples/lpc55s28/Cargo.toml b/examples/lpc55s28/Cargo.toml new file mode 100644 index 0000000..946ca2d --- /dev/null +++ b/examples/lpc55s28/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "usbd_uac2_demo" +version = "0.1.0" +edition = "2024" + +[dependencies] +lpc55-hal = "0.5.0" +lpc55-pac = "0.5.0" +usbd-uac2 = { version = "0.1.0", path = "../.." } diff --git a/examples/lpc55s28/src/main.rs b/examples/lpc55s28/src/main.rs new file mode 100644 index 0000000..b68428d --- /dev/null +++ b/examples/lpc55s28/src/main.rs @@ -0,0 +1,20 @@ +use lpc55_pac as pac; +use usbd_uac2::{self, UsbSpeed, UsbSpeedProvider}; + +struct Lpc55UsbSpeedProvider {} + +impl UsbSpeedProvider for Lpc55UsbSpeedProvider { + fn speed(&self) -> usbd_uac2::UsbSpeed { + let regs = unsafe { &*pac::USB1::ptr() }; + match regs.devcmdstat.read().speed().bits() { + 1 => UsbSpeed::Full, + 2 => UsbSpeed::High, + 3 => UsbSpeed::Super, + _ => panic!("Unknown USB speed"), + } + } +} + +fn main() { + println!("Hello, world!"); +} diff --git a/src/descriptors.rs b/src/descriptors.rs index ce675fd..d8c5812 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -975,15 +975,15 @@ pub enum Type1FormatBitmap { } pub struct AudioStreamingInterface { - terminal_id: u8, - active_alt_setting: AccessControl, - valid_alt_settings: AccessControl, + pub terminal_id: u8, + pub active_alt_setting: AccessControl, + pub valid_alt_settings: AccessControl, /// Only type 1 format is supported - format_type: FormatType, - format_bitmap: Type1FormatBitmap, - num_channels: u8, - channel_config: ChannelConfig, - string: Option, + pub format_type: FormatType, + pub format_bitmap: Type1FormatBitmap, + pub num_channels: u8, + pub channel_config: ChannelConfig, + pub string: Option, } impl AudioStreamingInterface { @@ -1023,6 +1023,77 @@ impl Descriptor for AudioStreamingInterface { } } +#[repr(u8)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum LockDelay { + Undefined(u16), + Milliseconds(u16), + DecodedSamples(u16), +} +impl LockDelay { + pub fn units(&self) -> u8 { + match self { + LockDelay::Undefined(_) => 0, + LockDelay::Milliseconds(_) => 1, + LockDelay::DecodedSamples(_) => 2, + } + } + pub fn value(&self) -> u16 { + match self { + LockDelay::Undefined(v) => *v, + LockDelay::Milliseconds(v) => *v, + LockDelay::DecodedSamples(v) => *v, + } + } +} + +pub struct AudioStreamingEndpoint { + pub max_packets_only: bool, + pub pitch_control: AccessControl, + pub overrun_control: AccessControl, + pub underrun_control: AccessControl, + pub lock_delay: LockDelay, +} + +impl AudioStreamingEndpoint { + fn bm_controls(&self) -> u8 { + (self.pitch_control as u8) + | (self.overrun_control as u8) << 2 + | (self.underrun_control as u8) << 4 + } + fn write_payload( + &self, + writer: &mut T, + ) -> core::result::Result<(), T::Error> { + writer.write_u8(ClassSpecificEndpointDescriptorSubtype::General as u8)?; // bDescriptorSubtype + writer.write_u8((self.max_packets_only as u8) << 7)?; // bmAttributes + writer.write_u8(self.bm_controls())?; // bmControls + writer.write_u8(self.lock_delay.units())?; // bLockDelayUnits + writer.write_u16::(self.lock_delay.value())?; // wLockDelay + Ok(()) + } +} + +impl Descriptor for AudioStreamingEndpoint { + const MAX_SIZE: usize = 8; + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(ClassSpecificDescriptorType::Endpoint as u8)?; + self.write_payload(writer) + } + fn write_descriptor<'w, 'd>( + &self, + writer: &'w mut DescriptorWriter<'d>, + ) -> Result<(), DescriptorWriterError> { + let mut writer = + DescriptorWriterAdapter::new(writer, ClassSpecificDescriptorType::Endpoint); + self.write_payload(&mut writer) + } +} + #[cfg(test)] extern crate std; #[cfg(test)] @@ -1539,7 +1610,7 @@ mod tests { print!("{:02x} ", b); } println!(); - write_usb_descriptor_pcap("./uac2.pcap", 0x02, 0, bytes); + write_usb_descriptor_pcap("./uac2.pcap", 0x02, 0, bytes).unwrap(); } #[test] @@ -1607,4 +1678,36 @@ mod tests { ] ); } + + #[test] + fn test_as_endpoint_desc() { + let ep = AudioStreamingEndpoint { + max_packets_only: true, + pitch_control: AccessControl::NotPresent, + overrun_control: AccessControl::ReadOnly, + underrun_control: AccessControl::Programmable, + lock_delay: LockDelay::DecodedSamples(1024), + }; + + let mut buf = [0u8; AudioStreamingEndpoint::MAX_SIZE]; + let len = { + let mut cur = Cursor::new(&mut buf[..]); + ep.write(&mut cur).unwrap(); + cur.position() + }; + let descriptor = &buf[..len]; + assert_eq!( + descriptor, + &[ + 8, + 0x25, // CS_ENDPOINT + 0x01, // EP_GENERAL + 0x80, // bmAttributes + 0 | (1 << 2) | (3 << 4), // bmControls + 2, // bLockDelayUnits + (1024u16 & 0xff) as u8, // bLockDelay[0] + (1024u16 >> 8) as u8, // bLockDelay[1] + ] + ); + } } diff --git a/src/lib.rs b/src/lib.rs index a9a883b..ac8b625 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,10 +10,8 @@ use core::marker::PhantomData; use constants::*; use descriptors::*; -use usb_device::control::{Recipient, Request, RequestType}; -use usb_device::device::DEFAULT_ALTERNATE_SETTING; +use usb_device::class_prelude::*; use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out}; -use usb_device::{UsbDirection, class_prelude::*}; mod sealed { pub trait Sealed {} @@ -165,42 +163,58 @@ pub trait UsbAudioClockSource { } } +// This trait is needed since we specialize on D +trait TerminalConfigurationDescriptors { + fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal); +} + pub struct TerminalConfig { + /// USB terminal in the D direction will have this id, audio terminal will have this id + 1 + base_id: u8, clock_source_id: u8, num_channels: u8, format: FormatType1, terminal_type: TerminalType, channel_config: ChannelConfig, + sync_type: IsochronousSynchronizationType, + lock_delay: LockDelay, string: Option, _direction: PhantomData, } -impl<'a, D: EndpointDirection> TerminalConfig { +// TODO: builder pattern +impl TerminalConfig { pub fn new( + base_id: u8, clock_source_id: u8, num_channels: u8, format: FormatType1, terminal_type: TerminalType, channel_config: ChannelConfig, + sync_type: IsochronousSynchronizationType, + lock_delay: LockDelay, string: Option, ) -> Self { TerminalConfig { + base_id, clock_source_id, num_channels, format, terminal_type, channel_config, + sync_type, + lock_delay, string, _direction: PhantomData, } } } -impl<'a> TerminalConfig { - fn get_configuration_descriptors(&self, start_id: u8) -> (InputTerminal, OutputTerminal) { +impl<'a> TerminalConfigurationDescriptors for TerminalConfig { + fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) { let input_terminal = InputTerminal { - id: start_id, + id: self.base_id, terminal_type: TerminalType::UsbStreaming, - assoc_terminal: start_id + 1, + assoc_terminal: self.base_id + 1, clock_source: self.clock_source_id, num_channels: self.num_channels, channel_config: self.channel_config, @@ -215,10 +229,10 @@ impl<'a> TerminalConfig { string: None, }; let output_terminal = OutputTerminal { - id: start_id + 1, + id: self.base_id + 1, terminal_type: self.terminal_type, - assoc_terminal: start_id, - source_id: start_id, + assoc_terminal: self.base_id, + source_id: self.base_id, clock_source: self.clock_source_id, copy_protect_control: AccessControl::NotPresent, connector_control: AccessControl::NotPresent, @@ -232,13 +246,13 @@ impl<'a> TerminalConfig { // fn get_interface_descriptor(&self, id: InterfaceIndex) ) } -impl<'a> TerminalConfig { - fn get_configuration_descriptors(&self, start_id: u8) -> (OutputTerminal, InputTerminal) { +impl<'a> TerminalConfigurationDescriptors for TerminalConfig { + fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) { let output_terminal = OutputTerminal { - id: start_id, + id: self.base_id, terminal_type: TerminalType::UsbStreaming, - assoc_terminal: start_id + 1, - source_id: start_id + 1, + assoc_terminal: self.base_id + 1, + source_id: self.base_id + 1, clock_source: self.clock_source_id, copy_protect_control: AccessControl::NotPresent, connector_control: AccessControl::NotPresent, @@ -248,9 +262,9 @@ impl<'a> TerminalConfig { string: self.string, }; let input_terminal = InputTerminal { - id: start_id + 1, + id: self.base_id + 1, terminal_type: self.terminal_type, - assoc_terminal: start_id, + assoc_terminal: self.base_id, clock_source: self.clock_source_id, num_channels: self.num_channels, channel_config: self.channel_config, @@ -264,34 +278,42 @@ impl<'a> TerminalConfig { phantom_power_control: AccessControl::NotPresent, string: self.string, }; - (output_terminal, input_terminal) + (input_terminal, output_terminal) } - fn write_interface_descriptors( - &self, - writer: &mut DescriptorWriter, - if_id: InterfaceNumber, - ) -> usb_device::Result<()> { - writer.interface( - if_id, - AUDIO, - InterfaceSubclass::AudioStreaming as u8, - InterfaceProtocol::Version2 as u8, - )?; - writer.interface_alt( - if_id, - 1, - AUDIO, - InterfaceSubclass::AudioStreaming as u8, - InterfaceProtocol::Version2 as u8, - None, - )?; +} +impl TerminalConfig {} - // TODO: - // 1. Interface specific AS_GENERAL descriptor (4.9.2) - // 2. Format Type I descriptor - // 3. Endpoint descriptor +#[derive(Copy, Clone, Debug)] +pub enum UsbSpeed { + Low, // Not supported for audio + Full, + High, + Super, // Not supported by crate +} - Ok(()) +/// Since usb-device doesn't expose the underlying speed of the bus, the user needs to provide an implementation. +/// +/// +/// +/// This will be called whenever descriptors are sent to the host.. +pub trait UsbSpeedProvider { + fn speed(&self) -> UsbSpeed; +} + +/// Convenience implementation of UsbSpeedProvider for devices which only support one speed. +pub struct ConstSpeedProvider { + speed: UsbSpeed, +} + +impl ConstSpeedProvider { + pub const fn new(speed: UsbSpeed) -> Self { + ConstSpeedProvider { speed } + } +} + +impl UsbSpeedProvider for ConstSpeedProvider { + fn speed(&self) -> UsbSpeed { + self.speed } } @@ -313,7 +335,8 @@ impl<'a> TerminalConfig { /// A single Clock Source is always required, but a fully custom descriptor set can be built by only providing /// the Clock Source and additional descriptors, if the Terminal descriptors are inappropriate. /// -pub struct AudioClassConfig<'a, CS: UsbAudioClockSource> { +pub struct AudioClassConfig<'a, CS: UsbAudioClockSource, SP: UsbSpeedProvider> { + pub speed_provider: SP, pub device_category: FunctionCode, pub clock: CS, pub input_config: Option>, @@ -321,9 +344,10 @@ pub struct AudioClassConfig<'a, CS: UsbAudioClockSource> { pub additional_descriptors: Option<&'a [AudioClassDescriptor]>, } -impl<'a, CS: UsbAudioClockSource> AudioClassConfig<'a, CS> { - pub fn new(device_category: FunctionCode, clock: CS) -> Self { +impl<'a, SP: UsbSpeedProvider, CS: UsbAudioClockSource> AudioClassConfig<'a, CS, SP> { + pub fn new(speed_provider: SP, device_category: FunctionCode, clock: CS) -> Self { Self { + speed_provider, device_category, clock, input_config: None, @@ -346,67 +370,10 @@ impl<'a, CS: UsbAudioClockSource> AudioClassConfig<'a, CS> { self.additional_descriptors = Some(additional_descriptors); self } - /// Writes the class-specific configuration descriptor set (after bDescriptortype INTERFACE) - fn get_configuration_descriptors( - &self, - writer: &mut DescriptorWriter<'_>, - ) -> usb_device::Result<()> { - // CONFIGURATION DESCRIPTORS // - let mut total_length: u16 = 9; // HEADER - let clock_desc = self.clock.get_configuration_descriptor(1, None)?; - total_length += clock_desc.size() as u16; - let output_descs = match &self.output_config { - Some(config) => { - let descs = config.get_configuration_descriptors(2); - total_length += descs.0.size() as u16 + descs.1.size() as u16; - Some(descs) - } - None => None, - }; - let input_descs = match &self.input_config { - Some(config) => { - let descs = config.get_configuration_descriptors(4); - total_length += descs.0.size() as u16 + descs.1.size() as u16; - Some(descs) - } - None => None, - }; - let additional_descs = match &self.additional_descriptors { - Some(descs) => { - total_length += descs.iter().map(|desc| desc.size() as u16).sum::(); - Some(descs) - } - None => None, - }; - let ac_header: [u8; 7] = [ - ClassSpecificACInterfaceDescriptorSubtype::Header as u8, - 0, // bcdADC[0] - 2, // bcdADC[1] - self.device_category as u8, // bCategory - (total_length & 0xff) as u8, // wTotalLength LSB - ((total_length >> 8) & 0xff) as u8, // wTotalLength MSB - 0, // bmControls - ]; - writer.write(ClassSpecificDescriptorType::Interface as u8, &ac_header)?; - clock_desc.write_descriptor(writer)?; - if let Some((a, b)) = output_descs { - a.write_descriptor(writer)?; - b.write_descriptor(writer)?; - } - if let Some((a, b)) = input_descs { - a.write_descriptor(writer)?; - b.write_descriptor(writer)?; - } - if let Some(descs) = additional_descs { - for desc in descs.into_iter() { - desc.write_descriptor(writer)?; - } - } - // INTERFACE DESCRIPTORS // - - Ok(()) - } + // pub fn build<'a, B: UsbBus>(self, alloc: &'a UsbBusAllocator) -> Result> { + // Err(Error::InvalidValue) + // } } /// USB audio errors, including possible USB Stack errors @@ -426,44 +393,197 @@ impl From for Error { type Result = core::result::Result; struct AudioStream<'a, B: UsbBus, D: EndpointDirection> { + // UsbStreaming terminal ID + config: TerminalConfig, interface: InterfaceNumber, endpoint: Endpoint<'a, B, D>, alt_setting: u8, } -pub struct AudioClass<'a, CS: UsbAudioClockSource> { - control_iface: InterfaceNumber, - config: AudioClassConfig<'a, CS>, +impl<'a, B: UsbBus, D: EndpointDirection> AudioStream<'a, B, D> { + fn write_interface_descriptors(&self, writer: &mut DescriptorWriter) -> usb_device::Result<()> { + // UAC2 4.9.1 Standard AS Interface Descriptor + // zero bandwidth configuration per 3.16.2 + // + // Whenever an AudioStreaming interface requires an isochronous data + // endpoint, it must at least provide the default Alternate Setting + // (Alternate Setting 0) with zero bandwidth requirements (no + // isochronous data endpoint defined) and an additional Alternate + // Setting that contains the actual isochronous data endpoint. + writer.interface( + self.interface, + AUDIO, + InterfaceSubclass::AudioStreaming as u8, + InterfaceProtocol::Version2 as u8, + )?; + // UAC2 4.9.1 Standard AS Interface Descriptor + // live data configuration + writer.interface_alt( + self.interface, + 1, + AUDIO, + InterfaceSubclass::AudioStreaming as u8, + InterfaceProtocol::Version2 as u8, + None, + )?; + + // UAC2 4.9.2 Class-Specific AS Interface Descriptor + let as_general = AudioStreamingInterface { + terminal_id: self.config.base_id, // Always the USB streaming terminal id + active_alt_setting: AccessControl::NotPresent, + valid_alt_settings: AccessControl::NotPresent, + format_type: FormatType::Type1, + format_bitmap: Type1FormatBitmap::Pcm, // only PCM is supported + num_channels: self.config.num_channels, + channel_config: self.config.channel_config, + string: self.config.string, + }; + as_general.write_descriptor(writer)?; + + // UAC2 4.9.3 Class-Specific AS Format Type Descriptor + self.config.format.write_descriptor(writer)?; + + Ok(()) + } + + fn write_endpoint_descriptors(&self, writer: &mut DescriptorWriter) -> usb_device::Result<()> { + // UAC2 4.10.1.1 Standard AS Isochronous Audio Data Endpoint Descriptor + writer.endpoint(&self.endpoint)?; + // UAC2 4.10.1.2 Class-Specific AS Isochronous Audio Data Endpoint Descriptor + let cs_ep = AudioStreamingEndpoint { + max_packets_only: false, + pitch_control: AccessControl::NotPresent, + overrun_control: AccessControl::NotPresent, + underrun_control: AccessControl::NotPresent, + lock_delay: self.config.lock_delay, + }; + cs_ep.write_descriptor(writer)?; + Ok(()) + } } -impl AudioClass<'_, CS> {} +pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockSource, SP: UsbSpeedProvider> { + config: AudioClassConfig<'a, CS, SP>, + control_iface: InterfaceNumber, + output: Option>, + input: Option>, + feedback: Option>, +} -impl UsbClass for AudioClass<'_, CS> { - fn get_configuration_descriptors( - &self, - writer: &mut DescriptorWriter, - ) -> usb_device::Result<()> { - // IN, OUT, CONTROL - let n_interfaces = self.config.input_config.is_some() as u8 - + self.config.output_config.is_some() as u8 - + 1; +impl<'a, B: UsbBus, CS: UsbAudioClockSource, SP: UsbSpeedProvider> AudioClass<'a, B, CS, SP> { + fn get_interface_descriptors(&self, writer: &mut DescriptorWriter) -> usb_device::Result<()> { + // Control + 1 or 2 streaming + let n_interfaces = 1 + + (self.config.input_config.is_some() as u8) + + (self.config.output_config.is_some() as u8); + // UAC2 4.6 Interface Association Descriptor writer.iad( self.control_iface, n_interfaces, AUDIO, - FunctionSubclass::Undefined as u8, + InterfaceSubclass::AudioControl as u8, FunctionProtocol::Version2 as u8, None, )?; + + // UAC2 4.7 Standard AC Interface Descriptor writer.interface( self.control_iface, + 0, AUDIO, - InterfaceSubclass::AudioControl as u8, InterfaceProtocol::Version2 as u8, )?; - self.config.get_configuration_descriptors(writer)?; + // BUILD CONFIGURATION DESCRIPTORS // + let mut total_length: u16 = 9; // HEADER + let clock_desc = self.config.clock.get_configuration_descriptor(1, None)?; + total_length += clock_desc.size() as u16; + let output_descs = match &self.config.output_config { + Some(config) => { + let descs = config.get_configuration_descriptors(); + total_length += descs.0.size() as u16 + descs.1.size() as u16; + Some(descs) + } + None => None, + }; + let input_descs = match &self.config.input_config { + Some(config) => { + let descs = config.get_configuration_descriptors(); + total_length += descs.0.size() as u16 + descs.1.size() as u16; + Some(descs) + } + None => None, + }; + let additional_descs = match &self.config.additional_descriptors { + Some(descs) => { + total_length += descs.iter().map(|desc| desc.size() as u16).sum::(); + Some(descs) + } + None => None, + }; + + // UAC2 4.7.2 Class-specific AC Interface Descriptor + let ac_header: [u8; 7] = [ + ClassSpecificACInterfaceDescriptorSubtype::Header as u8, + 0, // bcdADC[0] + 2, // bcdADC[1] + self.config.device_category as u8, // bCategory + (total_length & 0xff) as u8, // wTotalLength LSB + ((total_length >> 8) & 0xff) as u8, // wTotalLength MSB + 0, // bmControls + ]; + writer.write(ClassSpecificDescriptorType::Interface as u8, &ac_header)?; + + // UAC2 4.7.2.1 Clock Source Descriptor + clock_desc.write_descriptor(writer)?; + + // UAC2 4.7.2.4 & 4.7.2.5 Input & Output Terminal Descriptors + if let Some((a, b)) = output_descs { + a.write_descriptor(writer)?; + b.write_descriptor(writer)?; + } + if let Some((a, b)) = input_descs { + a.write_descriptor(writer)?; + b.write_descriptor(writer)?; + } + + // UAC2 4.7 + if let Some(descs) = additional_descs { + for desc in descs.into_iter() { + desc.write_descriptor(writer)?; + } + } + + // UAC2 4.9 & 2.4.10 + if let Some(stream) = &self.output { + stream.write_interface_descriptors(writer)?; + stream.write_endpoint_descriptors(writer)?; + } + // UAC2 4.9.2.1 Feedback Endpoint Descriptor + // Should always be present if an OUT endpoint is present + if let Some(feedback) = &self.feedback { + writer.endpoint(feedback)?; + } + + if let Some(stream) = &self.input { + stream.write_interface_descriptors(writer)?; + stream.write_endpoint_descriptors(writer)?; + } + + Ok(()) + } +} + +impl UsbClass + for AudioClass<'_, B, CS, SP> +{ + /// Writes the class-specific configuration descriptor set (after bDescriptortype INTERFACE) + fn get_configuration_descriptors( + &self, + writer: &mut DescriptorWriter<'_>, + ) -> usb_device::Result<()> { + self.get_interface_descriptors(writer)?; Ok(()) }