diff --git a/embassy-usb-logger/Cargo.toml b/embassy-usb-logger/Cargo.toml index 48b8bbcc..02d0ed8e 100644 --- a/embassy-usb-logger/Cargo.toml +++ b/embassy-usb-logger/Cargo.toml @@ -8,6 +8,9 @@ src_base = "https://github.com/embassy-rs/embassy/blob/embassy-usb-logger-v$VERS src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-usb-logger/src/" target = "thumbv7em-none-eabi" +[features] +msos-descriptor = ["embassy-usb/msos-descriptor"] + [dependencies] embassy-usb = { version = "0.1.0", path = "../embassy-usb" } embassy-sync = { version = "0.4.0", path = "../embassy-sync" } diff --git a/embassy-usb-logger/src/lib.rs b/embassy-usb-logger/src/lib.rs index 9178dd6d..95fc0a7e 100644 --- a/embassy-usb-logger/src/lib.rs +++ b/embassy-usb-logger/src/lib.rs @@ -19,6 +19,8 @@ pub struct LoggerState<'d> { device_descriptor: [u8; 32], config_descriptor: [u8; 128], bos_descriptor: [u8; 16], + #[cfg(feature = "msos-descriptor")] + msos_descriptor: [u8; 256], control_buf: [u8; 64], } @@ -30,6 +32,8 @@ impl<'d> LoggerState<'d> { device_descriptor: [0; 32], config_descriptor: [0; 128], bos_descriptor: [0; 16], + #[cfg(feature = "msos-descriptor")] + msos_descriptor: [0; 256], control_buf: [0; 64], } } @@ -73,6 +77,8 @@ impl UsbLogger { &mut state.device_descriptor, &mut state.config_descriptor, &mut state.bos_descriptor, + #[cfg(feature = "msos-descriptor")] + &mut state.msos_descriptor, &mut state.control_buf, ); diff --git a/examples/rp/Cargo.toml b/examples/rp/Cargo.toml index fbe7acae..5ff505e8 100644 --- a/examples/rp/Cargo.toml +++ b/examples/rp/Cargo.toml @@ -11,11 +11,11 @@ embassy-sync = { version = "0.4.0", path = "../../embassy-sync", features = ["de embassy-executor = { version = "0.3.1", path = "../../embassy-executor", features = ["nightly", "arch-cortex-m", "executor-thread", "executor-interrupt", "defmt", "integrated-timers"] } embassy-time = { version = "0.1.5", path = "../../embassy-time", features = ["nightly", "unstable-traits", "defmt", "defmt-timestamp-uptime"] } embassy-rp = { version = "0.1.0", path = "../../embassy-rp", features = ["defmt", "unstable-traits", "nightly", "unstable-pac", "time-driver", "critical-section-impl"] } -embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt"] } +embassy-usb = { version = "0.1.0", path = "../../embassy-usb", features = ["defmt", "msos-descriptor"] } embassy-net = { version = "0.2.0", path = "../../embassy-net", features = ["defmt", "nightly", "tcp", "udp", "dhcpv4", "medium-ethernet"] } embassy-net-wiznet = { version = "0.1.0", path = "../../embassy-net-wiznet", features = ["defmt"] } embassy-futures = { version = "0.1.0", path = "../../embassy-futures" } -embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger" } +embassy-usb-logger = { version = "0.1.0", path = "../../embassy-usb-logger", features = ["msos-descriptor"]} embassy-lora = { version = "0.1.0", path = "../../embassy-lora", features = ["time", "defmt"] } lora-phy = { version = "2" } lorawan-device = { version = "0.11.0", default-features = false, features = ["async", "external-lora-phy"] } diff --git a/examples/rp/src/bin/pio_uart.rs b/examples/rp/src/bin/pio_uart.rs index 45416c56..dca28d77 100644 --- a/examples/rp/src/bin/pio_uart.rs +++ b/examples/rp/src/bin/pio_uart.rs @@ -75,6 +75,7 @@ async fn main(_spawner: Spawner) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, + &mut [], &mut control_buf, ); diff --git a/examples/rp/src/bin/usb_ethernet.rs b/examples/rp/src/bin/usb_ethernet.rs index 6c2f27ac..aea9e648 100644 --- a/examples/rp/src/bin/usb_ethernet.rs +++ b/examples/rp/src/bin/usb_ethernet.rs @@ -71,6 +71,7 @@ async fn main(spawner: Spawner) { &mut make_static!([0; 256])[..], &mut make_static!([0; 256])[..], &mut make_static!([0; 256])[..], + &mut make_static!([0; 0])[..], &mut make_static!([0; 128])[..], ); diff --git a/examples/rp/src/bin/usb_hid_keyboard.rs b/examples/rp/src/bin/usb_hid_keyboard.rs index cc2090d2..569c9b12 100644 --- a/examples/rp/src/bin/usb_hid_keyboard.rs +++ b/examples/rp/src/bin/usb_hid_keyboard.rs @@ -41,7 +41,7 @@ async fn main(_spawner: Spawner) { let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; // You can also add a Microsoft OS descriptor. - // let mut msos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; let mut control_buf = [0; 64]; let request_handler = MyRequestHandler {}; let mut device_handler = MyDeviceHandler::new(); @@ -54,7 +54,7 @@ async fn main(_spawner: Spawner) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, - // &mut msos_descriptor, + &mut msos_descriptor, &mut control_buf, ); diff --git a/examples/rp/src/bin/usb_midi.rs b/examples/rp/src/bin/usb_midi.rs index f0b03c81..3ba34c80 100644 --- a/examples/rp/src/bin/usb_midi.rs +++ b/examples/rp/src/bin/usb_midi.rs @@ -58,6 +58,7 @@ async fn main(_spawner: Spawner) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, + &mut [], &mut control_buf, ); diff --git a/examples/rp/src/bin/usb_raw.rs b/examples/rp/src/bin/usb_raw.rs new file mode 100644 index 00000000..f59262e5 --- /dev/null +++ b/examples/rp/src/bin/usb_raw.rs @@ -0,0 +1,199 @@ +//! Example of using USB without a pre-defined class, but instead responding to +//! raw USB control requests. +//! +//! The host computer can either: +//! * send a command, with a 16-bit request ID, a 16-bit value, and an optional data buffer +//! * request some data, with a 16-bit request ID, a 16-bit value, and a length of data to receive +//! +//! For higher throughput data, you can add some bulk endpoints after creating the alternate, +//! but for low rate command/response, plain control transfers can be very simple and effective. +//! +//! Example code to send/receive data using `nusb`: +//! +//! ```ignore +//! use futures_lite::future::block_on; +//! use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient}; +//! +//! fn main() { +//! let di = nusb::list_devices() +//! .unwrap() +//! .find(|d| d.vendor_id() == 0xc0de && d.product_id() == 0xcafe) +//! .expect("no device found"); +//! let device = di.open().expect("error opening device"); +//! let interface = device.claim_interface(0).expect("error claiming interface"); +//! +//! // Send "hello world" to device +//! let result = block_on(interface.control_out(ControlOut { +//! control_type: ControlType::Vendor, +//! recipient: Recipient::Interface, +//! request: 100, +//! value: 200, +//! index: 0, +//! data: b"hello world", +//! })); +//! println!("{result:?}"); +//! +//! // Receive "hello" from device +//! let result = block_on(interface.control_in(ControlIn { +//! control_type: ControlType::Vendor, +//! recipient: Recipient::Interface, +//! request: 101, +//! value: 201, +//! index: 0, +//! length: 5, +//! })); +//! println!("{result:?}"); +//! } +//! ``` + +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_rp::bind_interrupts; +use embassy_rp::peripherals::USB; +use embassy_rp::usb::{Driver, InterruptHandler}; +use embassy_usb::control::{InResponse, OutResponse, Recipient, Request, RequestType}; +use embassy_usb::msos::{self, windows_version}; +use embassy_usb::types::InterfaceNumber; +use embassy_usb::{Builder, Config, Handler}; +use {defmt_rtt as _, panic_probe as _}; + +// This is a randomly generated GUID to allow clients on Windows to find our device +const DEVICE_INTERFACE_GUIDS: &[&str] = &["{AFB9A6FB-30BA-44BC-9232-806CFC875321}"]; + +bind_interrupts!(struct Irqs { + USBCTRL_IRQ => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + info!("Hello there!"); + + let p = embassy_rp::init(Default::default()); + + // Create the driver, from the HAL. + let driver = Driver::new(p.USB, Irqs); + + // Create embassy-usb Config + let mut config = Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB raw example"); + config.serial_number = Some("12345678"); + config.max_power = 100; + config.max_packet_size_0 = 64; + + // // Required for windows compatibility. + // // https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help + config.device_class = 0xEF; + config.device_sub_class = 0x02; + config.device_protocol = 0x01; + config.composite_with_iads = true; + + // Create embassy-usb DeviceBuilder using the driver and config. + // It needs some buffers for building the descriptors. + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut msos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + + let mut handler = ControlHandler { + if_num: InterfaceNumber(0), + }; + + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut msos_descriptor, + &mut control_buf, + ); + + // Add the Microsoft OS Descriptor (MSOS/MOD) descriptor. + // We tell Windows that this entire device is compatible with the "WINUSB" feature, + // which causes it to use the built-in WinUSB driver automatically, which in turn + // can be used by libusb/rusb software without needing a custom driver or INF file. + // In principle you might want to call msos_feature() just on a specific function, + // if your device also has other functions that still use standard class drivers. + builder.msos_descriptor(windows_version::WIN8_1, 0); + builder.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + builder.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS), + )); + + // Add a vendor-specific function (class 0xFF), and corresponding interface, + // that uses our custom handler. + let mut function = builder.function(0xFF, 0, 0); + let mut interface = function.interface(); + let _alt = interface.alt_setting(0xFF, 0, 0, None); + handler.if_num = interface.interface_number(); + drop(function); + builder.handler(&mut handler); + + // Build the builder. + let mut usb = builder.build(); + + // Run the USB device. + usb.run().await; +} + +/// Handle CONTROL endpoint requests and responses. For many simple requests and responses +/// you can get away with only using the control endpoint. +struct ControlHandler { + if_num: InterfaceNumber, +} + +impl Handler for ControlHandler { + /// Respond to HostToDevice control messages, where the host sends us a command and + /// optionally some data, and we can only acknowledge or reject it. + fn control_out<'a>(&'a mut self, req: Request, buf: &'a [u8]) -> Option { + // Log the request before filtering to help with debugging. + info!("Got control_out, request={}, buf={:a}", req, buf); + + // Only handle Vendor request types to an Interface. + if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface { + return None; + } + + // Ignore requests to other interfaces. + if req.index != self.if_num.0 as u16 { + return None; + } + + // Accept request 100, value 200, reject others. + if req.request == 100 && req.value == 200 { + Some(OutResponse::Accepted) + } else { + Some(OutResponse::Rejected) + } + } + + /// Respond to DeviceToHost control messages, where the host requests some data from us. + fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> Option> { + info!("Got control_in, request={}", req); + + // Only handle Vendor request types to an Interface. + if req.request_type != RequestType::Vendor || req.recipient != Recipient::Interface { + return None; + } + + // Ignore requests to other interfaces. + if req.index != self.if_num.0 as u16 { + return None; + } + + // Respond "hello" to request 101, value 201, when asked for 5 bytes, otherwise reject. + if req.request == 101 && req.value == 201 && req.length == 5 { + buf[..5].copy_from_slice(b"hello"); + Some(InResponse::Accepted(&buf[..5])) + } else { + Some(InResponse::Rejected) + } + } +} diff --git a/examples/rp/src/bin/usb_serial.rs b/examples/rp/src/bin/usb_serial.rs index 164e2052..0d0317cd 100644 --- a/examples/rp/src/bin/usb_serial.rs +++ b/examples/rp/src/bin/usb_serial.rs @@ -60,6 +60,7 @@ async fn main(_spawner: Spawner) { &mut device_descriptor, &mut config_descriptor, &mut bos_descriptor, + &mut [], &mut control_buf, );