Serge Bazanski | 8198136 | 2021-03-06 13:08:00 +0100 | [diff] [blame] | 1 | /// USB Device Class for I2C transactions. |
| 2 | // |
| 3 | // It's not very good, and the API is weird. Someone with more USB device design experience could |
| 4 | // easily come up with something better. |
| 5 | // |
| 6 | // Control OUT transactions are used to perform I2C transfers to/from an internal buffer. |
| 7 | // Bulk IN/OUT transactions are used to transfer contents of the buffer to the host. It has not |
| 8 | // been optimized for speed or pipelining. |
| 9 | // |
| 10 | // To perform an I2C read: |
| 11 | // 1) Control OUT: ReadI2C(Address: 0xAA, Length: N) |
| 12 | // (0xAA is the device address, N is the amount of bytes to read. Cannot be larger than |
| 13 | // BUFFER_SIZE). |
| 14 | // This performs an I2C read of N bytes into the inne buffer of the device, starting at |
| 15 | // address 0. |
| 16 | // 2) Control IN: GetStatus() |
| 17 | // The host ensures that the transaction was either ACK or NACK by getting one byte of status |
| 18 | // from the device. |
| 19 | // 3) Control OUT: ReadBuffer(Address: X, Length: N) |
| 20 | // (X is the address within the buffer, N is the amount of bytes to transfer to the host. N |
| 21 | // cannot be larger than PACKET_SIZE). |
| 22 | // 4) Bulk IN: Read PACKET_SIZE bytes. |
| 23 | // Steps 3/4 can be skipped for scanning (the device won't mind the inner buffer not being read). |
| 24 | // |
| 25 | // To perform an I2C write: |
| 26 | // 1) Control OUT: SetWritePointer(Addrss: X) |
| 27 | // 2) Bulk OUT: Write at most PACKET_SIZE bytes. |
| 28 | // Repeat steps 1/2 to fill buffer with an I2c transaction. |
| 29 | // 3) Control OUT: WriteI2C(Address: 0x00, Length: N) |
| 30 | // (0xAA is the device address, N is the amount of bytes to write. Cannot be larger than |
| 31 | // BUFFER_SIZE). |
| 32 | // 4) Control IN: GetStatus() |
| 33 | // The host ensures that the transaction was either ACK or NACK by getting one byte of status |
| 34 | // from the device. |
| 35 | |
| 36 | use embedded_hal::digital::v2::OutputPin; |
| 37 | use usb_device::class_prelude::*; |
| 38 | use nb::Error as NbError; |
| 39 | use stm32f1xx_hal::{ |
| 40 | gpio::{gpiob::*, Alternate, OpenDrain}, |
| 41 | i2c::{BlockingI2c, Error as I2CError}, |
| 42 | pac::I2C1, |
| 43 | prelude::*, |
| 44 | }; |
| 45 | |
| 46 | use num_derive::FromPrimitive; |
| 47 | use num_traits::FromPrimitive; |
| 48 | |
| 49 | use crate::{hprint, hprintln}; |
| 50 | |
| 51 | // Size of buffer within class, in bytes. Dictates maximum I2C transaction size. |
| 52 | const BUFFER_SIZE: usize = 1024; |
| 53 | // Size of bulk packets. |
| 54 | const PACKET_SIZE: usize = 64; |
| 55 | |
| 56 | // All IN/OUT references bellow conform to typical USB naming, where IN: from device to host; OUT: |
| 57 | // from host to device. |
| 58 | |
| 59 | /// Request number passed within Control IN requests to the I2C interface (ie. 'gets' from device). |
| 60 | #[derive(FromPrimitive)] |
| 61 | #[repr(u8)] |
| 62 | enum ControlInRequest { |
| 63 | /// Write the current status as a single byte in response. |
| 64 | GetStatus = 1, |
| 65 | } |
| 66 | |
| 67 | /// Request number passed within Control OUT requests to the I2C interface (ie. 'sets' from the |
| 68 | /// host). |
| 69 | #[derive(FromPrimitive)] |
| 70 | #[repr(u8)] |
| 71 | enum ControlOutRequest { |
| 72 | /// Set LED on or off (value == 0 -> off; on otherwise). |
| 73 | SetLED = 1, |
| 74 | |
| 75 | /// Perform I2C bus read of a given length from a given I2C address. |
| 76 | /// I2C Address: lower 8 bits of value. |
| 77 | /// Read Length: upper 8 bits of value. |
| 78 | ReadI2C = 2, |
| 79 | |
| 80 | /// Schedule a BULK IN transaction on the USB bus with the contents of the inner buffer. |
| 81 | /// Buffer start address: lower 8 bits of value |
| 82 | /// Read Length: upper 8 bits of value. |
| 83 | ReadBuffer = 3, |
| 84 | |
| 85 | /// Perform I2C bus write of a given length to a given I2C address. |
| 86 | /// I2C Address: lower 8 bits of value. |
| 87 | /// Read Length: upper 8 bits of value. |
| 88 | WriteI2C = 4, |
| 89 | |
| 90 | /// Set inner buffer write pointer. Any subsequent BULK OUT will write to the buffer at that |
| 91 | /// address (but will not auto advance the pointer). |
| 92 | SetWritePointer = 5, |
| 93 | } |
| 94 | |
| 95 | /// Status of the I2C class. Combines information about requested transactions and I2C bus |
| 96 | /// responses. |
| 97 | #[derive(Copy, Clone)] |
| 98 | #[repr(u8)] |
| 99 | enum Status { |
| 100 | /// Last request okay. |
| 101 | OK = 0, |
| 102 | /// Last request contained an invalid argument. |
| 103 | InvalidArgument = 1, |
| 104 | /// Last request okay, resulted in a successful I2C transaction. |
| 105 | Ack = 2, |
| 106 | /// Last request okay, resulted in a NACKd I2C transaction. |
| 107 | Nack = 3, |
| 108 | /// Last request okay, resulted in a fully failed I2C transaction. |
| 109 | BusError = 4, |
| 110 | } |
| 111 | |
| 112 | pub struct I2CClass<'a, B: UsbBus, LED> { |
| 113 | interface: InterfaceNumber, |
| 114 | /// Bulk IN endpoint for buffer transfers to host. |
| 115 | ep_in: EndpointIn<'a, B>, |
| 116 | /// Bulk OUT endpoint for buffer transfers from host. |
| 117 | ep_out: EndpointOut<'a, B>, |
| 118 | |
| 119 | /// LED used for debugging. |
| 120 | led: LED, |
| 121 | |
| 122 | /// The underlying I2C device. |
| 123 | i2c_dev: BlockingI2c<I2C1, (PB6<Alternate<OpenDrain>>, PB7<Alternate<OpenDrain>>)>, |
| 124 | |
| 125 | /// Marker that is true when the host requested a BULK OUT via ReadBuffer. |
| 126 | expect_bulk_out: bool, |
| 127 | |
| 128 | /// The underlying buffer and its write pointer. |
| 129 | buffer: [u8; BUFFER_SIZE], |
| 130 | write_pointer: usize, |
| 131 | |
| 132 | /// The device's main status byte, used by host to check whether operations were succesful. |
| 133 | status: Status, |
| 134 | } |
| 135 | |
| 136 | impl<B: UsbBus, LED: OutputPin> I2CClass<'_, B, LED> { |
| 137 | pub fn new( |
| 138 | alloc: &UsbBusAllocator<B>, |
| 139 | led: LED, |
| 140 | i2c_dev: BlockingI2c<I2C1, (PB6<Alternate<OpenDrain>>, PB7<Alternate<OpenDrain>>)>, |
| 141 | ) -> I2CClass<'_, B, LED> { |
| 142 | I2CClass { |
| 143 | interface: alloc.interface(), |
| 144 | ep_in: alloc.bulk(PACKET_SIZE as u16), |
| 145 | ep_out: alloc.bulk(PACKET_SIZE as u16), |
| 146 | led, i2c_dev, |
| 147 | |
| 148 | expect_bulk_out: false, |
| 149 | |
| 150 | buffer: [0; BUFFER_SIZE], |
| 151 | write_pointer: 0usize, |
| 152 | status: Status::OK, |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | impl<'a, B: UsbBus, LED: OutputPin> UsbClass<B> for I2CClass<'a, B, LED> { |
| 158 | fn reset(&mut self) { |
| 159 | self.expect_bulk_out = false; |
Serge Bazanski | c8b14e7 | 2021-03-27 11:52:44 +0000 | [diff] [blame] | 160 | self.status = Status::OK; |
Serge Bazanski | 8198136 | 2021-03-06 13:08:00 +0100 | [diff] [blame] | 161 | } |
| 162 | |
| 163 | fn control_in(&mut self, xfer: ControlIn<B>) { |
| 164 | let req = xfer.request(); |
| 165 | |
| 166 | if req.request_type != control::RequestType::Vendor |
| 167 | || req.recipient != control::Recipient::Interface |
| 168 | || req.index != u8::from(self.interface) as u16 { |
| 169 | return |
| 170 | } |
| 171 | |
| 172 | match FromPrimitive::from_u8(req.request) { |
| 173 | /// Serve GetStatus: return this.status. |
| 174 | Some(ControlInRequest::GetStatus) => { |
| 175 | let status = self.status.clone() as u8; |
| 176 | xfer.accept(|buf| { |
| 177 | buf[0] = status; |
| 178 | Ok(1usize) |
| 179 | }).ok(); |
| 180 | }, |
| 181 | _ => { |
| 182 | hprintln!("Unhandled control in on iface: {:?}", req).unwrap(); |
| 183 | }, |
| 184 | } |
| 185 | } |
| 186 | |
| 187 | fn control_out(&mut self, xfer: ControlOut<B>) { |
| 188 | let req = xfer.request(); |
| 189 | |
| 190 | if req.request_type != control::RequestType::Vendor |
| 191 | || req.recipient != control::Recipient::Interface |
| 192 | || req.index != u8::from(self.interface) as u16 { |
| 193 | return |
| 194 | } |
| 195 | |
| 196 | match FromPrimitive::from_u8(req.request) { |
| 197 | // Serve SetLED. |
| 198 | Some(ControlOutRequest::SetLED) => { |
| 199 | let on: bool = req.value > 0; |
| 200 | match on { |
| 201 | true => self.led.set_low(), |
| 202 | false => self.led.set_high(), |
| 203 | }.ok(); |
| 204 | xfer.accept().ok(); |
| 205 | }, |
| 206 | |
| 207 | // Serve ReadI2C: read len bytes from I2C addr into internal buffer. |
| 208 | Some(ControlOutRequest::ReadI2C) => { |
| 209 | let addr: u8 = (req.value & 0xff) as u8; |
| 210 | let len: u8 = (req.value >> 8) as u8; |
| 211 | if len as usize > BUFFER_SIZE || len < 1u8 { |
| 212 | self.status = Status::InvalidArgument; |
| 213 | xfer.accept().ok(); |
| 214 | return |
| 215 | } |
| 216 | if addr > 127u8 { |
| 217 | self.status = Status::InvalidArgument; |
| 218 | xfer.accept().ok(); |
| 219 | return |
| 220 | } |
| 221 | match self.i2c_dev.read(addr, &mut self.buffer[0usize..(len as usize)]) { |
| 222 | Ok(_) => { |
| 223 | self.status = Status::Ack; |
| 224 | }, |
| 225 | Err(NbError::Other(I2CError::Acknowledge)) => { |
| 226 | self.status = Status::Nack; |
| 227 | }, |
| 228 | Err(e) => { |
| 229 | hprintln!("When reading I2C (addr {}, {} bytes): {:?}", addr, len, e).ok(); |
| 230 | self.status = Status::BusError; |
| 231 | }, |
| 232 | } |
| 233 | xfer.accept().ok(); |
| 234 | }, |
| 235 | |
| 236 | // Serve ReadBuffer: send BULK IN with slice of buffer. |
| 237 | Some(ControlOutRequest::ReadBuffer) => { |
| 238 | let addr: u8 = (req.value & 0xff) as u8; |
| 239 | let len: u8 = (req.value >> 8) as u8; |
| 240 | |
| 241 | if len as usize > PACKET_SIZE || len < 1u8 { |
| 242 | self.status = Status::InvalidArgument; |
| 243 | xfer.accept().ok(); |
| 244 | return |
| 245 | } |
| 246 | |
| 247 | let start = addr as usize; |
| 248 | let end = (addr + len) as usize; |
| 249 | if end as usize > BUFFER_SIZE { |
| 250 | self.status = Status::InvalidArgument; |
| 251 | xfer.accept().ok(); |
| 252 | return |
| 253 | } |
| 254 | |
| 255 | hprintln!("READ BUFFER, addr: {}, len: {}", addr, len).ok(); |
| 256 | |
| 257 | self.status = Status::OK; |
| 258 | xfer.accept().ok(); |
| 259 | match self.ep_in.write(&self.buffer[start..end]) { |
| 260 | Ok(count) => { |
| 261 | }, |
| 262 | Err(UsbError::WouldBlock) => {}, |
| 263 | Err(err) => { |
| 264 | hprintln!("bulk write failed: {:?}", err).ok(); |
| 265 | }, |
| 266 | } |
| 267 | }, |
| 268 | |
| 269 | // Serve WriteI2C: write len bytes to I2C bus at addr from internal buffer. |
| 270 | Some(ControlOutRequest::WriteI2C) => { |
| 271 | let addr: u8 = (req.value & 0xff) as u8; |
| 272 | let len: u8 = (req.value >> 8) as u8; |
| 273 | if len as usize > BUFFER_SIZE || len < 1u8 { |
| 274 | self.status = Status::InvalidArgument; |
| 275 | xfer.accept().ok(); |
| 276 | return |
| 277 | } |
| 278 | if addr > 127u8 { |
| 279 | self.status = Status::InvalidArgument; |
| 280 | xfer.accept().ok(); |
| 281 | return |
| 282 | } |
| 283 | |
| 284 | hprintln!("WRITE I2C, addr: {}, len: {}", addr, len).ok(); |
| 285 | match self.i2c_dev.write(addr, &self.buffer[0usize..(len as usize)]) { |
| 286 | Ok(_) => { |
| 287 | self.status = Status::Ack; |
| 288 | }, |
| 289 | Err(NbError::Other(I2CError::Acknowledge)) => { |
| 290 | self.status = Status::Nack; |
| 291 | }, |
| 292 | Err(e) => { |
| 293 | hprintln!("When writing I2C (addr {}, {} bytes): {:?}", addr, len, e).ok(); |
| 294 | self.status = Status::BusError; |
| 295 | }, |
| 296 | } |
| 297 | xfer.accept().ok(); |
| 298 | }, |
| 299 | |
| 300 | // Serve SetWritePointer: set start address at which bytes from a BULK OUT will be |
| 301 | // written to. The write pointer does _not_ increment on every write, so will need to |
| 302 | // be manually controler after every BULK transfer. |
| 303 | Some(ControlOutRequest::SetWritePointer) => { |
| 304 | let pointer = req.value; |
| 305 | if (pointer as usize) >= BUFFER_SIZE { |
| 306 | self.status = Status::InvalidArgument; |
| 307 | xfer.accept().ok(); |
| 308 | return |
| 309 | } |
| 310 | hprintln!("SET WRITE PTR, pointer: {}", pointer).ok(); |
| 311 | self.write_pointer = pointer as usize; |
| 312 | self.status = Status::OK; |
| 313 | xfer.accept().ok(); |
| 314 | }, |
| 315 | _ => { |
| 316 | hprintln!("Unhandled control out on iface: {:?}", req).ok(); |
| 317 | }, |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | fn get_configuration_descriptors( |
| 322 | &self, |
| 323 | writer: &mut DescriptorWriter, |
| 324 | ) -> usb_device::Result<()> { |
| 325 | writer.interface( |
| 326 | self.interface, |
| 327 | 0xff, |
| 328 | 21, 37, |
| 329 | )?; |
| 330 | writer.endpoint(&self.ep_in)?; |
| 331 | writer.endpoint(&self.ep_out)?; |
| 332 | |
| 333 | Ok(()) |
| 334 | } |
| 335 | |
| 336 | fn poll(&mut self) { |
| 337 | let mut temp_buf = [0; PACKET_SIZE]; |
| 338 | // Serve BULK OUT writes - copy bytes into internal buffer. |
| 339 | match self.ep_out.read(&mut temp_buf) { |
| 340 | Ok(count) => { |
| 341 | if self.expect_bulk_out { |
| 342 | self.expect_bulk_out = false; |
| 343 | } else { |
| 344 | panic!("unexpectedly read data from bulk out endpoint"); |
| 345 | } |
| 346 | hprintln!("SET BUFFER: ptr {}, {} bytes", self.write_pointer, count).ok(); |
| 347 | for (i, c) in temp_buf.iter().enumerate() { |
| 348 | let ptr = self.write_pointer + i; |
| 349 | // Silently drop bytes that do not fit in buffer. |
| 350 | if ptr >= BUFFER_SIZE { |
| 351 | continue; |
| 352 | } |
| 353 | self.buffer[ptr] = c.clone(); |
| 354 | } |
| 355 | }, |
| 356 | Err(UsbError::WouldBlock) => {}, |
| 357 | Err(err) => panic!("bulk read {:?}", err), |
| 358 | } |
| 359 | } |
| 360 | |
| 361 | fn endpoint_out(&mut self, addr: EndpointAddress) { |
| 362 | if addr == self.ep_out.address() { |
| 363 | self.expect_bulk_out = true; |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | fn endpoint_in_complete(&mut self, addr: EndpointAddress) { |
| 368 | if addr == self.ep_in.address() { |
| 369 | // TODO(q3k): should we be doing something here? |
| 370 | } |
| 371 | } |
| 372 | } |