diff --git a/embassy-usb-hid/src/lib.rs b/embassy-usb-hid/src/lib.rs index e870becf..abcd6b5d 100644 --- a/embassy-usb-hid/src/lib.rs +++ b/embassy-usb-hid/src/lib.rs @@ -41,6 +41,24 @@ const HID_REQ_SET_REPORT: u8 = 0x09; const HID_REQ_GET_PROTOCOL: u8 = 0x03; const HID_REQ_SET_PROTOCOL: u8 = 0x0b; +pub struct Config<'d> { + /// HID report descriptor. + pub report_descriptor: &'d [u8], + + /// Handler for control requests. + pub request_handler: Option<&'d dyn RequestHandler>, + + /// Configures how frequently the host should poll for reading/writing HID reports. + /// + /// A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + pub poll_ms: u8, + + /// Max packet size for both the IN and OUT endpoints. + pub max_packet_size: u16, +} + #[derive(Debug, Clone, Copy, PartialEq, Eq)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub enum ReportId { @@ -60,12 +78,12 @@ impl ReportId { } } -pub struct State<'a, const IN_N: usize, const OUT_N: usize> { - control: MaybeUninit>, +pub struct State<'d> { + control: MaybeUninit>, out_report_offset: AtomicUsize, } -impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> { +impl<'d> State<'d> { pub fn new() -> Self { State { control: MaybeUninit::uninit(), @@ -74,107 +92,128 @@ impl<'a, const IN_N: usize, const OUT_N: usize> State<'a, IN_N, OUT_N> { } } -pub struct HidClass<'d, D: Driver<'d>, T, const IN_N: usize> { - input: ReportWriter<'d, D, IN_N>, - output: T, +pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { + reader: HidReader<'d, D, READ_N>, + writer: HidWriter<'d, D, WRITE_N>, } -impl<'d, D: Driver<'d>, const IN_N: usize> HidClass<'d, D, (), IN_N> { - /// Creates a new HidClass. - /// - /// poll_ms configures how frequently the host should poll for reading/writing - /// HID reports. A lower value means better throughput & latency, at the expense - /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for - /// high performance uses, and a value of 255 is good for best-effort usecases. - /// - /// This allocates an IN endpoint only. - pub fn new( - builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N>, - report_descriptor: &'static [u8], - request_handler: Option<&'d dyn RequestHandler>, - poll_ms: u8, - max_packet_size: u16, - ) -> Self { - let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); - let control = state.control.write(Control::new( - report_descriptor, - request_handler, - &state.out_report_offset, - )); - control.build(builder, None, &ep_in); +fn build<'d, D: Driver<'d>>( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d>, + config: Config<'d>, + with_out_endpoint: bool, +) -> (Option, D::EndpointIn, &'d AtomicUsize) { + let control = state.control.write(Control::new( + config.report_descriptor, + config.request_handler, + &state.out_report_offset, + )); - Self { - input: ReportWriter { ep_in }, - output: (), - } + let len = config.report_descriptor.len(); + let if_num = builder.alloc_interface_with_handler(control); + let ep_in = builder.alloc_interrupt_endpoint_in(config.max_packet_size, config.poll_ms); + let ep_out = if with_out_endpoint { + Some(builder.alloc_interrupt_endpoint_out(config.max_packet_size, config.poll_ms)) + } else { + None + }; + + builder.config_descriptor.interface( + if_num, + USB_CLASS_HID, + USB_SUBCLASS_NONE, + USB_PROTOCOL_NONE, + ); + + // HID descriptor + builder.config_descriptor.write( + HID_DESC_DESCTYPE_HID, + &[ + // HID Class spec version + HID_DESC_SPEC_1_10[0], + HID_DESC_SPEC_1_10[1], + // Country code not supported + HID_DESC_COUNTRY_UNSPEC, + // Number of following descriptors + 1, + // We have a HID report descriptor the host should read + HID_DESC_DESCTYPE_HID_REPORT, + // HID report descriptor size, + (len & 0xFF) as u8, + (len >> 8 & 0xFF) as u8, + ], + ); + + builder.config_descriptor.endpoint(ep_in.info()); + if let Some(ep_out) = &ep_out { + builder.config_descriptor.endpoint(ep_out.info()); } + + (ep_out, ep_in, &state.out_report_offset) } -impl<'d, D: Driver<'d>, T, const IN_N: usize> HidClass<'d, D, T, IN_N> { - /// Gets the [`ReportWriter`] for input reports. - /// - /// **Note:** If the `HidClass` was created with [`new_ep_out()`](Self::new_ep_out) - /// this writer will be useless as no endpoint is availabe to send reports. - pub fn input(&mut self) -> &mut ReportWriter<'d, D, IN_N> { - &mut self.input - } -} - -impl<'d, D: Driver<'d>, const IN_N: usize, const OUT_N: usize> - HidClass<'d, D, ReportReader<'d, D, OUT_N>, IN_N> +impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> + HidReaderWriter<'d, D, READ_N, WRITE_N> { - /// Creates a new HidClass. + /// Creates a new HidReaderWriter. /// - /// poll_ms configures how frequently the host should poll for reading/writing - /// HID reports. A lower value means better throughput & latency, at the expense - /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for - /// high performance uses, and a value of 255 is good for best-effort usecases. + /// This will allocate one IN and one OUT endpoints. If you only need writing (sending) + /// HID reports, consider using [`HidWriter::new`] instead, which allocates an IN endpoint only. /// - /// This allocates two endpoints (IN and OUT). - pub fn with_output_ep( + pub fn new( builder: &mut UsbDeviceBuilder<'d, D>, - state: &'d mut State<'d, IN_N, OUT_N>, - report_descriptor: &'static [u8], - request_handler: Option<&'d dyn RequestHandler>, - poll_ms: u8, - max_packet_size: u16, + state: &'d mut State<'d>, + config: Config<'d>, ) -> Self { - let ep_out = builder.alloc_interrupt_endpoint_out(max_packet_size, poll_ms); - let ep_in = builder.alloc_interrupt_endpoint_in(max_packet_size, poll_ms); - - let control = state.control.write(Control::new( - report_descriptor, - request_handler, - &state.out_report_offset, - )); - control.build(builder, Some(&ep_out), &ep_in); + let (ep_out, ep_in, offset) = build(builder, state, config, true); Self { - input: ReportWriter { ep_in }, - output: ReportReader { - ep_out, - offset: &state.out_report_offset, + reader: HidReader { + ep_out: ep_out.unwrap(), + offset, }, + writer: HidWriter { ep_in }, } } - /// Gets the [`ReportReader`] for output reports. - pub fn output(&mut self) -> &mut ReportReader<'d, D, OUT_N> { - &mut self.output + /// Splits into seperate readers/writers for input and output reports. + pub fn split(self) -> (HidReader<'d, D, READ_N>, HidWriter<'d, D, WRITE_N>) { + (self.reader, self.writer) } - /// Splits this `HidClass` into seperate readers/writers for input and output reports. - pub fn split(self) -> (ReportWriter<'d, D, IN_N>, ReportReader<'d, D, OUT_N>) { - (self.input, self.output) + /// Waits for both IN and OUT endpoints to be enabled. + pub async fn ready(&mut self) -> () { + self.reader.ready().await; + self.writer.ready().await; + } + + /// Writes an input report by serializing the given report structure. + #[cfg(feature = "usbd-hid")] + pub async fn write_serialize( + &mut self, + r: &IR, + ) -> Result<(), EndpointError> { + self.writer.write_serialize(r).await + } + + /// Writes `report` to its interrupt endpoint. + pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { + self.writer.write(report).await + } + + /// Reads an output report from the Interrupt Out pipe. + /// + /// See [`HidReader::read`]. + pub async fn read(&mut self, buf: &mut [u8]) -> Result { + self.reader.read(buf).await } } -pub struct ReportWriter<'d, D: Driver<'d>, const N: usize> { +pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { ep_in: D::EndpointIn, } -pub struct ReportReader<'d, D: Driver<'d>, const N: usize> { +pub struct HidReader<'d, D: Driver<'d>, const N: usize> { ep_out: D::EndpointOut, offset: &'d AtomicUsize, } @@ -197,17 +236,39 @@ impl From for ReadError { } } -impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { +impl<'d, D: Driver<'d>, const N: usize> HidWriter<'d, D, N> { + /// Creates a new HidWriter. + /// + /// This will allocate one IN endpoint only, so the host won't be able to send + /// reports to us. If you need that, consider using [`HidReaderWriter::new`] instead. + /// + /// poll_ms configures how frequently the host should poll for reading/writing + /// HID reports. A lower value means better throughput & latency, at the expense + /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for + /// high performance uses, and a value of 255 is good for best-effort usecases. + pub fn new( + builder: &mut UsbDeviceBuilder<'d, D>, + state: &'d mut State<'d>, + config: Config<'d>, + ) -> Self { + let (ep_out, ep_in, _offset) = build(builder, state, config, false); + + assert!(ep_out.is_none()); + + Self { ep_in } + } + /// Waits for the interrupt in endpoint to be enabled. pub async fn ready(&mut self) -> () { self.ep_in.wait_enabled().await } - /// Tries to write an input report by serializing the given report structure. - /// - /// Panics if no endpoint is available. + /// Writes an input report by serializing the given report structure. #[cfg(feature = "usbd-hid")] - pub async fn serialize(&mut self, r: &IR) -> Result<(), EndpointError> { + pub async fn write_serialize( + &mut self, + r: &IR, + ) -> Result<(), EndpointError> { let mut buf: [u8; N] = [0; N]; let size = match serialize(&mut buf, r) { Ok(size) => size, @@ -217,8 +278,6 @@ impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { } /// Writes `report` to its interrupt endpoint. - /// - /// Panics if no endpoint is available. pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { assert!(report.len() <= N); @@ -236,16 +295,13 @@ impl<'d, D: Driver<'d>, const N: usize> ReportWriter<'d, D, N> { } } -impl<'d, D: Driver<'d>, const N: usize> ReportReader<'d, D, N> { +impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { /// Waits for the interrupt out endpoint to be enabled. pub async fn ready(&mut self) -> () { self.ep_out.wait_enabled().await } - /// Starts a task to deliver output reports from the Interrupt Out pipe to - /// `handler`. - /// - /// Terminates when the interface becomes disabled. + /// Delivers output reports from the Interrupt Out pipe to `handler`. /// /// If `use_report_ids` is true, the first byte of the report will be used as /// the `ReportId` value. Otherwise the `ReportId` value will be 0. @@ -355,17 +411,17 @@ pub trait RequestHandler { } struct Control<'d> { - report_descriptor: &'static [u8], + report_descriptor: &'d [u8], request_handler: Option<&'d dyn RequestHandler>, out_report_offset: &'d AtomicUsize, hid_descriptor: [u8; 9], } -impl<'a> Control<'a> { +impl<'d> Control<'d> { fn new( - report_descriptor: &'static [u8], - request_handler: Option<&'a dyn RequestHandler>, - out_report_offset: &'a AtomicUsize, + report_descriptor: &'d [u8], + request_handler: Option<&'d dyn RequestHandler>, + out_report_offset: &'d AtomicUsize, ) -> Self { Control { report_descriptor, @@ -391,47 +447,6 @@ impl<'a> Control<'a> { ], } } - - fn build<'d, D: Driver<'d>>( - &'d mut self, - builder: &mut UsbDeviceBuilder<'d, D>, - ep_out: Option<&D::EndpointOut>, - ep_in: &D::EndpointIn, - ) { - let len = self.report_descriptor.len(); - let if_num = builder.alloc_interface_with_handler(self); - - builder.config_descriptor.interface( - if_num, - USB_CLASS_HID, - USB_SUBCLASS_NONE, - USB_PROTOCOL_NONE, - ); - - // HID descriptor - builder.config_descriptor.write( - HID_DESC_DESCTYPE_HID, - &[ - // HID Class spec version - HID_DESC_SPEC_1_10[0], - HID_DESC_SPEC_1_10[1], - // Country code not supported - HID_DESC_COUNTRY_UNSPEC, - // Number of following descriptors - 1, - // We have a HID report descriptor the host should read - HID_DESC_DESCTYPE_HID_REPORT, - // HID report descriptor size, - (len & 0xFF) as u8, - (len >> 8 & 0xFF) as u8, - ], - ); - - builder.config_descriptor.endpoint(ep_in.info()); - if let Some(ep) = ep_out { - builder.config_descriptor.endpoint(ep.info()); - } - } } impl<'d> ControlHandler for Control<'d> { diff --git a/embassy-usb/src/control.rs b/embassy-usb/src/control.rs index a613f114..9300d8c4 100644 --- a/embassy-usb/src/control.rs +++ b/embassy-usb/src/control.rs @@ -271,9 +271,9 @@ impl ControlPipe { let res = &buf[0..total]; #[cfg(feature = "defmt")] - trace!(" control out data: {:02x}", buf); + trace!(" control out data: {:02x}", res); #[cfg(not(feature = "defmt"))] - trace!(" control out data: {:02x?}", buf); + trace!(" control out data: {:02x?}", res); Ok((res, StatusStage {})) } diff --git a/examples/nrf/src/bin/usb_hid_keyboard.rs b/examples/nrf/src/bin/usb_hid_keyboard.rs index 5f03f512..a2d78b08 100644 --- a/examples/nrf/src/bin/usb_hid_keyboard.rs +++ b/examples/nrf/src/bin/usb_hid_keyboard.rs @@ -18,7 +18,7 @@ use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; use embassy_usb::control::OutResponse; use embassy_usb::{Config, DeviceStateHandler, UsbDeviceBuilder}; -use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; +use embassy_usb_hid::{HidReaderWriter, ReportId, RequestHandler, State}; use futures::future::join; use usbd_hid::descriptor::{KeyboardReport, SerializedDescriptor}; @@ -75,7 +75,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let request_handler = MyRequestHandler {}; let device_state_handler = MyDeviceStateHandler::new(); - let mut state = State::<8, 1>::new(); + let mut state = State::new(); let mut builder = UsbDeviceBuilder::new( driver, @@ -88,14 +88,13 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Create classes on the builder. - let hid = HidClass::with_output_ep( - &mut builder, - &mut state, - KeyboardReport::desc(), - Some(&request_handler), - 60, - 64, - ); + let config = embassy_usb_hid::Config { + report_descriptor: KeyboardReport::desc(), + request_handler: Some(&request_handler), + poll_ms: 60, + max_packet_size: 64, + }; + let hid = HidReaderWriter::<_, 1, 8>::new(&mut builder, &mut state, config); // Build the builder. let mut usb = builder.build(); @@ -135,7 +134,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut button = Input::new(p.P0_11.degrade(), Pull::Up); - let (mut hid_in, hid_out) = hid.split(); + let (reader, mut writer) = hid.split(); // Do stuff with the class! let in_fut = async { @@ -153,7 +152,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { modifier: 0, reserved: 0, }; - match hid_in.serialize(&report).await { + match writer.write_serialize(&report).await { Ok(()) => {} Err(e) => warn!("Failed to send report: {:?}", e), }; @@ -167,7 +166,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { modifier: 0, reserved: 0, }; - match hid_in.serialize(&report).await { + match writer.write_serialize(&report).await { Ok(()) => {} Err(e) => warn!("Failed to send report: {:?}", e), }; @@ -175,7 +174,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { }; let out_fut = async { - hid_out.run(false, &request_handler).await; + reader.run(false, &request_handler).await; }; let power_irq = interrupt::take!(POWER_CLOCK); diff --git a/examples/nrf/src/bin/usb_hid_mouse.rs b/examples/nrf/src/bin/usb_hid_mouse.rs index fe27e76f..1e98dd1a 100644 --- a/examples/nrf/src/bin/usb_hid_mouse.rs +++ b/examples/nrf/src/bin/usb_hid_mouse.rs @@ -13,7 +13,7 @@ use embassy_nrf::usb::Driver; use embassy_nrf::Peripherals; use embassy_usb::control::OutResponse; use embassy_usb::{Config, UsbDeviceBuilder}; -use embassy_usb_hid::{HidClass, ReportId, RequestHandler, State}; +use embassy_usb_hid::{HidWriter, ReportId, RequestHandler, State}; use futures::future::join; use usbd_hid::descriptor::{MouseReport, SerializedDescriptor}; @@ -52,7 +52,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { let mut control_buf = [0; 16]; let request_handler = MyRequestHandler {}; - let mut control = State::<5, 0>::new(); + let mut state = State::new(); let mut builder = UsbDeviceBuilder::new( driver, @@ -65,14 +65,14 @@ async fn main(_spawner: Spawner, p: Peripherals) { ); // Create classes on the builder. - let mut hid = HidClass::new( - &mut builder, - &mut control, - MouseReport::desc(), - Some(&request_handler), - 60, - 8, - ); + let config = embassy_usb_hid::Config { + report_descriptor: MouseReport::desc(), + request_handler: Some(&request_handler), + poll_ms: 60, + max_packet_size: 8, + }; + + let mut writer = HidWriter::<_, 5>::new(&mut builder, &mut state, config); // Build the builder. let mut usb = builder.build(); @@ -94,7 +94,7 @@ async fn main(_spawner: Spawner, p: Peripherals) { wheel: 0, pan: 0, }; - match hid.input().serialize(&report).await { + match writer.write_serialize(&report).await { Ok(()) => {} Err(e) => warn!("Failed to send report: {:?}", e), }