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/cdc_acm.rs | |
| parent | f4f58249722bc656a13865e06535d208440c3e4a (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.rs | 354 |
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 @@ | |||
| 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 | } | ||
