aboutsummaryrefslogtreecommitdiff
path: root/embassy-usb
diff options
context:
space:
mode:
authorDario Nieuwenhuis <[email protected]>2022-09-26 13:00:21 +0200
committerDario Nieuwenhuis <[email protected]>2022-09-26 13:00:21 +0200
commitf27a47a37b59bf3b9079f4d4d5f43caf7b7872f8 (patch)
tree732f73b4da7a2e726203f2876651a2141d9468be /embassy-usb
parentf4f58249722bc656a13865e06535d208440c3e4a (diff)
usb: move classes into the `embassy-usb` crate.
Diffstat (limited to 'embassy-usb')
-rw-r--r--embassy-usb/Cargo.toml9
-rw-r--r--embassy-usb/src/class/cdc_acm.rs354
-rw-r--r--embassy-usb/src/class/cdc_ncm.rs478
-rw-r--r--embassy-usb/src/class/hid.rs504
-rw-r--r--embassy-usb/src/class/mod.rs3
-rw-r--r--embassy-usb/src/lib.rs1
6 files changed, 1348 insertions, 1 deletions
diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml
index 660ecc8cc..aad54dbaf 100644
--- a/embassy-usb/Cargo.toml
+++ b/embassy-usb/Cargo.toml
@@ -11,11 +11,18 @@ target = "thumbv7em-none-eabi"
11 11
12[features] 12[features]
13defmt = ["dep:defmt", "embassy-usb-driver/defmt"] 13defmt = ["dep:defmt", "embassy-usb-driver/defmt"]
14usbd-hid = ["dep:usbd-hid", "dep:ssmarshal"]
15default = ["usbd-hid"]
14 16
15[dependencies] 17[dependencies]
16embassy-futures = { version = "0.1.0", path = "../embassy-futures" } 18embassy-futures = { version = "0.1.0", path = "../embassy-futures" }
17embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" } 19embassy-usb-driver = { version = "0.1.0", path = "../embassy-usb-driver" }
20embassy-sync = { version = "0.1.0", path = "../embassy-sync" }
18 21
19defmt = { version = "0.3", optional = true } 22defmt = { version = "0.3", optional = true }
20log = { version = "0.4.14", optional = true } 23log = { version = "0.4.14", optional = true }
21heapless = "0.7.10" \ No newline at end of file 24heapless = "0.7.10"
25
26# for HID
27usbd-hid = { version = "0.6.0", optional = true }
28ssmarshal = { version = "1.0", default-features = false, optional = true }
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 @@
1use core::cell::Cell;
2use core::mem::{self, MaybeUninit};
3use core::sync::atomic::{AtomicBool, Ordering};
4
5use embassy_sync::blocking_mutex::CriticalSectionMutex;
6
7use crate::control::{self, ControlHandler, InResponse, OutResponse, Request};
8use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
9use crate::types::*;
10use crate::Builder;
11
12/// This should be used as `device_class` when building the `UsbDevice`.
13pub const USB_CLASS_CDC: u8 = 0x02;
14
15const USB_CLASS_CDC_DATA: u8 = 0x0a;
16const CDC_SUBCLASS_ACM: u8 = 0x02;
17const CDC_PROTOCOL_NONE: u8 = 0x00;
18
19const CS_INTERFACE: u8 = 0x24;
20const CDC_TYPE_HEADER: u8 = 0x00;
21const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01;
22const CDC_TYPE_ACM: u8 = 0x02;
23const CDC_TYPE_UNION: u8 = 0x06;
24
25const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00;
26#[allow(unused)]
27const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01;
28const REQ_SET_LINE_CODING: u8 = 0x20;
29const REQ_GET_LINE_CODING: u8 = 0x21;
30const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22;
31
32pub struct State<'a> {
33 control: MaybeUninit<Control<'a>>,
34 shared: ControlShared,
35}
36
37impl<'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.
58pub 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
66struct Control<'a> {
67 shared: &'a ControlShared,
68}
69
70/// Shared data between Control and CdcAcmClass
71struct ControlShared {
72 line_coding: CriticalSectionMutex<Cell<LineCoding>>,
73 dtr: AtomicBool,
74 rts: AtomicBool,
75}
76
77impl 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
92impl<'a> Control<'a> {
93 fn shared(&mut self) -> &'a ControlShared {
94 self.shared
95 }
96}
97
98impl<'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
158impl<'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))]
268pub 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
279impl 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))]
292pub enum ParityType {
293 None = 0,
294 Odd = 1,
295 Even = 2,
296 Mark = 3,
297 Space = 4,
298}
299
300impl 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))]
316pub struct LineCoding {
317 stop_bits: StopBits,
318 data_bits: u8,
319 parity_type: ParityType,
320 data_rate: u32,
321}
322
323impl 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
345impl 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 @@
1use core::intrinsics::copy_nonoverlapping;
2use core::mem::{size_of, MaybeUninit};
3
4use crate::control::{self, ControlHandler, InResponse, OutResponse, Request};
5use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
6use crate::types::*;
7use crate::Builder;
8
9/// This should be used as `device_class` when building the `UsbDevice`.
10pub const USB_CLASS_CDC: u8 = 0x02;
11
12const USB_CLASS_CDC_DATA: u8 = 0x0a;
13const CDC_SUBCLASS_NCM: u8 = 0x0d;
14
15const CDC_PROTOCOL_NONE: u8 = 0x00;
16const CDC_PROTOCOL_NTB: u8 = 0x01;
17
18const CS_INTERFACE: u8 = 0x24;
19const CDC_TYPE_HEADER: u8 = 0x00;
20const CDC_TYPE_UNION: u8 = 0x06;
21const CDC_TYPE_ETHERNET: u8 = 0x0F;
22const CDC_TYPE_NCM: u8 = 0x1A;
23
24const 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;
31const 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;
37const 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
46const NTB_MAX_SIZE: usize = 2048;
47const SIG_NTH: u32 = 0x484d434e;
48const SIG_NDP_NO_FCS: u32 = 0x304d434e;
49const SIG_NDP_WITH_FCS: u32 = 0x314d434e;
50
51const ALTERNATE_SETTING_DISABLED: u8 = 0x00;
52const ALTERNATE_SETTING_ENABLED: u8 = 0x01;
53
54/// Simple NTB header (NTH+NDP all in one) for sending packets
55#[repr(packed)]
56#[allow(unused)]
57struct 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)]
77struct NtbParameters {
78 length: u16,
79 formats_supported: u16,
80 in_params: NtbParametersDir,
81 out_params: NtbParametersDir,
82}
83
84#[repr(packed)]
85#[allow(unused)]
86struct NtbParametersDir {
87 max_size: u32,
88 divisor: u16,
89 payload_remainder: u16,
90 out_alignment: u16,
91 max_datagram_count: u16,
92}
93
94fn 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
100pub struct State<'a> {
101 comm_control: MaybeUninit<CommControl<'a>>,
102 data_control: MaybeUninit<DataControl>,
103 shared: ControlShared,
104}
105
106impl<'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
117struct ControlShared {
118 mac_addr: [u8; 6],
119}
120
121impl Default for ControlShared {
122 fn default() -> Self {
123 ControlShared { mac_addr: [0; 6] }
124 }
125}
126
127struct CommControl<'a> {
128 mac_addr_string: StringIndex,
129 shared: &'a ControlShared,
130 mac_addr_str: [u8; 12],
131}
132
133impl<'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
197struct DataControl {}
198
199impl 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
209pub 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
220impl<'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
320pub struct Sender<'d, D: Driver<'d>> {
321 write_ep: D::EndpointIn,
322 seq: u16,
323}
324
325impl<'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
379pub struct Receiver<'d, D: Driver<'d>> {
380 data_if: InterfaceNumber,
381 comm_ep: D::EndpointIn,
382 read_ep: D::EndpointOut,
383}
384
385impl<'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 @@
1use core::mem::MaybeUninit;
2use core::ops::Range;
3use core::sync::atomic::{AtomicUsize, Ordering};
4
5#[cfg(feature = "usbd-hid")]
6use ssmarshal::serialize;
7#[cfg(feature = "usbd-hid")]
8use usbd_hid::descriptor::AsInputReport;
9
10use crate::control::{ControlHandler, InResponse, OutResponse, Request, RequestType};
11use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut};
12use crate::Builder;
13
14const USB_CLASS_HID: u8 = 0x03;
15const USB_SUBCLASS_NONE: u8 = 0x00;
16const USB_PROTOCOL_NONE: u8 = 0x00;
17
18// HID
19const HID_DESC_DESCTYPE_HID: u8 = 0x21;
20const HID_DESC_DESCTYPE_HID_REPORT: u8 = 0x22;
21const HID_DESC_SPEC_1_10: [u8; 2] = [0x10, 0x01];
22const HID_DESC_COUNTRY_UNSPEC: u8 = 0x00;
23
24const HID_REQ_SET_IDLE: u8 = 0x0a;
25const HID_REQ_GET_IDLE: u8 = 0x02;
26const HID_REQ_GET_REPORT: u8 = 0x01;
27const HID_REQ_SET_REPORT: u8 = 0x09;
28const HID_REQ_GET_PROTOCOL: u8 = 0x03;
29const HID_REQ_SET_PROTOCOL: u8 = 0x0b;
30
31pub 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))]
51pub enum ReportId {
52 In(u8),
53 Out(u8),
54 Feature(u8),
55}
56
57impl 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
68pub struct State<'d> {
69 control: MaybeUninit<Control<'d>>,
70 out_report_offset: AtomicUsize,
71}
72
73impl<'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
82pub 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
87fn 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
135impl<'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
183pub struct HidWriter<'d, D: Driver<'d>, const N: usize> {
184 ep_in: D::EndpointIn,
185}
186
187pub 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))]
194pub enum ReadError {
195 BufferOverflow,
196 Disabled,
197 Sync(Range<usize>),
198}
199
200impl 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
210impl<'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
262impl<'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
347pub 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
381struct 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
388impl<'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
420impl<'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 @@
1pub mod cdc_acm;
2pub mod cdc_ncm;
3pub mod hid;
diff --git a/embassy-usb/src/lib.rs b/embassy-usb/src/lib.rs
index e1a99cfae..661b84119 100644
--- a/embassy-usb/src/lib.rs
+++ b/embassy-usb/src/lib.rs
@@ -7,6 +7,7 @@ pub(crate) mod fmt;
7pub use embassy_usb_driver as driver; 7pub use embassy_usb_driver as driver;
8 8
9mod builder; 9mod builder;
10pub mod class;
10pub mod control; 11pub mod control;
11pub mod descriptor; 12pub mod descriptor;
12mod descriptor_reader; 13mod descriptor_reader;