diff options
| author | Dario Nieuwenhuis <[email protected]> | 2022-05-04 01:00:38 +0200 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2022-05-04 01:41:37 +0200 |
| commit | fc32b3750c448a81b7dd44cf9de98723b8eb4fcf (patch) | |
| tree | 989562f5750d28ab11732633839db8f25a1ad773 /embassy-hal-common | |
| parent | 85c0525e01c52bbb85c7b93600a60837ee7b87dc (diff) | |
Remove embassy_hal_common::usb.
The replacement is `embassy-usb`. There's a WIP driver for stm32 USBD in #709,
there's no WIP driver for stm32 USB_OTG. This means we're left without
USB_OTG support for now.
Reason for removing is I'm going to soon remove `embassy::io`, and
USB uses it. I don't want to spend time maintaining "dead" code
that is going to be removed. Volunteers welcome, either to update
old USB to the new IO, or write a USB_OTG driver fo the new USB.
Diffstat (limited to 'embassy-hal-common')
| -rw-r--r-- | embassy-hal-common/Cargo.toml | 1 | ||||
| -rw-r--r-- | embassy-hal-common/src/lib.rs | 1 | ||||
| -rw-r--r-- | embassy-hal-common/src/usb/cdc_acm.rs | 338 | ||||
| -rw-r--r-- | embassy-hal-common/src/usb/mod.rs | 267 | ||||
| -rw-r--r-- | embassy-hal-common/src/usb/usb_serial.rs | 345 |
5 files changed, 0 insertions, 952 deletions
diff --git a/embassy-hal-common/Cargo.toml b/embassy-hal-common/Cargo.toml index 2028b0e0c..fee6da6ff 100644 --- a/embassy-hal-common/Cargo.toml +++ b/embassy-hal-common/Cargo.toml | |||
| @@ -12,5 +12,4 @@ embassy = { version = "0.1.0", path = "../embassy" } | |||
| 12 | defmt = { version = "0.3", optional = true } | 12 | defmt = { version = "0.3", optional = true } |
| 13 | log = { version = "0.4.14", optional = true } | 13 | log = { version = "0.4.14", optional = true } |
| 14 | cortex-m = "0.7.3" | 14 | cortex-m = "0.7.3" |
| 15 | usb-device = "0.2.8" | ||
| 16 | num-traits = { version = "0.2.14", default-features = false } | 15 | num-traits = { version = "0.2.14", default-features = false } |
diff --git a/embassy-hal-common/src/lib.rs b/embassy-hal-common/src/lib.rs index 1af30c6b4..6ee2ccd59 100644 --- a/embassy-hal-common/src/lib.rs +++ b/embassy-hal-common/src/lib.rs | |||
| @@ -10,7 +10,6 @@ mod macros; | |||
| 10 | pub mod peripheral; | 10 | pub mod peripheral; |
| 11 | pub mod ratio; | 11 | pub mod ratio; |
| 12 | pub mod ring_buffer; | 12 | pub mod ring_buffer; |
| 13 | pub mod usb; | ||
| 14 | 13 | ||
| 15 | /// Low power blocking wait loop using WFE/SEV. | 14 | /// Low power blocking wait loop using WFE/SEV. |
| 16 | pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) { | 15 | pub fn low_power_wait_until(mut condition: impl FnMut() -> bool) { |
diff --git a/embassy-hal-common/src/usb/cdc_acm.rs b/embassy-hal-common/src/usb/cdc_acm.rs deleted file mode 100644 index 5a85b3846..000000000 --- a/embassy-hal-common/src/usb/cdc_acm.rs +++ /dev/null | |||
| @@ -1,338 +0,0 @@ | |||
| 1 | // Copied from https://github.com/mvirkkunen/usbd-serial | ||
| 2 | #![allow(dead_code)] | ||
| 3 | |||
| 4 | use core::convert::TryInto; | ||
| 5 | use core::mem; | ||
| 6 | use usb_device::class_prelude::*; | ||
| 7 | use usb_device::Result; | ||
| 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_ACM: u8 = 0x02; | ||
| 14 | const CDC_PROTOCOL_NONE: u8 = 0x00; | ||
| 15 | |||
| 16 | const CS_INTERFACE: u8 = 0x24; | ||
| 17 | const CDC_TYPE_HEADER: u8 = 0x00; | ||
| 18 | const CDC_TYPE_CALL_MANAGEMENT: u8 = 0x01; | ||
| 19 | const CDC_TYPE_ACM: u8 = 0x02; | ||
| 20 | const CDC_TYPE_UNION: u8 = 0x06; | ||
| 21 | |||
| 22 | const REQ_SEND_ENCAPSULATED_COMMAND: u8 = 0x00; | ||
| 23 | #[allow(unused)] | ||
| 24 | const REQ_GET_ENCAPSULATED_COMMAND: u8 = 0x01; | ||
| 25 | const REQ_SET_LINE_CODING: u8 = 0x20; | ||
| 26 | const REQ_GET_LINE_CODING: u8 = 0x21; | ||
| 27 | const REQ_SET_CONTROL_LINE_STATE: u8 = 0x22; | ||
| 28 | |||
| 29 | /// Packet level implementation of a CDC-ACM serial port. | ||
| 30 | /// | ||
| 31 | /// This class can be used directly and it has the least overhead due to directly reading and | ||
| 32 | /// writing USB packets with no intermediate buffers, but it will not act like a stream-like serial | ||
| 33 | /// port. The following constraints must be followed if you use this class directly: | ||
| 34 | /// | ||
| 35 | /// - `read_packet` must be called with a buffer large enough to hold max_packet_size bytes, and the | ||
| 36 | /// method will return a `WouldBlock` error if there is no packet to be read. | ||
| 37 | /// - `write_packet` must not be called with a buffer larger than max_packet_size bytes, and the | ||
| 38 | /// method will return a `WouldBlock` error if the previous packet has not been sent yet. | ||
| 39 | /// - If you write a packet that is exactly max_packet_size bytes long, it won't be processed by the | ||
| 40 | /// host operating system until a subsequent shorter packet is sent. A zero-length packet (ZLP) | ||
| 41 | /// can be sent if there is no other data to send. This is because USB bulk transactions must be | ||
| 42 | /// terminated with a short packet, even if the bulk endpoint is used for stream-like data. | ||
| 43 | pub struct CdcAcmClass<'a, B: UsbBus> { | ||
| 44 | comm_if: InterfaceNumber, | ||
| 45 | comm_ep: EndpointIn<'a, B>, | ||
| 46 | data_if: InterfaceNumber, | ||
| 47 | read_ep: EndpointOut<'a, B>, | ||
| 48 | write_ep: EndpointIn<'a, B>, | ||
| 49 | line_coding: LineCoding, | ||
| 50 | dtr: bool, | ||
| 51 | rts: bool, | ||
| 52 | } | ||
| 53 | |||
| 54 | impl<B: UsbBus> CdcAcmClass<'_, B> { | ||
| 55 | /// Creates a new CdcAcmClass with the provided UsbBus and max_packet_size in bytes. For | ||
| 56 | /// full-speed devices, max_packet_size has to be one of 8, 16, 32 or 64. | ||
| 57 | pub fn new(alloc: &UsbBusAllocator<B>, max_packet_size: u16) -> CdcAcmClass<'_, B> { | ||
| 58 | CdcAcmClass { | ||
| 59 | comm_if: alloc.interface(), | ||
| 60 | comm_ep: alloc.interrupt(8, 255), | ||
| 61 | data_if: alloc.interface(), | ||
| 62 | read_ep: alloc.bulk(max_packet_size), | ||
| 63 | write_ep: alloc.bulk(max_packet_size), | ||
| 64 | line_coding: LineCoding { | ||
| 65 | stop_bits: StopBits::One, | ||
| 66 | data_bits: 8, | ||
| 67 | parity_type: ParityType::None, | ||
| 68 | data_rate: 8_000, | ||
| 69 | }, | ||
| 70 | dtr: false, | ||
| 71 | rts: false, | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | /// Gets the maximum packet size in bytes. | ||
| 76 | pub fn max_packet_size(&self) -> u16 { | ||
| 77 | // The size is the same for both endpoints. | ||
| 78 | self.read_ep.max_packet_size() | ||
| 79 | } | ||
| 80 | |||
| 81 | /// Gets the current line coding. The line coding contains information that's mainly relevant | ||
| 82 | /// for USB to UART serial port emulators, and can be ignored if not relevant. | ||
| 83 | pub fn line_coding(&self) -> &LineCoding { | ||
| 84 | &self.line_coding | ||
| 85 | } | ||
| 86 | |||
| 87 | /// Gets the DTR (data terminal ready) state | ||
| 88 | pub fn dtr(&self) -> bool { | ||
| 89 | self.dtr | ||
| 90 | } | ||
| 91 | |||
| 92 | /// Gets the RTS (request to send) state | ||
| 93 | pub fn rts(&self) -> bool { | ||
| 94 | self.rts | ||
| 95 | } | ||
| 96 | |||
| 97 | /// Writes a single packet into the IN endpoint. | ||
| 98 | pub fn write_packet(&mut self, data: &[u8]) -> Result<usize> { | ||
| 99 | self.write_ep.write(data) | ||
| 100 | } | ||
| 101 | |||
| 102 | /// Reads a single packet from the OUT endpoint. | ||
| 103 | pub fn read_packet(&mut self, data: &mut [u8]) -> Result<usize> { | ||
| 104 | self.read_ep.read(data) | ||
| 105 | } | ||
| 106 | |||
| 107 | /// Gets the address of the IN endpoint. | ||
| 108 | pub fn write_ep_address(&self) -> EndpointAddress { | ||
| 109 | self.write_ep.address() | ||
| 110 | } | ||
| 111 | |||
| 112 | /// Gets the address of the OUT endpoint. | ||
| 113 | pub fn read_ep_address(&self) -> EndpointAddress { | ||
| 114 | self.read_ep.address() | ||
| 115 | } | ||
| 116 | } | ||
| 117 | |||
| 118 | impl<B: UsbBus> UsbClass<B> for CdcAcmClass<'_, B> { | ||
| 119 | fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> { | ||
| 120 | writer.iad( | ||
| 121 | self.comm_if, | ||
| 122 | 2, | ||
| 123 | USB_CLASS_CDC, | ||
| 124 | CDC_SUBCLASS_ACM, | ||
| 125 | CDC_PROTOCOL_NONE, | ||
| 126 | )?; | ||
| 127 | |||
| 128 | writer.interface( | ||
| 129 | self.comm_if, | ||
| 130 | USB_CLASS_CDC, | ||
| 131 | CDC_SUBCLASS_ACM, | ||
| 132 | CDC_PROTOCOL_NONE, | ||
| 133 | )?; | ||
| 134 | |||
| 135 | writer.write( | ||
| 136 | CS_INTERFACE, | ||
| 137 | &[ | ||
| 138 | CDC_TYPE_HEADER, // bDescriptorSubtype | ||
| 139 | 0x10, | ||
| 140 | 0x01, // bcdCDC (1.10) | ||
| 141 | ], | ||
| 142 | )?; | ||
| 143 | |||
| 144 | writer.write( | ||
| 145 | CS_INTERFACE, | ||
| 146 | &[ | ||
| 147 | CDC_TYPE_ACM, // bDescriptorSubtype | ||
| 148 | 0x00, // bmCapabilities | ||
| 149 | ], | ||
| 150 | )?; | ||
| 151 | |||
| 152 | writer.write( | ||
| 153 | CS_INTERFACE, | ||
| 154 | &[ | ||
| 155 | CDC_TYPE_UNION, // bDescriptorSubtype | ||
| 156 | self.comm_if.into(), // bControlInterface | ||
| 157 | self.data_if.into(), // bSubordinateInterface | ||
| 158 | ], | ||
| 159 | )?; | ||
| 160 | |||
| 161 | writer.write( | ||
| 162 | CS_INTERFACE, | ||
| 163 | &[ | ||
| 164 | CDC_TYPE_CALL_MANAGEMENT, // bDescriptorSubtype | ||
| 165 | 0x00, // bmCapabilities | ||
| 166 | self.data_if.into(), // bDataInterface | ||
| 167 | ], | ||
| 168 | )?; | ||
| 169 | |||
| 170 | writer.endpoint(&self.comm_ep)?; | ||
| 171 | |||
| 172 | writer.interface(self.data_if, USB_CLASS_CDC_DATA, 0x00, 0x00)?; | ||
| 173 | |||
| 174 | writer.endpoint(&self.write_ep)?; | ||
| 175 | writer.endpoint(&self.read_ep)?; | ||
| 176 | |||
| 177 | Ok(()) | ||
| 178 | } | ||
| 179 | |||
| 180 | fn reset(&mut self) { | ||
| 181 | self.line_coding = LineCoding::default(); | ||
| 182 | self.dtr = false; | ||
| 183 | self.rts = false; | ||
| 184 | } | ||
| 185 | |||
| 186 | fn control_in(&mut self, xfer: ControlIn<B>) { | ||
| 187 | let req = xfer.request(); | ||
| 188 | |||
| 189 | if !(req.request_type == control::RequestType::Class | ||
| 190 | && req.recipient == control::Recipient::Interface | ||
| 191 | && req.index == u8::from(self.comm_if) as u16) | ||
| 192 | { | ||
| 193 | return; | ||
| 194 | } | ||
| 195 | |||
| 196 | match req.request { | ||
| 197 | // REQ_GET_ENCAPSULATED_COMMAND is not really supported - it will be rejected below. | ||
| 198 | REQ_GET_LINE_CODING if req.length == 7 => { | ||
| 199 | xfer.accept(|data| { | ||
| 200 | data[0..4].copy_from_slice(&self.line_coding.data_rate.to_le_bytes()); | ||
| 201 | data[4] = self.line_coding.stop_bits as u8; | ||
| 202 | data[5] = self.line_coding.parity_type as u8; | ||
| 203 | data[6] = self.line_coding.data_bits; | ||
| 204 | |||
| 205 | Ok(7) | ||
| 206 | }) | ||
| 207 | .ok(); | ||
| 208 | } | ||
| 209 | _ => { | ||
| 210 | xfer.reject().ok(); | ||
| 211 | } | ||
| 212 | } | ||
| 213 | } | ||
| 214 | |||
| 215 | fn control_out(&mut self, xfer: ControlOut<B>) { | ||
| 216 | let req = xfer.request(); | ||
| 217 | |||
| 218 | if !(req.request_type == control::RequestType::Class | ||
| 219 | && req.recipient == control::Recipient::Interface | ||
| 220 | && req.index == u8::from(self.comm_if) as u16) | ||
| 221 | { | ||
| 222 | return; | ||
| 223 | } | ||
| 224 | |||
| 225 | match req.request { | ||
| 226 | REQ_SEND_ENCAPSULATED_COMMAND => { | ||
| 227 | // We don't actually support encapsulated commands but pretend we do for standards | ||
| 228 | // compatibility. | ||
| 229 | xfer.accept().ok(); | ||
| 230 | } | ||
| 231 | REQ_SET_LINE_CODING if xfer.data().len() >= 7 => { | ||
| 232 | self.line_coding.data_rate = | ||
| 233 | u32::from_le_bytes(xfer.data()[0..4].try_into().unwrap()); | ||
| 234 | self.line_coding.stop_bits = xfer.data()[4].into(); | ||
| 235 | self.line_coding.parity_type = xfer.data()[5].into(); | ||
| 236 | self.line_coding.data_bits = xfer.data()[6]; | ||
| 237 | |||
| 238 | xfer.accept().ok(); | ||
| 239 | } | ||
| 240 | REQ_SET_CONTROL_LINE_STATE => { | ||
| 241 | self.dtr = (req.value & 0x0001) != 0; | ||
| 242 | self.rts = (req.value & 0x0002) != 0; | ||
| 243 | |||
| 244 | xfer.accept().ok(); | ||
| 245 | } | ||
| 246 | _ => { | ||
| 247 | xfer.reject().ok(); | ||
| 248 | } | ||
| 249 | }; | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | /// Number of stop bits for LineCoding | ||
| 254 | #[derive(Copy, Clone, PartialEq, Eq)] | ||
| 255 | pub enum StopBits { | ||
| 256 | /// 1 stop bit | ||
| 257 | One = 0, | ||
| 258 | |||
| 259 | /// 1.5 stop bits | ||
| 260 | OnePointFive = 1, | ||
| 261 | |||
| 262 | /// 2 stop bits | ||
| 263 | Two = 2, | ||
| 264 | } | ||
| 265 | |||
| 266 | impl From<u8> for StopBits { | ||
| 267 | fn from(value: u8) -> Self { | ||
| 268 | if value <= 2 { | ||
| 269 | unsafe { mem::transmute(value) } | ||
| 270 | } else { | ||
| 271 | StopBits::One | ||
| 272 | } | ||
| 273 | } | ||
| 274 | } | ||
| 275 | |||
| 276 | /// Parity for LineCoding | ||
| 277 | #[derive(Copy, Clone, PartialEq, Eq)] | ||
| 278 | pub enum ParityType { | ||
| 279 | None = 0, | ||
| 280 | Odd = 1, | ||
| 281 | Event = 2, | ||
| 282 | Mark = 3, | ||
| 283 | Space = 4, | ||
| 284 | } | ||
| 285 | |||
| 286 | impl From<u8> for ParityType { | ||
| 287 | fn from(value: u8) -> Self { | ||
| 288 | if value <= 4 { | ||
| 289 | unsafe { mem::transmute(value) } | ||
| 290 | } else { | ||
| 291 | ParityType::None | ||
| 292 | } | ||
| 293 | } | ||
| 294 | } | ||
| 295 | |||
| 296 | /// Line coding parameters | ||
| 297 | /// | ||
| 298 | /// This is provided by the host for specifying the standard UART parameters such as baud rate. Can | ||
| 299 | /// be ignored if you don't plan to interface with a physical UART. | ||
| 300 | pub struct LineCoding { | ||
| 301 | stop_bits: StopBits, | ||
| 302 | data_bits: u8, | ||
| 303 | parity_type: ParityType, | ||
| 304 | data_rate: u32, | ||
| 305 | } | ||
| 306 | |||
| 307 | impl LineCoding { | ||
| 308 | /// Gets the number of stop bits for UART communication. | ||
| 309 | pub fn stop_bits(&self) -> StopBits { | ||
| 310 | self.stop_bits | ||
| 311 | } | ||
| 312 | |||
| 313 | /// Gets the number of data bits for UART communication. | ||
| 314 | pub fn data_bits(&self) -> u8 { | ||
| 315 | self.data_bits | ||
| 316 | } | ||
| 317 | |||
| 318 | /// Gets the parity type for UART communication. | ||
| 319 | pub fn parity_type(&self) -> ParityType { | ||
| 320 | self.parity_type | ||
| 321 | } | ||
| 322 | |||
| 323 | /// Gets the data rate in bits per second for UART communication. | ||
| 324 | pub fn data_rate(&self) -> u32 { | ||
| 325 | self.data_rate | ||
| 326 | } | ||
| 327 | } | ||
| 328 | |||
| 329 | impl Default for LineCoding { | ||
| 330 | fn default() -> Self { | ||
| 331 | LineCoding { | ||
| 332 | stop_bits: StopBits::One, | ||
| 333 | data_bits: 8, | ||
| 334 | parity_type: ParityType::None, | ||
| 335 | data_rate: 8_000, | ||
| 336 | } | ||
| 337 | } | ||
| 338 | } | ||
diff --git a/embassy-hal-common/src/usb/mod.rs b/embassy-hal-common/src/usb/mod.rs deleted file mode 100644 index bab72d8b6..000000000 --- a/embassy-hal-common/src/usb/mod.rs +++ /dev/null | |||
| @@ -1,267 +0,0 @@ | |||
| 1 | use core::cell::RefCell; | ||
| 2 | use core::marker::PhantomData; | ||
| 3 | use core::pin::Pin; | ||
| 4 | |||
| 5 | use usb_device::bus::UsbBus; | ||
| 6 | use usb_device::class::UsbClass; | ||
| 7 | use usb_device::device::UsbDevice; | ||
| 8 | |||
| 9 | mod cdc_acm; | ||
| 10 | pub mod usb_serial; | ||
| 11 | |||
| 12 | use crate::peripheral::{PeripheralMutex, PeripheralState, StateStorage}; | ||
| 13 | use embassy::interrupt::Interrupt; | ||
| 14 | pub use usb_serial::{ReadInterface, UsbSerial, WriteInterface}; | ||
| 15 | |||
| 16 | /// Marker trait to mark an interrupt to be used with the [`Usb`] abstraction. | ||
| 17 | pub unsafe trait USBInterrupt: Interrupt + Send {} | ||
| 18 | |||
| 19 | pub struct State<'bus, B, T, I>(StateStorage<StateInner<'bus, B, T, I>>) | ||
| 20 | where | ||
| 21 | B: UsbBus, | ||
| 22 | T: ClassSet<B>, | ||
| 23 | I: USBInterrupt; | ||
| 24 | |||
| 25 | impl<'bus, B, T, I> State<'bus, B, T, I> | ||
| 26 | where | ||
| 27 | B: UsbBus, | ||
| 28 | T: ClassSet<B>, | ||
| 29 | I: USBInterrupt, | ||
| 30 | { | ||
| 31 | pub fn new() -> Self { | ||
| 32 | Self(StateStorage::new()) | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | pub(crate) struct StateInner<'bus, B, T, I> | ||
| 37 | where | ||
| 38 | B: UsbBus, | ||
| 39 | T: ClassSet<B>, | ||
| 40 | I: USBInterrupt, | ||
| 41 | { | ||
| 42 | device: UsbDevice<'bus, B>, | ||
| 43 | pub(crate) classes: T, | ||
| 44 | _interrupt: PhantomData<I>, | ||
| 45 | } | ||
| 46 | |||
| 47 | pub struct Usb<'bus, B, T, I> | ||
| 48 | where | ||
| 49 | B: UsbBus, | ||
| 50 | T: ClassSet<B>, | ||
| 51 | I: USBInterrupt, | ||
| 52 | { | ||
| 53 | // Don't you dare moving out `PeripheralMutex` | ||
| 54 | inner: RefCell<PeripheralMutex<'bus, StateInner<'bus, B, T, I>>>, | ||
| 55 | } | ||
| 56 | |||
| 57 | impl<'bus, B, T, I> Usb<'bus, B, T, I> | ||
| 58 | where | ||
| 59 | B: UsbBus, | ||
| 60 | T: ClassSet<B>, | ||
| 61 | I: USBInterrupt, | ||
| 62 | { | ||
| 63 | /// safety: the returned instance is not leak-safe | ||
| 64 | pub unsafe fn new<S: IntoClassSet<B, T>>( | ||
| 65 | state: &'bus mut State<'bus, B, T, I>, | ||
| 66 | device: UsbDevice<'bus, B>, | ||
| 67 | class_set: S, | ||
| 68 | irq: I, | ||
| 69 | ) -> Self { | ||
| 70 | let mutex = PeripheralMutex::new_unchecked(irq, &mut state.0, || StateInner { | ||
| 71 | device, | ||
| 72 | classes: class_set.into_class_set(), | ||
| 73 | _interrupt: PhantomData, | ||
| 74 | }); | ||
| 75 | Self { | ||
| 76 | inner: RefCell::new(mutex), | ||
| 77 | } | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | impl<'bus, 'c, B, T, I> Usb<'bus, B, T, I> | ||
| 82 | where | ||
| 83 | B: UsbBus, | ||
| 84 | T: ClassSet<B> + SerialState<'bus, 'c, B, Index0>, | ||
| 85 | I: USBInterrupt, | ||
| 86 | { | ||
| 87 | /// Take a serial class that was passed as the first class in a tuple | ||
| 88 | pub fn take_serial_0<'a>( | ||
| 89 | self: Pin<&'a Self>, | ||
| 90 | ) -> ( | ||
| 91 | ReadInterface<'a, 'bus, 'c, Index0, B, T, I>, | ||
| 92 | WriteInterface<'a, 'bus, 'c, Index0, B, T, I>, | ||
| 93 | ) { | ||
| 94 | let this = self.get_ref(); | ||
| 95 | |||
| 96 | let r = ReadInterface { | ||
| 97 | inner: &this.inner, | ||
| 98 | _buf_lifetime: PhantomData, | ||
| 99 | _index: PhantomData, | ||
| 100 | }; | ||
| 101 | |||
| 102 | let w = WriteInterface { | ||
| 103 | inner: &this.inner, | ||
| 104 | _buf_lifetime: PhantomData, | ||
| 105 | _index: PhantomData, | ||
| 106 | }; | ||
| 107 | (r, w) | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | impl<'bus, 'c, B, T, I> Usb<'bus, B, T, I> | ||
| 112 | where | ||
| 113 | B: UsbBus, | ||
| 114 | T: ClassSet<B> + SerialState<'bus, 'c, B, Index1>, | ||
| 115 | I: USBInterrupt, | ||
| 116 | { | ||
| 117 | /// Take a serial class that was passed as the second class in a tuple | ||
| 118 | pub fn take_serial_1<'a>( | ||
| 119 | self: Pin<&'a Self>, | ||
| 120 | ) -> ( | ||
| 121 | ReadInterface<'a, 'bus, 'c, Index1, B, T, I>, | ||
| 122 | WriteInterface<'a, 'bus, 'c, Index1, B, T, I>, | ||
| 123 | ) { | ||
| 124 | let this = self.get_ref(); | ||
| 125 | |||
| 126 | let r = ReadInterface { | ||
| 127 | inner: &this.inner, | ||
| 128 | _buf_lifetime: PhantomData, | ||
| 129 | _index: PhantomData, | ||
| 130 | }; | ||
| 131 | |||
| 132 | let w = WriteInterface { | ||
| 133 | inner: &this.inner, | ||
| 134 | _buf_lifetime: PhantomData, | ||
| 135 | _index: PhantomData, | ||
| 136 | }; | ||
| 137 | (r, w) | ||
| 138 | } | ||
| 139 | } | ||
| 140 | |||
| 141 | impl<'bus, B, T, I> PeripheralState for StateInner<'bus, B, T, I> | ||
| 142 | where | ||
| 143 | B: UsbBus, | ||
| 144 | T: ClassSet<B>, | ||
| 145 | I: USBInterrupt, | ||
| 146 | { | ||
| 147 | type Interrupt = I; | ||
| 148 | fn on_interrupt(&mut self) { | ||
| 149 | self.classes.poll_all(&mut self.device); | ||
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | pub trait ClassSet<B: UsbBus>: Send { | ||
| 154 | fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool; | ||
| 155 | } | ||
| 156 | |||
| 157 | pub trait IntoClassSet<B: UsbBus, C: ClassSet<B>> { | ||
| 158 | fn into_class_set(self) -> C; | ||
| 159 | } | ||
| 160 | |||
| 161 | pub struct ClassSet1<B, C1> | ||
| 162 | where | ||
| 163 | B: UsbBus, | ||
| 164 | C1: UsbClass<B>, | ||
| 165 | { | ||
| 166 | class: C1, | ||
| 167 | _bus: PhantomData<B>, | ||
| 168 | } | ||
| 169 | |||
| 170 | pub struct ClassSet2<B, C1, C2> | ||
| 171 | where | ||
| 172 | B: UsbBus, | ||
| 173 | C1: UsbClass<B>, | ||
| 174 | C2: UsbClass<B>, | ||
| 175 | { | ||
| 176 | class1: C1, | ||
| 177 | class2: C2, | ||
| 178 | _bus: PhantomData<B>, | ||
| 179 | } | ||
| 180 | |||
| 181 | /// The first class into a [`ClassSet`] | ||
| 182 | pub struct Index0; | ||
| 183 | |||
| 184 | /// The second class into a [`ClassSet`] | ||
| 185 | pub struct Index1; | ||
| 186 | |||
| 187 | impl<B, C1> ClassSet<B> for ClassSet1<B, C1> | ||
| 188 | where | ||
| 189 | B: UsbBus + Send, | ||
| 190 | C1: UsbClass<B> + Send, | ||
| 191 | { | ||
| 192 | fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool { | ||
| 193 | device.poll(&mut [&mut self.class]) | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | impl<B, C1, C2> ClassSet<B> for ClassSet2<B, C1, C2> | ||
| 198 | where | ||
| 199 | B: UsbBus + Send, | ||
| 200 | C1: UsbClass<B> + Send, | ||
| 201 | C2: UsbClass<B> + Send, | ||
| 202 | { | ||
| 203 | fn poll_all(&mut self, device: &mut UsbDevice<'_, B>) -> bool { | ||
| 204 | device.poll(&mut [&mut self.class1, &mut self.class2]) | ||
| 205 | } | ||
| 206 | } | ||
| 207 | |||
| 208 | impl<B, C1> IntoClassSet<B, ClassSet1<B, C1>> for C1 | ||
| 209 | where | ||
| 210 | B: UsbBus + Send, | ||
| 211 | C1: UsbClass<B> + Send, | ||
| 212 | { | ||
| 213 | fn into_class_set(self) -> ClassSet1<B, C1> { | ||
| 214 | ClassSet1 { | ||
| 215 | class: self, | ||
| 216 | _bus: PhantomData, | ||
| 217 | } | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | impl<B, C1, C2> IntoClassSet<B, ClassSet2<B, C1, C2>> for (C1, C2) | ||
| 222 | where | ||
| 223 | B: UsbBus + Send, | ||
| 224 | C1: UsbClass<B> + Send, | ||
| 225 | C2: UsbClass<B> + Send, | ||
| 226 | { | ||
| 227 | fn into_class_set(self) -> ClassSet2<B, C1, C2> { | ||
| 228 | ClassSet2 { | ||
| 229 | class1: self.0, | ||
| 230 | class2: self.1, | ||
| 231 | _bus: PhantomData, | ||
| 232 | } | ||
| 233 | } | ||
| 234 | } | ||
| 235 | |||
| 236 | /// Trait for a USB State that has a serial class inside | ||
| 237 | pub trait SerialState<'bus, 'a, B: UsbBus, I> { | ||
| 238 | fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B>; | ||
| 239 | } | ||
| 240 | |||
| 241 | impl<'bus, 'a, B: UsbBus> SerialState<'bus, 'a, B, Index0> | ||
| 242 | for ClassSet1<B, UsbSerial<'bus, 'a, B>> | ||
| 243 | { | ||
| 244 | fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B> { | ||
| 245 | &mut self.class | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | impl<'bus, 'a, B, C2> SerialState<'bus, 'a, B, Index0> for ClassSet2<B, UsbSerial<'bus, 'a, B>, C2> | ||
| 250 | where | ||
| 251 | B: UsbBus, | ||
| 252 | C2: UsbClass<B>, | ||
| 253 | { | ||
| 254 | fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B> { | ||
| 255 | &mut self.class1 | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | impl<'bus, 'a, B, C1> SerialState<'bus, 'a, B, Index1> for ClassSet2<B, C1, UsbSerial<'bus, 'a, B>> | ||
| 260 | where | ||
| 261 | B: UsbBus, | ||
| 262 | C1: UsbClass<B>, | ||
| 263 | { | ||
| 264 | fn get_serial(&mut self) -> &mut UsbSerial<'bus, 'a, B> { | ||
| 265 | &mut self.class2 | ||
| 266 | } | ||
| 267 | } | ||
diff --git a/embassy-hal-common/src/usb/usb_serial.rs b/embassy-hal-common/src/usb/usb_serial.rs deleted file mode 100644 index 94f687890..000000000 --- a/embassy-hal-common/src/usb/usb_serial.rs +++ /dev/null | |||
| @@ -1,345 +0,0 @@ | |||
| 1 | use core::cell::RefCell; | ||
| 2 | use core::marker::{PhantomData, Unpin}; | ||
| 3 | use core::pin::Pin; | ||
| 4 | use core::task::{Context, Poll}; | ||
| 5 | |||
| 6 | use embassy::io::{self, AsyncBufRead, AsyncWrite}; | ||
| 7 | use embassy::waitqueue::WakerRegistration; | ||
| 8 | use usb_device::bus::UsbBus; | ||
| 9 | use usb_device::class_prelude::*; | ||
| 10 | use usb_device::UsbError; | ||
| 11 | |||
| 12 | use super::cdc_acm::CdcAcmClass; | ||
| 13 | use super::StateInner; | ||
| 14 | use crate::peripheral::PeripheralMutex; | ||
| 15 | use crate::ring_buffer::RingBuffer; | ||
| 16 | use crate::usb::{ClassSet, SerialState, USBInterrupt}; | ||
| 17 | |||
| 18 | pub struct ReadInterface<'a, 'bus, 'c, I, B, T, INT> | ||
| 19 | where | ||
| 20 | I: Unpin, | ||
| 21 | B: UsbBus, | ||
| 22 | T: SerialState<'bus, 'c, B, I> + ClassSet<B>, | ||
| 23 | INT: USBInterrupt, | ||
| 24 | { | ||
| 25 | // Don't you dare moving out `PeripheralMutex` | ||
| 26 | pub(crate) inner: &'a RefCell<PeripheralMutex<'bus, StateInner<'bus, B, T, INT>>>, | ||
| 27 | pub(crate) _buf_lifetime: PhantomData<&'c T>, | ||
| 28 | pub(crate) _index: PhantomData<I>, | ||
| 29 | } | ||
| 30 | |||
| 31 | /// Write interface for USB CDC_ACM | ||
| 32 | /// | ||
| 33 | /// This interface is buffered, meaning that after the write returns the bytes might not be fully | ||
| 34 | /// on the wire just yet | ||
| 35 | pub struct WriteInterface<'a, 'bus, 'c, I, B, T, INT> | ||
| 36 | where | ||
| 37 | I: Unpin, | ||
| 38 | B: UsbBus, | ||
| 39 | T: SerialState<'bus, 'c, B, I> + ClassSet<B>, | ||
| 40 | INT: USBInterrupt, | ||
| 41 | { | ||
| 42 | // Don't you dare moving out `PeripheralMutex` | ||
| 43 | pub(crate) inner: &'a RefCell<PeripheralMutex<'bus, StateInner<'bus, B, T, INT>>>, | ||
| 44 | pub(crate) _buf_lifetime: PhantomData<&'c T>, | ||
| 45 | pub(crate) _index: PhantomData<I>, | ||
| 46 | } | ||
| 47 | |||
| 48 | impl<'a, 'bus, 'c, I, B, T, INT> AsyncBufRead for ReadInterface<'a, 'bus, 'c, I, B, T, INT> | ||
| 49 | where | ||
| 50 | I: Unpin, | ||
| 51 | B: UsbBus, | ||
| 52 | T: SerialState<'bus, 'c, B, I> + ClassSet<B>, | ||
| 53 | INT: USBInterrupt, | ||
| 54 | { | ||
| 55 | fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> { | ||
| 56 | let this = self.get_mut(); | ||
| 57 | let mut mutex = this.inner.borrow_mut(); | ||
| 58 | mutex.with(|state| { | ||
| 59 | let serial = state.classes.get_serial(); | ||
| 60 | let serial = Pin::new(serial); | ||
| 61 | |||
| 62 | match serial.poll_fill_buf(cx) { | ||
| 63 | Poll::Ready(Ok(buf)) => { | ||
| 64 | let buf: &[u8] = buf; | ||
| 65 | // NOTE(unsafe) This part of the buffer won't be modified until the user calls | ||
| 66 | // consume, which will invalidate this ref | ||
| 67 | let buf: &[u8] = unsafe { core::mem::transmute(buf) }; | ||
| 68 | Poll::Ready(Ok(buf)) | ||
| 69 | } | ||
| 70 | Poll::Ready(Err(_)) => Poll::Ready(Err(io::Error::Other)), | ||
| 71 | Poll::Pending => Poll::Pending, | ||
| 72 | } | ||
| 73 | }) | ||
| 74 | } | ||
| 75 | |||
| 76 | fn consume(self: Pin<&mut Self>, amt: usize) { | ||
| 77 | let this = self.get_mut(); | ||
| 78 | let mut mutex = this.inner.borrow_mut(); | ||
| 79 | mutex.with(|state| { | ||
| 80 | let serial = state.classes.get_serial(); | ||
| 81 | let serial = Pin::new(serial); | ||
| 82 | |||
| 83 | serial.consume(amt); | ||
| 84 | }) | ||
| 85 | } | ||
| 86 | } | ||
| 87 | |||
| 88 | impl<'a, 'bus, 'c, I, B, T, INT> AsyncWrite for WriteInterface<'a, 'bus, 'c, I, B, T, INT> | ||
| 89 | where | ||
| 90 | I: Unpin, | ||
| 91 | B: UsbBus, | ||
| 92 | T: SerialState<'bus, 'c, B, I> + ClassSet<B>, | ||
| 93 | INT: USBInterrupt, | ||
| 94 | { | ||
| 95 | fn poll_write( | ||
| 96 | self: Pin<&mut Self>, | ||
| 97 | cx: &mut Context<'_>, | ||
| 98 | buf: &[u8], | ||
| 99 | ) -> Poll<io::Result<usize>> { | ||
| 100 | let this = self.get_mut(); | ||
| 101 | let mut mutex = this.inner.borrow_mut(); | ||
| 102 | mutex.with(|state| { | ||
| 103 | let serial = state.classes.get_serial(); | ||
| 104 | let serial = Pin::new(serial); | ||
| 105 | |||
| 106 | serial.poll_write(cx, buf) | ||
| 107 | }) | ||
| 108 | } | ||
| 109 | |||
| 110 | fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<()>> { | ||
| 111 | let this = self.get_mut(); | ||
| 112 | let mut mutex = this.inner.borrow_mut(); | ||
| 113 | mutex.with(|state| { | ||
| 114 | let serial = state.classes.get_serial(); | ||
| 115 | let serial = Pin::new(serial); | ||
| 116 | |||
| 117 | serial.poll_flush(cx) | ||
| 118 | }) | ||
| 119 | } | ||
| 120 | } | ||
| 121 | |||
| 122 | pub struct UsbSerial<'bus, 'a, B: UsbBus> { | ||
| 123 | inner: CdcAcmClass<'bus, B>, | ||
| 124 | read_buf: RingBuffer<'a>, | ||
| 125 | write_buf: RingBuffer<'a>, | ||
| 126 | read_waker: WakerRegistration, | ||
| 127 | write_waker: WakerRegistration, | ||
| 128 | write_state: WriteState, | ||
| 129 | read_error: bool, | ||
| 130 | write_error: bool, | ||
| 131 | } | ||
| 132 | |||
| 133 | impl<'bus, 'a, B: UsbBus> AsyncBufRead for UsbSerial<'bus, 'a, B> { | ||
| 134 | fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> { | ||
| 135 | let this = self.get_mut(); | ||
| 136 | |||
| 137 | if this.read_error { | ||
| 138 | this.read_error = false; | ||
| 139 | return Poll::Ready(Err(io::Error::Other)); | ||
| 140 | } | ||
| 141 | |||
| 142 | let buf = this.read_buf.pop_buf(); | ||
| 143 | if buf.is_empty() { | ||
| 144 | this.read_waker.register(cx.waker()); | ||
| 145 | return Poll::Pending; | ||
| 146 | } | ||
| 147 | Poll::Ready(Ok(buf)) | ||
| 148 | } | ||
| 149 | |||
| 150 | fn consume(self: Pin<&mut Self>, amt: usize) { | ||
| 151 | self.get_mut().read_buf.pop(amt); | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | impl<'bus, 'a, B: UsbBus> AsyncWrite for UsbSerial<'bus, 'a, B> { | ||
| 156 | fn poll_write( | ||
| 157 | self: Pin<&mut Self>, | ||
| 158 | cx: &mut Context<'_>, | ||
| 159 | buf: &[u8], | ||
| 160 | ) -> Poll<io::Result<usize>> { | ||
| 161 | let this = self.get_mut(); | ||
| 162 | |||
| 163 | if this.write_error { | ||
| 164 | this.write_error = false; | ||
| 165 | return Poll::Ready(Err(io::Error::Other)); | ||
| 166 | } | ||
| 167 | |||
| 168 | let write_buf = this.write_buf.push_buf(); | ||
| 169 | if write_buf.is_empty() { | ||
| 170 | trace!("buf full, registering waker"); | ||
| 171 | this.write_waker.register(cx.waker()); | ||
| 172 | return Poll::Pending; | ||
| 173 | } | ||
| 174 | |||
| 175 | let count = write_buf.len().min(buf.len()); | ||
| 176 | write_buf[..count].copy_from_slice(&buf[..count]); | ||
| 177 | this.write_buf.push(count); | ||
| 178 | |||
| 179 | this.flush_write(); | ||
| 180 | Poll::Ready(Ok(count)) | ||
| 181 | } | ||
| 182 | |||
| 183 | fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> { | ||
| 184 | Poll::Ready(Ok(())) | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | /// Keeps track of the type of the last written packet. | ||
| 189 | enum WriteState { | ||
| 190 | /// No packets in-flight | ||
| 191 | Idle, | ||
| 192 | |||
| 193 | /// Short packet currently in-flight | ||
| 194 | Short, | ||
| 195 | |||
| 196 | /// Full packet current in-flight. A full packet must be followed by a short packet for the host | ||
| 197 | /// OS to see the transaction. The data is the number of subsequent full packets sent so far. A | ||
| 198 | /// short packet is forced every SHORT_PACKET_INTERVAL packets so that the OS sees data in a | ||
| 199 | /// timely manner. | ||
| 200 | Full(usize), | ||
| 201 | } | ||
| 202 | |||
| 203 | impl<'bus, 'a, B: UsbBus> UsbSerial<'bus, 'a, B> { | ||
| 204 | pub fn new( | ||
| 205 | alloc: &'bus UsbBusAllocator<B>, | ||
| 206 | read_buf: &'a mut [u8], | ||
| 207 | write_buf: &'a mut [u8], | ||
| 208 | ) -> Self { | ||
| 209 | Self { | ||
| 210 | inner: CdcAcmClass::new(alloc, 64), | ||
| 211 | read_buf: RingBuffer::new(read_buf), | ||
| 212 | write_buf: RingBuffer::new(write_buf), | ||
| 213 | read_waker: WakerRegistration::new(), | ||
| 214 | write_waker: WakerRegistration::new(), | ||
| 215 | write_state: WriteState::Idle, | ||
| 216 | read_error: false, | ||
| 217 | write_error: false, | ||
| 218 | } | ||
| 219 | } | ||
| 220 | |||
| 221 | fn flush_write(&mut self) { | ||
| 222 | /// If this many full size packets have been sent in a row, a short packet will be sent so that the | ||
| 223 | /// host sees the data in a timely manner. | ||
| 224 | const SHORT_PACKET_INTERVAL: usize = 10; | ||
| 225 | |||
| 226 | let full_size_packets = match self.write_state { | ||
| 227 | WriteState::Full(c) => c, | ||
| 228 | _ => 0, | ||
| 229 | }; | ||
| 230 | |||
| 231 | let ep_size = self.inner.max_packet_size() as usize; | ||
| 232 | let max_size = if full_size_packets > SHORT_PACKET_INTERVAL { | ||
| 233 | ep_size - 1 | ||
| 234 | } else { | ||
| 235 | ep_size | ||
| 236 | }; | ||
| 237 | |||
| 238 | let buf = { | ||
| 239 | let buf = self.write_buf.pop_buf(); | ||
| 240 | if buf.len() > max_size { | ||
| 241 | &buf[..max_size] | ||
| 242 | } else { | ||
| 243 | buf | ||
| 244 | } | ||
| 245 | }; | ||
| 246 | |||
| 247 | if !buf.is_empty() { | ||
| 248 | trace!("writing packet len {}", buf.len()); | ||
| 249 | let count = match self.inner.write_packet(buf) { | ||
| 250 | Ok(c) => { | ||
| 251 | trace!("write packet: OK {}", c); | ||
| 252 | c | ||
| 253 | } | ||
| 254 | Err(UsbError::WouldBlock) => { | ||
| 255 | trace!("write packet: WouldBlock"); | ||
| 256 | 0 | ||
| 257 | } | ||
| 258 | Err(_) => { | ||
| 259 | trace!("write packet: error"); | ||
| 260 | self.write_error = true; | ||
| 261 | return; | ||
| 262 | } | ||
| 263 | }; | ||
| 264 | |||
| 265 | if buf.len() == ep_size { | ||
| 266 | self.write_state = WriteState::Full(full_size_packets + 1); | ||
| 267 | } else { | ||
| 268 | self.write_state = WriteState::Short; | ||
| 269 | } | ||
| 270 | self.write_buf.pop(count); | ||
| 271 | } else if full_size_packets > 0 { | ||
| 272 | trace!("writing empty packet"); | ||
| 273 | match self.inner.write_packet(&[]) { | ||
| 274 | Ok(_) => { | ||
| 275 | trace!("write empty packet: OK"); | ||
| 276 | } | ||
| 277 | Err(UsbError::WouldBlock) => { | ||
| 278 | trace!("write empty packet: WouldBlock"); | ||
| 279 | return; | ||
| 280 | } | ||
| 281 | Err(_) => { | ||
| 282 | trace!("write empty packet: Error"); | ||
| 283 | self.write_error = true; | ||
| 284 | return; | ||
| 285 | } | ||
| 286 | } | ||
| 287 | self.write_state = WriteState::Idle; | ||
| 288 | } | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | impl<B> UsbClass<B> for UsbSerial<'_, '_, B> | ||
| 293 | where | ||
| 294 | B: UsbBus, | ||
| 295 | { | ||
| 296 | fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<(), UsbError> { | ||
| 297 | self.inner.get_configuration_descriptors(writer) | ||
| 298 | } | ||
| 299 | |||
| 300 | fn reset(&mut self) { | ||
| 301 | self.inner.reset(); | ||
| 302 | self.read_buf.clear(); | ||
| 303 | self.write_buf.clear(); | ||
| 304 | self.write_state = WriteState::Idle; | ||
| 305 | self.read_waker.wake(); | ||
| 306 | self.write_waker.wake(); | ||
| 307 | } | ||
| 308 | |||
| 309 | fn endpoint_in_complete(&mut self, addr: EndpointAddress) { | ||
| 310 | trace!("DONE endpoint_in_complete"); | ||
| 311 | if addr == self.inner.write_ep_address() { | ||
| 312 | trace!("DONE writing packet, waking"); | ||
| 313 | self.write_waker.wake(); | ||
| 314 | |||
| 315 | self.flush_write(); | ||
| 316 | } | ||
| 317 | } | ||
| 318 | |||
| 319 | fn endpoint_out(&mut self, addr: EndpointAddress) { | ||
| 320 | if addr == self.inner.read_ep_address() { | ||
| 321 | let buf = self.read_buf.push_buf(); | ||
| 322 | let count = match self.inner.read_packet(buf) { | ||
| 323 | Ok(c) => c, | ||
| 324 | Err(UsbError::WouldBlock) => 0, | ||
| 325 | Err(_) => { | ||
| 326 | self.read_error = true; | ||
| 327 | return; | ||
| 328 | } | ||
| 329 | }; | ||
| 330 | |||
| 331 | if count > 0 { | ||
| 332 | self.read_buf.push(count); | ||
| 333 | self.read_waker.wake(); | ||
| 334 | } | ||
| 335 | } | ||
| 336 | } | ||
| 337 | |||
| 338 | fn control_in(&mut self, xfer: ControlIn<B>) { | ||
| 339 | self.inner.control_in(xfer); | ||
| 340 | } | ||
| 341 | |||
| 342 | fn control_out(&mut self, xfer: ControlOut<B>) { | ||
| 343 | self.inner.control_out(xfer); | ||
| 344 | } | ||
| 345 | } | ||
