From 539e0aab98e3832cf21126e8c73b717b6ed37db4 Mon Sep 17 00:00:00 2001 From: Keenan Tims Date: Thu, 23 Apr 2026 14:38:28 -0700 Subject: [PATCH] much progress, descriptors and sample rates getting basically working? --- Cargo.lock | 28 +- Cargo.toml | 5 +- examples/lpc55s28-evk/.cargo/config.toml | 17 + .../{lpc55s28 => lpc55s28-evk}/.gitignore | 0 .../{lpc55s28 => lpc55s28-evk}/Cargo.lock | 269 ++++++- examples/lpc55s28-evk/Cargo.toml | 25 + examples/lpc55s28-evk/Embed.toml | 9 + examples/lpc55s28-evk/memory.x | 26 + examples/lpc55s28-evk/src/main.rs | 258 +++++++ examples/lpc55s28/Cargo.toml | 9 - examples/lpc55s28/src/main.rs | 20 - src/constants.rs | 26 +- src/cursor.rs | 94 ++- src/descriptors.rs | 483 ++++--------- src/lib.rs | 669 +++++++++++++++--- src/log.rs | 30 + 16 files changed, 1435 insertions(+), 533 deletions(-) create mode 100644 examples/lpc55s28-evk/.cargo/config.toml rename examples/{lpc55s28 => lpc55s28-evk}/.gitignore (100%) rename examples/{lpc55s28 => lpc55s28-evk}/Cargo.lock (66%) create mode 100644 examples/lpc55s28-evk/Cargo.toml create mode 100644 examples/lpc55s28-evk/Embed.toml create mode 100644 examples/lpc55s28-evk/memory.x create mode 100644 examples/lpc55s28-evk/src/main.rs delete mode 100644 examples/lpc55s28/Cargo.toml delete mode 100644 examples/lpc55s28/src/main.rs create mode 100644 src/log.rs diff --git a/Cargo.lock b/Cargo.lock index 062fc16..f963bce 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # 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 = "bitflags" version = "1.3.2" @@ -24,6 +30,15 @@ dependencies = [ "embedded-io", ] +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + [[package]] name = "defmt" version = "1.0.1" @@ -102,6 +117,15 @@ dependencies = [ "syn", ] +[[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" @@ -203,6 +227,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98816b1accafbb09085168b90f27e93d790b4bfa19d883466b5e53315b5f06a6" dependencies = [ + "defmt 0.3.100", "heapless", "portable-atomic", ] @@ -212,8 +237,9 @@ name = "usbd-uac2" version = "0.1.0" dependencies = [ "byteorder-embedded-io", - "defmt", + "defmt 1.0.1", "embedded-io", "modular-bitfield", + "num-traits", "usb-device", ] diff --git a/Cargo.toml b/Cargo.toml index a43f73f..c38200d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,11 +7,12 @@ edition = "2024" keywords = ["no-std", "usb-device"] [features] -defmt = ["dep:defmt"] +defmt = ["dep:defmt", "usb-device/defmt"] [dependencies] byteorder-embedded-io = { version = "0.1.1", features = ["embedded-io"] } defmt = { version = "1.0.1", optional = true } embedded-io = "0.7.1" modular-bitfield = "0.13.1" -usb-device = "0.3" +num-traits = { version = "0.2.19", default-features = false } +usb-device = { version = "0.3", features = ["control-buffer-256"] } diff --git a/examples/lpc55s28-evk/.cargo/config.toml b/examples/lpc55s28-evk/.cargo/config.toml new file mode 100644 index 0000000..0117d72 --- /dev/null +++ b/examples/lpc55s28-evk/.cargo/config.toml @@ -0,0 +1,17 @@ +[target.thumbv8m.main-none-eabi] +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[target.thumbv8m.main-none-eabihf] +rustflags = [ + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", +] + +[build] +target = "thumbv8m.main-none-eabihf" + +[env] +DEFMT_LOG = "debug" diff --git a/examples/lpc55s28/.gitignore b/examples/lpc55s28-evk/.gitignore similarity index 100% rename from examples/lpc55s28/.gitignore rename to examples/lpc55s28-evk/.gitignore diff --git a/examples/lpc55s28/Cargo.lock b/examples/lpc55s28-evk/Cargo.lock similarity index 66% rename from examples/lpc55s28/Cargo.lock rename to examples/lpc55s28-evk/Cargo.lock index 24cce86..1dd6b37 100644 --- a/examples/lpc55s28/Cargo.lock +++ b/examples/lpc55s28-evk/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "atomic-polyfill" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8cf2bce30dfe09ef0bfaef228b9d414faaf7e563035494d7fe092dba54b300f4" +dependencies = [ + "critical-section", +] + [[package]] name = "autocfg" version = "1.5.0" @@ -14,7 +23,7 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5deb64efa5bd81e31fcd1938615a6d98c82eafcbcd787162b6f63b91d6bac5b3" dependencies = [ - "rustc_version", + "rustc_version 0.2.3", ] [[package]] @@ -23,6 +32,12 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "46afbd2983a5d5a7bd740ccb198caf5b82f45c40c09c0eed36052d91cb92e719" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "block-buffer" version = "0.10.4" @@ -66,7 +81,8 @@ checksum = "8ec610d8f49840a5b376c69663b6369e71f4b34484b9b2eb29fb918d92516cb9" dependencies = [ "bare-metal", "bitfield", - "embedded-hal", + "critical-section", + "embedded-hal 0.2.7", "volatile-register", ] @@ -90,6 +106,12 @@ dependencies = [ "syn", ] +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + [[package]] name = "crypto-common" version = "0.1.7" @@ -100,6 +122,57 @@ dependencies = [ "typenum", ] +[[package]] +name = "defmt" +version = "0.3.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0963443817029b2024136fc4dd07a5107eb8f977eaf18fcd1fdeb11306b64ad" +dependencies = [ + "defmt 1.0.1", +] + +[[package]] +name = "defmt" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "548d977b6da32fa1d1fda2876453da1e7df63ad0304c8b3dae4dbe7b96f39b78" +dependencies = [ + "bitflags", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d4fc12a85bcf441cfe44344c4b72d58493178ce635338a3f3b78943aceb258e" +dependencies = [ + "defmt-parser", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror", +] + +[[package]] +name = "defmt-rtt" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d5a25c99d89c40f5676bec8cefe0614f17f0f40e916f98e345dae941807f9e" +dependencies = [ + "critical-section", + "defmt 1.0.1", +] + [[package]] name = "digest" version = "0.10.7" @@ -120,6 +193,12 @@ dependencies = [ "void", ] +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + [[package]] name = "embedded-io" version = "0.7.1" @@ -155,6 +234,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "hash32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c35f58762feb77d74ebe43bdbc3210f09be9fe6742234d573bacc26ed92b67" +dependencies = [ + "byteorder", +] + [[package]] name = "hash32" version = "0.3.1" @@ -164,13 +252,26 @@ dependencies = [ "byteorder", ] +[[package]] +name = "heapless" +version = "0.7.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdc6457c0eb62c71aac4bc17216026d8410337c4126773b9c5daba343f17964f" +dependencies = [ + "atomic-polyfill", + "hash32 0.2.1", + "rustc_version 0.4.1", + "spin", + "stable_deref_trait", +] + [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ - "hash32", + "hash32 0.3.1", "stable_deref_trait", ] @@ -183,23 +284,47 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "log-to-defmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9454118d78a9089a15d702fbaf413da2fa23f331b8af6c5eed5784a4369173c6" +dependencies = [ + "defmt 0.3.100", + "heapless 0.7.17", + "log", +] + [[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-hal 0.2.7", "embedded-time", "generic-array 1.3.5", "lpc55-pac", "nb 1.1.0", "rand_core", - "usb-device 0.2.9", + "usb-device", "vcell", "void", ] @@ -215,6 +340,25 @@ dependencies = [ "vcell", ] +[[package]] +name = "lpc55s28-evk" +version = "0.1.0" +dependencies = [ + "cortex-m", + "cortex-m-rt", + "defmt 1.0.1", + "defmt-rtt", + "embedded-hal 1.0.0", + "embedded-io", + "log-to-defmt", + "lpc55-hal", + "nb 1.1.0", + "panic-halt", + "static_cell", + "usb-device", + "usbd-uac2", +] + [[package]] name = "modular-bitfield" version = "0.13.1" @@ -313,12 +457,40 @@ dependencies = [ "autocfg", ] +[[package]] +name = "panic-halt" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a513e167849a384b7f9b746e517604398518590a9142f4846a32e3c2a4de7b11" + [[package]] name = "portable-atomic" version = "1.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -349,7 +521,16 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" dependencies = [ - "semver", + "semver 0.9.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.28", ] [[package]] @@ -358,6 +539,12 @@ version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "0.9.0" @@ -367,12 +554,27 @@ dependencies = [ "semver-parser", ] +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + [[package]] name = "semver-parser" version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +dependencies = [ + "lock_api", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -385,6 +587,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +[[package]] +name = "static_cell" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0530892bb4fa575ee0da4b86f86c667132a94b74bb72160f58ee5a4afec74c23" +dependencies = [ + "portable-atomic", +] + [[package]] name = "syn" version = "2.0.117" @@ -396,6 +607,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "typenum" version = "1.20.0" @@ -408,19 +639,14 @@ 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", + "defmt 0.3.100", + "heapless 0.8.0", "portable-atomic", ] @@ -429,18 +655,11 @@ name = "usbd-uac2" version = "0.1.0" dependencies = [ "byteorder-embedded-io", + "defmt 1.0.1", "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", + "num-traits", + "usb-device", ] [[package]] diff --git a/examples/lpc55s28-evk/Cargo.toml b/examples/lpc55s28-evk/Cargo.toml new file mode 100644 index 0000000..969e5d4 --- /dev/null +++ b/examples/lpc55s28-evk/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "lpc55s28-evk" +version = "0.1.0" +edition = "2024" + +[dependencies] +cortex-m = { version = "0.7.7", features = ["critical-section-single-core"] } +cortex-m-rt = "0.7.5" +defmt = "1.0.1" +defmt-rtt = "1.1.0" +embedded-hal = "1.0.0" +embedded-io = "0.7.1" +log-to-defmt = "0.1.0" +lpc55-hal = { version = "0.5.0", path = "../lpc55-hal" } +nb = "1.1.0" +panic-halt = "1.0.0" +static_cell = "2.1.1" +usb-device = "0.3" +usbd-uac2 = { version = "0.1.0", path = "../..", features = ["defmt"]} + +[profile.release] +opt-level = "z" +lto = true +debug = true +codegen-units = 1 diff --git a/examples/lpc55s28-evk/Embed.toml b/examples/lpc55s28-evk/Embed.toml new file mode 100644 index 0000000..a897fdf --- /dev/null +++ b/examples/lpc55s28-evk/Embed.toml @@ -0,0 +1,9 @@ +[default.general] +chip = "LPC55S28JBD100" +[default.rtt] +enabled = true +[default.gdb] +enabled = true + +[debug.rtt] +enabled = false diff --git a/examples/lpc55s28-evk/memory.x b/examples/lpc55s28-evk/memory.x new file mode 100644 index 0000000..fdd289f --- /dev/null +++ b/examples/lpc55s28-evk/memory.x @@ -0,0 +1,26 @@ +MEMORY +{ + FLASH : ORIGIN = 0x00000000, LENGTH = 512K + + /* for use with standard link.x */ + RAM : ORIGIN = 0x20000000, LENGTH = 64K + + /* would be used with proper link.x */ + /* needs changes to r0 (initialization code) */ + /* SRAM0 : ORIGIN = 0x20000000, LENGTH = 64K */ + /* SRAM1 : ORIGIN = 0x20010000, LENGTH = 64K */ + /* SRAM2 : ORIGIN = 0x20020000, LENGTH = 64K */ + /* SRAM3 : ORIGIN = 0x20030000, LENGTH = 64K */ + + /* CASPER SRAM regions */ + /* SRAMX0: ORIGIN = 0x1400_0000, LENGTH = 4K /1* to 0x1400_0FFF *1/ */ + /* SRAMX1: ORIGIN = 0x1400_4000, LENGTH = 4K /1* to 0x1400_4FFF *1/ */ + + /* USB1 SRAM regin */ + /* USB1_SRAM : ORIGIN = 0x40100000, LENGTH = 16K */ + + /* To define our own USB RAM section in one regular */ + /* RAM, probably easiest to shorten length of RAM */ + /* above, and use this freed RAM section */ + +} diff --git a/examples/lpc55s28-evk/src/main.rs b/examples/lpc55s28-evk/src/main.rs new file mode 100644 index 0000000..5251776 --- /dev/null +++ b/examples/lpc55s28-evk/src/main.rs @@ -0,0 +1,258 @@ +#![no_main] +#![no_std] + +// extern crate panic_semihosting; +extern crate panic_halt; +use core::cell::OnceCell; +use cortex_m::asm::delay; +use cortex_m_rt::entry; +use embedded_io::{ErrorType, Write}; +use nb; + +#[allow(unused_imports)] +use hal::prelude::*; +#[allow(unused_imports)] +use lpc55_hal as hal; +use lpc55_hal::{ + drivers::{ + Serial, + pins::{PinId, Pio0_29, Pio0_30}, + serial::Tx, + }, + peripherals::flexcomm::Usart0, + typestates::pin::{ + flexcomm::{Usart, UsartPins}, + function::{FC0_RXD_SDA_MOSI_DATA, FC0_TXD_SCL_MISO_WS}, + state::Special, + }, +}; + +use core::convert::Infallible; +use defmt; +use defmt_rtt as _; +use hal::drivers::{Timer, UsbBus, pins}; +use static_cell::StaticCell; +use usb_device::{ + bus, + device::{StringDescriptors, UsbDeviceBuilder, UsbVidPid}, + endpoint::IsochronousSynchronizationType, +}; +use usbd_uac2::{ + self, AudioClassConfig, RangeEntry, TerminalConfig, USB_CLASS_AUDIO, UsbAudioClass, + UsbAudioClockImpl, UsbSpeed, + constants::{FunctionCode, TerminalType}, + descriptors::{ChannelConfig, ClockSource, ClockType, FormatType1, LockDelay}, +}; + +type SERIAL_RX_PIN = hal::Pin>; +type SERIAL_TX_PIN = hal::Pin>; +type SERIAL_PINS = (SERIAL_TX_PIN, SERIAL_RX_PIN); + +static SERIAL: StaticCell> = StaticCell::new(); + +pub struct DefmtUart(pub Tx) +where + U: Usart; + +impl ErrorType for DefmtUart +where + U: Usart, +{ + type Error = Infallible; +} +impl Write for DefmtUart +where + U: Usart, +{ + fn write(&mut self, buf: &[u8]) -> Result { + buf.iter().map(|c| nb::block!(self.0.write(*c))).last(); + Ok(buf.len()) + } + + fn flush(&mut self) -> Result<(), Self::Error> { + // Blocking write, so flush is a no-op + Ok(()) + } +} + +struct Clock {} +impl Clock { + const RATES: [RangeEntry; 1] = [RangeEntry::new_fixed(44100)]; +} +impl UsbAudioClockImpl for Clock { + const CLOCK_TYPE: usbd_uac2::descriptors::ClockType = ClockType::InternalFixed; + const SOF_SYNC: bool = false; + fn get_sample_rate(&self) -> core::result::Result { + Ok(Clock::RATES[0].min) + } + fn get_rates( + &self, + ) -> core::result::Result<&[usbd_uac2::RangeEntry], usbd_uac2::UsbAudioClassError> { + Ok(&Clock::RATES) + } +} + +struct Audio {} +impl<'a, B: bus::UsbBus> UsbAudioClass<'a, B> for Audio {} + +#[entry] +fn main() -> ! { + let hal = hal::new(); + + let mut anactrl = hal.anactrl; + let mut pmc = hal.pmc; + let mut syscon = hal.syscon; + + let mut gpio = hal.gpio.enabled(&mut syscon); + let mut iocon = hal.iocon.enabled(&mut syscon); + + let mut red_led = pins::Pio1_6::take() + .unwrap() + .into_gpio_pin(&mut iocon, &mut gpio) + .into_output(hal::drivers::pins::Level::Low); // start turned on + + let usb0_vbus_pin = pins::Pio0_22::take() + .unwrap() + .into_usb0_vbus_pin(&mut iocon); + + let serial_rx_pin = pins::Pio0_29::take() + .unwrap() + .into_usart0_rx_pin(&mut iocon); + let serial_tx_pin = pins::Pio0_30::take() + .unwrap() + .into_usart0_tx_pin(&mut iocon); + + iocon.disabled(&mut syscon).release(); // save the environment :) + + let clocks = hal::ClockRequirements::default() + // .system_frequency(24.mhz()) + // .system_frequency(72.mhz()) + .system_frequency(96.MHz()) + .configure(&mut anactrl, &mut pmc, &mut syscon) + .unwrap(); + let mut _delay_timer = Timer::new( + hal.ctimer + .0 + .enabled(&mut syscon, clocks.support_1mhz_fro_token().unwrap()), + ); + + let usart = hal + .flexcomm + .0 + .enabled_as_usart(&mut syscon, &clocks.support_flexcomm_token().unwrap()); + + let serial_config = hal::drivers::serial::config::Config::default().speed(115_200.Hz()); + let serial = Serial::new(usart, (serial_tx_pin, serial_rx_pin), serial_config).split(); + let serial_tx = DefmtUart(serial.0); + + // defmt_serial::defmt_serial(SERIAL.init(serial_tx)); + + let usb_peripheral = hal.usbhs.enabled_as_device( + &mut anactrl, + &mut pmc, + &mut syscon, + &mut _delay_timer, + clocks.support_usbhs_token().unwrap(), + ); + + let usb_bus = UsbBus::new(usb_peripheral, usb0_vbus_pin); + let clock = Clock {}; + let audio = Audio {}; + + let config = AudioClassConfig::new(UsbSpeed::High, FunctionCode::Other, &clock, &audio) + .with_input_config(TerminalConfig::new( + 2, + 1, + 2, + FormatType1 { + bit_resolution: 24, + bytes_per_sample: 4, + }, + TerminalType::ExtLineConnector, + ChannelConfig::default_chans(2), + IsochronousSynchronizationType::Adaptive, + LockDelay::Milliseconds(10), + None, + )) + .with_output_config(TerminalConfig::new( + 4, + 1, + 2, + FormatType1 { + bit_resolution: 24, + bytes_per_sample: 4, + }, + TerminalType::ExtLineConnector, + ChannelConfig::default_chans(2), + IsochronousSynchronizationType::Adaptive, + LockDelay::Milliseconds(10), + None, + )); + + let mut uac2 = config.build(&usb_bus).unwrap(); + + let mut usb_dev = UsbDeviceBuilder::new(&usb_bus, UsbVidPid(0x1209, 0xcc1d)) + .composite_with_iads() + .strings(&[StringDescriptors::default() + .manufacturer("VE7XEN") + .product("Guac") + .serial_number("123456789")]) + .unwrap() + .max_packet_size_0(64) + .unwrap() + .device_class(0xef) + .device_sub_class(0x02) + .device_protocol(0x01) + .build(); + + defmt::info!("main loop"); + let mut need_zlp = false; + let mut buf = [0u8; 8]; + let mut size = 0; + let mut buf_in_use = false; + loop { + // if !usb_dev.poll(&mut []) { + // if !usb_dev.poll(&mut [&mut serial]) { + if !usb_dev.poll(&mut [&mut uac2]) { + continue; + } + + // let mut buf = [0u8; 512]; + + // match serial.read(&mut buf) { + // Ok(count) if count > 0 => { + // assert!(count == 1); + // // hprintln!("received some data on the serial port: {:?}", &buf[..count]).ok(); + // // cortex_m_semihosting::hprintln!("received:\n{}", core::str::from_utf8(&buf[..count]).unwrap()).ok(); + // red_led.set_low().ok(); // Turn on + + // // cortex_m_semihosting::hprintln!("read {:?}", &buf[..count]).ok(); + // cortex_m_semihosting::hprintln!("read {:?}", count).ok(); + + // // Echo back in upper case + // for c in buf[0..count].iter_mut() { + // if (0x61 <= *c && *c <= 0x7a) || (0x41 <= *c && *c <= 0x5a) { + // *c ^= 0x20; + // } + // } + + // let mut write_offset = 0; + // while write_offset < count { + // match serial.write(&buf[write_offset..count]) { + // Ok(len) if len > 0 => { + // write_offset += len; + // cortex_m_semihosting::hprintln!("wrote {:?}", len).ok(); + + // }, + // _ => {}, + // } + // } + + // // hprintln!("wrote it back").ok(); + // } + // _ => {} + // } + + red_led.set_high().ok(); // Turn off + } +} diff --git a/examples/lpc55s28/Cargo.toml b/examples/lpc55s28/Cargo.toml deleted file mode 100644 index 946ca2d..0000000 --- a/examples/lpc55s28/Cargo.toml +++ /dev/null @@ -1,9 +0,0 @@ -[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 deleted file mode 100644 index b68428d..0000000 --- a/examples/lpc55s28/src/main.rs +++ /dev/null @@ -1,20 +0,0 @@ -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/constants.rs b/src/constants.rs index cb27e78..66d824c 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,4 +1,4 @@ -pub const AUDIO: u8 = 0x1; +pub const USB_CLASS_AUDIO: u8 = 0x1; pub const HEADER: u8 = 0x1; /// A.2 Audio Function Subclass Codes @@ -136,6 +136,18 @@ pub enum ClassSpecificRequest { Mem = 3, } +impl From for ClassSpecificRequest { + fn from(value: u8) -> Self { + match value { + 0 => Self::Undefined, + 1 => Self::Cur, + 2 => Self::Range, + 3 => Self::Mem, + _ => Self::Undefined, + } + } +} + /// A.15 Encoder Type Codes #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] @@ -167,6 +179,18 @@ pub enum ClockSourceControlSelector { SamFreqControl = 1, ClockValidControl = 2, } + +impl From for ClockSourceControlSelector { + fn from(value: u8) -> Self { + match value { + 0 => Self::Undefined, + 1 => Self::SamFreqControl, + 2 => Self::ClockValidControl, + _ => Self::Undefined, + } + } +} + /// A.17.2 Clock Selector Control Selectors #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] diff --git a/src/cursor.rs b/src/cursor.rs index 7007161..f2d1bd1 100644 --- a/src/cursor.rs +++ b/src/cursor.rs @@ -1,7 +1,32 @@ -// Copy most of embedded_io_cursor here to avoid multiple embedded-io versions in dep tree +// Copy most of the write side of embedded_io_cursor +// +// Modified specifically for writing USB descriptors, not used for anything else. use core::cmp; -use embedded_io::{BufRead, Error, ErrorKind, ErrorType, Read, Seek, SeekFrom, Write}; +use core::fmt::Display; +use embedded_io::{BufRead, ErrorKind, ErrorType, Read, SliceWriteError, Write}; +use usb_device::UsbError; + +#[derive(Debug)] +pub struct CursorError(pub UsbError); + +impl Display for CursorError { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self.0 { + UsbError::BufferOverflow => f.write_str("CursorError(UsbError::BufferOverflow))"), + UsbError::WouldBlock => f.write_str("CursorError(UsbError::WouldBlock))"), + _ => f.write_str("CursorError(UsbError::)"), + } + } +} + +impl core::error::Error for CursorError {} + +impl embedded_io::Error for CursorError { + fn kind(&self) -> ErrorKind { + ErrorKind::Other + } +} #[derive(Debug, Default, Eq, PartialEq)] pub struct Cursor { @@ -89,7 +114,13 @@ where } impl ErrorType for Cursor { - type Error = ErrorKind; + type Error = CursorError; +} + +impl From for UsbError { + fn from(value: CursorError) -> Self { + value.0 + } } // Read implementation for AsRef<[u8]> types @@ -125,34 +156,38 @@ where } // Seek implementation for AsRef<[u8]> types -impl Seek for Cursor -where - T: AsRef<[u8]>, -{ - fn seek(&mut self, style: SeekFrom) -> Result { - let (base_pos, offset) = match style { - SeekFrom::Start(n) => { - self.pos = n as usize; - return Ok(n); - } - SeekFrom::End(n) => (self.inner.as_ref().len() as u64, n), - SeekFrom::Current(n) => (self.pos as u64, n), - }; +// impl Seek for Cursor +// where +// T: AsRef<[u8]>, +// { +// fn seek(&mut self, style: SeekFrom) -> Result { +// let (base_pos, offset) = match style { +// SeekFrom::Start(n) => { +// self.pos = n as usize; +// return Ok(n); +// } +// SeekFrom::End(n) => (self.inner.as_ref().len() as u64, n), +// SeekFrom::Current(n) => (self.pos as u64, n), +// }; - match base_pos.checked_add_signed(offset) { - Some(n) => { - self.pos = n as usize; - Ok(self.pos as u64) - } - None => Err(ErrorKind::InvalidInput), - } - } -} +// match base_pos.checked_add_signed(offset) { +// Some(n) => { +// self.pos = n as usize; +// Ok(self.pos as u64) +// } +// None => Err(ErrorKind::InvalidInput), +// } +// } +// } /// Helper function for writing to fixed-size slices -fn slice_write(pos_mut: &mut usize, slice: &mut [u8], buf: &[u8]) -> Result { +fn slice_write( + pos_mut: &mut usize, + slice: &mut [u8], + buf: &[u8], +) -> Result { let pos = cmp::min(*pos_mut, slice.len()) as usize; - let amt = (&mut slice[pos..]).write(buf).map_err(|err| err.kind())?; + let amt = (&mut slice[pos..]).write(buf)?; *pos_mut += amt; Ok(amt) } @@ -160,7 +195,10 @@ fn slice_write(pos_mut: &mut usize, slice: &mut [u8], buf: &[u8]) -> Result { fn write(&mut self, buf: &[u8]) -> Result { - slice_write(&mut self.pos, self.inner, buf) + slice_write(&mut self.pos, self.inner, buf).map_err(|e| match e { + SliceWriteError::Full => CursorError(UsbError::BufferOverflow), + _ => CursorError(UsbError::Unsupported), + }) } fn flush(&mut self) -> Result<(), Self::Error> { diff --git a/src/descriptors.rs b/src/descriptors.rs index d8c5812..ea7fcff 100644 --- a/src/descriptors.rs +++ b/src/descriptors.rs @@ -1,91 +1,12 @@ -use core::fmt::{Display, Formatter}; - -use crate::constants::ClassSpecificACInterfaceDescriptorSubtype; use crate::constants::*; +use crate::debug; +use crate::error; +use crate::{constants::ClassSpecificACInterfaceDescriptorSubtype, cursor::Cursor}; use byteorder_embedded_io::{LittleEndian, WriteBytesExt}; -use embedded_io::ErrorType; use modular_bitfield::prelude::*; -use usb_device::{UsbError, bus::StringIndex, descriptor::DescriptorWriter}; - -#[derive(Debug)] -pub struct DescriptorWriterError { - error: UsbError, -} - -impl Display for DescriptorWriterError { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - write!( - f, - "UsbError: {}", - match self.error { - UsbError::InvalidState => "InvalidState", - UsbError::WouldBlock => "WouldBlock", - UsbError::Unsupported => "Unsupported", - UsbError::BufferOverflow => "BufferOverflow", - UsbError::EndpointMemoryOverflow => "EndpointMemoryOverflow", - UsbError::EndpointOverflow => "EndpointOverflow", - UsbError::InvalidEndpoint => "InvalidEndpoint", - UsbError::ParseError => "ParseError", - } - ) - } -} - -impl core::error::Error for DescriptorWriterError {} - -impl embedded_io::Error for DescriptorWriterError { - fn kind(&self) -> embedded_io::ErrorKind { - embedded_io::ErrorKind::Other - } -} - -impl From for DescriptorWriterError { - fn from(error: UsbError) -> Self { - Self { error } - } -} - -impl From for UsbError { - fn from(error: DescriptorWriterError) -> Self { - error.error - } -} - -struct DescriptorWriterAdapter<'w, 'd> { - writer: &'w mut DescriptorWriter<'d>, - descriptor_type: ClassSpecificDescriptorType, - written: usize, -} - -impl<'w, 'd> embedded_io::Write for DescriptorWriterAdapter<'w, 'd> { - fn write(&mut self, buf: &[u8]) -> Result { - self.writer.write(self.descriptor_type as u8, buf)?; - self.written += buf.len(); - Ok(buf.len()) - } - - fn flush(&mut self) -> Result<(), Self::Error> { - Ok(()) - } -} - -impl ErrorType for DescriptorWriterAdapter<'_, '_> { - type Error = DescriptorWriterError; -} - -impl<'w, 'd> DescriptorWriterAdapter<'w, 'd> { - fn new( - writer: &'w mut DescriptorWriter<'d>, - descriptor_type: ClassSpecificDescriptorType, - ) -> Self { - Self { - writer, - descriptor_type, - written: 0, - } - } -} +use usb_device::UsbError; +use usb_device::{bus::StringIndex, descriptor::DescriptorWriter}; #[repr(u8)] #[derive(Copy, Clone, Debug, Eq, PartialEq)] @@ -207,15 +128,35 @@ pub struct FeatureControls { pub trait Descriptor { /// The maximum possible size of the descriptor, including the length and descriptor type bytes. For creation of Sized buffers. const MAX_SIZE: usize; + /// The bDescriptorType of the descriptor + fn descriptor_type(&self) -> u8; /// The actual size of the descriptor, including the length and descriptor type bytes. fn size(&self) -> u8; - /// Write the descriptor using the provided usb_device DescriptorWriter. + /// The payload of the descriptor, not including the bLength and bDescriptorType bytes + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error>; + /// Write the descriptor to the provided buffer. Includes the length and descriptor type bytes. The default implementation defers to write_payload and self.size(). + fn write(&self, writer: &mut T) -> Result<(), T::Error> { + writer.write_u8(self.size())?; + writer.write_u8(self.descriptor_type())?; + self.write_payload(writer)?; + Ok(()) + } + /// Write the descriptor using the provided usb_device DescriptorWriter. The default implementation defers to write_payload. fn write_descriptor<'w, 'd>( &self, writer: &'w mut DescriptorWriter<'d>, - ) -> Result<(), DescriptorWriterError>; - /// Write the descriptor to the provided buffer. Includes the length and descriptor type bytes. - fn write(&self, writer: &mut T) -> Result<(), T::Error>; + ) -> Result<(), usb_device::UsbError> { + debug!("writer.write {}", core::any::type_name_of_val(self)); + writer.write_with(self.descriptor_type(), |buf| { + let mut cur = Cursor::new(buf); + if let Err(e) = self.write_payload(&mut cur) { + error!("Write payload err {}", defmt::Display2Format(&e)); + Err(e.into()) + } else { + Ok(cur.position()) + } + }) + } } #[derive(Clone)] @@ -236,6 +177,17 @@ impl ClockSource { fn bm_controls(&self) -> u8 { ((self.validity_access as u8) << 2) | self.frequency_access as u8 } +} + +impl Descriptor for ClockSource { + const MAX_SIZE: usize = 8; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ClockSource as u8)?; // bDescriptorSubtype @@ -244,33 +196,10 @@ impl ClockSource { writer.write_u8(self.bm_controls())?; // bmControls writer.write_u8(self.assoc_terminal)?; // bAssocTerminal writer.write_u8(self.string.map_or(0, |n| u8::from(n)))?; // iClockSource - Ok(()) } } -impl Descriptor for ClockSource { - 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::Interface 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::Interface); - self.write_payload(&mut writer) - } -} - #[derive(Clone)] pub struct InputTerminal { pub id: u8, @@ -290,7 +219,14 @@ pub struct InputTerminal { pub string: Option, } -impl InputTerminal { +impl Descriptor for InputTerminal { + const MAX_SIZE: usize = 17; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::InputTerminal as u8)?; // bDescriptorSubtype writer.write_u8(self.id)?; // bTerminalID @@ -316,27 +252,6 @@ impl InputTerminal { } } -impl Descriptor for InputTerminal { - const MAX_SIZE: usize = 17; - 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::Interface 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::Interface); - - self.write_payload(&mut writer) - } -} - #[derive(Clone)] pub struct OutputTerminal { pub id: u8, @@ -352,7 +267,14 @@ pub struct OutputTerminal { pub string: Option, } -impl OutputTerminal { +impl Descriptor for OutputTerminal { + const MAX_SIZE: usize = 12; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::OutputTerminal as u8)?; // bDescriptorSubtype writer.write_u8(self.id)?; // bTerminalID @@ -372,27 +294,6 @@ impl OutputTerminal { } } -impl Descriptor for OutputTerminal { - const MAX_SIZE: usize = 12; - 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::Interface 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::Interface); - - self.write_payload(&mut writer) - } -} - #[derive(Clone)] pub enum Terminal { Input(InputTerminal), @@ -405,25 +306,19 @@ impl Descriptor for Terminal { } else { OutputTerminal::MAX_SIZE }; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } fn size(&self) -> u8 { match self { Self::Input(t) => t.size(), Self::Output(t) => t.size(), } } - fn write(&self, writer: &mut T) -> Result<(), T::Error> { + fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { match self { - Self::Input(t) => t.write(writer), - Self::Output(t) => t.write(writer), - } - } - fn write_descriptor<'w, 'd>( - &self, - writer: &'w mut DescriptorWriter<'d>, - ) -> Result<(), DescriptorWriterError> { - match self { - Self::Input(t) => t.write_descriptor(writer), - Self::Output(t) => t.write_descriptor(writer), + Self::Input(t) => t.write_payload(writer), + Self::Output(t) => t.write_payload(writer), } } } @@ -437,7 +332,14 @@ pub struct ClockSelector { pub string: u8, // iClockSelector } -impl ClockSelector { +impl Descriptor for ClockSelector { + const MAX_SIZE: usize = 7 + N; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + 7 + self.n_sources + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ClockSelector as u8)?; // bDescriptorSubtype writer.write_u8(self.id)?; // bClockID @@ -449,26 +351,6 @@ impl ClockSelector { } } -impl Descriptor for ClockSelector { - const MAX_SIZE: usize = 7 + N; - fn size(&self) -> u8 { - 7 + self.n_sources - } - fn write(&self, writer: &mut T) -> Result<(), T::Error> { - writer.write_u8(self.size())?; - writer.write_u8(ClassSpecificDescriptorType::Interface 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::Interface); - self.write_payload(&mut writer) - } -} - #[derive(Clone, Debug)] pub struct ClockMultiplier { pub id: u8, @@ -482,6 +364,24 @@ impl ClockMultiplier { fn bm_controls(&self) -> u8 { (self.numerator_access as u8) | ((self.denominator_access as u8) << 2) } + fn write_payload(&self, writer: &mut T) -> Result { + writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ClockMultiplier as u8)?; // bDescriptorSubtype + writer.write_u8(self.id)?; // bClockID + writer.write_u8(self.source_id)?; // bCSourceID + writer.write_u8(self.bm_controls())?; // bmControls + writer.write_u8(self.string)?; // iClockMultiplier + Ok(self.size() as usize) + } +} + +impl Descriptor for ClockMultiplier { + const MAX_SIZE: usize = 7; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ClockMultiplier as u8)?; // bDescriptorSubtype writer.write_u8(self.id)?; // bClockID @@ -491,27 +391,6 @@ impl ClockMultiplier { Ok(()) } } - -impl Descriptor for ClockMultiplier { - const MAX_SIZE: usize = 7; - 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::Interface 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::Interface); - - self.write_payload(&mut writer) - } -} // Feature Unit's size depends on the number of channels in its source node, which we don't have a way // to look up with the current API. Ignore and leave unimplemented for now. // @@ -617,6 +496,16 @@ impl SelectorUnit { fn bm_controls(&self) -> u8 { self.selector_control as u8 } +} + +impl Descriptor for SelectorUnit { + const MAX_SIZE: usize = 7 + N; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + 7 + self.n_sources + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::SelectorUnit as u8)?; // bDescriptorSubtype writer.write_u8(self.id)?; // bUnitID @@ -628,27 +517,6 @@ impl SelectorUnit { } } -impl Descriptor for SelectorUnit { - const MAX_SIZE: usize = 7 + N; - fn size(&self) -> u8 { - 7 + self.n_sources - } - fn write(&self, writer: &mut T) -> Result<(), T::Error> { - writer.write_u8(self.size())?; - writer.write_u8(ClassSpecificDescriptorType::Interface 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::Interface); - - self.write_payload(&mut writer) - } -} - #[derive(Clone, Debug)] pub struct ProcessingUnit { pub id: u8, @@ -676,6 +544,16 @@ impl ProcessingUnit { | ((self.overflow_control as u16) << 8) | ((self.latency_control as u16) << 10) } +} + +impl Descriptor for ProcessingUnit { + const MAX_SIZE: usize = 17 + N; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + 17 + self.n_sources + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ProcessingUnit as u8)?; // bDescriptorSubtype writer.write_u8(self.id)?; // bUnitID @@ -691,27 +569,6 @@ impl ProcessingUnit { } } -impl Descriptor for ProcessingUnit { - const MAX_SIZE: usize = 17 + N; - fn size(&self) -> u8 { - 17 + self.n_sources - } - fn write(&self, writer: &mut T) -> Result<(), T::Error> { - writer.write_u8(self.size())?; - writer.write_u8(ClassSpecificDescriptorType::Interface 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::Interface); - - self.write_payload(&mut writer) - } -} - #[derive(Clone, Debug)] pub struct ExtensionUnit { pub id: u8, @@ -735,6 +592,16 @@ impl ExtensionUnit { | ((self.underflow_control as u8) << 4) | ((self.overflow_control as u8) << 6) } +} + +impl Descriptor for ExtensionUnit { + const MAX_SIZE: usize = 16 + N; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + 16 + self.n_sources + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificACInterfaceDescriptorSubtype::ExtensionUnit as u8)?; // bDescriptorSubtype writer.write_u8(self.id)?; // bUnitID @@ -749,26 +616,6 @@ impl ExtensionUnit { Ok(()) } } - -impl Descriptor for ExtensionUnit { - const MAX_SIZE: usize = 16 + N; - fn size(&self) -> u8 { - 16 + self.n_sources - } - fn write(&self, writer: &mut T) -> Result<(), T::Error> { - writer.write_u8(self.size())?; - writer.write_u8(ClassSpecificDescriptorType::Interface 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::Interface); - self.write_payload(&mut writer) - } -} // Effect unit is also variable based on its source node, leave unimplemented for now. // // pub struct EffectUnit { @@ -830,10 +677,7 @@ impl AudioClassDescriptor { AudioClassDescriptor::OutputTerminal(ot) => ot.write(writer), } } - pub fn write_descriptor( - &self, - writer: &mut DescriptorWriter, - ) -> Result<(), DescriptorWriterError> { + pub fn write_descriptor(&self, writer: &mut DescriptorWriter) -> Result<(), UsbError> { match self { AudioClassDescriptor::ClockSource(cs) => cs.write_descriptor(writer), AudioClassDescriptor::ClockMultiplier(cm) => cm.write_descriptor(writer), @@ -933,7 +777,14 @@ pub struct FormatType1 { pub bit_resolution: u8, // bBitResolution } -impl FormatType1 { +impl Descriptor for FormatType1 { + const MAX_SIZE: usize = 6; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + 6 + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificASInterfaceDescriptorSubtype::FormatType as u8)?; writer.write_u8(FormatType::Type1 as u8)?; // bFormatType @@ -943,26 +794,6 @@ impl FormatType1 { } } -impl Descriptor for FormatType1 { - const MAX_SIZE: usize = 6; - fn size(&self) -> u8 { - 6 - } - fn write(&self, writer: &mut T) -> Result<(), T::Error> { - writer.write_u8(self.size())?; - writer.write_u8(ClassSpecificDescriptorType::Interface 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::Interface); - self.write_payload(&mut writer) - } -} - #[repr(u32)] #[derive(Clone, Copy, Debug)] pub enum Type1FormatBitmap { @@ -990,6 +821,16 @@ impl AudioStreamingInterface { fn bm_controls(&self) -> u8 { self.active_alt_setting as u8 | ((self.valid_alt_settings as u8) << 2) } +} + +impl Descriptor for AudioStreamingInterface { + const MAX_SIZE: usize = 16; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Interface as u8 + } + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } fn write_payload(&self, writer: &mut T) -> Result<(), T::Error> { writer.write_u8(ClassSpecificASInterfaceDescriptorSubtype::General as u8)?; writer.write_u8(self.terminal_id)?; @@ -1003,26 +844,6 @@ impl AudioStreamingInterface { } } -impl Descriptor for AudioStreamingInterface { - const MAX_SIZE: usize = 16; - 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::Interface 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::Interface); - self.write_payload(&mut writer) - } -} - #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum LockDelay { @@ -1061,6 +882,16 @@ impl AudioStreamingEndpoint { | (self.overrun_control as u8) << 2 | (self.underrun_control as u8) << 4 } +} + +impl Descriptor for AudioStreamingEndpoint { + const MAX_SIZE: usize = 8; + fn descriptor_type(&self) -> u8 { + ClassSpecificDescriptorType::Endpoint as u8 + } + fn size(&self) -> u8 { + Self::MAX_SIZE as u8 + } fn write_payload( &self, writer: &mut T, @@ -1074,26 +905,6 @@ impl AudioStreamingEndpoint { } } -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)] diff --git a/src/lib.rs b/src/lib.rs index ac8b625..f657b98 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,22 +1,35 @@ #![no_std] #![allow(dead_code)] -mod constants; +pub mod constants; mod cursor; -mod descriptors; +pub mod descriptors; +mod log; +use core::cmp::Ordering; use core::marker::PhantomData; +use byteorder_embedded_io::{LittleEndian, WriteBytesExt}; use constants::*; use descriptors::*; +use log::*; -use usb_device::class_prelude::*; +use num_traits::{ConstZero, Zero}; +use usb_device::control::{Recipient, Request, RequestType}; +use usb_device::device::DEFAULT_ALTERNATE_SETTING; use usb_device::endpoint::{self, Endpoint, EndpointDirection, In, Out}; +use usb_device::{UsbDirection, class_prelude::*}; + +pub use constants::USB_CLASS_AUDIO; + +#[cfg(feature = "defmt")] +use defmt; mod sealed { pub trait Sealed {} } +#[derive(Debug)] pub enum UsbAudioClassError { NotImplemented, Other, @@ -28,7 +41,16 @@ impl From for UsbAudioClassError { } } -pub trait RangeType: sealed::Sealed {} +impl From for UsbError { + fn from(_value: UsbAudioClassError) -> Self { + UsbError::Unsupported + } +} + +pub trait RangeType: + sealed::Sealed + num_traits::PrimInt + num_traits::ToBytes + ConstZero +{ +} impl sealed::Sealed for i8 {} impl sealed::Sealed for i16 {} impl sealed::Sealed for i32 {} @@ -43,55 +65,81 @@ impl RangeType for u8 {} impl RangeType for u16 {} impl RangeType for u32 {} +#[derive(PartialEq, Eq, Ord)] pub struct RangeEntry { - min: T, - max: T, - res: T, + pub min: T, + pub max: T, + pub res: T, } impl RangeEntry { - pub fn new(min: T, max: T, res: T) -> Self { + pub const fn new(min: T, max: T, res: T) -> Self { Self { min, max, res } } + pub const fn new_fixed(fixed: T) -> Self { + Self { + min: fixed, + max: fixed, + res: T::ZERO, + } + } + + pub fn write( + &self, + mut buf: W, + ) -> core::result::Result { + buf.write_all(self.min.to_le_bytes().as_ref())?; + buf.write_all(self.max.to_le_bytes().as_ref())?; + buf.write_all(self.res.to_le_bytes().as_ref())?; + Ok(T::ZERO.count_zeros() as usize * 3) + } +} + +/// The spec guarantees that ranges do not overlap, so compare by min is correct. +impl PartialOrd for RangeEntry { + fn partial_cmp(&self, other: &Self) -> Option { + self.min.partial_cmp(&other.min) + } } /// A trait for implementing USB Audio Class 2 devices /// -/// Contains optional callback methods which will be called by the class driver. All +/// Contains callback methods which will be called by the class driver. All /// callbacks are optional, which may be useful for a tight-loop polling implementation /// but most implementations will want to implement at least `audio_data_rx`. /// -/// Unimplemented callbacks should return `Err(UsbAudioClassError::NotImplemented)`. Other -/// errors will panic (the underlying callbacks are not fallible). If you need to handle errors, -/// you should use the callback to infalliably signal another task. +/// Unimplemented callbacks should return `Ok(())` if a result is required. + pub trait UsbAudioClass<'a, B: UsbBus> { - /// Called when audio data is received from the host. The `Endpoint` - /// is ready for `read()`. - fn audio_data_rx( + /// Called when audio data is received from the host. `ep` is ready for + /// `ep.read()`. + fn audio_data_rx(&self, ep: &Endpoint<'a, B, endpoint::Out>) {} + + /// Called when the alternate setting of `terminal`'s interface is changed, + /// before the `AudioStream` is updated. Currently not very useful since we + /// don't implement alternate settings. + fn alternate_setting_changed>( &self, - ep: &Endpoint<'a, B, endpoint::Out>, - ) -> core::result::Result<(), UsbAudioClassError> { - Err(UsbAudioClassError::NotImplemented) + ac: &mut AudioClass<'a, B, CS, AU>, + terminal: UsbDirection, + alt_setting: u8, + ) { } } /// A trait for implementing Sampling Frequency Control for USB Audio Clock Sources /// ref: USB Audio Class Specification 2.0 5.2.5.1.1 /// -/// Contains optional callback methods which will be called by the class driver. If -/// `set_sample_rate` is implemented, `get_sample_rate` must also be implemented. -/// Callbacks run in USB context, so should not block. +/// Contains callback methods which will be called by the class driver. /// /// Unimplemented callbacks should return `Err(UsbAudioClassError::NotImplemented)`. Other /// errors will panic (the underlying callbacks are not fallible). If you need to handle errors, /// you should use the callback to infalliably signal another task. -pub trait UsbAudioClockSource { +pub trait UsbAudioClockImpl { const CLOCK_TYPE: ClockType; const SOF_SYNC: bool; /// Called when the host requests the current sample rate. Returns the sample rate in Hz. - fn get_sample_rate(&self) -> core::result::Result { - Err(UsbAudioClassError::NotImplemented) - } + fn get_sample_rate(&self) -> core::result::Result; /// Called when the host requests to set the sample rate. Should reconfigure the clock source /// if necessary. fn set_sample_rate( @@ -116,9 +164,9 @@ pub trait UsbAudioClockSource { } } - /// Called when the hosts makes a RANGE request for the clock source. Returns a slice of possible sample rates. + /// Called during allocation and when the hosts makes a RANGE request for the clock source. /// - /// Must be implemented if the clock source returns programmable get_frequency_access + /// Returns a slice of possible sample rates. /// /// Rates must meet the invariants in the specification: /// * The subranges must be ordered in ascendingorder @@ -127,9 +175,7 @@ pub trait UsbAudioClockSource { /// its MIN and MAX subattribute and the RES subattribute must be set to zero /// /// ref: USB Audio Class Specification 2.0 5.2.1 & 5.2.3.3 - fn get_rates(&self) -> core::result::Result<&[RangeEntry], UsbAudioClassError> { - Err(UsbAudioClassError::NotImplemented) - } + fn get_rates(&self) -> core::result::Result<&[RangeEntry], UsbAudioClassError>; /// Build the ClockSource descriptor. It is not intended to override this method. /// @@ -208,13 +254,17 @@ impl TerminalConfig { _direction: PhantomData, } } + // Bytes per audio frame + pub fn bytes_per_frame(&self) -> u32 { + self.format.bytes_per_sample as u32 * self.num_channels as u32 + } } impl<'a> TerminalConfigurationDescriptors for TerminalConfig { fn get_configuration_descriptors(&self) -> (InputTerminal, OutputTerminal) { let input_terminal = InputTerminal { id: self.base_id, terminal_type: TerminalType::UsbStreaming, - assoc_terminal: self.base_id + 1, + assoc_terminal: 0, clock_source: self.clock_source_id, num_channels: self.num_channels, channel_config: self.channel_config, @@ -226,12 +276,12 @@ impl<'a> TerminalConfigurationDescriptors for TerminalConfig { underflow_control: AccessControl::NotPresent, overflow_control: AccessControl::NotPresent, phantom_power_control: AccessControl::NotPresent, - string: None, + string: self.string, }; let output_terminal = OutputTerminal { id: self.base_id + 1, terminal_type: self.terminal_type, - assoc_terminal: self.base_id, + assoc_terminal: 0, source_id: self.base_id, clock_source: self.clock_source_id, copy_protect_control: AccessControl::NotPresent, @@ -251,7 +301,7 @@ impl<'a> TerminalConfigurationDescriptors for TerminalConfig { let output_terminal = OutputTerminal { id: self.base_id, terminal_type: TerminalType::UsbStreaming, - assoc_terminal: self.base_id + 1, + assoc_terminal: 0, source_id: self.base_id + 1, clock_source: self.clock_source_id, copy_protect_control: AccessControl::NotPresent, @@ -264,7 +314,7 @@ impl<'a> TerminalConfigurationDescriptors for TerminalConfig { let input_terminal = InputTerminal { id: self.base_id + 1, terminal_type: self.terminal_type, - assoc_terminal: self.base_id, + assoc_terminal: 0, clock_source: self.clock_source_id, num_channels: self.num_channels, channel_config: self.channel_config, @@ -281,40 +331,13 @@ impl<'a> TerminalConfigurationDescriptors for TerminalConfig { (input_terminal, output_terminal) } } -impl TerminalConfig {} #[derive(Copy, Clone, Debug)] pub enum UsbSpeed { Low, // Not supported for audio Full, High, - Super, // Not supported by crate -} - -/// 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 - } + Super, } /// Configuration and references to the Audio Class descriptors @@ -335,32 +358,43 @@ impl UsbSpeedProvider for ConstSpeedProvider { /// 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, SP: UsbSpeedProvider> { - pub speed_provider: SP, +pub struct AudioClassConfig<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> { + pub speed: UsbSpeed, pub device_category: FunctionCode, - pub clock: CS, + pub clock_impl: &'a CS, + pub audio_impl: &'a AU, pub input_config: Option>, pub output_config: Option>, pub additional_descriptors: Option<&'a [AudioClassDescriptor]>, + _bus: PhantomData, } -impl<'a, SP: UsbSpeedProvider, CS: UsbAudioClockSource> AudioClassConfig<'a, CS, SP> { - pub fn new(speed_provider: SP, device_category: FunctionCode, clock: CS) -> Self { +impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> + AudioClassConfig<'a, B, CS, AU> +{ + pub fn new( + speed: UsbSpeed, + device_category: FunctionCode, + clock_impl: &'a CS, + audio_impl: &'a AU, + ) -> Self { Self { - speed_provider, + speed, device_category, - clock, + clock_impl, + audio_impl, input_config: None, output_config: None, additional_descriptors: None, + _bus: PhantomData, } } pub fn with_input_config(mut self, input_config: TerminalConfig) -> Self { self.input_config = Some(input_config); self } - pub fn with_output_terminal(mut self, output_terminal: TerminalConfig) -> Self { - self.output_config = Some(output_terminal); + pub fn with_output_config(mut self, output_config: TerminalConfig) -> Self { + self.output_config = Some(output_config); self } pub fn with_additional_descriptors( @@ -371,9 +405,88 @@ impl<'a, SP: UsbSpeedProvider, CS: UsbAudioClockSource> AudioClassConfig<'a, CS, self } - // pub fn build<'a, B: UsbBus>(self, alloc: &'a UsbBusAllocator) -> Result> { - // Err(Error::InvalidValue) - // } + /// Allocate the various USB IDs, and build the class implementation + pub fn build(self, alloc: &'a UsbBusAllocator) -> Result> { + let speed = self.speed; + let interval = match speed { + UsbSpeed::Full => 1, + UsbSpeed::High | UsbSpeed::Super => 4, // rate = 2^(4-1) * 125us = 1ms, same as full speed + UsbSpeed::Low => return Err(Error::InvalidSpeed), + }; + let max_rate = self + .clock_impl + .get_rates() + .unwrap() + .iter() + .max() + .unwrap() + .max; + let control_iface = alloc.interface(); + + let mut ac = AudioClass { + control_iface, + clock_impl: self.clock_impl, + audio_impl: self.audio_impl, + output: None, + input: None, + feedback: None, + additional_descriptors: self.additional_descriptors, + device_category: self.device_category, + in_iface: 0, + out_iface: 0, + in_ep: 0, + out_ep: 0, + fb_ep: 0, + }; + + if let Some(config) = self.output_config { + let interface = alloc.interface(); + let endpoint = alloc.isochronous( + config.sync_type, + IsochronousUsageType::Data, + ((config.bytes_per_frame() * max_rate) / 1000) as u16, + interval, + ); + let feedback_ep = alloc.isochronous( + IsochronousSynchronizationType::NoSynchronization, + IsochronousUsageType::Feedback, + 4, + interval, + ); + let alt_setting = DEFAULT_ALTERNATE_SETTING; + ac.in_iface = interface.into(); + ac.in_ep = endpoint.address().index(); + ac.fb_ep = feedback_ep.address().index(); + ac.input = Some(AudioStream { + config, + interface, + endpoint, + alt_setting, + }); + ac.feedback = Some(feedback_ep); + } + + if let Some(config) = self.input_config { + let interface = alloc.interface(); + let endpoint = alloc.isochronous( + config.sync_type, + IsochronousUsageType::Data, + ((config.bytes_per_frame() * max_rate) / 1000) as u16, + interval, + ); + let alt_setting = DEFAULT_ALTERNATE_SETTING; + ac.out_iface = interface.into(); + ac.out_ep = endpoint.address().index(); + ac.output = Some(AudioStream { + config, + interface, + endpoint, + alt_setting, + }); + } + + Ok(ac) + } } /// USB audio errors, including possible USB Stack errors @@ -382,6 +495,7 @@ pub enum Error { InvalidValue, BandwidthExceeded, StreamNotInitialized, + InvalidSpeed, UsbError(usb_device::UsbError), } @@ -402,6 +516,10 @@ struct AudioStream<'a, B: UsbBus, D: EndpointDirection> { impl<'a, B: UsbBus, D: EndpointDirection> AudioStream<'a, B, D> { fn write_interface_descriptors(&self, writer: &mut DescriptorWriter) -> usb_device::Result<()> { + debug!( + " AudioStream<{}>.write_interface_descriptors", + core::any::type_name::() + ); // UAC2 4.9.1 Standard AS Interface Descriptor // zero bandwidth configuration per 3.16.2 // @@ -410,18 +528,20 @@ impl<'a, B: UsbBus, D: EndpointDirection> AudioStream<'a, B, D> { // (Alternate Setting 0) with zero bandwidth requirements (no // isochronous data endpoint defined) and an additional Alternate // Setting that contains the actual isochronous data endpoint. + debug!("writer.interface AudioStreaming"); writer.interface( self.interface, - AUDIO, + USB_CLASS_AUDIO, InterfaceSubclass::AudioStreaming as u8, InterfaceProtocol::Version2 as u8, )?; // UAC2 4.9.1 Standard AS Interface Descriptor // live data configuration + debug!("writer.interface_alt AudioStreaming"); writer.interface_alt( self.interface, 1, - AUDIO, + USB_CLASS_AUDIO, InterfaceSubclass::AudioStreaming as u8, InterfaceProtocol::Version2 as u8, None, @@ -448,6 +568,11 @@ impl<'a, B: UsbBus, D: EndpointDirection> AudioStream<'a, B, D> { fn write_endpoint_descriptors(&self, writer: &mut DescriptorWriter) -> usb_device::Result<()> { // UAC2 4.10.1.1 Standard AS Isochronous Audio Data Endpoint Descriptor + debug!( + " AudioStream<{}>.write_endpoint_descriptors", + core::any::type_name::() + ); + debug!("writer.endpoint"); writer.endpoint(&self.endpoint)?; // UAC2 4.10.1.2 Class-Specific AS Isochronous Audio Data Endpoint Descriptor let cs_ep = AudioStreamingEndpoint { @@ -462,77 +587,99 @@ impl<'a, B: UsbBus, D: EndpointDirection> AudioStream<'a, B, D> { } } -pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockSource, SP: UsbSpeedProvider> { - config: AudioClassConfig<'a, CS, SP>, +pub struct AudioClass<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> { control_iface: InterfaceNumber, + clock_impl: &'a CS, + audio_impl: &'a AU, output: Option>, input: Option>, feedback: Option>, + additional_descriptors: Option<&'a [AudioClassDescriptor]>, + device_category: FunctionCode, + in_iface: u8, + out_iface: u8, + in_ep: usize, + out_ep: usize, + fb_ep: usize, } -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); +impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> UsbClass + for AudioClass<'a, B, CS, AU> +{ + fn get_configuration_descriptors( + &self, + writer: &mut DescriptorWriter<'_>, + ) -> usb_device::Result<()> { + info!(" AudioClass::get_configuration_descriptors"); + // Control + 0-2 streaming + let n_interfaces = 1 + (self.input.is_some() as u8) + (self.input.is_some() as u8); + debug!("writer.iad()"); // UAC2 4.6 Interface Association Descriptor writer.iad( self.control_iface, n_interfaces, - AUDIO, - InterfaceSubclass::AudioControl as u8, + USB_CLASS_AUDIO, + InterfaceSubclass::Undefined as u8, FunctionProtocol::Version2 as u8, None, )?; + debug!("writer.interface()"); // UAC2 4.7 Standard AC Interface Descriptor writer.interface( self.control_iface, - 0, - AUDIO, + USB_CLASS_AUDIO, + InterfaceSubclass::AudioControl as u8, InterfaceProtocol::Version2 as u8, )?; // BUILD CONFIGURATION DESCRIPTORS // let mut total_length: u16 = 9; // HEADER - let clock_desc = self.config.clock.get_configuration_descriptor(1, None)?; + let clock_desc = self.clock_impl.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(); + let output_descs = match &self.output { + Some(stream) => { + let descs = stream.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(); + let input_descs = match &self.input { + Some(stream) => { + let descs = stream.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 { + let additional_descs = match &self.additional_descriptors { Some(descs) => { total_length += descs.iter().map(|desc| desc.size() as u16).sum::(); Some(descs) } None => None, }; + debug!( + "have output: {}, have input: {}, have additional: {}, total length: {}", + output_descs.is_some(), + input_descs.is_some(), + additional_descs.is_some(), + total_length + ); // 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 + self.device_category as u8, // bCategory (total_length & 0xff) as u8, // wTotalLength LSB ((total_length >> 8) & 0xff) as u8, // wTotalLength MSB 0, // bmControls ]; + debug!("writer.write (AC header)"); writer.write(ClassSpecificDescriptorType::Interface as u8, &ac_header)?; // UAC2 4.7.2.1 Clock Source Descriptor @@ -563,6 +710,7 @@ impl<'a, B: UsbBus, CS: UsbAudioClockSource, SP: UsbSpeedProvider> AudioClass<'a // UAC2 4.9.2.1 Feedback Endpoint Descriptor // Should always be present if an OUT endpoint is present if let Some(feedback) = &self.feedback { + debug!("writer.endpoint (feedback)"); writer.endpoint(feedback)?; } @@ -573,18 +721,317 @@ impl<'a, B: UsbBus, CS: UsbAudioClockSource, SP: UsbSpeedProvider> AudioClass<'a 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(()) + fn control_out(&mut self, xfer: ControlOut) { + let req = xfer.request(); + match req.request_type { + RequestType::Standard => self.standard_request_out(xfer), + RequestType::Class => self.class_request_out(xfer), + _ => { + debug!(" Unimplemented."); + } + } + } + fn control_in(&mut self, xfer: ControlIn) { + let req = xfer.request(); + match req.request_type { + RequestType::Standard => self.standard_request_in(xfer), + RequestType::Class => self.class_request_in(xfer), + _ => { + debug!(" Unimplemented."); + } + } + } +} + +impl<'a, B: UsbBus, CS: UsbAudioClockImpl, AU: UsbAudioClass<'a, B>> AudioClass<'a, B, CS, AU> { + fn standard_request_out(&mut self, xfer: ControlOut) { + let req = xfer.request(); + match (req.recipient, req.request) { + (Recipient::Interface, Request::SET_INTERFACE) => self.set_alt_interface(xfer), + _ => { + debug!(" Unimplemented."); + } + } + } + + fn standard_request_in(&mut self, xfer: ControlIn) { + let req = xfer.request(); + match (req.recipient, req.request) { + (Recipient::Interface, Request::GET_INTERFACE) => self.get_alt_interface(xfer), + _ => { + debug!(" Unimplemented."); + } + } + } + + fn class_request_out(&mut self, xfer: ControlOut) { + let req = xfer.request(); + match (req.recipient, req.request.try_into()) { + (Recipient::Interface, Ok(ClassSpecificRequest::Cur)) => self.set_interface_cur(xfer), + (Recipient::Interface, Ok(ClassSpecificRequest::Range)) => {} + (Recipient::Endpoint, Ok(ClassSpecificRequest::Cur)) => self.set_endpoint_cur(xfer), + (Recipient::Endpoint, Ok(ClassSpecificRequest::Range)) => {} + _ => { + debug!(" Unimplemented."); + } + } + } + + fn class_request_in(&mut self, xfer: ControlIn) { + let req = xfer.request(); + + match (req.recipient, req.request.try_into()) { + (Recipient::Interface, Ok(ClassSpecificRequest::Cur)) => self.get_interface_cur(xfer), + (Recipient::Interface, Ok(ClassSpecificRequest::Range)) => { + self.get_interface_range(xfer) + } + (Recipient::Endpoint, Ok(ClassSpecificRequest::Cur)) => self.get_endpoint_cur(xfer), + (Recipient::Endpoint, Ok(ClassSpecificRequest::Range)) => self.get_endpoint_range(xfer), + _ => { + debug!(" Unimplemented."); + } + } + } + + fn set_alt_interface(&mut self, xfer: ControlOut) { + let req = xfer.request(); + let iface = req.index as u8; + let alt_setting = req.value as u8; + + debug!(" SET_ALT_INTERFACE {} {}", iface, alt_setting); + + if self.input.is_some() && iface == self.in_iface { + let old_alt = self.input.as_ref().unwrap().alt_setting; + if old_alt != alt_setting { + self.audio_impl + .alternate_setting_changed(self, UsbDirection::In, alt_setting); + self.input.as_mut().unwrap().alt_setting = alt_setting; + xfer.accept().ok(); + } + } else if self.output.is_some() && iface == self.out_iface { + let old_alt = self.output.as_ref().unwrap().alt_setting; + if old_alt != alt_setting { + self.audio_impl + .alternate_setting_changed(self, UsbDirection::Out, alt_setting); + self.output.as_mut().unwrap().alt_setting = alt_setting; + xfer.accept().ok(); + } + } + } + fn get_alt_interface(&mut self, xfer: ControlIn) { + let req = xfer.request(); + let iface = req.index as u8; + debug!(" GET_ALT_INTERFACE {}", iface); + if self.input.is_some() && iface == self.in_iface { + xfer.accept_with(&[self.input.as_ref().unwrap().alt_setting]) + .ok(); + return; + } else if self.output.is_some() && iface == self.out_iface { + xfer.accept_with(&[self.output.as_ref().unwrap().alt_setting]) + .ok(); + return; + } + debug!(" Unimplemented."); + } + fn get_interface_cur(&mut self, xfer: ControlIn) { + let req = xfer.request(); + let entity = (req.index >> 8) as u8; + let interface = (req.index & 0xff) as u8; + let control = (req.value >> 8) as u8; + let channel = (req.value & 0xff) as u8; + + debug!( + " GET_INTERFACE_CUR entity: {} iface: {} control: {} channel: {}", + entity, interface, control, channel + ); + if interface == self.control_iface.into() { + return self.get_control_interface_cur(xfer, entity, channel, control); + } + debug!(" Unimpleneted."); + } + fn set_interface_cur(&mut self, xfer: ControlOut) { + let req = xfer.request(); + let entity = (req.index >> 8) as u8; + let interface = (req.index & 0xff) as u8; + let control = (req.value >> 8) as u8; + let channel = (req.value & 0xff) as u8; + + debug!( + " SET_INTERFACE_CUR entity: {} iface: {} control: {} channel: {}", + entity, interface, control, channel + ); + + if interface == self.control_iface.into() { + return self.set_control_interface_cur(xfer, entity, channel, control); + } else if interface == self.in_iface { + return self.set_streaming_interface_cur( + xfer, + UsbDirection::In, + entity, + channel, + control, + ); + } else if interface == self.out_iface { + return self.set_streaming_interface_cur( + xfer, + UsbDirection::Out, + entity, + channel, + control, + ); + } + debug!(" Unimplemented."); + } + + fn get_control_interface_cur( + &mut self, + xfer: ControlIn, + entity: u8, + channel: u8, + control: u8, + ) { + match entity { + 1 => return self.get_clock_cur(xfer, channel, control), + _ => {} + } + debug!(" Unimplemented."); + } + + fn set_control_interface_cur( + &mut self, + xfer: ControlOut, + entity: u8, + channel: u8, + control: u8, + ) { + debug!(" Unimplemented."); + } + + fn set_streaming_interface_cur( + &mut self, + xfer: ControlOut, + direction: UsbDirection, + entity: u8, + channel: u8, + control: u8, + ) { + debug!(" Unimplemented."); + } + + fn get_endpoint_cur(&mut self, xfer: ControlIn) { + let req = xfer.request(); + let entity = (req.index >> 8) as u8; + let interface = (req.index & 0xff) as u8; + let control = (req.value >> 8) as u8; + let channel = (req.value & 0xff) as u8; + + debug!(" Unimplemented."); + } + fn get_endpoint_range(&mut self, xfer: ControlIn) { + let req = xfer.request(); + let entity = (req.index >> 8) as u8; + let interface = (req.index & 0xff) as u8; + let control = (req.value >> 8) as u8; + let channel = (req.value & 0xff) as u8; + + debug!(" Unimplemented."); + } + + fn set_endpoint_cur(&mut self, xfer: ControlOut) { + let req = xfer.request(); + let entity = (req.index >> 8) as u8; + let interface = (req.index & 0xff) as u8; + let control = (req.value >> 8) as u8; + let channel = (req.value & 0xff) as u8; + + debug!(" Unimplemented."); + } + + fn get_interface_range(&mut self, xfer: ControlIn) { + let req = xfer.request(); + + let entity = (req.index >> 8) as u8; + let interface = (req.index & 0xff) as u8; + let control = (req.value >> 8) as u8; + let channel = (req.value & 0xff) as u8; + + debug!( + " GET_INTERFACE_RANGE entity: {} iface: {} control: {} channel: {}", + entity, interface, control, channel + ); + if interface == self.control_iface.into() { + return self.get_control_interface_range(xfer, entity, channel, control); + } + debug!(" Unimplemented."); + } + + fn get_control_interface_range( + &mut self, + xfer: ControlIn, + entity: u8, + channel: u8, + control: u8, + ) { + match entity { + 1 => self.get_clock_range(xfer, channel, control), // clock source + _ => { + debug!(" Unimplemented."); + } + } + } + fn get_clock_cur(&mut self, xfer: ControlIn, channel: u8, control: u8) { + match control.try_into() { + Ok(ClockSourceControlSelector::SamFreqControl) => { + debug!(" SamplingFreqControl"); + if channel != 0 { + error!( + " Invalid channel {} for SamplingFreqControl GET RANGE. Ignoring.", + channel + ); + } + xfer.accept(|mut buf| match self.clock_impl.get_sample_rate() { + Ok(rate) => { + debug!(" {}", rate); + buf.write_u32::(rate) + .map_err(|_e| UsbError::BufferOverflow)?; + Ok(4) + } + Err(_e) => Err(UsbError::InvalidState), + }) + .ok(); + } + _ => debug!(" Unimplemented."), + } + } + fn get_clock_range(&mut self, xfer: ControlIn, channel: u8, control: u8) { + match control.try_into() { + Ok(ClockSourceControlSelector::SamFreqControl) => { + debug!(" SamplingFreqControl"); + if channel != 0 { + error!( + " Invalid channel {} for SamplingFreqControl GET RANGE. Ignoring.", + channel + ); + } + xfer.accept(|mut buf| match self.clock_impl.get_rates() { + Ok(rates) => { + buf.write_u16::(rates.len() as u16) + .map_err(|_e| UsbError::BufferOverflow)?; + let mut written = 2usize; + for rate in rates { + written += rate + .write(&mut buf) + .map_err(|_e| UsbError::BufferOverflow)? + } + Ok(written) + } + Err(_) => Err(UsbError::InvalidState), + }) + .ok(); + } + _ => { + debug!(" Unimplemented."); + } + } } } diff --git a/src/log.rs b/src/log.rs new file mode 100644 index 0000000..a85c0a4 --- /dev/null +++ b/src/log.rs @@ -0,0 +1,30 @@ +// src/log.rs (or log/mod.rs) +#[cfg(feature = "defmt")] +pub use defmt::{debug, error, info, trace, warn}; + +#[cfg(not(feature = "defmt"))] +mod no_defmt { + #[macro_export] + macro_rules! trace { + ($($t:tt)*) => {}; + } + #[macro_export] + macro_rules! debug { + ($($t:tt)*) => {}; + } + #[macro_export] + macro_rules! info { + ($($t:tt)*) => {}; + } + #[macro_export] + macro_rules! warn { + ($($t:tt)*) => {}; + } + #[macro_export] + macro_rules! error { + ($($t:tt)*) => {}; + } +} + +#[cfg(not(feature = "defmt"))] +pub use no_defmt::*;