aboutsummaryrefslogtreecommitdiff
path: root/embassy-usb/src/class/cdc_acm.rs
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/src/class/cdc_acm.rs
parentf4f58249722bc656a13865e06535d208440c3e4a (diff)
usb: move classes into the `embassy-usb` crate.
Diffstat (limited to 'embassy-usb/src/class/cdc_acm.rs')
-rw-r--r--embassy-usb/src/class/cdc_acm.rs354
1 files changed, 354 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 @@
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}