aboutsummaryrefslogtreecommitdiffstats
path: root/usbh/src/device.rs
diff options
context:
space:
mode:
Diffstat (limited to 'usbh/src/device.rs')
-rw-r--r--usbh/src/device.rs185
1 files changed, 171 insertions, 14 deletions
diff --git a/usbh/src/device.rs b/usbh/src/device.rs
index 3bb0304..ac48c14 100644
--- a/usbh/src/device.rs
+++ b/usbh/src/device.rs
@@ -1,20 +1,56 @@
+use super::pipe::{DataBuf, PipeErr, PipeTable};
+use super::usbproto::*;
+
use core::convert::TryInto;
+use log::{debug, error, trace};
+
+// 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;
const MAX_ENDPOINTS: usize = 8;
-pub struct DeviceTable {
+// 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 = 300;
+
+#[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,
+ 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 fn new() -> Self {
+ pub(crate) fn new() -> Self {
+ // TODO: should we create addr 0 here for use during
+ // enumeration? Probably.
Self {
devices: Default::default(),
}
}
/// Return the device at address `addr`.
- pub fn device_for(&mut self, addr: u8) -> Option<&mut Device> {
+ 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 {
@@ -23,12 +59,13 @@ impl DeviceTable {
}
/// Allocate a device with the next available address.
- pub fn next(&mut self) -> Option<&mut Device> {
- for i in 0..self.devices.len() {
+ // 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, 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 mut d: Device = Default::default();
- d.addr = a;
+ let d = Device::new(a, millis);
self.devices[i] = Some(d);
return self.device_for(a);
}
@@ -37,24 +74,144 @@ impl DeviceTable {
}
/// Remove the device at address `addr`.
- pub fn remove(&mut self, addr: u8) -> Option<Device> {
+ 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 {
- addr: u8,
- max_packet_size: u8,
- endpoints: [Endpoint; MAX_ENDPOINTS],
+ pub addr: u8,
+ pub max_packet_size: u8,
+ pub endpoints: [Option<Endpoint>; MAX_ENDPOINTS],
+
+ state: FSM,
+ millis: &'static dyn Fn() -> usize,
}
-impl Default for Device {
- fn default() -> Self {
+impl Device {
+ fn new(addr: u8, millis: &'static dyn Fn() -> usize) -> Self {
Self {
- addr: 0,
+ addr: addr,
max_packet_size: 8,
endpoints: Default::default(),
+
+ 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.addr, 0);
+
+ let mut vol_descr =
+ ::vcell::VolatileCell::<USBDeviceDescriptor>::new(Default::default());
+ pipe.control_req(
+ BMRequestType::get_descr(),
+ USBRequest::GetDescriptor,
+ WValue::from((0, USBDescriptor::Device as u8)),
+ 0,
+ Some(DataBuf::from(&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.addr, 0);
+
+ let mut vol_descr =
+ ::vcell::VolatileCell::<USBConfigurationDescriptor>::new(Default::default());
+ pipe.control_req(
+ BMRequestType::get_descr(),
+ USBRequest::GetDescriptor,
+ WValue::from((0, USBDescriptor::Configuration as u8)),
+ 0,
+ Some(DataBuf::from(&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_req(
+ BMRequestType::get_descr(),
+ USBRequest::GetDescriptor,
+ WValue::from((0, USBDescriptor::Configuration as u8)),
+ 0,
+ Some(DataBuf::from(&mut tmp)),
+ self.millis,
+ )?;
+
+ self.state = FSM::SetConfig
+ }
+
+ FSM::SetConfig => {
+ let mut pipe = pipe_table.pipe_for(host, self.addr, 0);
+
+ debug!("+++ setting configuration");
+ let conf: u8 = 1;
+ pipe.control_req(
+ BMRequestType::set(),
+ USBRequest::SetConfiguration,
+ WValue::from((conf, 0)),
+ 0,
+ None,
+ self.millis,
+ )?;
+ debug!(" -- configuration set");
+
+ self.state = FSM::Steady
+ }
+
+ FSM::Steady => {
+ // Now we should be able to access it normally.
+ }
}
+ Ok(())
}
}