diff options
Diffstat (limited to 'embassy-usb')
| -rw-r--r-- | embassy-usb/CHANGELOG.md | 2 | ||||
| -rw-r--r-- | embassy-usb/src/class/hid.rs | 101 |
2 files changed, 92 insertions, 11 deletions
diff --git a/embassy-usb/CHANGELOG.md b/embassy-usb/CHANGELOG.md index 0a30bc24b..cfb1bf021 100644 --- a/embassy-usb/CHANGELOG.md +++ b/embassy-usb/CHANGELOG.md | |||
| @@ -8,6 +8,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 | |||
| 8 | <!-- next-header --> | 8 | <!-- next-header --> |
| 9 | ## Unreleased - ReleaseDate | 9 | ## Unreleased - ReleaseDate |
| 10 | 10 | ||
| 11 | - Add support for USB HID Boot Protocol Mode | ||
| 12 | |||
| 11 | ## 0.5.1 - 2025-08-26 | 13 | ## 0.5.1 - 2025-08-26 |
| 12 | 14 | ||
| 13 | ## 0.5.0 - 2025-07-16 | 15 | ## 0.5.0 - 2025-07-16 |
diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs index 182e1f83f..64e8fd59f 100644 --- a/embassy-usb/src/class/hid.rs +++ b/embassy-usb/src/class/hid.rs | |||
| @@ -15,8 +15,6 @@ use crate::types::InterfaceNumber; | |||
| 15 | use crate::{Builder, Handler}; | 15 | use crate::{Builder, Handler}; |
| 16 | 16 | ||
| 17 | const USB_CLASS_HID: u8 = 0x03; | 17 | const USB_CLASS_HID: u8 = 0x03; |
| 18 | const USB_SUBCLASS_NONE: u8 = 0x00; | ||
| 19 | const USB_PROTOCOL_NONE: u8 = 0x00; | ||
| 20 | 18 | ||
| 21 | // HID | 19 | // HID |
| 22 | const HID_DESC_DESCTYPE_HID: u8 = 0x21; | 20 | const HID_DESC_DESCTYPE_HID: u8 = 0x21; |
| @@ -31,6 +29,52 @@ const HID_REQ_SET_REPORT: u8 = 0x09; | |||
| 31 | const HID_REQ_GET_PROTOCOL: u8 = 0x03; | 29 | const HID_REQ_GET_PROTOCOL: u8 = 0x03; |
| 32 | const HID_REQ_SET_PROTOCOL: u8 = 0x0b; | 30 | const HID_REQ_SET_PROTOCOL: u8 = 0x0b; |
| 33 | 31 | ||
| 32 | /// Get/Set Protocol mapping | ||
| 33 | /// See (7.2.5 and 7.2.6): <https://www.usb.org/sites/default/files/hid1_11.pdf> | ||
| 34 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 35 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 36 | #[repr(u8)] | ||
| 37 | pub enum HidProtocolMode { | ||
| 38 | /// Hid Boot Protocol Mode | ||
| 39 | Boot = 0, | ||
| 40 | /// Hid Report Protocol Mode | ||
| 41 | Report = 1, | ||
| 42 | } | ||
| 43 | |||
| 44 | impl From<u8> for HidProtocolMode { | ||
| 45 | fn from(mode: u8) -> HidProtocolMode { | ||
| 46 | if mode == HidProtocolMode::Boot as u8 { | ||
| 47 | HidProtocolMode::Boot | ||
| 48 | } else { | ||
| 49 | HidProtocolMode::Report | ||
| 50 | } | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | /// USB HID interface subclass values. | ||
| 55 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 56 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 57 | #[repr(u8)] | ||
| 58 | pub enum HidSubclass { | ||
| 59 | /// No subclass, standard HID device. | ||
| 60 | No = 0, | ||
| 61 | /// Boot interface subclass, supports BIOS boot protocol. | ||
| 62 | Boot = 1, | ||
| 63 | } | ||
| 64 | |||
| 65 | /// USB HID protocol values. | ||
| 66 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 67 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 68 | #[repr(u8)] | ||
| 69 | pub enum HidBootProtocol { | ||
| 70 | /// No boot protocol. | ||
| 71 | None = 0, | ||
| 72 | /// Keyboard boot protocol. | ||
| 73 | Keyboard = 1, | ||
| 74 | /// Mouse boot protocol. | ||
| 75 | Mouse = 2, | ||
| 76 | } | ||
| 77 | |||
| 34 | /// Configuration for the HID class. | 78 | /// Configuration for the HID class. |
| 35 | pub struct Config<'d> { | 79 | pub struct Config<'d> { |
| 36 | /// HID report descriptor. | 80 | /// HID report descriptor. |
| @@ -48,6 +92,12 @@ pub struct Config<'d> { | |||
| 48 | 92 | ||
| 49 | /// Max packet size for both the IN and OUT endpoints. | 93 | /// Max packet size for both the IN and OUT endpoints. |
| 50 | pub max_packet_size: u16, | 94 | pub max_packet_size: u16, |
| 95 | |||
| 96 | /// The HID subclass of this interface | ||
| 97 | pub hid_subclass: HidSubclass, | ||
| 98 | |||
| 99 | /// The HID boot protocol of this interface | ||
| 100 | pub hid_boot_protocol: HidBootProtocol, | ||
| 51 | } | 101 | } |
| 52 | 102 | ||
| 53 | /// Report ID | 103 | /// Report ID |
| @@ -109,10 +159,15 @@ fn build<'d, D: Driver<'d>>( | |||
| 109 | ) -> (Option<D::EndpointOut>, D::EndpointIn, &'d AtomicUsize) { | 159 | ) -> (Option<D::EndpointOut>, D::EndpointIn, &'d AtomicUsize) { |
| 110 | let len = config.report_descriptor.len(); | 160 | let len = config.report_descriptor.len(); |
| 111 | 161 | ||
| 112 | let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); | 162 | let mut func = builder.function(USB_CLASS_HID, config.hid_subclass as u8, config.hid_boot_protocol as u8); |
| 113 | let mut iface = func.interface(); | 163 | let mut iface = func.interface(); |
| 114 | let if_num = iface.interface_number(); | 164 | let if_num = iface.interface_number(); |
| 115 | let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE, None); | 165 | let mut alt = iface.alt_setting( |
| 166 | USB_CLASS_HID, | ||
| 167 | config.hid_subclass as u8, | ||
| 168 | config.hid_boot_protocol as u8, | ||
| 169 | None, | ||
| 170 | ); | ||
| 116 | 171 | ||
| 117 | // HID descriptor | 172 | // HID descriptor |
| 118 | alt.descriptor( | 173 | alt.descriptor( |
| @@ -389,6 +444,23 @@ pub trait RequestHandler { | |||
| 389 | OutResponse::Rejected | 444 | OutResponse::Rejected |
| 390 | } | 445 | } |
| 391 | 446 | ||
| 447 | /// Gets the current hid protocol. | ||
| 448 | /// | ||
| 449 | /// Returns `Report` protocol by default. | ||
| 450 | fn get_protocol(&self) -> HidProtocolMode { | ||
| 451 | HidProtocolMode::Report | ||
| 452 | } | ||
| 453 | |||
| 454 | /// Sets the current hid protocol to `protocol`. | ||
| 455 | /// | ||
| 456 | /// Accepts only `Report` protocol by default. | ||
| 457 | fn set_protocol(&mut self, protocol: HidProtocolMode) -> OutResponse { | ||
| 458 | match protocol { | ||
| 459 | HidProtocolMode::Report => OutResponse::Accepted, | ||
| 460 | HidProtocolMode::Boot => OutResponse::Rejected, | ||
| 461 | } | ||
| 462 | } | ||
| 463 | |||
| 392 | /// Get the idle rate for `id`. | 464 | /// Get the idle rate for `id`. |
| 393 | /// | 465 | /// |
| 394 | /// If `id` is `None`, get the idle rate for all reports. Returning `None` | 466 | /// If `id` is `None`, get the idle rate for all reports. Returning `None` |
| @@ -482,11 +554,14 @@ impl<'d> Handler for Control<'d> { | |||
| 482 | _ => Some(OutResponse::Rejected), | 554 | _ => Some(OutResponse::Rejected), |
| 483 | }, | 555 | }, |
| 484 | HID_REQ_SET_PROTOCOL => { | 556 | HID_REQ_SET_PROTOCOL => { |
| 485 | if req.value == 1 { | 557 | let hid_protocol = HidProtocolMode::from(req.value as u8); |
| 486 | Some(OutResponse::Accepted) | 558 | match (self.request_handler.as_mut(), hid_protocol) { |
| 487 | } else { | 559 | (Some(request_handler), hid_protocol) => Some(request_handler.set_protocol(hid_protocol)), |
| 488 | warn!("HID Boot Protocol is unsupported."); | 560 | (None, HidProtocolMode::Report) => Some(OutResponse::Accepted), |
| 489 | Some(OutResponse::Rejected) // UNSUPPORTED: Boot Protocol | 561 | (None, HidProtocolMode::Boot) => { |
| 562 | info!("Received request to switch to Boot protocol mode, but it is disabled by default."); | ||
| 563 | Some(OutResponse::Rejected) | ||
| 564 | } | ||
| 490 | } | 565 | } |
| 491 | } | 566 | } |
| 492 | _ => Some(OutResponse::Rejected), | 567 | _ => Some(OutResponse::Rejected), |
| @@ -539,8 +614,12 @@ impl<'d> Handler for Control<'d> { | |||
| 539 | } | 614 | } |
| 540 | } | 615 | } |
| 541 | HID_REQ_GET_PROTOCOL => { | 616 | HID_REQ_GET_PROTOCOL => { |
| 542 | // UNSUPPORTED: Boot Protocol | 617 | if let Some(request_handler) = self.request_handler.as_mut() { |
| 543 | buf[0] = 1; | 618 | buf[0] = request_handler.get_protocol() as u8; |
| 619 | } else { | ||
| 620 | // Return `Report` protocol mode by default | ||
| 621 | buf[0] = HidProtocolMode::Report as u8; | ||
| 622 | } | ||
| 544 | Some(InResponse::Accepted(&buf[0..1])) | 623 | Some(InResponse::Accepted(&buf[0..1])) |
| 545 | } | 624 | } |
| 546 | _ => Some(InResponse::Rejected), | 625 | _ => Some(InResponse::Rejected), |
