blob: c66a1338e0f46311b9ab23dc18aaf20fe69eaa21 [file] [log] [blame]
/// USB Device Class for I2C transactions.
//
// It's not very good, and the API is weird. Someone with more USB device design experience could
// easily come up with something better.
//
// Control OUT transactions are used to perform I2C transfers to/from an internal buffer.
// Bulk IN/OUT transactions are used to transfer contents of the buffer to the host. It has not
// been optimized for speed or pipelining.
//
// To perform an I2C read:
// 1) Control OUT: ReadI2C(Address: 0xAA, Length: N)
// (0xAA is the device address, N is the amount of bytes to read. Cannot be larger than
// BUFFER_SIZE).
// This performs an I2C read of N bytes into the inne buffer of the device, starting at
// address 0.
// 2) Control IN: GetStatus()
// The host ensures that the transaction was either ACK or NACK by getting one byte of status
// from the device.
// 3) Control OUT: ReadBuffer(Address: X, Length: N)
// (X is the address within the buffer, N is the amount of bytes to transfer to the host. N
// cannot be larger than PACKET_SIZE).
// 4) Bulk IN: Read PACKET_SIZE bytes.
// Steps 3/4 can be skipped for scanning (the device won't mind the inner buffer not being read).
//
// To perform an I2C write:
// 1) Control OUT: SetWritePointer(Addrss: X)
// 2) Bulk OUT: Write at most PACKET_SIZE bytes.
// Repeat steps 1/2 to fill buffer with an I2c transaction.
// 3) Control OUT: WriteI2C(Address: 0x00, Length: N)
// (0xAA is the device address, N is the amount of bytes to write. Cannot be larger than
// BUFFER_SIZE).
// 4) Control IN: GetStatus()
// The host ensures that the transaction was either ACK or NACK by getting one byte of status
// from the device.
use embedded_hal::digital::v2::OutputPin;
use usb_device::class_prelude::*;
use nb::Error as NbError;
use stm32f1xx_hal::{
gpio::{gpiob::*, Alternate, OpenDrain},
i2c::{BlockingI2c, Error as I2CError},
pac::I2C1,
prelude::*,
};
use num_derive::FromPrimitive;
use num_traits::FromPrimitive;
use crate::{hprint, hprintln};
// Size of buffer within class, in bytes. Dictates maximum I2C transaction size.
const BUFFER_SIZE: usize = 1024;
// Size of bulk packets.
const PACKET_SIZE: usize = 64;
// All IN/OUT references bellow conform to typical USB naming, where IN: from device to host; OUT:
// from host to device.
/// Request number passed within Control IN requests to the I2C interface (ie. 'gets' from device).
#[derive(FromPrimitive)]
#[repr(u8)]
enum ControlInRequest {
/// Write the current status as a single byte in response.
GetStatus = 1,
}
/// Request number passed within Control OUT requests to the I2C interface (ie. 'sets' from the
/// host).
#[derive(FromPrimitive)]
#[repr(u8)]
enum ControlOutRequest {
/// Set LED on or off (value == 0 -> off; on otherwise).
SetLED = 1,
/// Perform I2C bus read of a given length from a given I2C address.
/// I2C Address: lower 8 bits of value.
/// Read Length: upper 8 bits of value.
ReadI2C = 2,
/// Schedule a BULK IN transaction on the USB bus with the contents of the inner buffer.
/// Buffer start address: lower 8 bits of value
/// Read Length: upper 8 bits of value.
ReadBuffer = 3,
/// Perform I2C bus write of a given length to a given I2C address.
/// I2C Address: lower 8 bits of value.
/// Read Length: upper 8 bits of value.
WriteI2C = 4,
/// Set inner buffer write pointer. Any subsequent BULK OUT will write to the buffer at that
/// address (but will not auto advance the pointer).
SetWritePointer = 5,
}
/// Status of the I2C class. Combines information about requested transactions and I2C bus
/// responses.
#[derive(Copy, Clone)]
#[repr(u8)]
enum Status {
/// Last request okay.
OK = 0,
/// Last request contained an invalid argument.
InvalidArgument = 1,
/// Last request okay, resulted in a successful I2C transaction.
Ack = 2,
/// Last request okay, resulted in a NACKd I2C transaction.
Nack = 3,
/// Last request okay, resulted in a fully failed I2C transaction.
BusError = 4,
}
pub struct I2CClass<'a, B: UsbBus, LED> {
interface: InterfaceNumber,
/// Bulk IN endpoint for buffer transfers to host.
ep_in: EndpointIn<'a, B>,
/// Bulk OUT endpoint for buffer transfers from host.
ep_out: EndpointOut<'a, B>,
/// LED used for debugging.
led: LED,
/// The underlying I2C device.
i2c_dev: BlockingI2c<I2C1, (PB6<Alternate<OpenDrain>>, PB7<Alternate<OpenDrain>>)>,
/// Marker that is true when the host requested a BULK OUT via ReadBuffer.
expect_bulk_out: bool,
/// The underlying buffer and its write pointer.
buffer: [u8; BUFFER_SIZE],
write_pointer: usize,
/// The device's main status byte, used by host to check whether operations were succesful.
status: Status,
}
impl<B: UsbBus, LED: OutputPin> I2CClass<'_, B, LED> {
pub fn new(
alloc: &UsbBusAllocator<B>,
led: LED,
i2c_dev: BlockingI2c<I2C1, (PB6<Alternate<OpenDrain>>, PB7<Alternate<OpenDrain>>)>,
) -> I2CClass<'_, B, LED> {
I2CClass {
interface: alloc.interface(),
ep_in: alloc.bulk(PACKET_SIZE as u16),
ep_out: alloc.bulk(PACKET_SIZE as u16),
led, i2c_dev,
expect_bulk_out: false,
buffer: [0; BUFFER_SIZE],
write_pointer: 0usize,
status: Status::OK,
}
}
}
impl<'a, B: UsbBus, LED: OutputPin> UsbClass<B> for I2CClass<'a, B, LED> {
fn reset(&mut self) {
self.expect_bulk_out = false;
self.status = Status::OK;
}
fn control_in(&mut self, xfer: ControlIn<B>) {
let req = xfer.request();
if req.request_type != control::RequestType::Vendor
|| req.recipient != control::Recipient::Interface
|| req.index != u8::from(self.interface) as u16 {
return
}
match FromPrimitive::from_u8(req.request) {
/// Serve GetStatus: return this.status.
Some(ControlInRequest::GetStatus) => {
let status = self.status.clone() as u8;
xfer.accept(|buf| {
buf[0] = status;
Ok(1usize)
}).ok();
},
_ => {
hprintln!("Unhandled control in on iface: {:?}", req).unwrap();
},
}
}
fn control_out(&mut self, xfer: ControlOut<B>) {
let req = xfer.request();
if req.request_type != control::RequestType::Vendor
|| req.recipient != control::Recipient::Interface
|| req.index != u8::from(self.interface) as u16 {
return
}
match FromPrimitive::from_u8(req.request) {
// Serve SetLED.
Some(ControlOutRequest::SetLED) => {
let on: bool = req.value > 0;
match on {
true => self.led.set_low(),
false => self.led.set_high(),
}.ok();
xfer.accept().ok();
},
// Serve ReadI2C: read len bytes from I2C addr into internal buffer.
Some(ControlOutRequest::ReadI2C) => {
let addr: u8 = (req.value & 0xff) as u8;
let len: u8 = (req.value >> 8) as u8;
if len as usize > BUFFER_SIZE || len < 1u8 {
self.status = Status::InvalidArgument;
xfer.accept().ok();
return
}
if addr > 127u8 {
self.status = Status::InvalidArgument;
xfer.accept().ok();
return
}
match self.i2c_dev.read(addr, &mut self.buffer[0usize..(len as usize)]) {
Ok(_) => {
self.status = Status::Ack;
},
Err(NbError::Other(I2CError::Acknowledge)) => {
self.status = Status::Nack;
},
Err(e) => {
hprintln!("When reading I2C (addr {}, {} bytes): {:?}", addr, len, e).ok();
self.status = Status::BusError;
},
}
xfer.accept().ok();
},
// Serve ReadBuffer: send BULK IN with slice of buffer.
Some(ControlOutRequest::ReadBuffer) => {
let addr: u8 = (req.value & 0xff) as u8;
let len: u8 = (req.value >> 8) as u8;
if len as usize > PACKET_SIZE || len < 1u8 {
self.status = Status::InvalidArgument;
xfer.accept().ok();
return
}
let start = addr as usize;
let end = (addr + len) as usize;
if end as usize > BUFFER_SIZE {
self.status = Status::InvalidArgument;
xfer.accept().ok();
return
}
hprintln!("READ BUFFER, addr: {}, len: {}", addr, len).ok();
self.status = Status::OK;
xfer.accept().ok();
match self.ep_in.write(&self.buffer[start..end]) {
Ok(count) => {
},
Err(UsbError::WouldBlock) => {},
Err(err) => {
hprintln!("bulk write failed: {:?}", err).ok();
},
}
},
// Serve WriteI2C: write len bytes to I2C bus at addr from internal buffer.
Some(ControlOutRequest::WriteI2C) => {
let addr: u8 = (req.value & 0xff) as u8;
let len: u8 = (req.value >> 8) as u8;
if len as usize > BUFFER_SIZE || len < 1u8 {
self.status = Status::InvalidArgument;
xfer.accept().ok();
return
}
if addr > 127u8 {
self.status = Status::InvalidArgument;
xfer.accept().ok();
return
}
hprintln!("WRITE I2C, addr: {}, len: {}", addr, len).ok();
match self.i2c_dev.write(addr, &self.buffer[0usize..(len as usize)]) {
Ok(_) => {
self.status = Status::Ack;
},
Err(NbError::Other(I2CError::Acknowledge)) => {
self.status = Status::Nack;
},
Err(e) => {
hprintln!("When writing I2C (addr {}, {} bytes): {:?}", addr, len, e).ok();
self.status = Status::BusError;
},
}
xfer.accept().ok();
},
// Serve SetWritePointer: set start address at which bytes from a BULK OUT will be
// written to. The write pointer does _not_ increment on every write, so will need to
// be manually controler after every BULK transfer.
Some(ControlOutRequest::SetWritePointer) => {
let pointer = req.value;
if (pointer as usize) >= BUFFER_SIZE {
self.status = Status::InvalidArgument;
xfer.accept().ok();
return
}
hprintln!("SET WRITE PTR, pointer: {}", pointer).ok();
self.write_pointer = pointer as usize;
self.status = Status::OK;
xfer.accept().ok();
},
_ => {
hprintln!("Unhandled control out on iface: {:?}", req).ok();
},
}
}
fn get_configuration_descriptors(
&self,
writer: &mut DescriptorWriter,
) -> usb_device::Result<()> {
writer.interface(
self.interface,
0xff,
21, 37,
)?;
writer.endpoint(&self.ep_in)?;
writer.endpoint(&self.ep_out)?;
Ok(())
}
fn poll(&mut self) {
let mut temp_buf = [0; PACKET_SIZE];
// Serve BULK OUT writes - copy bytes into internal buffer.
match self.ep_out.read(&mut temp_buf) {
Ok(count) => {
if self.expect_bulk_out {
self.expect_bulk_out = false;
} else {
panic!("unexpectedly read data from bulk out endpoint");
}
hprintln!("SET BUFFER: ptr {}, {} bytes", self.write_pointer, count).ok();
for (i, c) in temp_buf.iter().enumerate() {
let ptr = self.write_pointer + i;
// Silently drop bytes that do not fit in buffer.
if ptr >= BUFFER_SIZE {
continue;
}
self.buffer[ptr] = c.clone();
}
},
Err(UsbError::WouldBlock) => {},
Err(err) => panic!("bulk read {:?}", err),
}
}
fn endpoint_out(&mut self, addr: EndpointAddress) {
if addr == self.ep_out.address() {
self.expect_bulk_out = true;
}
}
fn endpoint_in_complete(&mut self, addr: EndpointAddress) {
if addr == self.ep_in.address() {
// TODO(q3k): should we be doing something here?
}
}
}