diff options
| author | Dario Nieuwenhuis <[email protected]> | 2022-09-26 13:00:21 +0200 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2022-09-26 13:00:21 +0200 |
| commit | f27a47a37b59bf3b9079f4d4d5f43caf7b7872f8 (patch) | |
| tree | 732f73b4da7a2e726203f2876651a2141d9468be /embassy-usb/src/class | |
| parent | f4f58249722bc656a13865e06535d208440c3e4a (diff) | |
usb: move classes into the `embassy-usb` crate.
Diffstat (limited to 'embassy-usb/src/class')
| -rw-r--r-- | embassy-usb/src/class/cdc_acm.rs | 354 | ||||
| -rw-r--r-- | embassy-usb/src/class/cdc_ncm.rs | 478 | ||||
| -rw-r--r-- | embassy-usb/src/class/hid.rs | 504 | ||||
| -rw-r--r-- | embassy-usb/src/class/mod.rs | 3 |
4 files changed, 1339 insertions, 0 deletions
diff --git a/embassy-usb/src/class/cdc_acm.rs b/embassy-usb/src/class/cdc_acm.rs new file mode 100644 index 000000000..09bb1cc8d --- /dev/null +++ b/embassy-usb/src/class/cdc_acm.rs | |||
| @@ -0,0 +1,354 @@ | |||
| 1 | use core::cell::Cell; | ||
| 2 | use core::mem::{self, MaybeUninit}; | ||
| 3 | use core::sync::atomic::{AtomicBool, Ordering}; | ||
| 4 | |||
| 5 | use embassy_sync::blocking_mutex::CriticalSectionMutex; | ||
| 6 | |||
| 7 | use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; | ||
| 8 | use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; | ||
| 9 | use crate::types::*; | ||
| 10 | use crate::Builder; | ||
| 11 | |||
| 12 | /// This should be used as `device_class` when building the `UsbDevice`. | ||
| 13 | pub const USB_CLASS_CDC: u8 = 0x02; | ||
| 14 | |||
| 15 | const USB_CLASS_CDC_DATA: u8 = 0x0a; | ||
| 16 | const CDC_SUBCLASS_ACM: u8 = 0x02; | ||
| 17 | const CDC_PROTOCOL_NONE: u8 = 0x00; | ||
| 18 | |||
| 19 | const CS_INTERFACE: u8 = 0x24; | ||
| 20 | const CDC_TYPE_HEADER: u8 = 0x00; | ||
| 21 | const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; | ||
| 22 | const CDC_TYPE_ACM: u8 = 0x02; | ||
| 23 | const CDC_TYPE_UNION: u8 = 0x06; | ||
| 24 | |||
| 25 | const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; | ||
| 26 | #[allow(unused)] | ||
| 27 | const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; | ||
| 28 | const REQ_SET_LINE_CODING: u8 = 0x20; | ||
| 29 | const REQ_GET_LINE_CODING: u8 = 0x21; | ||
| 30 | const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; | ||
| 31 | |||
| 32 | pub struct State<'a> { | ||
| 33 | control: MaybeUninit<Control<'a>>, | ||
| 34 | shared: ControlShared, | ||
| 35 | } | ||
| 36 | |||
| 37 | impl<'a> State<'a> { | ||
| 38 | pub fn new() -> Self { | ||
| 39 | Self { | ||
| 40 | control: MaybeUninit::uninit(), | ||
| 41 | shared: Default::default(), | ||
| 42 | } | ||
| 43 | } | ||
| 44 | } | ||
| 45 | |||
| 46 | /// Packet level implementation of a CDC-ACM serial port. | ||
| 47 | /// | ||
| 48 | /// This class can be used directly and it has the least overhead due to directly reading and | ||
| 49 | /// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial | ||
| 50 | /// port. The following constraints must be followed if you use this class directly: | ||
| 51 | /// | ||
| 52 | /// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes. | ||
| 53 | /// - `write_packet` must not be called with a buffer larger than max_packet_size bytes. | ||
| 54 | /// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the | ||
| 55 | /// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) | ||
| 56 | /// can be sent if there is no other data to send. This is because USB bulk transactions must be | ||
| 57 | /// terminated with a short packet, even if the bulk endpoint is used for stream-like data. | ||
| 58 | pub struct CdcAcmClass<'d, D: Driver<'d>> { | ||
| 59 | _comm_ep: D::EndpointIn, | ||
| 60 | _data_if: InterfaceNumber, | ||
| 61 | read_ep: D::EndpointOut, | ||
| 62 | write_ep: D::EndpointIn, | ||
| 63 | control: &'d ControlShared, | ||
| 64 | } | ||
| 65 | |||
| 66 | struct Control<'a> { | ||
| 67 | shared: &'a ControlShared, | ||
| 68 | } | ||
| 69 | |||
| 70 | /// Shared data between Control and CdcAcmClass | ||
| 71 | struct ControlShared { | ||
| 72 | line_coding: CriticalSectionMutex<Cell<LineCoding>>, | ||
| 73 | dtr: AtomicBool, | ||
| 74 | rts: AtomicBool, | ||
| 75 | } | ||
| 76 | |||
| 77 | impl Default for ControlShared { | ||
| 78 | fn default() -> Self { | ||
| 79 | ControlShared { | ||
| 80 | dtr: AtomicBool::new(false), | ||
| 81 | rts: AtomicBool::new(false), | ||
| 82 | line_coding: CriticalSectionMutex::new(Cell::new(LineCoding { | ||
| 83 | stop_bits: StopBits::One, | ||
| 84 | data_bits: 8, | ||
| 85 | parity_type: ParityType::None, | ||
| 86 | data_rate: 8_000, | ||
| 87 | })), | ||
| 88 | } | ||
| 89 | } | ||
| 90 | } | ||
| 91 | |||
| 92 | impl<'a> Control<'a> { | ||
| 93 | fn shared(&mut self) -> &'a ControlShared { | ||
| 94 | self.shared | ||
| 95 | } | ||
| 96 | } | ||
| 97 | |||
| 98 | impl<'d> ControlHandler for Control<'d> { | ||
| 99 | fn reset(&mut self) { | ||
| 100 | let shared = self.shared(); | ||
| 101 | shared.line_coding.lock(|x| x.set(LineCoding::default())); | ||
| 102 | shared.dtr.store(false, Ordering::Relaxed); | ||
| 103 | shared.rts.store(false, Ordering::Relaxed); | ||
| 104 | } | ||
| 105 | |||
| 106 | fn control_out(&mut self, req: control::Request, data: &[u8]) -> OutResponse { | ||
| 107 | match req.request { | ||
| 108 | REQ_SEND_ENCAPSULATED_COMMAND => { | ||
| 109 | // We don't actually support encapsulated commands but pretend we do for standards | ||
| 110 | // compatibility. | ||
| 111 | OutResponse::Accepted | ||
| 112 | } | ||
| 113 | REQ_SET_LINE_CODING if data.len() >= 7 => { | ||
| 114 | let coding = LineCoding { | ||
| 115 | data_rate: u32::from_le_bytes(data[0..4].try_into().unwrap()), | ||
| 116 | stop_bits: data[4].into(), | ||
| 117 | parity_type: data[5].into(), | ||
| 118 | data_bits: data[6], | ||
| 119 | }; | ||
| 120 | self.shared().line_coding.lock(|x| x.set(coding)); | ||
| 121 | debug!("Set line coding to: {:?}", coding); | ||
| 122 | |||
| 123 | OutResponse::Accepted | ||
| 124 | } | ||
| 125 | REQ_SET_CONTROL_LINE_STATE => { | ||
| 126 | let dtr = (req.value & 0x0001) != 0; | ||
| 127 | let rts = (req.value & 0x0002) != 0; | ||
| 128 | |||
| 129 | let shared = self.shared(); | ||
| 130 | shared.dtr.store(dtr, Ordering::Relaxed); | ||
| 131 | shared.rts.store(rts, Ordering::Relaxed); | ||
| 132 | debug!("Set dtr {}, rts {}", dtr, rts); | ||
| 133 | |||
| 134 | OutResponse::Accepted | ||
| 135 | } | ||
| 136 | _ => OutResponse::Rejected, | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { | ||
| 141 | match req.request { | ||
| 142 | // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. | ||
| 143 | REQ_GET_LINE_CODING if req.length == 7 => { | ||
| 144 | debug!("Sending line coding"); | ||
| 145 | let coding = self.shared().line_coding.lock(|x| x.get()); | ||
| 146 | assert!(buf.len() >= 7); | ||
| 147 | buf[0..4].copy_from_slice(&coding.data_rate.to_le_bytes()); | ||
| 148 | buf[4] = coding.stop_bits as u8; | ||
| 149 | buf[5] = coding.parity_type as u8; | ||
| 150 | buf[6] = coding.data_bits; | ||
| 151 | InResponse::Accepted(&buf[0..7]) | ||
| 152 | } | ||
| 153 | _ => InResponse::Rejected, | ||
| 154 | } | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | impl<'d, D: Driver<'d>> CdcAcmClass<'d, D> { | ||
| 159 | /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For | ||
| 160 | /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. | ||
| 161 | pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, max_packet_size: u16) -> Self { | ||
| 162 | let control = state.control.write(Control { shared: &state.shared }); | ||
| 163 | |||
| 164 | let control_shared = &state.shared; | ||
| 165 | |||
| 166 | assert!(builder.control_buf_len() >= 7); | ||
| 167 | |||
| 168 | let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); | ||
| 169 | |||
| 170 | // Control interface | ||
| 171 | let mut iface = func.interface(); | ||
| 172 | iface.handler(control); | ||
| 173 | let comm_if = iface.interface_number(); | ||
| 174 | let data_if = u8::from(comm_if) + 1; | ||
| 175 | let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_ACM, CDC_PROTOCOL_NONE); | ||
| 176 | |||
| 177 | alt.descriptor( | ||
| 178 | CS_INTERFACE, | ||
| 179 | &[ | ||
| 180 | CDC_TYPE_HEADER, // bDescriptorSubtype | ||
| 181 | 0x10, | ||
| 182 | 0x01, // bcdCDC (1.10) | ||
| 183 | ], | ||
| 184 | ); | ||
| 185 | alt.descriptor( | ||
| 186 | CS_INTERFACE, | ||
| 187 | &[ | ||
| 188 | CDC_TYPE_ACM, // bDescriptorSubtype | ||
| 189 | 0x00, // bmCapabilities | ||
| 190 | ], | ||
| 191 | ); | ||
| 192 | alt.descriptor( | ||
| 193 | CS_INTERFACE, | ||
| 194 | &[ | ||
| 195 | CDC_TYPE_UNION, // bDescriptorSubtype | ||
| 196 | comm_if.into(), // bControlInterface | ||
| 197 | data_if.into(), // bSubordinateInterface | ||
| 198 | ], | ||
| 199 | ); | ||
| 200 | alt.descriptor( | ||
| 201 | CS_INTERFACE, | ||
| 202 | &[ | ||
| 203 | CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype | ||
| 204 | 0x00, // bmCapabilities | ||
| 205 | data_if.into(), // bDataInterface | ||
| 206 | ], | ||
| 207 | ); | ||
| 208 | |||
| 209 | let comm_ep = alt.endpoint_interrupt_in(8, 255); | ||
| 210 | |||
| 211 | // Data interface | ||
| 212 | let mut iface = func.interface(); | ||
| 213 | let data_if = iface.interface_number(); | ||
| 214 | let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NONE); | ||
| 215 | let read_ep = alt.endpoint_bulk_out(max_packet_size); | ||
| 216 | let write_ep = alt.endpoint_bulk_in(max_packet_size); | ||
| 217 | |||
| 218 | CdcAcmClass { | ||
| 219 | _comm_ep: comm_ep, | ||
| 220 | _data_if: data_if, | ||
| 221 | read_ep, | ||
| 222 | write_ep, | ||
| 223 | control: control_shared, | ||
| 224 | } | ||
| 225 | } | ||
| 226 | |||
| 227 | /// Gets the maximum packet size in bytes. | ||
| 228 | pub fn max_packet_size(&self) -> u16 { | ||
| 229 | // The size is the same for both endpoints. | ||
| 230 | self.read_ep.info().max_packet_size | ||
| 231 | } | ||
| 232 | |||
| 233 | /// Gets the current line coding. The line coding contains information that's mainly relevant | ||
| 234 | /// for USB to UART serial port emulators, and can be ignored if not relevant. | ||
| 235 | pub fn line_coding(&self) -> LineCoding { | ||
| 236 | self.control.line_coding.lock(|x| x.get()) | ||
| 237 | } | ||
| 238 | |||
| 239 | /// Gets the DTR (data terminal ready) state | ||
| 240 | pub fn dtr(&self) -> bool { | ||
| 241 | self.control.dtr.load(Ordering::Relaxed) | ||
| 242 | } | ||
| 243 | |||
| 244 | /// Gets the RTS (request to send) state | ||
| 245 | pub fn rts(&self) -> bool { | ||
| 246 | self.control.rts.load(Ordering::Relaxed) | ||
| 247 | } | ||
| 248 | |||
| 249 | /// Writes a single packet into the IN endpoint. | ||
| 250 | pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { | ||
| 251 | self.write_ep.write(data).await | ||
| 252 | } | ||
| 253 | |||
| 254 | /// Reads a single packet from the OUT endpoint. | ||
| 255 | pub async fn read_packet(&mut self, data: &mut [u8]) -> Result<usize, EndpointError> { | ||
| 256 | self.read_ep.read(data).await | ||
| 257 | } | ||
| 258 | |||
| 259 | /// Waits for the USB host to enable this interface | ||
| 260 | pub async fn wait_connection(&mut self) { | ||
| 261 | self.read_ep.wait_enabled().await | ||
| 262 | } | ||
| 263 | } | ||
| 264 | |||
| 265 | /// Number of stop bits for LineCoding | ||
| 266 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 267 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 268 | pub enum StopBits { | ||
| 269 | /// 1 stop bit | ||
| 270 | One = 0, | ||
| 271 | |||
| 272 | /// 1.5 stop bits | ||
| 273 | OnePointFive = 1, | ||
| 274 | |||
| 275 | /// 2 stop bits | ||
| 276 | Two = 2, | ||
| 277 | } | ||
| 278 | |||
| 279 | impl From<u8> for StopBits { | ||
| 280 | fn from(value: u8) -> Self { | ||
| 281 | if value <= 2 { | ||
| 282 | unsafe { mem::transmute(value) } | ||
| 283 | } else { | ||
| 284 | StopBits::One | ||
| 285 | } | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | /// Parity for LineCoding | ||
| 290 | #[derive(Copy, Clone, Debug, PartialEq, Eq)] | ||
| 291 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 292 | pub enum ParityType { | ||
| 293 | None = 0, | ||
| 294 | Odd = 1, | ||
| 295 | Even = 2, | ||
| 296 | Mark = 3, | ||
| 297 | Space = 4, | ||
| 298 | } | ||
| 299 | |||
| 300 | impl From<u8> for ParityType { | ||
| 301 | fn from(value: u8) -> Self { | ||
| 302 | if value <= 4 { | ||
| 303 | unsafe { mem::transmute(value) } | ||
| 304 | } else { | ||
| 305 | ParityType::None | ||
| 306 | } | ||
| 307 | } | ||
| 308 | } | ||
| 309 | |||
| 310 | /// Line coding parameters | ||
| 311 | /// | ||
| 312 | /// This is provided by the host for specifying the standard UART parameters such as baud rate. Can | ||
| 313 | /// be ignored if you don't plan to interface with a physical UART. | ||
| 314 | #[derive(Clone, Copy, Debug)] | ||
| 315 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 316 | pub struct LineCoding { | ||
| 317 | stop_bits: StopBits, | ||
| 318 | data_bits: u8, | ||
| 319 | parity_type: ParityType, | ||
| 320 | data_rate: u32, | ||
| 321 | } | ||
| 322 | |||
| 323 | impl LineCoding { | ||
| 324 | /// Gets the number of stop bits for UART communication. | ||
| 325 | pub fn stop_bits(&self) -> StopBits { | ||
| 326 | self.stop_bits | ||
| 327 | } | ||
| 328 | |||
| 329 | /// Gets the number of data bits for UART communication. | ||
| 330 | pub fn data_bits(&self) -> u8 { | ||
| 331 | self.data_bits | ||
| 332 | } | ||
| 333 | |||
| 334 | /// Gets the parity type for UART communication. | ||
| 335 | pub fn parity_type(&self) -> ParityType { | ||
| 336 | self.parity_type | ||
| 337 | } | ||
| 338 | |||
| 339 | /// Gets the data rate in bits per second for UART communication. | ||
| 340 | pub fn data_rate(&self) -> u32 { | ||
| 341 | self.data_rate | ||
| 342 | } | ||
| 343 | } | ||
| 344 | |||
| 345 | impl Default for LineCoding { | ||
| 346 | fn default() -> Self { | ||
| 347 | LineCoding { | ||
| 348 | stop_bits: StopBits::One, | ||
| 349 | data_bits: 8, | ||
| 350 | parity_type: ParityType::None, | ||
| 351 | data_rate: 8_000, | ||
| 352 | } | ||
| 353 | } | ||
| 354 | } | ||
diff --git a/embassy-usb/src/class/cdc_ncm.rs b/embassy-usb/src/class/cdc_ncm.rs new file mode 100644 index 000000000..a39b87e9b --- /dev/null +++ b/embassy-usb/src/class/cdc_ncm.rs | |||
| @@ -0,0 +1,478 @@ | |||
| 1 | use core::intrinsics::copy_nonoverlapping; | ||
| 2 | use core::mem::{size_of, MaybeUninit}; | ||
| 3 | |||
| 4 | use crate::control::{self, ControlHandler, InResponse, OutResponse, Request}; | ||
| 5 | use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; | ||
| 6 | use crate::types::*; | ||
| 7 | use crate::Builder; | ||
| 8 | |||
| 9 | /// This should be used as `device_class` when building the `UsbDevice`. | ||
| 10 | pub const USB_CLASS_CDC: u8 = 0x02; | ||
| 11 | |||
| 12 | const USB_CLASS_CDC_DATA: u8 = 0x0a; | ||
| 13 | const CDC_SUBCLASS_NCM: u8 = 0x0d; | ||
| 14 | |||
| 15 | const CDC_PROTOCOL_NONE: u8 = 0x00; | ||
| 16 | const CDC_PROTOCOL_NTB: u8 = 0x01; | ||
| 17 | |||
| 18 | const CS_INTERFACE: u8 = 0x24; | ||
| 19 | const CDC_TYPE_HEADER: u8 = 0x00; | ||
| 20 | const CDC_TYPE_UNION: u8 = 0x06; | ||
| 21 | const CDC_TYPE_ETHERNET: u8 = 0x0F; | ||
| 22 | const CDC_TYPE_NCM: u8 = 0x1A; | ||
| 23 | |||
| 24 | const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; | ||
| 25 | //const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; | ||
| 26 | //const REQ_SET_ETHERNET_MULTICAST_FILTERS: u8 = 0x40; | ||
| 27 | //const REQ_SET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x41; | ||
| 28 | //const REQ_GET_ETHERNET_POWER_MANAGEMENT_PATTERN_FILTER: u8 = 0x42; | ||
| 29 | //const REQ_SET_ETHERNET_PACKET_FILTER: u8 = 0x43; | ||
| 30 | //const REQ_GET_ETHERNET_STATISTIC: u8 = 0x44; | ||
| 31 | const REQ_GET_NTB_PARAMETERS: u8 = 0x80; | ||
| 32 | //const REQ_GET_NET_ADDRESS: u8 = 0x81; | ||
| 33 | //const REQ_SET_NET_ADDRESS: u8 = 0x82; | ||
| 34 | //const REQ_GET_NTB_FORMAT: u8 = 0x83; | ||
| 35 | //const REQ_SET_NTB_FORMAT: u8 = 0x84; | ||
| 36 | //const REQ_GET_NTB_INPUT_SIZE: u8 = 0x85; | ||
| 37 | const REQ_SET_NTB_INPUT_SIZE: u8 = 0x86; | ||
| 38 | //const REQ_GET_MAX_DATAGRAM_SIZE: u8 = 0x87; | ||
| 39 | //const REQ_SET_MAX_DATAGRAM_SIZE: u8 = 0x88; | ||
| 40 | //const REQ_GET_CRC_MODE: u8 = 0x89; | ||
| 41 | //const REQ_SET_CRC_MODE: u8 = 0x8A; | ||
| 42 | |||
| 43 | //const NOTIF_MAX_PACKET_SIZE: u16 = 8; | ||
| 44 | //const NOTIF_POLL_INTERVAL: u8 = 20; | ||
| 45 | |||
| 46 | const NTB_MAX_SIZE: usize = 2048; | ||
| 47 | const SIG_NTH: u32 = 0x484d434e; | ||
| 48 | const SIG_NDP_NO_FCS: u32 = 0x304d434e; | ||
| 49 | const SIG_NDP_WITH_FCS: u32 = 0x314d434e; | ||
| 50 | |||
| 51 | const ALTERNATE_SETTING_DISABLED: u8 = 0x00; | ||
| 52 | const ALTERNATE_SETTING_ENABLED: u8 = 0x01; | ||
| 53 | |||
| 54 | /// Simple NTB header (NTH+NDP all in one) for sending packets | ||
| 55 | #[repr(packed)] | ||
| 56 | #[allow(unused)] | ||
| 57 | struct NtbOutHeader { | ||
| 58 | // NTH | ||
| 59 | nth_sig: u32, | ||
| 60 | nth_len: u16, | ||
| 61 | nth_seq: u16, | ||
| 62 | nth_total_len: u16, | ||
| 63 | nth_first_index: u16, | ||
| 64 | |||
| 65 | // NDP | ||
| 66 | ndp_sig: u32, | ||
| 67 | ndp_len: u16, | ||
| 68 | ndp_next_index: u16, | ||
| 69 | ndp_datagram_index: u16, | ||
| 70 | ndp_datagram_len: u16, | ||
| 71 | ndp_term1: u16, | ||
| 72 | ndp_term2: u16, | ||
| 73 | } | ||
| 74 | |||
| 75 | #[repr(packed)] | ||
| 76 | #[allow(unused)] | ||
| 77 | struct NtbParameters { | ||
| 78 | length: u16, | ||
| 79 | formats_supported: u16, | ||
| 80 | in_params: NtbParametersDir, | ||
| 81 | out_params: NtbParametersDir, | ||
| 82 | } | ||
| 83 | |||
| 84 | #[repr(packed)] | ||
| 85 | #[allow(unused)] | ||
| 86 | struct NtbParametersDir { | ||
| 87 | max_size: u32, | ||
| 88 | divisor: u16, | ||
| 89 | payload_remainder: u16, | ||
| 90 | out_alignment: u16, | ||
| 91 | max_datagram_count: u16, | ||
| 92 | } | ||
| 93 | |||
| 94 | fn byteify<T>(buf: &mut [u8], data: T) -> &[u8] { | ||
| 95 | let len = size_of::<T>(); | ||
| 96 | unsafe { copy_nonoverlapping(&data as *const _ as *const u8, buf.as_mut_ptr(), len) } | ||
| 97 | &buf[..len] | ||
| 98 | } | ||
| 99 | |||
| 100 | pub struct State<'a> { | ||
| 101 | comm_control: MaybeUninit<CommControl<'a>>, | ||
| 102 | data_control: MaybeUninit<DataControl>, | ||
| 103 | shared: ControlShared, | ||
| 104 | } | ||
| 105 | |||
| 106 | impl<'a> State<'a> { | ||
| 107 | pub fn new() -> Self { | ||
| 108 | Self { | ||
| 109 | comm_control: MaybeUninit::uninit(), | ||
| 110 | data_control: MaybeUninit::uninit(), | ||
| 111 | shared: Default::default(), | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | /// Shared data between Control and CdcAcmClass | ||
| 117 | struct ControlShared { | ||
| 118 | mac_addr: [u8; 6], | ||
| 119 | } | ||
| 120 | |||
| 121 | impl Default for ControlShared { | ||
| 122 | fn default() -> Self { | ||
| 123 | ControlShared { mac_addr: [0; 6] } | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | struct CommControl<'a> { | ||
| 128 | mac_addr_string: StringIndex, | ||
| 129 | shared: &'a ControlShared, | ||
| 130 | mac_addr_str: [u8; 12], | ||
| 131 | } | ||
| 132 | |||
| 133 | impl<'d> ControlHandler for CommControl<'d> { | ||
| 134 | fn control_out(&mut self, req: control::Request, _data: &[u8]) -> OutResponse { | ||
| 135 | match req.request { | ||
| 136 | REQ_SEND_ENCAPSULATED_COMMAND => { | ||
| 137 | // We don't actually support encapsulated commands but pretend we do for standards | ||
| 138 | // compatibility. | ||
| 139 | OutResponse::Accepted | ||
| 140 | } | ||
| 141 | REQ_SET_NTB_INPUT_SIZE => { | ||
| 142 | // TODO | ||
| 143 | OutResponse::Accepted | ||
| 144 | } | ||
| 145 | _ => OutResponse::Rejected, | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { | ||
| 150 | match req.request { | ||
| 151 | REQ_GET_NTB_PARAMETERS => { | ||
| 152 | let res = NtbParameters { | ||
| 153 | length: size_of::<NtbParameters>() as _, | ||
| 154 | formats_supported: 1, // only 16bit, | ||
| 155 | in_params: NtbParametersDir { | ||
| 156 | max_size: NTB_MAX_SIZE as _, | ||
| 157 | divisor: 4, | ||
| 158 | payload_remainder: 0, | ||
| 159 | out_alignment: 4, | ||
| 160 | max_datagram_count: 0, // not used | ||
| 161 | }, | ||
| 162 | out_params: NtbParametersDir { | ||
| 163 | max_size: NTB_MAX_SIZE as _, | ||
| 164 | divisor: 4, | ||
| 165 | payload_remainder: 0, | ||
| 166 | out_alignment: 4, | ||
| 167 | max_datagram_count: 1, // We only decode 1 packet per NTB | ||
| 168 | }, | ||
| 169 | }; | ||
| 170 | InResponse::Accepted(byteify(buf, res)) | ||
| 171 | } | ||
| 172 | _ => InResponse::Rejected, | ||
| 173 | } | ||
| 174 | } | ||
| 175 | |||
| 176 | fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> { | ||
| 177 | if index == self.mac_addr_string { | ||
| 178 | let mac_addr = self.shared.mac_addr; | ||
| 179 | let s = &mut self.mac_addr_str; | ||
| 180 | for i in 0..12 { | ||
| 181 | let n = (mac_addr[i / 2] >> ((1 - i % 2) * 4)) & 0xF; | ||
| 182 | s[i] = match n { | ||
| 183 | 0x0..=0x9 => b'0' + n, | ||
| 184 | 0xA..=0xF => b'A' + n - 0xA, | ||
| 185 | _ => unreachable!(), | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | Some(unsafe { core::str::from_utf8_unchecked(s) }) | ||
| 190 | } else { | ||
| 191 | warn!("unknown string index requested"); | ||
| 192 | None | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | struct DataControl {} | ||
| 198 | |||
| 199 | impl ControlHandler for DataControl { | ||
| 200 | fn set_alternate_setting(&mut self, alternate_setting: u8) { | ||
| 201 | match alternate_setting { | ||
| 202 | ALTERNATE_SETTING_ENABLED => info!("ncm: interface enabled"), | ||
| 203 | ALTERNATE_SETTING_DISABLED => info!("ncm: interface disabled"), | ||
| 204 | _ => unreachable!(), | ||
| 205 | } | ||
| 206 | } | ||
| 207 | } | ||
| 208 | |||
| 209 | pub struct CdcNcmClass<'d, D: Driver<'d>> { | ||
| 210 | _comm_if: InterfaceNumber, | ||
| 211 | comm_ep: D::EndpointIn, | ||
| 212 | |||
| 213 | data_if: InterfaceNumber, | ||
| 214 | read_ep: D::EndpointOut, | ||
| 215 | write_ep: D::EndpointIn, | ||
| 216 | |||
| 217 | _control: &'d ControlShared, | ||
| 218 | } | ||
| 219 | |||
| 220 | impl<'d, D: Driver<'d>> CdcNcmClass<'d, D> { | ||
| 221 | pub fn new( | ||
| 222 | builder: &mut Builder<'d, D>, | ||
| 223 | state: &'d mut State<'d>, | ||
| 224 | mac_address: [u8; 6], | ||
| 225 | max_packet_size: u16, | ||
| 226 | ) -> Self { | ||
| 227 | state.shared.mac_addr = mac_address; | ||
| 228 | |||
| 229 | let mut func = builder.function(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); | ||
| 230 | |||
| 231 | // Control interface | ||
| 232 | let mut iface = func.interface(); | ||
| 233 | let mac_addr_string = iface.string(); | ||
| 234 | iface.handler(state.comm_control.write(CommControl { | ||
| 235 | mac_addr_string, | ||
| 236 | shared: &state.shared, | ||
| 237 | mac_addr_str: [0; 12], | ||
| 238 | })); | ||
| 239 | let comm_if = iface.interface_number(); | ||
| 240 | let mut alt = iface.alt_setting(USB_CLASS_CDC, CDC_SUBCLASS_NCM, CDC_PROTOCOL_NONE); | ||
| 241 | |||
| 242 | alt.descriptor( | ||
| 243 | CS_INTERFACE, | ||
| 244 | &[ | ||
| 245 | CDC_TYPE_HEADER, // bDescriptorSubtype | ||
| 246 | 0x10, | ||
| 247 | 0x01, // bcdCDC (1.10) | ||
| 248 | ], | ||
| 249 | ); | ||
| 250 | alt.descriptor( | ||
| 251 | CS_INTERFACE, | ||
| 252 | &[ | ||
| 253 | CDC_TYPE_UNION, // bDescriptorSubtype | ||
| 254 | comm_if.into(), // bControlInterface | ||
| 255 | u8::from(comm_if) + 1, // bSubordinateInterface | ||
| 256 | ], | ||
| 257 | ); | ||
| 258 | alt.descriptor( | ||
| 259 | CS_INTERFACE, | ||
| 260 | &[ | ||
| 261 | CDC_TYPE_ETHERNET, // bDescriptorSubtype | ||
| 262 | mac_addr_string.into(), // iMACAddress | ||
| 263 | 0, // bmEthernetStatistics | ||
| 264 | 0, // | | ||
| 265 | 0, // | | ||
| 266 | 0, // | | ||
| 267 | 0xea, // wMaxSegmentSize = 1514 | ||
| 268 | 0x05, // | | ||
| 269 | 0, // wNumberMCFilters | ||
| 270 | 0, // | | ||
| 271 | 0, // bNumberPowerFilters | ||
| 272 | ], | ||
| 273 | ); | ||
| 274 | alt.descriptor( | ||
| 275 | CS_INTERFACE, | ||
| 276 | &[ | ||
| 277 | CDC_TYPE_NCM, // bDescriptorSubtype | ||
| 278 | 0x00, // bcdNCMVersion | ||
| 279 | 0x01, // | | ||
| 280 | 0, // bmNetworkCapabilities | ||
| 281 | ], | ||
| 282 | ); | ||
| 283 | |||
| 284 | let comm_ep = alt.endpoint_interrupt_in(8, 255); | ||
| 285 | |||
| 286 | // Data interface | ||
| 287 | let mut iface = func.interface(); | ||
| 288 | iface.handler(state.data_control.write(DataControl {})); | ||
| 289 | let data_if = iface.interface_number(); | ||
| 290 | let _alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); | ||
| 291 | let mut alt = iface.alt_setting(USB_CLASS_CDC_DATA, 0x00, CDC_PROTOCOL_NTB); | ||
| 292 | let read_ep = alt.endpoint_bulk_out(max_packet_size); | ||
| 293 | let write_ep = alt.endpoint_bulk_in(max_packet_size); | ||
| 294 | |||
| 295 | CdcNcmClass { | ||
| 296 | _comm_if: comm_if, | ||
| 297 | comm_ep, | ||
| 298 | data_if, | ||
| 299 | read_ep, | ||
| 300 | write_ep, | ||
| 301 | _control: &state.shared, | ||
| 302 | } | ||
| 303 | } | ||
| 304 | |||
| 305 | pub fn split(self) -> (Sender<'d, D>, Receiver<'d, D>) { | ||
| 306 | ( | ||
| 307 | Sender { | ||
| 308 | write_ep: self.write_ep, | ||
| 309 | seq: 0, | ||
| 310 | }, | ||
| 311 | Receiver { | ||
| 312 | data_if: self.data_if, | ||
| 313 | comm_ep: self.comm_ep, | ||
| 314 | read_ep: self.read_ep, | ||
| 315 | }, | ||
| 316 | ) | ||
| 317 | } | ||
| 318 | } | ||
| 319 | |||
| 320 | pub struct Sender<'d, D: Driver<'d>> { | ||
| 321 | write_ep: D::EndpointIn, | ||
| 322 | seq: u16, | ||
| 323 | } | ||
| 324 | |||
| 325 | impl<'d, D: Driver<'d>> Sender<'d, D> { | ||
| 326 | pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { | ||
| 327 | let seq = self.seq; | ||
| 328 | self.seq = self.seq.wrapping_add(1); | ||
| 329 | |||
| 330 | const MAX_PACKET_SIZE: usize = 64; // TODO unhardcode | ||
| 331 | const OUT_HEADER_LEN: usize = 28; | ||
| 332 | |||
| 333 | let header = NtbOutHeader { | ||
| 334 | nth_sig: SIG_NTH, | ||
| 335 | nth_len: 0x0c, | ||
| 336 | nth_seq: seq, | ||
| 337 | nth_total_len: (data.len() + OUT_HEADER_LEN) as u16, | ||
| 338 | nth_first_index: 0x0c, | ||
| 339 | |||
| 340 | ndp_sig: SIG_NDP_NO_FCS, | ||
| 341 | ndp_len: 0x10, | ||
| 342 | ndp_next_index: 0x00, | ||
| 343 | ndp_datagram_index: OUT_HEADER_LEN as u16, | ||
| 344 | ndp_datagram_len: data.len() as u16, | ||
| 345 | ndp_term1: 0x00, | ||
| 346 | ndp_term2: 0x00, | ||
| 347 | }; | ||
| 348 | |||
| 349 | // Build first packet on a buffer, send next packets straight from `data`. | ||
| 350 | let mut buf = [0; MAX_PACKET_SIZE]; | ||
| 351 | let n = byteify(&mut buf, header); | ||
| 352 | assert_eq!(n.len(), OUT_HEADER_LEN); | ||
| 353 | |||
| 354 | if OUT_HEADER_LEN + data.len() < MAX_PACKET_SIZE { | ||
| 355 | // First packet is not full, just send it. | ||
| 356 | // No need to send ZLP because it's short for sure. | ||
| 357 | buf[OUT_HEADER_LEN..][..data.len()].copy_from_slice(data); | ||
| 358 | self.write_ep.write(&buf[..OUT_HEADER_LEN + data.len()]).await?; | ||
| 359 | } else { | ||
| 360 | let (d1, d2) = data.split_at(MAX_PACKET_SIZE - OUT_HEADER_LEN); | ||
| 361 | |||
| 362 | buf[OUT_HEADER_LEN..].copy_from_slice(d1); | ||
| 363 | self.write_ep.write(&buf).await?; | ||
| 364 | |||
| 365 | for chunk in d2.chunks(MAX_PACKET_SIZE) { | ||
| 366 | self.write_ep.write(&chunk).await?; | ||
| 367 | } | ||
| 368 | |||
| 369 | // Send ZLP if needed. | ||
| 370 | if d2.len() % MAX_PACKET_SIZE == 0 { | ||
| 371 | self.write_ep.write(&[]).await?; | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | Ok(()) | ||
| 376 | } | ||
| 377 | } | ||
| 378 | |||
| 379 | pub struct Receiver<'d, D: Driver<'d>> { | ||
| 380 | data_if: InterfaceNumber, | ||
| 381 | comm_ep: D::EndpointIn, | ||
| 382 | read_ep: D::EndpointOut, | ||
| 383 | } | ||
| 384 | |||
| 385 | impl<'d, D: Driver<'d>> Receiver<'d, D> { | ||
| 386 | /// Reads a single packet from the OUT endpoint. | ||
| 387 | pub async fn read_packet(&mut self, buf: &mut [u8]) -> Result<usize, EndpointError> { | ||
| 388 | // Retry loop | ||
| 389 | loop { | ||
| 390 | // read NTB | ||
| 391 | let mut ntb = [0u8; NTB_MAX_SIZE]; | ||
| 392 | let mut pos = 0; | ||
| 393 | loop { | ||
| 394 | let n = self.read_ep.read(&mut ntb[pos..]).await?; | ||
| 395 | pos += n; | ||
| 396 | if n < self.read_ep.info().max_packet_size as usize || pos == NTB_MAX_SIZE { | ||
| 397 | break; | ||
| 398 | } | ||
| 399 | } | ||
| 400 | |||
| 401 | let ntb = &ntb[..pos]; | ||
| 402 | |||
| 403 | // Process NTB header (NTH) | ||
| 404 | let nth = match ntb.get(..12) { | ||
| 405 | Some(x) => x, | ||
| 406 | None => { | ||
| 407 | warn!("Received too short NTB"); | ||
| 408 | continue; | ||
| 409 | } | ||
| 410 | }; | ||
| 411 | let sig = u32::from_le_bytes(nth[0..4].try_into().unwrap()); | ||
| 412 | if sig != SIG_NTH { | ||
| 413 | warn!("Received bad NTH sig."); | ||
| 414 | continue; | ||
| 415 | } | ||
| 416 | let ndp_idx = u16::from_le_bytes(nth[10..12].try_into().unwrap()) as usize; | ||
| 417 | |||
| 418 | // Process NTB Datagram Pointer (NDP) | ||
| 419 | let ndp = match ntb.get(ndp_idx..ndp_idx + 12) { | ||
| 420 | Some(x) => x, | ||
| 421 | None => { | ||
| 422 | warn!("NTH has an NDP pointer out of range."); | ||
| 423 | continue; | ||
| 424 | } | ||
| 425 | }; | ||
| 426 | let sig = u32::from_le_bytes(ndp[0..4].try_into().unwrap()); | ||
| 427 | if sig != SIG_NDP_NO_FCS && sig != SIG_NDP_WITH_FCS { | ||
| 428 | warn!("Received bad NDP sig."); | ||
| 429 | continue; | ||
| 430 | } | ||
| 431 | let datagram_index = u16::from_le_bytes(ndp[8..10].try_into().unwrap()) as usize; | ||
| 432 | let datagram_len = u16::from_le_bytes(ndp[10..12].try_into().unwrap()) as usize; | ||
| 433 | |||
| 434 | if datagram_index == 0 || datagram_len == 0 { | ||
| 435 | // empty, ignore. This is allowed by the spec, so don't warn. | ||
| 436 | continue; | ||
| 437 | } | ||
| 438 | |||
| 439 | // Process actual datagram, finally. | ||
| 440 | let datagram = match ntb.get(datagram_index..datagram_index + datagram_len) { | ||
| 441 | Some(x) => x, | ||
| 442 | None => { | ||
| 443 | warn!("NDP has a datagram pointer out of range."); | ||
| 444 | continue; | ||
| 445 | } | ||
| 446 | }; | ||
| 447 | buf[..datagram_len].copy_from_slice(datagram); | ||
| 448 | |||
| 449 | return Ok(datagram_len); | ||
| 450 | } | ||
| 451 | } | ||
| 452 | |||
| 453 | /// Waits for the USB host to enable this interface | ||
| 454 | pub async fn wait_connection(&mut self) -> Result<(), EndpointError> { | ||
| 455 | loop { | ||
| 456 | self.read_ep.wait_enabled().await; | ||
| 457 | self.comm_ep.wait_enabled().await; | ||
| 458 | |||
| 459 | let buf = [ | ||
| 460 | 0xA1, //bmRequestType | ||
| 461 | 0x00, //bNotificationType = NETWORK_CONNECTION | ||
| 462 | 0x01, // wValue = connected | ||
| 463 | 0x00, | ||
| 464 | self.data_if.into(), // wIndex = interface | ||
| 465 | 0x00, | ||
| 466 | 0x00, // wLength | ||
| 467 | 0x00, | ||
| 468 | ]; | ||
| 469 | match self.comm_ep.write(&buf).await { | ||
| 470 | Ok(()) => break, // Done! | ||
| 471 | Err(EndpointError::Disabled) => {} // Got disabled again, wait again. | ||
| 472 | Err(e) => return Err(e), | ||
| 473 | } | ||
| 474 | } | ||
| 475 | |||
| 476 | Ok(()) | ||
| 477 | } | ||
| 478 | } | ||
diff --git a/embassy-usb/src/class/hid.rs b/embassy-usb/src/class/hid.rs new file mode 100644 index 000000000..4d1fa995f --- /dev/null +++ b/embassy-usb/src/class/hid.rs | |||
| @@ -0,0 +1,504 @@ | |||
| 1 | use core::mem::MaybeUninit; | ||
| 2 | use core::ops::Range; | ||
| 3 | use core::sync::atomic::{AtomicUsize, Ordering}; | ||
| 4 | |||
| 5 | #[cfg(feature = "usbd-hid")] | ||
| 6 | use ssmarshal::serialize; | ||
| 7 | #[cfg(feature = "usbd-hid")] | ||
| 8 | use usbd_hid::descriptor::AsInputReport; | ||
| 9 | |||
| 10 | use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType}; | ||
| 11 | use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; | ||
| 12 | use crate::Builder; | ||
| 13 | |||
| 14 | const USB_CLASS_HID: u8 = 0x03; | ||
| 15 | const USB_SUBCLASS_NONE: u8 = 0x00; | ||
| 16 | const USB_PROTOCOL_NONE: u8 = 0x00; | ||
| 17 | |||
| 18 | // HID | ||
| 19 | const HID_DESC_DESCTYPE_HID: u8 = 0x21; | ||
| 20 | const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22; | ||
| 21 | const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01]; | ||
| 22 | const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00; | ||
| 23 | |||
| 24 | const HID_REQ_SET_IDLE: u8 = 0x0a; | ||
| 25 | const HID_REQ_GET_IDLE: u8 = 0x02; | ||
| 26 | const HID_REQ_GET_REPORT: u8 = 0x01; | ||
| 27 | const HID_REQ_SET_REPORT: u8 = 0x09; | ||
| 28 | const HID_REQ_GET_PROTOCOL: u8 = 0x03; | ||
| 29 | const HID_REQ_SET_PROTOCOL: u8 = 0x0b; | ||
| 30 | |||
| 31 | pub struct Config<'d> { | ||
| 32 | /// HID report descriptor. | ||
| 33 | pub report_descriptor: &'d [u8], | ||
| 34 | |||
| 35 | /// Handler for control requests. | ||
| 36 | pub request_handler: Option<&'d dyn RequestHandler>, | ||
| 37 | |||
| 38 | /// Configures how frequently the host should poll for reading/writing HID reports. | ||
| 39 | /// | ||
| 40 | /// A lower value means better throughput & latency, at the expense | ||
| 41 | /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for | ||
| 42 | /// high performance uses, and a value of 255 is good for best-effort usecases. | ||
| 43 | pub poll_ms: u8, | ||
| 44 | |||
| 45 | /// Max packet size for both the IN and OUT endpoints. | ||
| 46 | pub max_packet_size: u16, | ||
| 47 | } | ||
| 48 | |||
| 49 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||
| 50 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 51 | pub enum ReportId { | ||
| 52 | In(u8), | ||
| 53 | Out(u8), | ||
| 54 | Feature(u8), | ||
| 55 | } | ||
| 56 | |||
| 57 | impl ReportId { | ||
| 58 | fn try_from(value: u16) -> Result<Self, ()> { | ||
| 59 | match value >> 8 { | ||
| 60 | 1 => Ok(ReportId::In(value as u8)), | ||
| 61 | 2 => Ok(ReportId::Out(value as u8)), | ||
| 62 | 3 => Ok(ReportId::Feature(value as u8)), | ||
| 63 | _ => Err(()), | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | pub struct State<'d> { | ||
| 69 | control: MaybeUninit<Control<'d>>, | ||
| 70 | out_report_offset: AtomicUsize, | ||
| 71 | } | ||
| 72 | |||
| 73 | impl<'d> State<'d> { | ||
| 74 | pub fn new() -> Self { | ||
| 75 | State { | ||
| 76 | control: MaybeUninit::uninit(), | ||
| 77 | out_report_offset: AtomicUsize::new(0), | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | |||
| 82 | pub struct HidReaderWriter<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> { | ||
| 83 | reader: HidReader<'d, D, READ_N>, | ||
| 84 | writer: HidWriter<'d, D, WRITE_N>, | ||
| 85 | } | ||
| 86 | |||
| 87 | fn build<'d, D: Driver<'d>>( | ||
| 88 | builder: &mut Builder<'d, D>, | ||
| 89 | state: &'d mut State<'d>, | ||
| 90 | config: Config<'d>, | ||
| 91 | with_out_endpoint: bool, | ||
| 92 | ) -> (Option<D::EndpointOut>, D::EndpointIn, &'d AtomicUsize) { | ||
| 93 | let control = state.control.write(Control::new( | ||
| 94 | config.report_descriptor, | ||
| 95 | config.request_handler, | ||
| 96 | &state.out_report_offset, | ||
| 97 | )); | ||
| 98 | |||
| 99 | let len = config.report_descriptor.len(); | ||
| 100 | |||
| 101 | let mut func = builder.function(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); | ||
| 102 | let mut iface = func.interface(); | ||
| 103 | iface.handler(control); | ||
| 104 | let mut alt = iface.alt_setting(USB_CLASS_HID, USB_SUBCLASS_NONE, USB_PROTOCOL_NONE); | ||
| 105 | |||
| 106 | // HID descriptor | ||
| 107 | alt.descriptor( | ||
| 108 | HID_DESC_DESCTYPE_HID, | ||
| 109 | &[ | ||
| 110 | // HID Class spec version | ||
| 111 | HID_DESC_SPEC_1_10[0], | ||
| 112 | HID_DESC_SPEC_1_10[1], | ||
| 113 | // Country code not supported | ||
| 114 | HID_DESC_COUNTRY_UNSPEC, | ||
| 115 | // Number of following descriptors | ||
| 116 | 1, | ||
| 117 | // We have a HID report descriptor the host should read | ||
| 118 | HID_DESC_DESCTYPE_HID_REPORT, | ||
| 119 | // HID report descriptor size, | ||
| 120 | (len & 0xFF) as u8, | ||
| 121 | (len >> 8 & 0xFF) as u8, | ||
| 122 | ], | ||
| 123 | ); | ||
| 124 | |||
| 125 | let ep_in = alt.endpoint_interrupt_in(config.max_packet_size, config.poll_ms); | ||
| 126 | let ep_out = if with_out_endpoint { | ||
| 127 | Some(alt.endpoint_interrupt_out(config.max_packet_size, config.poll_ms)) | ||
| 128 | } else { | ||
| 129 | None | ||
| 130 | }; | ||
| 131 | |||
| 132 | (ep_out, ep_in, &state.out_report_offset) | ||
| 133 | } | ||
| 134 | |||
| 135 | impl<'d, D: Driver<'d>, const READ_N: usize, const WRITE_N: usize> HidReaderWriter<'d, D, READ_N, WRITE_N> { | ||
| 136 | /// Creates a new HidReaderWriter. | ||
| 137 | /// | ||
| 138 | /// This will allocate one IN and one OUT endpoints. If you only need writing (sending) | ||
| 139 | /// HID reports, consider using [`HidWriter::new`] instead, which allocates an IN endpoint only. | ||
| 140 | /// | ||
| 141 | pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { | ||
| 142 | let (ep_out, ep_in, offset) = build(builder, state, config, true); | ||
| 143 | |||
| 144 | Self { | ||
| 145 | reader: HidReader { | ||
| 146 | ep_out: ep_out.unwrap(), | ||
| 147 | offset, | ||
| 148 | }, | ||
| 149 | writer: HidWriter { ep_in }, | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | /// Splits into seperate readers/writers for input and output reports. | ||
| 154 | pub fn split(self) -> (HidReader<'d, D, READ_N>, HidWriter<'d, D, WRITE_N>) { | ||
| 155 | (self.reader, self.writer) | ||
| 156 | } | ||
| 157 | |||
| 158 | /// Waits for both IN and OUT endpoints to be enabled. | ||
| 159 | pub async fn ready(&mut self) -> () { | ||
| 160 | self.reader.ready().await; | ||
| 161 | self.writer.ready().await; | ||
| 162 | } | ||
| 163 | |||
| 164 | /// Writes an input report by serializing the given report structure. | ||
| 165 | #[cfg(feature = "usbd-hid")] | ||
| 166 | pub async fn write_serialize<IR: AsInputReport>(&mut self, r: &IR) -> Result<(), EndpointError> { | ||
| 167 | self.writer.write_serialize(r).await | ||
| 168 | } | ||
| 169 | |||
| 170 | /// Writes `report` to its interrupt endpoint. | ||
| 171 | pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { | ||
| 172 | self.writer.write(report).await | ||
| 173 | } | ||
| 174 | |||
| 175 | /// Reads an output report from the Interrupt Out pipe. | ||
| 176 | /// | ||
| 177 | /// See [`HidReader::read`]. | ||
| 178 | pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> { | ||
| 179 | self.reader.read(buf).await | ||
| 180 | } | ||
| 181 | } | ||
| 182 | |||
| 183 | pub struct HidWriter<'d, D: Driver<'d>, const N: usize> { | ||
| 184 | ep_in: D::EndpointIn, | ||
| 185 | } | ||
| 186 | |||
| 187 | pub struct HidReader<'d, D: Driver<'d>, const N: usize> { | ||
| 188 | ep_out: D::EndpointOut, | ||
| 189 | offset: &'d AtomicUsize, | ||
| 190 | } | ||
| 191 | |||
| 192 | #[derive(Debug, Clone, PartialEq, Eq)] | ||
| 193 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 194 | pub enum ReadError { | ||
| 195 | BufferOverflow, | ||
| 196 | Disabled, | ||
| 197 | Sync(Range<usize>), | ||
| 198 | } | ||
| 199 | |||
| 200 | impl From<EndpointError> for ReadError { | ||
| 201 | fn from(val: EndpointError) -> Self { | ||
| 202 | use EndpointError::*; | ||
| 203 | match val { | ||
| 204 | BufferOverflow => ReadError::BufferOverflow, | ||
| 205 | Disabled => ReadError::Disabled, | ||
| 206 | } | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | impl<'d, D: Driver<'d>, const N: usize> HidWriter<'d, D, N> { | ||
| 211 | /// Creates a new HidWriter. | ||
| 212 | /// | ||
| 213 | /// This will allocate one IN endpoint only, so the host won't be able to send | ||
| 214 | /// reports to us. If you need that, consider using [`HidReaderWriter::new`] instead. | ||
| 215 | /// | ||
| 216 | /// poll_ms configures how frequently the host should poll for reading/writing | ||
| 217 | /// HID reports. A lower value means better throughput & latency, at the expense | ||
| 218 | /// of CPU on the device & bandwidth on the bus. A value of 10 is reasonable for | ||
| 219 | /// high performance uses, and a value of 255 is good for best-effort usecases. | ||
| 220 | pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State<'d>, config: Config<'d>) -> Self { | ||
| 221 | let (ep_out, ep_in, _offset) = build(builder, state, config, false); | ||
| 222 | |||
| 223 | assert!(ep_out.is_none()); | ||
| 224 | |||
| 225 | Self { ep_in } | ||
| 226 | } | ||
| 227 | |||
| 228 | /// Waits for the interrupt in endpoint to be enabled. | ||
| 229 | pub async fn ready(&mut self) -> () { | ||
| 230 | self.ep_in.wait_enabled().await | ||
| 231 | } | ||
| 232 | |||
| 233 | /// Writes an input report by serializing the given report structure. | ||
| 234 | #[cfg(feature = "usbd-hid")] | ||
| 235 | pub async fn write_serialize<IR: AsInputReport>(&mut self, r: &IR) -> Result<(), EndpointError> { | ||
| 236 | let mut buf: [u8; N] = [0; N]; | ||
| 237 | let size = match serialize(&mut buf, r) { | ||
| 238 | Ok(size) => size, | ||
| 239 | Err(_) => return Err(EndpointError::BufferOverflow), | ||
| 240 | }; | ||
| 241 | self.write(&buf[0..size]).await | ||
| 242 | } | ||
| 243 | |||
| 244 | /// Writes `report` to its interrupt endpoint. | ||
| 245 | pub async fn write(&mut self, report: &[u8]) -> Result<(), EndpointError> { | ||
| 246 | assert!(report.len() <= N); | ||
| 247 | |||
| 248 | let max_packet_size = usize::from(self.ep_in.info().max_packet_size); | ||
| 249 | let zlp_needed = report.len() < N && (report.len() % max_packet_size == 0); | ||
| 250 | for chunk in report.chunks(max_packet_size) { | ||
| 251 | self.ep_in.write(chunk).await?; | ||
| 252 | } | ||
| 253 | |||
| 254 | if zlp_needed { | ||
| 255 | self.ep_in.write(&[]).await?; | ||
| 256 | } | ||
| 257 | |||
| 258 | Ok(()) | ||
| 259 | } | ||
| 260 | } | ||
| 261 | |||
| 262 | impl<'d, D: Driver<'d>, const N: usize> HidReader<'d, D, N> { | ||
| 263 | /// Waits for the interrupt out endpoint to be enabled. | ||
| 264 | pub async fn ready(&mut self) -> () { | ||
| 265 | self.ep_out.wait_enabled().await | ||
| 266 | } | ||
| 267 | |||
| 268 | /// Delivers output reports from the Interrupt Out pipe to `handler`. | ||
| 269 | /// | ||
| 270 | /// If `use_report_ids` is true, the first byte of the report will be used as | ||
| 271 | /// the `ReportId` value. Otherwise the `ReportId` value will be 0. | ||
| 272 | pub async fn run<T: RequestHandler>(mut self, use_report_ids: bool, handler: &T) -> ! { | ||
| 273 | let offset = self.offset.load(Ordering::Acquire); | ||
| 274 | assert!(offset == 0); | ||
| 275 | let mut buf = [0; N]; | ||
| 276 | loop { | ||
| 277 | match self.read(&mut buf).await { | ||
| 278 | Ok(len) => { | ||
| 279 | let id = if use_report_ids { buf[0] } else { 0 }; | ||
| 280 | handler.set_report(ReportId::Out(id), &buf[..len]); | ||
| 281 | } | ||
| 282 | Err(ReadError::BufferOverflow) => warn!( | ||
| 283 | "Host sent output report larger than the configured maximum output report length ({})", | ||
| 284 | N | ||
| 285 | ), | ||
| 286 | Err(ReadError::Disabled) => self.ep_out.wait_enabled().await, | ||
| 287 | Err(ReadError::Sync(_)) => unreachable!(), | ||
| 288 | } | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | /// Reads an output report from the Interrupt Out pipe. | ||
| 293 | /// | ||
| 294 | /// **Note:** Any reports sent from the host over the control pipe will be | ||
| 295 | /// passed to [`RequestHandler::set_report()`] for handling. The application | ||
| 296 | /// is responsible for ensuring output reports from both pipes are handled | ||
| 297 | /// correctly. | ||
| 298 | /// | ||
| 299 | /// **Note:** If `N` > the maximum packet size of the endpoint (i.e. output | ||
| 300 | /// reports may be split across multiple packets) and this method's future | ||
| 301 | /// is dropped after some packets have been read, the next call to `read()` | ||
| 302 | /// will return a [`ReadError::SyncError()`]. The range in the sync error | ||
| 303 | /// indicates the portion `buf` that was filled by the current call to | ||
| 304 | /// `read()`. If the dropped future used the same `buf`, then `buf` will | ||
| 305 | /// contain the full report. | ||
| 306 | pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize, ReadError> { | ||
| 307 | assert!(N != 0); | ||
| 308 | assert!(buf.len() >= N); | ||
| 309 | |||
| 310 | // Read packets from the endpoint | ||
| 311 | let max_packet_size = usize::from(self.ep_out.info().max_packet_size); | ||
| 312 | let starting_offset = self.offset.load(Ordering::Acquire); | ||
| 313 | let mut total = starting_offset; | ||
| 314 | loop { | ||
| 315 | for chunk in buf[starting_offset..N].chunks_mut(max_packet_size) { | ||
| 316 | match self.ep_out.read(chunk).await { | ||
| 317 | Ok(size) => { | ||
| 318 | total += size; | ||
| 319 | if size < max_packet_size || total == N { | ||
| 320 | self.offset.store(0, Ordering::Release); | ||
| 321 | break; | ||
| 322 | } else { | ||
| 323 | self.offset.store(total, Ordering::Release); | ||
| 324 | } | ||
| 325 | } | ||
| 326 | Err(err) => { | ||
| 327 | self.offset.store(0, Ordering::Release); | ||
| 328 | return Err(err.into()); | ||
| 329 | } | ||
| 330 | } | ||
| 331 | } | ||
| 332 | |||
| 333 | // Some hosts may send ZLPs even when not required by the HID spec, so we'll loop as long as total == 0. | ||
| 334 | if total > 0 { | ||
| 335 | break; | ||
| 336 | } | ||
| 337 | } | ||
| 338 | |||
| 339 | if starting_offset > 0 { | ||
| 340 | Err(ReadError::Sync(starting_offset..total)) | ||
| 341 | } else { | ||
| 342 | Ok(total) | ||
| 343 | } | ||
| 344 | } | ||
| 345 | } | ||
| 346 | |||
| 347 | pub trait RequestHandler { | ||
| 348 | /// Reads the value of report `id` into `buf` returning the size. | ||
| 349 | /// | ||
| 350 | /// Returns `None` if `id` is invalid or no data is available. | ||
| 351 | fn get_report(&self, id: ReportId, buf: &mut [u8]) -> Option<usize> { | ||
| 352 | let _ = (id, buf); | ||
| 353 | None | ||
| 354 | } | ||
| 355 | |||
| 356 | /// Sets the value of report `id` to `data`. | ||
| 357 | fn set_report(&self, id: ReportId, data: &[u8]) -> OutResponse { | ||
| 358 | let _ = (id, data); | ||
| 359 | OutResponse::Rejected | ||
| 360 | } | ||
| 361 | |||
| 362 | /// Get the idle rate for `id`. | ||
| 363 | /// | ||
| 364 | /// If `id` is `None`, get the idle rate for all reports. Returning `None` | ||
| 365 | /// will reject the control request. Any duration at or above 1.024 seconds | ||
| 366 | /// or below 4ms will be returned as an indefinite idle rate. | ||
| 367 | fn get_idle_ms(&self, id: Option<ReportId>) -> Option<u32> { | ||
| 368 | let _ = id; | ||
| 369 | None | ||
| 370 | } | ||
| 371 | |||
| 372 | /// Set the idle rate for `id` to `dur`. | ||
| 373 | /// | ||
| 374 | /// If `id` is `None`, set the idle rate of all input reports to `dur`. If | ||
| 375 | /// an indefinite duration is requested, `dur` will be set to `u32::MAX`. | ||
| 376 | fn set_idle_ms(&self, id: Option<ReportId>, duration_ms: u32) { | ||
| 377 | let _ = (id, duration_ms); | ||
| 378 | } | ||
| 379 | } | ||
| 380 | |||
| 381 | struct Control<'d> { | ||
| 382 | report_descriptor: &'d [u8], | ||
| 383 | request_handler: Option<&'d dyn RequestHandler>, | ||
| 384 | out_report_offset: &'d AtomicUsize, | ||
| 385 | hid_descriptor: [u8; 9], | ||
| 386 | } | ||
| 387 | |||
| 388 | impl<'d> Control<'d> { | ||
| 389 | fn new( | ||
| 390 | report_descriptor: &'d [u8], | ||
| 391 | request_handler: Option<&'d dyn RequestHandler>, | ||
| 392 | out_report_offset: &'d AtomicUsize, | ||
| 393 | ) -> Self { | ||
| 394 | Control { | ||
| 395 | report_descriptor, | ||
| 396 | request_handler, | ||
| 397 | out_report_offset, | ||
| 398 | hid_descriptor: [ | ||
| 399 | // Length of buf inclusive of size prefix | ||
| 400 | 9, | ||
| 401 | // Descriptor type | ||
| 402 | HID_DESC_DESCTYPE_HID, | ||
| 403 | // HID Class spec version | ||
| 404 | HID_DESC_SPEC_1_10[0], | ||
| 405 | HID_DESC_SPEC_1_10[1], | ||
| 406 | // Country code not supported | ||
| 407 | HID_DESC_COUNTRY_UNSPEC, | ||
| 408 | // Number of following descriptors | ||
| 409 | 1, | ||
| 410 | // We have a HID report descriptor the host should read | ||
| 411 | HID_DESC_DESCTYPE_HID_REPORT, | ||
| 412 | // HID report descriptor size, | ||
| 413 | (report_descriptor.len() & 0xFF) as u8, | ||
| 414 | (report_descriptor.len() >> 8 & 0xFF) as u8, | ||
| 415 | ], | ||
| 416 | } | ||
| 417 | } | ||
| 418 | } | ||
| 419 | |||
| 420 | impl<'d> ControlHandler for Control<'d> { | ||
| 421 | fn reset(&mut self) { | ||
| 422 | self.out_report_offset.store(0, Ordering::Release); | ||
| 423 | } | ||
| 424 | |||
| 425 | fn get_descriptor<'a>(&'a mut self, req: Request, _buf: &'a mut [u8]) -> InResponse<'a> { | ||
| 426 | match (req.value >> 8) as u8 { | ||
| 427 | HID_DESC_DESCTYPE_HID_REPORT => InResponse::Accepted(self.report_descriptor), | ||
| 428 | HID_DESC_DESCTYPE_HID => InResponse::Accepted(&self.hid_descriptor), | ||
| 429 | _ => InResponse::Rejected, | ||
| 430 | } | ||
| 431 | } | ||
| 432 | |||
| 433 | fn control_out(&mut self, req: Request, data: &[u8]) -> OutResponse { | ||
| 434 | trace!("HID control_out {:?} {=[u8]:x}", req, data); | ||
| 435 | if let RequestType::Class = req.request_type { | ||
| 436 | match req.request { | ||
| 437 | HID_REQ_SET_IDLE => { | ||
| 438 | if let Some(handler) = self.request_handler { | ||
| 439 | let id = req.value as u8; | ||
| 440 | let id = (id != 0).then(|| ReportId::In(id)); | ||
| 441 | let dur = u32::from(req.value >> 8); | ||
| 442 | let dur = if dur == 0 { u32::MAX } else { 4 * dur }; | ||
| 443 | handler.set_idle_ms(id, dur); | ||
| 444 | } | ||
| 445 | OutResponse::Accepted | ||
| 446 | } | ||
| 447 | HID_REQ_SET_REPORT => match (ReportId::try_from(req.value), self.request_handler) { | ||
| 448 | (Ok(id), Some(handler)) => handler.set_report(id, data), | ||
| 449 | _ => OutResponse::Rejected, | ||
| 450 | }, | ||
| 451 | HID_REQ_SET_PROTOCOL => { | ||
| 452 | if req.value == 1 { | ||
| 453 | OutResponse::Accepted | ||
| 454 | } else { | ||
| 455 | warn!("HID Boot Protocol is unsupported."); | ||
| 456 | OutResponse::Rejected // UNSUPPORTED: Boot Protocol | ||
| 457 | } | ||
| 458 | } | ||
| 459 | _ => OutResponse::Rejected, | ||
| 460 | } | ||
| 461 | } else { | ||
| 462 | OutResponse::Rejected // UNSUPPORTED: SET_DESCRIPTOR | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | fn control_in<'a>(&'a mut self, req: Request, buf: &'a mut [u8]) -> InResponse<'a> { | ||
| 467 | trace!("HID control_in {:?}", req); | ||
| 468 | match req.request { | ||
| 469 | HID_REQ_GET_REPORT => { | ||
| 470 | let size = match ReportId::try_from(req.value) { | ||
| 471 | Ok(id) => self.request_handler.and_then(|x| x.get_report(id, buf)), | ||
| 472 | Err(_) => None, | ||
| 473 | }; | ||
| 474 | |||
| 475 | if let Some(size) = size { | ||
| 476 | InResponse::Accepted(&buf[0..size]) | ||
| 477 | } else { | ||
| 478 | InResponse::Rejected | ||
| 479 | } | ||
| 480 | } | ||
| 481 | HID_REQ_GET_IDLE => { | ||
| 482 | if let Some(handler) = self.request_handler { | ||
| 483 | let id = req.value as u8; | ||
| 484 | let id = (id != 0).then(|| ReportId::In(id)); | ||
| 485 | if let Some(dur) = handler.get_idle_ms(id) { | ||
| 486 | let dur = u8::try_from(dur / 4).unwrap_or(0); | ||
| 487 | buf[0] = dur; | ||
| 488 | InResponse::Accepted(&buf[0..1]) | ||
| 489 | } else { | ||
| 490 | InResponse::Rejected | ||
| 491 | } | ||
| 492 | } else { | ||
| 493 | InResponse::Rejected | ||
| 494 | } | ||
| 495 | } | ||
| 496 | HID_REQ_GET_PROTOCOL => { | ||
| 497 | // UNSUPPORTED: Boot Protocol | ||
| 498 | buf[0] = 1; | ||
| 499 | InResponse::Accepted(&buf[0..1]) | ||
| 500 | } | ||
| 501 | _ => InResponse::Rejected, | ||
| 502 | } | ||
| 503 | } | ||
| 504 | } | ||
diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs new file mode 100644 index 000000000..af27577a6 --- /dev/null +++ b/embassy-usb/src/class/mod.rs | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | pub mod cdc_acm; | ||
| 2 | pub mod cdc_ncm; | ||
| 3 | pub mod hid; | ||
