aboutsummaryrefslogtreecommitdiffstats
path: root/usbh/src/device.rs
diff options
context:
space:
mode:
authorBrian Cully <bjc@kublai.com>2019-08-04 14:19:25 -0400
committerBrian Cully <bjc@kublai.com>2019-08-04 14:19:25 -0400
commite405c474f5e0e94288191223de7ae0f31ae0b15f (patch)
tree4ca89a92f0c868eb8feae272513c1e924b834adc /usbh/src/device.rs
parentabd478d9425dd2d4acd5b58202b1a95b73ff217b (diff)
downloadsamd21-demo-e405c474f5e0e94288191223de7ae0f31ae0b15f.tar.gz
samd21-demo-e405c474f5e0e94288191223de7ae0f31ae0b15f.zip
Migrate everything over to separate libraries.
* `usb-host` is the crate containing the HAL traits. * `bootkbd` is a crate for a bare-bones boot protocol keyboard driver. * `samd21-host` is a crate with an implementation of `usb-host` for a SAMD21 platform, with an example for the trinket-m0 which uses the `bootkbd` driver to print keyboard reports.
Diffstat (limited to 'usbh/src/device.rs')
-rw-r--r--usbh/src/device.rs392
1 files changed, 0 insertions, 392 deletions
diff --git a/usbh/src/device.rs b/usbh/src/device.rs
deleted file mode 100644
index fd5c1a1..0000000
--- a/usbh/src/device.rs
+++ /dev/null
@@ -1,392 +0,0 @@
-use super::pipe::{PipeErr, PipeTable, NO_DATA_STAGE};
-use super::usbproto::*;
-
-use core::convert::TryInto;
-use log::{debug, error, info, trace};
-
-// TODO: impl Drop for Device/Endpoint cleanup if any ends up being
-// required.
-
-// FIXME: once again, this doesn't belong here. The issue is that
-// we're using `pipe_for`, which requires it.
-use atsamd_hal::target_device::usb;
-
-const MAX_DEVICES: usize = 16;
-
-// TODO:
-// This may be wrong. It may be 15 additional input + 15 additional
-// output! cf §5.3.1.2 of USB 2.0.
-const MAX_ENDPOINTS: usize = 16;
-
-// How long to wait before talking to the device again after setting
-// its address. cf §9.2.6.3 of USB 2.0
-const SETTLE_DELAY: usize = 2;
-
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub(crate) enum Error {
- PipeErr(PipeErr),
-}
-impl From<PipeErr> for Error {
- fn from(e: PipeErr) -> Self {
- Self::PipeErr(e)
- }
-}
-
-enum FSM {
- AddressSet,
- WaitForSettle(usize),
- GetConfigDescriptor,
- SetConfig,
- GetReport(usize),
- Steady,
-}
-
-pub(crate) struct DeviceTable {
- devices: [Option<Device>; MAX_DEVICES],
-}
-// TODO: Untie device address from table index. Right now it's wasting
-// a table slot because addr 0 isn't used here. And rather than just
-// putting in an offset, which can be forgotten, it's better to let
-// something else handle address assignment.
-impl DeviceTable {
- pub(crate) fn new<F>(millis: &'static F) -> Self
- where
- F: Fn() -> usize + 'static,
- {
- let mut devices: [Option<Device>; MAX_DEVICES] = {
- let mut devs: [core::mem::MaybeUninit<Option<Device>>; MAX_DEVICES] =
- unsafe { core::mem::MaybeUninit::uninit().assume_init() };
- for d in &mut devs[..] {
- unsafe { core::ptr::write(d.as_mut_ptr(), None) }
- }
- unsafe { core::mem::transmute(devs) }
- };
- devices[0] = Some(Device::new(0, 8, millis));
- Self { devices: devices }
- }
-
- /// Return the device at address `addr`.
- pub(crate) fn device_for(&mut self, addr: u8) -> Option<&mut Device> {
- if let Some(ref mut d) = self.devices[addr as usize] {
- Some(d)
- } else {
- None
- }
- }
-
- /// Allocate a device with the next available address.
- // TODO: get rid of the millis argument somehow, but the device
- // does need a way of tracking time for Settle reasons.
- pub(crate) fn next(
- &mut self,
- max_packet_size: u8,
- millis: &'static dyn Fn() -> usize,
- ) -> Option<&mut Device> {
- for i in 1..self.devices.len() {
- if self.devices[i].is_none() {
- let a = i.try_into().unwrap();
- let d = Device::new(a, max_packet_size, millis);
- self.devices[i] = Some(d);
- return self.device_for(a);
- }
- }
- None
- }
-
- /// Remove the device at address `addr`.
- pub(crate) fn remove(&mut self, addr: u8) -> Option<Device> {
- let v = core::mem::replace(&mut self.devices[addr as usize], None);
- v
- }
-
- pub(crate) fn run(&mut self, pipe_table: &mut PipeTable, host: &mut usb::HOST) {
- for i in 1..self.devices.len() {
- // TODO: Woof, this is ugly, but I'm not sure of a better
- // way to avoid mutably borrowing self twice.
- let mut remove_addr: Option<u8> = None;
- if let Some(ref mut d) = self.devices[i] {
- if let Err(e) = d.fsm(pipe_table, host) {
- error!("Removing device {}: {:?}", d.addr, e);
- remove_addr = Some(d.addr);
- } else {
- remove_addr = None;
- }
- }
-
- if let Some(addr) = remove_addr {
- self.remove(addr);
- }
- }
- }
-}
-
-pub struct Device {
- pub addr: u8,
- pub max_packet_size: u8,
- pub endpoints: [Option<Endpoint>; MAX_ENDPOINTS],
- pub ep0: Endpoint,
-
- state: FSM,
- millis: &'static dyn Fn() -> usize,
-}
-impl Device {
- // TODO: get max packet size from device descriptor.
- pub fn new(addr: u8, max_packet_size: u8, millis: &'static dyn Fn() -> usize) -> Self {
- // Set up endpoints array with 0 as default control endpoint.
- let endpoints: [Option<Endpoint>; MAX_ENDPOINTS] = {
- let mut eps: [core::mem::MaybeUninit<Option<Endpoint>>; MAX_ENDPOINTS] =
- unsafe { core::mem::MaybeUninit::uninit().assume_init() };
- for ep in &mut eps[..] {
- unsafe { core::ptr::write(ep.as_mut_ptr(), None) }
- }
- unsafe { core::mem::transmute(eps) }
- };
-
- Self {
- addr: addr,
- max_packet_size: max_packet_size,
- endpoints: endpoints,
- ep0: Endpoint::new(
- addr,
- 0,
- TransferType::Control,
- TransferDirection::In,
- max_packet_size,
- ),
-
- state: FSM::AddressSet,
-
- // TODO: This doesn't belong here. Ideally the current
- // time is passed in to the FSM routine.
- millis: millis,
- }
- }
-}
-
-impl Device {
- pub(crate) fn fsm(
- &mut self,
- pipe_table: &mut PipeTable,
- host: &mut usb::HOST,
- ) -> Result<(), Error> {
- match self.state {
- FSM::AddressSet => self.state = FSM::WaitForSettle((self.millis)() + SETTLE_DELAY),
-
- FSM::WaitForSettle(until) => {
- if (self.millis)() >= until {
- // Dunno why we get the device descriptor a second time.
- let mut pipe = pipe_table.pipe_for(host, &self.ep0);
-
- let mut vol_descr =
- ::vcell::VolatileCell::<DeviceDescriptor>::new(Default::default());
- pipe.control_transfer(
- &mut self.ep0,
- RequestType::get_descr(),
- RequestCode::GetDescriptor,
- WValue::from((0, DescriptorType::Device as u8)),
- 0,
- Some(&mut vol_descr),
- self.millis,
- )?;
-
- let desc = vol_descr.get();
- trace!(" -- devDesc: {:?}", desc);
-
- self.state = FSM::GetConfigDescriptor
- }
- }
-
- FSM::GetConfigDescriptor => {
- // Get config descriptor with minimal data, to see how much we need to allocate for the full descriptor.
- let mut pipe = pipe_table.pipe_for(host, &self.ep0);
-
- let mut vol_descr =
- ::vcell::VolatileCell::<ConfigurationDescriptor>::new(Default::default());
- pipe.control_transfer(
- &mut self.ep0,
- RequestType::get_descr(),
- RequestCode::GetDescriptor,
- WValue::from((0, DescriptorType::Configuration as u8)),
- 0,
- Some(&mut vol_descr),
- self.millis,
- )?;
- let desc = vol_descr.get();
- debug!("config: {:?}", desc);
-
- // TODO: do real allocation later.
- assert!(desc.w_total_length < 64);
- let buf: [u8; 64] = [0; 64];
- let mut tmp = &buf[..desc.w_total_length as usize];
- pipe.control_transfer(
- &mut self.ep0,
- RequestType::get_descr(),
- RequestCode::GetDescriptor,
- WValue::from((0, DescriptorType::Configuration as u8)),
- 0,
- Some(&mut tmp),
- self.millis,
- )?;
-
- self.state = FSM::SetConfig
- }
-
- FSM::SetConfig => {
- let mut pipe = pipe_table.pipe_for(host, &self.ep0);
-
- debug!("+++ setting configuration");
- let conf: u8 = 1;
- pipe.control_transfer(
- &mut self.ep0,
- RequestType::set(),
- RequestCode::SetConfiguration,
- WValue::from((conf, 0)),
- 0,
- NO_DATA_STAGE,
- self.millis,
- )?;
- debug!(" -- configuration set");
-
- debug!("+++ setting idle");
- pipe.control_transfer(
- &mut self.ep0,
- RequestType::from((
- RequestDirection::HostToDevice,
- RequestKind::Class,
- RequestRecipient::Interface,
- )),
- RequestCode::GetInterface, // This is also idle, but can't have two enums with the same value.
- WValue::from((0, 0)),
- 0,
- NO_DATA_STAGE,
- self.millis,
- )?;
- debug!(" -- idle set");
-
- debug!("+++ setting report");
- let mut report: u8 = 0;
- pipe.control_transfer(
- &mut self.ep0,
- RequestType::from((
- RequestDirection::HostToDevice,
- RequestKind::Class,
- RequestRecipient::Interface,
- )),
- RequestCode::SetConfiguration,
- WValue::from((0, 2)),
- 0,
- Some(&mut report),
- self.millis,
- )?;
- debug!(" -- report set");
-
- // Stub in some endpoints until we can parse the
- // configuration descriptor.
- self.endpoints[1] = Some(Endpoint::new(
- self.addr,
- 1,
- TransferType::Interrupt,
- TransferDirection::In,
- 8,
- ));
- self.endpoints[2] = Some(Endpoint::new(
- self.addr,
- 2,
- TransferType::Interrupt,
- TransferDirection::In,
- 8,
- ));
- self.state = FSM::GetReport(2)
- }
-
- FSM::GetReport(0) => self.state = FSM::Steady,
-
- FSM::GetReport(count) => {
- debug!("+++ getting report {}", count);
-
- // For now, just do an IN transfer to see if we can
- // get some keyboard reports without further setup.
-
- // EP 1 is boot proto keyboard.
- self.read_report(pipe_table, host, 1);
-
- // EP 2 is consumer control keys.
- self.read_report(pipe_table, host, 2);
-
- self.state = FSM::GetReport(count)
- }
-
- FSM::Steady => {}
- }
- Ok(())
- }
-
- fn read_report(&mut self, pipe_table: &mut PipeTable, host: &mut usb::HOST, id: u8) {
- if let Some(ref mut ep) = self.endpoints[id as usize] {
- let mut pipe = pipe_table.pipe_for(host, ep);
- let mut buf: [u8; 8] = [0; 8];
- match pipe.in_transfer(ep, &mut buf, 15, self.millis) {
- Ok(bytes_received) => info!("report {}: {} - {:?}", id, bytes_received, buf),
-
- Err(PipeErr::Flow) => return,
-
- Err(e) => trace!("error {}: {:?}", id, e),
- }
- } else {
- error!("endpoint {} doesn't exist!", id)
- }
- }
-}
-
-// TransferType (INTERRUPT)
-// Direction (IN)
-pub struct Endpoint {
- // This just points back to the address because we need to know it
- // for all endpoint operations, but we don't want to pass the
- // address struct (which contains all endpoints) around.
- pub addr: u8,
- pub num: u8,
- pub transfer_type: TransferType,
- pub direction: TransferDirection,
- pub in_toggle: bool,
- pub out_toggle: bool,
- pub max_packet_size: u8,
-}
-
-// cf §9.6.6 of USB 2.0
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum TransferDirection {
- Out,
- In,
-}
-
-// ibid
-#[derive(Copy, Clone, Debug, PartialEq)]
-pub enum TransferType {
- Control = 0,
- Isochronous = 1,
- Bulk = 2,
- Interrupt = 3,
-}
-
-impl Endpoint {
- // TODO: direction is ignored on control endpoints. Try to remove
- // it from the API in those cases as well.
- pub fn new(
- addr: u8,
- num: u8,
- transfer_type: TransferType,
- direction: TransferDirection,
- max_packet_size: u8,
- ) -> Self {
- Self {
- addr: addr,
- num: num,
- transfer_type: transfer_type,
- direction: direction,
- in_toggle: false,
- out_toggle: false,
- max_packet_size: max_packet_size,
- }
- }
-}