Hopefully without breaking the others. Summary of the changes: - Device descriptor reports the device is bus powered and requires 100mA max. - HID descriptor version bumped to 1.11 (was 1.10) - Added string index for Interface and HID descriptors (which seems to make OS X happy)
1120 lines
40 KiB
Diff
1120 lines
40 KiB
Diff
diff --git a/boards/components/src/lib.rs b/boards/components/src/lib.rs
|
|
index 92d45f7e3..51041545b 100644
|
|
--- a/boards/components/src/lib.rs
|
|
+++ b/boards/components/src/lib.rs
|
|
@@ -64,3 +64,4 @@ pub mod tickv;
|
|
pub mod touch;
|
|
pub mod udp_driver;
|
|
pub mod udp_mux;
|
|
+pub mod usb_ctap;
|
|
diff --git a/boards/components/src/usb_ctap.rs b/boards/components/src/usb_ctap.rs
|
|
new file mode 100644
|
|
index 000000000..eed34a268
|
|
--- /dev/null
|
|
+++ b/boards/components/src/usb_ctap.rs
|
|
@@ -0,0 +1,87 @@
|
|
+//! Component for CTAP over USB.
|
|
+
|
|
+use capsules::usb::usb_ctap::CtapUsbSyscallDriver;
|
|
+use capsules::usb::usbc_ctap_hid::ClientCtapHID;
|
|
+use core::mem::MaybeUninit;
|
|
+use kernel::capabilities;
|
|
+use kernel::component::Component;
|
|
+use kernel::create_capability;
|
|
+use kernel::hil;
|
|
+
|
|
+// Setup static space for the objects.
|
|
+#[macro_export]
|
|
+macro_rules! usb_ctap_component_helper {
|
|
+ ($C:ty $(,)?) => {{
|
|
+ use capsules::usb::usb_ctap::CtapUsbSyscallDriver;
|
|
+ use capsules::usb::usbc_ctap_hid::ClientCtapHID;
|
|
+ use core::mem::MaybeUninit;
|
|
+
|
|
+ static mut hid: MaybeUninit<ClientCtapHID<'static, 'static, $C>> = MaybeUninit::uninit();
|
|
+ static mut driver: MaybeUninit<CtapUsbSyscallDriver<'static, 'static, $C>> =
|
|
+ MaybeUninit::uninit();
|
|
+
|
|
+ (&mut hid, &mut driver)
|
|
+ };};
|
|
+}
|
|
+
|
|
+pub struct UsbCtapComponent<C: 'static + hil::usb::UsbController<'static>> {
|
|
+ board_kernel: &'static kernel::Kernel,
|
|
+ driver_num: usize,
|
|
+ controller: &'static C,
|
|
+ max_ctrl_packet_size: u8,
|
|
+ vendor_id: u16,
|
|
+ product_id: u16,
|
|
+ strings: &'static [&'static str],
|
|
+}
|
|
+
|
|
+impl<C: 'static + hil::usb::UsbController<'static>> UsbCtapComponent<C> {
|
|
+ pub fn new(
|
|
+ board_kernel: &'static kernel::Kernel,
|
|
+ driver_num: usize,
|
|
+ controller: &'static C,
|
|
+ max_ctrl_packet_size: u8,
|
|
+ vendor_id: u16,
|
|
+ product_id: u16,
|
|
+ strings: &'static [&'static str],
|
|
+ ) -> Self {
|
|
+ Self {
|
|
+ board_kernel,
|
|
+ driver_num,
|
|
+ controller,
|
|
+ max_ctrl_packet_size,
|
|
+ vendor_id,
|
|
+ product_id,
|
|
+ strings,
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+impl<C: 'static + hil::usb::UsbController<'static>> Component for UsbCtapComponent<C> {
|
|
+ type StaticInput = (
|
|
+ &'static mut MaybeUninit<ClientCtapHID<'static, 'static, C>>,
|
|
+ &'static mut MaybeUninit<CtapUsbSyscallDriver<'static, 'static, C>>,
|
|
+ );
|
|
+ type Output = &'static CtapUsbSyscallDriver<'static, 'static, C>;
|
|
+
|
|
+ unsafe fn finalize(self, s: Self::StaticInput) -> Self::Output {
|
|
+ let grant_cap = create_capability!(capabilities::MemoryAllocationCapability);
|
|
+
|
|
+ let usb_ctap = s.0.write(ClientCtapHID::new(
|
|
+ self.controller,
|
|
+ self.max_ctrl_packet_size,
|
|
+ self.vendor_id,
|
|
+ self.product_id,
|
|
+ self.strings,
|
|
+ ));
|
|
+ self.controller.set_client(usb_ctap);
|
|
+
|
|
+ // Configure the USB userspace driver
|
|
+ let usb_driver = s.1.write(CtapUsbSyscallDriver::new(
|
|
+ usb_ctap,
|
|
+ self.board_kernel.create_grant(self.driver_num, &grant_cap),
|
|
+ ));
|
|
+ usb_ctap.set_client(usb_driver);
|
|
+
|
|
+ usb_driver
|
|
+ }
|
|
+}
|
|
diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs
|
|
index f189dfd09..c48c7e094 100644
|
|
--- a/capsules/src/driver.rs
|
|
+++ b/capsules/src/driver.rs
|
|
@@ -29,6 +29,8 @@ pub enum NUM {
|
|
UsbUser = 0x20005,
|
|
I2cMasterSlave = 0x20006,
|
|
|
|
+ UsbCtap = 0x20009,
|
|
+
|
|
// Radio
|
|
BleAdvertising = 0x30000,
|
|
Ieee802154 = 0x30001,
|
|
diff --git a/capsules/src/usb/app.rs b/capsules/src/usb/app.rs
|
|
new file mode 100644
|
|
index 000000000..f2e248bc9
|
|
--- /dev/null
|
|
+++ b/capsules/src/usb/app.rs
|
|
@@ -0,0 +1,61 @@
|
|
+#[derive(Clone, Copy, PartialEq, Eq)]
|
|
+pub enum Side {
|
|
+ Transmit,
|
|
+ Receive,
|
|
+ TransmitOrReceive,
|
|
+}
|
|
+
|
|
+impl Side {
|
|
+ pub fn can_transmit(&self) -> bool {
|
|
+ match self {
|
|
+ Side::Transmit | Side::TransmitOrReceive => true,
|
|
+ Side::Receive => false,
|
|
+ }
|
|
+ }
|
|
+
|
|
+ pub fn can_receive(&self) -> bool {
|
|
+ match self {
|
|
+ Side::Receive | Side::TransmitOrReceive => true,
|
|
+ Side::Transmit => false,
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+#[derive(Default)]
|
|
+pub struct App {
|
|
+ // Only one app can be connected to this driver, to avoid needing to route packets among apps.
|
|
+ // This field tracks this status.
|
|
+ pub connected: bool,
|
|
+ // Currently enabled transaction side. Subscribing to a callback or allowing a buffer
|
|
+ // automatically sets the corresponding side. Clearing both the callback and the buffer resets
|
|
+ // the side to None.
|
|
+ pub side: Option<Side>,
|
|
+ // Whether the app is waiting for the kernel signaling a packet transfer.
|
|
+ pub waiting: bool,
|
|
+}
|
|
+
|
|
+impl App {
|
|
+ pub fn can_receive_packet(&self) -> bool {
|
|
+ self.waiting && self.side.map_or(false, |side| side.can_receive())
|
|
+ }
|
|
+
|
|
+ pub fn check_side(&mut self) {
|
|
+ if !self.waiting {
|
|
+ self.side = None;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ pub fn set_side(&mut self, side: Side) -> bool {
|
|
+ match self.side {
|
|
+ None => {
|
|
+ self.side = Some(side);
|
|
+ true
|
|
+ }
|
|
+ Some(app_side) => side == app_side,
|
|
+ }
|
|
+ }
|
|
+
|
|
+ pub fn is_ready_for_command(&self, side: Side) -> bool {
|
|
+ self.side == Some(side)
|
|
+ }
|
|
+}
|
|
diff --git a/capsules/src/usb/descriptors.rs b/capsules/src/usb/descriptors.rs
|
|
index 67f708239..9c5bc9cd1 100644
|
|
--- a/capsules/src/usb/descriptors.rs
|
|
+++ b/capsules/src/usb/descriptors.rs
|
|
@@ -568,8 +568,8 @@ impl Default for ConfigurationDescriptor {
|
|
num_interfaces: 1,
|
|
configuration_value: 1,
|
|
string_index: 0,
|
|
- attributes: ConfigurationAttributes::new(true, false),
|
|
- max_power: 0, // in 2mA units
|
|
+ attributes: ConfigurationAttributes::new(false, false),
|
|
+ max_power: 50, // in 2mA units
|
|
related_descriptor_length: 0,
|
|
}
|
|
}
|
|
diff --git a/capsules/src/usb/mod.rs b/capsules/src/usb/mod.rs
|
|
index 767f5de83..cb5e0af97 100644
|
|
--- a/capsules/src/usb/mod.rs
|
|
+++ b/capsules/src/usb/mod.rs
|
|
@@ -1,5 +1,8 @@
|
|
+pub mod app;
|
|
pub mod cdc;
|
|
pub mod descriptors;
|
|
+pub mod usb_ctap;
|
|
pub mod usb_user;
|
|
pub mod usbc_client;
|
|
pub mod usbc_client_ctrl;
|
|
+pub mod usbc_ctap_hid;
|
|
diff --git a/capsules/src/usb/usb_ctap.rs b/capsules/src/usb/usb_ctap.rs
|
|
new file mode 100644
|
|
index 000000000..30cac1323
|
|
--- /dev/null
|
|
+++ b/capsules/src/usb/usb_ctap.rs
|
|
@@ -0,0 +1,343 @@
|
|
+use super::app::{App, Side};
|
|
+use super::usbc_ctap_hid::ClientCtapHID;
|
|
+use kernel::errorcode::ErrorCode;
|
|
+use kernel::grant::{AllowRoCount, AllowRwCount, Grant, GrantKernelData, UpcallCount};
|
|
+use kernel::hil;
|
|
+use kernel::hil::usb::Client;
|
|
+use kernel::processbuffer::{ReadableProcessBuffer, WriteableProcessBuffer};
|
|
+use kernel::syscall::{CommandReturn, SyscallDriver};
|
|
+use kernel::ProcessId;
|
|
+
|
|
+/// Syscall number
|
|
+use crate::driver;
|
|
+pub const DRIVER_NUM: usize = driver::NUM::UsbCtap as usize;
|
|
+
|
|
+pub const CTAP_CMD_CHECK: usize = 0;
|
|
+pub const CTAP_CMD_CONNECT: usize = 1;
|
|
+pub const CTAP_CMD_TRANSMIT: usize = 2;
|
|
+pub const CTAP_CMD_RECEIVE: usize = 3;
|
|
+pub const CTAP_CMD_TRANSMIT_OR_RECEIVE: usize = 4;
|
|
+pub const CTAP_CMD_CANCEL: usize = 5;
|
|
+
|
|
+/// Ids for read-only allow buffers
|
|
+mod ro_allow {
|
|
+ pub const TRANSMIT: usize = 0;
|
|
+ pub const COUNT: u8 = 1;
|
|
+}
|
|
+
|
|
+/// Ids for read-write allow buffers
|
|
+mod rw_allow {
|
|
+ pub const RECEIVE: usize = 0;
|
|
+ pub const COUNT: u8 = 1;
|
|
+}
|
|
+
|
|
+/// Ids for scheduling the upcalls
|
|
+///
|
|
+/// They **must** match the the subscribe numbers which were used by the process to
|
|
+/// subscribe to the upcall.
|
|
+mod upcalls {
|
|
+ pub const TRANSMITTED: usize = 0;
|
|
+ pub const RECEIVED: usize = 1;
|
|
+ pub const COUNT: u8 = 2;
|
|
+}
|
|
+
|
|
+type CtabUsbDriverGrant = Grant<
|
|
+ App,
|
|
+ UpcallCount<{ upcalls::COUNT }>,
|
|
+ AllowRoCount<{ ro_allow::COUNT }>,
|
|
+ AllowRwCount<{ rw_allow::COUNT }>,
|
|
+>;
|
|
+
|
|
+pub trait CtapUsbClient {
|
|
+ // Whether this client is ready to receive a packet. This must be checked before calling
|
|
+ // packet_received(). If App is not supplied, it will be found from the implemntation's
|
|
+ // members.
|
|
+ fn can_receive_packet(&self, app: &Option<&mut App>) -> bool;
|
|
+
|
|
+ // Signal to the client that a packet has been received.
|
|
+ fn packet_received(
|
|
+ &self,
|
|
+ packet: &[u8; 64],
|
|
+ endpoint: usize,
|
|
+ app_data: (Option<&mut App>, Option<&GrantKernelData>),
|
|
+ );
|
|
+
|
|
+ // Signal to the client that a packet has been transmitted.
|
|
+ fn packet_transmitted(&self);
|
|
+}
|
|
+
|
|
+pub struct CtapUsbSyscallDriver<'a, 'b, C: 'a> {
|
|
+ usb_client: &'a ClientCtapHID<'a, 'b, C>,
|
|
+ apps: CtabUsbDriverGrant,
|
|
+}
|
|
+
|
|
+impl<'a, 'b, C: hil::usb::UsbController<'a>> CtapUsbSyscallDriver<'a, 'b, C> {
|
|
+ pub fn new(usb_client: &'a ClientCtapHID<'a, 'b, C>, apps: CtabUsbDriverGrant) -> Self {
|
|
+ CtapUsbSyscallDriver { usb_client, apps }
|
|
+ }
|
|
+
|
|
+ fn app_packet_received(
|
|
+ &self,
|
|
+ packet: &[u8; 64],
|
|
+ endpoint: usize,
|
|
+ app: &mut App,
|
|
+ kernel: &GrantKernelData,
|
|
+ ) {
|
|
+ if app.connected && app.waiting && app.side.map_or(false, |side| side.can_receive()) {
|
|
+ let _ = kernel
|
|
+ .get_readwrite_processbuffer(rw_allow::RECEIVE)
|
|
+ .and_then(|buf| buf.mut_enter(|dest| {
|
|
+ if dest.len() >= 64 {
|
|
+ dest.copy_from_slice(packet);
|
|
+ } else {
|
|
+ panic!();
|
|
+ }
|
|
+ }));
|
|
+ app.waiting = false;
|
|
+ // reset the client state
|
|
+ app.check_side();
|
|
+ // Signal to the app that a packet is ready.
|
|
+ // TODO: passing the upcallid again in the registers is not needed anymore with Tock 2.0,
|
|
+ // but is currently still there for backwards compatibility
|
|
+ kernel
|
|
+ .schedule_upcall(upcalls::RECEIVED, (upcalls::RECEIVED, endpoint, 0))
|
|
+ .ok();
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+impl<'a, 'b, C: hil::usb::UsbController<'a>> CtapUsbClient for CtapUsbSyscallDriver<'a, 'b, C> {
|
|
+ fn can_receive_packet(&self, app: &Option<&mut App>) -> bool {
|
|
+ let mut result = false;
|
|
+ match app {
|
|
+ None => {
|
|
+ for app in self.apps.iter() {
|
|
+ app.enter(|a, _| {
|
|
+ if a.connected {
|
|
+ result = a.can_receive_packet();
|
|
+ }
|
|
+ })
|
|
+ }
|
|
+ }
|
|
+ Some(a) => result = a.can_receive_packet(),
|
|
+ }
|
|
+ result
|
|
+ }
|
|
+
|
|
+ fn packet_received(
|
|
+ &self,
|
|
+ packet: &[u8; 64],
|
|
+ endpoint: usize,
|
|
+ app_data: (Option<&mut App>, Option<&GrantKernelData>),
|
|
+ ) {
|
|
+ match app_data {
|
|
+ (None, _) => {
|
|
+ for app in self.apps.iter() {
|
|
+ app.enter(|a, kernel_grant| {
|
|
+ self.app_packet_received(packet, endpoint, a, kernel_grant);
|
|
+ })
|
|
+ }
|
|
+ }
|
|
+ (Some(app), Some(kernel_grant)) => {
|
|
+ self.app_packet_received(packet, endpoint, app, kernel_grant)
|
|
+ }
|
|
+ // this should never happen as having a valid app always results
|
|
+ // in also having the grant data
|
|
+ _ => panic!("invalid app_data combination!"),
|
|
+ }
|
|
+ }
|
|
+
|
|
+ fn packet_transmitted(&self) {
|
|
+ for app in self.apps.iter() {
|
|
+ app.enter(|app, kernel_data| {
|
|
+ if app.connected
|
|
+ && app.waiting
|
|
+ && app.side.map_or(false, |side| side.can_transmit())
|
|
+ {
|
|
+ app.waiting = false;
|
|
+ // reset the client state
|
|
+ app.check_side();
|
|
+ // Signal to the app that the packet was sent.
|
|
+ kernel_data
|
|
+ .schedule_upcall(upcalls::TRANSMITTED, (upcalls::TRANSMITTED, 0, 0))
|
|
+ .unwrap();
|
|
+ }
|
|
+ });
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+impl<'a, 'b, C: hil::usb::UsbController<'a>> SyscallDriver for CtapUsbSyscallDriver<'a, 'b, C> {
|
|
+ fn allocate_grant(&self, process_id: ProcessId) -> Result<(), kernel::process::Error> {
|
|
+ self.apps.enter(process_id, |_, _| {})
|
|
+ }
|
|
+
|
|
+ fn command(
|
|
+ &self,
|
|
+ cmd_num: usize,
|
|
+ endpoint: usize,
|
|
+ _arg2: usize,
|
|
+ process_id: ProcessId,
|
|
+ ) -> CommandReturn {
|
|
+ match cmd_num {
|
|
+ CTAP_CMD_CHECK => CommandReturn::success(),
|
|
+ CTAP_CMD_CONNECT => {
|
|
+ // First, check if any app is already connected to this driver.
|
|
+ let mut busy = false;
|
|
+ for app in self.apps.iter() {
|
|
+ app.enter(|app, _| {
|
|
+ busy |= app.connected;
|
|
+ });
|
|
+ }
|
|
+
|
|
+ self.apps
|
|
+ .enter(process_id, |app, _| {
|
|
+ if app.connected {
|
|
+ CommandReturn::failure(ErrorCode::ALREADY)
|
|
+ } else if busy {
|
|
+ CommandReturn::failure(ErrorCode::BUSY)
|
|
+ } else {
|
|
+ self.usb_client.enable();
|
|
+ self.usb_client.attach();
|
|
+ app.connected = true;
|
|
+ CommandReturn::success()
|
|
+ }
|
|
+ })
|
|
+ .unwrap_or_else(|err| err.into())
|
|
+ }
|
|
+ CTAP_CMD_TRANSMIT => self
|
|
+ .apps
|
|
+ .enter(process_id, |app, kernel| {
|
|
+ if !app.connected {
|
|
+ CommandReturn::failure(ErrorCode::RESERVE)
|
|
+ } else {
|
|
+ // set the client state to transmit packets
|
|
+ if !app.set_side(Side::Transmit) {
|
|
+ return CommandReturn::failure(ErrorCode::INVAL);
|
|
+ }
|
|
+
|
|
+ if app.is_ready_for_command(Side::Transmit) {
|
|
+ if app.waiting {
|
|
+ CommandReturn::failure(ErrorCode::ALREADY)
|
|
+ } else {
|
|
+ kernel
|
|
+ .get_readonly_processbuffer(ro_allow::TRANSMIT)
|
|
+ .and_then(|buffer| {
|
|
+ buffer.enter(|buf| {
|
|
+ let mut packet: [u8; 64] = [0; 64];
|
|
+ buf.copy_to_slice(&mut packet);
|
|
+ let r =
|
|
+ self.usb_client.transmit_packet(&packet, endpoint);
|
|
+
|
|
+ if r.is_success() {
|
|
+ app.waiting = true;
|
|
+ }
|
|
+
|
|
+ r
|
|
+ })
|
|
+ })
|
|
+ .unwrap_or(CommandReturn::failure(ErrorCode::FAIL))
|
|
+ }
|
|
+ } else {
|
|
+ CommandReturn::failure(ErrorCode::INVAL)
|
|
+ }
|
|
+ }
|
|
+ })
|
|
+ .unwrap_or_else(|err| err.into()),
|
|
+ CTAP_CMD_RECEIVE => self
|
|
+ .apps
|
|
+ .enter(process_id, |app, kernel_grant| {
|
|
+ if !app.connected {
|
|
+ CommandReturn::failure(ErrorCode::RESERVE)
|
|
+ } else {
|
|
+ // set the client state to recive packets
|
|
+ if !app.set_side(Side::Receive) {
|
|
+ return CommandReturn::failure(ErrorCode::INVAL);
|
|
+ }
|
|
+ if app.is_ready_for_command(Side::Receive) {
|
|
+ if app.waiting {
|
|
+ CommandReturn::failure(ErrorCode::ALREADY)
|
|
+ } else {
|
|
+ app.waiting = true;
|
|
+ self.usb_client.receive_packet(app, kernel_grant);
|
|
+ CommandReturn::success()
|
|
+ }
|
|
+ } else {
|
|
+ CommandReturn::failure(ErrorCode::INVAL)
|
|
+ }
|
|
+ }
|
|
+ })
|
|
+ .unwrap_or_else(|err| err.into()),
|
|
+ CTAP_CMD_TRANSMIT_OR_RECEIVE => self
|
|
+ .apps
|
|
+ .enter(process_id, |app, kernel_grant| {
|
|
+ if !app.connected {
|
|
+ CommandReturn::failure(ErrorCode::RESERVE)
|
|
+ } else {
|
|
+ if !app.set_side(Side::TransmitOrReceive) {
|
|
+ return CommandReturn::failure(ErrorCode::INVAL);
|
|
+ }
|
|
+
|
|
+ if app.is_ready_for_command(Side::TransmitOrReceive) {
|
|
+ if app.waiting {
|
|
+ CommandReturn::failure(ErrorCode::ALREADY)
|
|
+ } else {
|
|
+ // Indicates to the driver that we can receive any pending packet.
|
|
+ app.waiting = true;
|
|
+ self.usb_client.receive_packet(app, kernel_grant);
|
|
+ if !app.waiting {
|
|
+ return CommandReturn::success();
|
|
+ }
|
|
+
|
|
+ let r = kernel_grant
|
|
+ .get_readonly_processbuffer(ro_allow::TRANSMIT)
|
|
+ .and_then(|process_buffer| {
|
|
+ process_buffer.enter(|buf| {
|
|
+ let mut packet: [u8; 64] = [0; 64];
|
|
+ buf.copy_to_slice(&mut packet);
|
|
+
|
|
+ // Indicates to the driver that we have a packet to send.
|
|
+ self.usb_client.transmit_packet(&packet, endpoint)
|
|
+ })
|
|
+ })
|
|
+ .unwrap_or(CommandReturn::failure(ErrorCode::FAIL));
|
|
+ if !r.is_success() {
|
|
+ return r;
|
|
+ }
|
|
+
|
|
+ CommandReturn::success()
|
|
+ }
|
|
+ } else {
|
|
+ CommandReturn::failure(ErrorCode::INVAL)
|
|
+ }
|
|
+ }
|
|
+ })
|
|
+ .unwrap_or_else(|err| err.into()),
|
|
+ CTAP_CMD_CANCEL => self
|
|
+ .apps
|
|
+ .enter(process_id, |app, _| {
|
|
+ if !app.connected {
|
|
+ CommandReturn::failure(ErrorCode::RESERVE)
|
|
+ } else {
|
|
+ if app.waiting {
|
|
+ // FIXME: if cancellation failed, the app should still wait. But that
|
|
+ // doesn't work yet.
|
|
+ app.waiting = false;
|
|
+ app.check_side();
|
|
+ if self.usb_client.cancel_transaction(endpoint) {
|
|
+ CommandReturn::success()
|
|
+ } else {
|
|
+ // Cannot cancel now because the transaction is already in process.
|
|
+ // The app should wait for the callback instead.
|
|
+ CommandReturn::failure(ErrorCode::BUSY)
|
|
+ }
|
|
+ } else {
|
|
+ CommandReturn::failure(ErrorCode::ALREADY)
|
|
+ }
|
|
+ }
|
|
+ })
|
|
+ .unwrap_or_else(|err| err.into()),
|
|
+ _ => CommandReturn::failure(ErrorCode::NOSUPPORT),
|
|
+ }
|
|
+ }
|
|
+}
|
|
diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
|
|
new file mode 100644
|
|
index 000000000..5ad2c44b3
|
|
--- /dev/null
|
|
+++ b/capsules/src/usb/usbc_ctap_hid.rs
|
|
@@ -0,0 +1,554 @@
|
|
+//! A USB HID client of the USB hardware interface
|
|
+
|
|
+use super::app::App;
|
|
+use super::descriptors;
|
|
+use super::descriptors::Buffer64;
|
|
+use super::descriptors::DescriptorType;
|
|
+use super::descriptors::EndpointAddress;
|
|
+use super::descriptors::EndpointDescriptor;
|
|
+use super::descriptors::HIDCountryCode;
|
|
+use super::descriptors::HIDDescriptor;
|
|
+use super::descriptors::HIDSubordinateDescriptor;
|
|
+use super::descriptors::InterfaceDescriptor;
|
|
+use super::descriptors::ReportDescriptor;
|
|
+use super::descriptors::TransferDirection;
|
|
+use super::usb_ctap::CtapUsbClient;
|
|
+use super::usbc_client_ctrl::ClientCtrl;
|
|
+use core::cell::Cell;
|
|
+use kernel::debug;
|
|
+use kernel::grant::GrantKernelData;
|
|
+use kernel::hil;
|
|
+use kernel::hil::usb::TransferType;
|
|
+use kernel::syscall::CommandReturn;
|
|
+use kernel::utilities::cells::OptionalCell;
|
|
+
|
|
+static LANGUAGES: &'static [u16; 1] = &[
|
|
+ 0x0409, // English (United States)
|
|
+];
|
|
+
|
|
+#[cfg(not(feature = "vendor_hid"))]
|
|
+const NUM_ENDPOINTS: usize = 1;
|
|
+#[cfg(feature = "vendor_hid")]
|
|
+const NUM_ENDPOINTS: usize = 2;
|
|
+
|
|
+const ENDPOINT_NUM: usize = 1;
|
|
+#[cfg(feature = "vendor_hid")]
|
|
+const VENDOR_ENDPOINT_NUM: usize = ENDPOINT_NUM + 1;
|
|
+
|
|
+static ENDPOINTS: &'static [usize] = &[
|
|
+ ENDPOINT_NUM,
|
|
+ #[cfg(feature = "vendor_hid")]
|
|
+ VENDOR_ENDPOINT_NUM,
|
|
+];
|
|
+
|
|
+static CTAP_REPORT_DESCRIPTOR: &'static [u8] = &[
|
|
+ 0x06, 0xD0, 0xF1, // HID_UsagePage ( FIDO_USAGE_PAGE ),
|
|
+ 0x09, 0x01, // HID_Usage ( FIDO_USAGE_CTAPHID ),
|
|
+ 0xA1, 0x01, // HID_Collection ( HID_Application ),
|
|
+ 0x09, 0x20, // HID_Usage ( FIDO_USAGE_DATA_IN ),
|
|
+ 0x15, 0x00, // HID_LogicalMin ( 0 ),
|
|
+ 0x26, 0xFF, 0x00, // HID_LogicalMaxS ( 0xff ),
|
|
+ 0x75, 0x08, // HID_ReportSize ( 8 ),
|
|
+ 0x95, 0x40, // HID_ReportCount ( HID_INPUT_REPORT_BYTES ),
|
|
+ 0x81, 0x02, // HID_Input ( HID_Data | HID_Absolute | HID_Variable ),
|
|
+ 0x09, 0x21, // HID_Usage ( FIDO_USAGE_DATA_OUT ),
|
|
+ 0x15, 0x00, // HID_LogicalMin ( 0 ),
|
|
+ 0x26, 0xFF, 0x00, // HID_LogicalMaxS ( 0xff ),
|
|
+ 0x75, 0x08, // HID_ReportSize ( 8 ),
|
|
+ 0x95, 0x40, // HID_ReportCount ( HID_OUTPUT_REPORT_BYTES ),
|
|
+ 0x91, 0x02, // HID_Output ( HID_Data | HID_Absolute | HID_Variable ),
|
|
+ 0xC0, // HID_EndCollection
|
|
+];
|
|
+
|
|
+#[cfg(feature = "vendor_hid")]
|
|
+static VENDOR_REPORT_DESCRIPTOR: &'static [u8] = &[
|
|
+ 0x06, 0x00, 0xFF, // HID_UsagePage ( VENDOR ),
|
|
+ 0x09, 0x01, // HID_Usage ( Unused ),
|
|
+ 0xA1, 0x01, // HID_Collection ( HID_Application ),
|
|
+ 0x09, 0x20, // HID_Usage ( FIDO_USAGE_DATA_IN ),
|
|
+ 0x15, 0x00, // HID_LogicalMin ( 0 ),
|
|
+ 0x26, 0xFF, 0x00, // HID_LogicalMaxS ( 0xff ),
|
|
+ 0x75, 0x08, // HID_ReportSize ( 8 ),
|
|
+ 0x95, 0x40, // HID_ReportCount ( HID_INPUT_REPORT_BYTES ),
|
|
+ 0x81, 0x02, // HID_Input ( HID_Data | HID_Absolute | HID_Variable ),
|
|
+ 0x09, 0x21, // HID_Usage ( FIDO_USAGE_DATA_OUT ),
|
|
+ 0x15, 0x00, // HID_LogicalMin ( 0 ),
|
|
+ 0x26, 0xFF, 0x00, // HID_LogicalMaxS ( 0xff ),
|
|
+ 0x75, 0x08, // HID_ReportSize ( 8 ),
|
|
+ 0x95, 0x40, // HID_ReportCount ( HID_OUTPUT_REPORT_BYTES ),
|
|
+ 0x91, 0x02, // HID_Output ( HID_Data | HID_Absolute | HID_Variable ),
|
|
+ 0xC0, // HID_EndCollection
|
|
+];
|
|
+
|
|
+static CTAP_REPORT: ReportDescriptor<'static> = ReportDescriptor {
|
|
+ desc: CTAP_REPORT_DESCRIPTOR,
|
|
+};
|
|
+
|
|
+#[cfg(feature = "vendor_hid")]
|
|
+static VENDOR_REPORT: ReportDescriptor<'static> = ReportDescriptor {
|
|
+ desc: VENDOR_REPORT_DESCRIPTOR,
|
|
+};
|
|
+
|
|
+static HID_SUB_DESCRIPTORS: &'static [HIDSubordinateDescriptor] = &[HIDSubordinateDescriptor {
|
|
+ typ: DescriptorType::Report,
|
|
+ len: CTAP_REPORT_DESCRIPTOR.len() as u16,
|
|
+}];
|
|
+
|
|
+#[cfg(feature = "vendor_hid")]
|
|
+static VENDOR_HID_SUB_DESCRIPTORS: &'static [HIDSubordinateDescriptor] =
|
|
+ &[HIDSubordinateDescriptor {
|
|
+ typ: DescriptorType::Report,
|
|
+ len: VENDOR_REPORT_DESCRIPTOR.len() as u16,
|
|
+ }];
|
|
+
|
|
+static HID: HIDDescriptor<'static> = HIDDescriptor {
|
|
+ hid_class: 0x0111,
|
|
+ country_code: HIDCountryCode::NotSupported,
|
|
+ sub_descriptors: HID_SUB_DESCRIPTORS,
|
|
+};
|
|
+
|
|
+#[cfg(feature = "vendor_hid")]
|
|
+static VENDOR_HID: HIDDescriptor<'static> = HIDDescriptor {
|
|
+ hid_class: 0x0111,
|
|
+ country_code: HIDCountryCode::NotSupported,
|
|
+ sub_descriptors: VENDOR_HID_SUB_DESCRIPTORS,
|
|
+};
|
|
+
|
|
+/// The state of each endpoint
|
|
+struct EndpointState {
|
|
+ endpoint: usize,
|
|
+ in_buffer: Buffer64,
|
|
+ out_buffer: Buffer64,
|
|
+ tx_packet: OptionalCell<[u8; 64]>,
|
|
+ pending_in: Cell<bool>,
|
|
+ /// Is there a delayed packet?
|
|
+ delayed_out: Cell<bool>,
|
|
+}
|
|
+
|
|
+impl EndpointState {
|
|
+ pub fn new(endpoint: usize) -> Self {
|
|
+ EndpointState {
|
|
+ endpoint: endpoint,
|
|
+ in_buffer: Buffer64::default(),
|
|
+ out_buffer: Buffer64::default(),
|
|
+ tx_packet: OptionalCell::empty(),
|
|
+ pending_in: Cell::new(false),
|
|
+ delayed_out: Cell::new(false),
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+pub struct ClientCtapHID<'a, 'b, C: 'a> {
|
|
+ client_ctrl: ClientCtrl<'a, 'static, C>,
|
|
+
|
|
+ endpoints: [EndpointState; NUM_ENDPOINTS],
|
|
+
|
|
+ /// Interaction with the client
|
|
+ client: OptionalCell<&'b dyn CtapUsbClient>,
|
|
+
|
|
+ // Is there a pending OUT transaction happening?
|
|
+ pending_out: Cell<bool>,
|
|
+ next_endpoint_index: Cell<usize>,
|
|
+}
|
|
+
|
|
+impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> {
|
|
+ pub fn new(
|
|
+ controller: &'a C,
|
|
+ max_ctrl_packet_size: u8,
|
|
+ vendor_id: u16,
|
|
+ product_id: u16,
|
|
+ strings: &'static [&'static str],
|
|
+ ) -> Self {
|
|
+ #[cfg(feature = "vendor_hid")]
|
|
+ debug!("vendor_hid enabled.");
|
|
+
|
|
+ let interfaces: &mut [InterfaceDescriptor] = &mut [
|
|
+ // Interface declared in the FIDO2 specification, section 8.1.8.1
|
|
+ InterfaceDescriptor {
|
|
+ interface_number: 0,
|
|
+ interface_class: 0x03, // HID
|
|
+ interface_subclass: 0x00, // no subcall
|
|
+ interface_protocol: 0x00, // no protocol
|
|
+ string_index: 4,
|
|
+ ..InterfaceDescriptor::default()
|
|
+ },
|
|
+ // Vendor HID interface.
|
|
+ #[cfg(feature = "vendor_hid")]
|
|
+ InterfaceDescriptor {
|
|
+ interface_number: 1,
|
|
+ interface_class: 0x03, // HID
|
|
+ interface_subclass: 0x00,
|
|
+ interface_protocol: 0x00,
|
|
+ string_index: 5,
|
|
+ ..InterfaceDescriptor::default()
|
|
+ },
|
|
+ ];
|
|
+
|
|
+ let endpoints: &[&[EndpointDescriptor]] = &[
|
|
+ &[
|
|
+ // 2 Endpoints for FIDO
|
|
+ EndpointDescriptor {
|
|
+ endpoint_address: EndpointAddress::new_const(
|
|
+ ENDPOINT_NUM,
|
|
+ TransferDirection::HostToDevice,
|
|
+ ),
|
|
+ transfer_type: TransferType::Interrupt,
|
|
+ max_packet_size: 64,
|
|
+ interval: 5,
|
|
+ },
|
|
+ EndpointDescriptor {
|
|
+ endpoint_address: EndpointAddress::new_const(
|
|
+ ENDPOINT_NUM,
|
|
+ TransferDirection::DeviceToHost,
|
|
+ ),
|
|
+ transfer_type: TransferType::Interrupt,
|
|
+ max_packet_size: 64,
|
|
+ interval: 5,
|
|
+ },
|
|
+ ],
|
|
+ // 2 Endpoints for FIDO
|
|
+ #[cfg(feature = "vendor_hid")]
|
|
+ &[
|
|
+ EndpointDescriptor {
|
|
+ endpoint_address: EndpointAddress::new_const(
|
|
+ VENDOR_ENDPOINT_NUM,
|
|
+ TransferDirection::HostToDevice,
|
|
+ ),
|
|
+ transfer_type: TransferType::Interrupt,
|
|
+ max_packet_size: 64,
|
|
+ interval: 5,
|
|
+ },
|
|
+ EndpointDescriptor {
|
|
+ endpoint_address: EndpointAddress::new_const(
|
|
+ VENDOR_ENDPOINT_NUM,
|
|
+ TransferDirection::DeviceToHost,
|
|
+ ),
|
|
+ transfer_type: TransferType::Interrupt,
|
|
+ max_packet_size: 64,
|
|
+ interval: 5,
|
|
+ },
|
|
+ ],
|
|
+ ];
|
|
+
|
|
+ let (device_descriptor_buffer, other_descriptor_buffer) =
|
|
+ descriptors::create_descriptor_buffers(
|
|
+ descriptors::DeviceDescriptor {
|
|
+ vendor_id,
|
|
+ product_id,
|
|
+ manufacturer_string: 1,
|
|
+ product_string: 2,
|
|
+ serial_number_string: 3,
|
|
+ class: 0x00, // Class is specified at the interface level
|
|
+ max_packet_size_ep0: max_ctrl_packet_size,
|
|
+ ..descriptors::DeviceDescriptor::default()
|
|
+ },
|
|
+ descriptors::ConfigurationDescriptor {
|
|
+ configuration_value: 1,
|
|
+ ..descriptors::ConfigurationDescriptor::default()
|
|
+ },
|
|
+ interfaces,
|
|
+ endpoints,
|
|
+ Some(&[
|
|
+ &HID,
|
|
+ #[cfg(feature = "vendor_hid")]
|
|
+ &VENDOR_HID,
|
|
+ ]),
|
|
+ None, // No CDC descriptor array
|
|
+ );
|
|
+
|
|
+ ClientCtapHID {
|
|
+ client_ctrl: ClientCtrl::new(
|
|
+ controller,
|
|
+ device_descriptor_buffer,
|
|
+ other_descriptor_buffer,
|
|
+ Some([
|
|
+ &HID,
|
|
+ #[cfg(feature = "vendor_hid")]
|
|
+ &VENDOR_HID,
|
|
+ ]),
|
|
+ Some([
|
|
+ &CTAP_REPORT,
|
|
+ #[cfg(feature = "vendor_hid")]
|
|
+ &VENDOR_REPORT,
|
|
+ ]),
|
|
+ LANGUAGES,
|
|
+ strings,
|
|
+ ),
|
|
+ pending_out: Cell::new(false),
|
|
+ next_endpoint_index: Cell::new(0),
|
|
+ endpoints: [
|
|
+ EndpointState::new(ENDPOINT_NUM),
|
|
+ #[cfg(feature = "vendor_hid")]
|
|
+ EndpointState::new(VENDOR_ENDPOINT_NUM),
|
|
+ ],
|
|
+ client: OptionalCell::empty(),
|
|
+ }
|
|
+ }
|
|
+
|
|
+ fn get_endpoint(&'a self, endpoint: usize) -> Option<&'a EndpointState> {
|
|
+ for (i, ep) in ENDPOINTS.iter().enumerate() {
|
|
+ if endpoint == *ep {
|
|
+ return Some(&self.endpoints[i]);
|
|
+ }
|
|
+ }
|
|
+ None
|
|
+ }
|
|
+
|
|
+ pub fn set_client(&'a self, client: &'b dyn CtapUsbClient) {
|
|
+ self.client.set(client);
|
|
+ }
|
|
+
|
|
+ pub fn transmit_packet(&'a self, packet: &[u8], endpoint: usize) -> CommandReturn {
|
|
+ if let Some(s) = self.get_endpoint(endpoint) {
|
|
+ if s.pending_in.get() {
|
|
+ // The previous packet has not yet been transmitted, reject the new one.
|
|
+ return CommandReturn::failure(kernel::ErrorCode::BUSY);
|
|
+ }
|
|
+ s.pending_in.set(true);
|
|
+ let mut buf: [u8; 64] = [0; 64];
|
|
+ buf.copy_from_slice(packet);
|
|
+ s.tx_packet.set(buf);
|
|
+ // Alert the controller that we now have data to send on the Interrupt IN endpoint.
|
|
+ self.controller().endpoint_resume_in(endpoint);
|
|
+ CommandReturn::success()
|
|
+ } else {
|
|
+ // unsupported endpoint
|
|
+ CommandReturn::failure(kernel::ErrorCode::INVAL)
|
|
+ }
|
|
+ }
|
|
+
|
|
+ pub fn receive_packet(&'a self, app: &mut App, kernel_grant: &GrantKernelData) {
|
|
+ if self.pending_out.get() {
|
|
+ // The previous packet has not yet been received, reject the new one.
|
|
+ } else {
|
|
+ self.pending_out.set(true);
|
|
+ // Process the next endpoint that has a delayed packet.
|
|
+ for i in self.next_endpoint_index.get()..self.next_endpoint_index.get() + NUM_ENDPOINTS
|
|
+ {
|
|
+ let s = &self.endpoints[i % NUM_ENDPOINTS];
|
|
+ // In case we reported Delay before, send the pending packet back to the client.
|
|
+ // Otherwise, there's nothing to do, the controller will send us a packet_out when a
|
|
+ // packet arrives.
|
|
+ if s.delayed_out.take() {
|
|
+ if self.send_packet_to_client(s.endpoint, Some(app), Some(kernel_grant)) {
|
|
+ // If that succeeds, alert the controller that we can now
|
|
+ // receive data on the Interrupt OUT endpoint.
|
|
+ self.controller().endpoint_resume_out(s.endpoint);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ // Send an OUT packet available in the controller back to the client.
|
|
+ // This returns false if the client is not ready to receive a packet, and true if the client
|
|
+ // successfully accepted the packet.
|
|
+ fn send_packet_to_client(
|
|
+ &'a self,
|
|
+ endpoint: usize,
|
|
+ app: Option<&mut App>,
|
|
+ kernel_grant: Option<&GrantKernelData>,
|
|
+ ) -> bool {
|
|
+ if let Some(s) = self.get_endpoint(endpoint) {
|
|
+ // Copy the packet into a buffer to send to the client.
|
|
+ let mut buf: [u8; 64] = [0; 64];
|
|
+ for (i, x) in s.out_buffer.buf.iter().enumerate() {
|
|
+ buf[i] = x.get();
|
|
+ }
|
|
+
|
|
+ assert!(!s.delayed_out.get());
|
|
+
|
|
+ // Notify the client
|
|
+ if self
|
|
+ .client
|
|
+ .map_or(false, |client| client.can_receive_packet(&app))
|
|
+ {
|
|
+ assert!(self.pending_out.take());
|
|
+
|
|
+ // Clear any pending packet on the transmitting side.
|
|
+ // It's up to the client to handle the received packet and decide if this packet
|
|
+ // should be re-transmitted or not.
|
|
+ self.cancel_in_transaction(endpoint);
|
|
+
|
|
+ self.client
|
|
+ .map(|client| client.packet_received(&buf, endpoint, (app, kernel_grant)));
|
|
+ // Update next packet to send.
|
|
+ for (i, ep) in self.endpoints.iter().enumerate() {
|
|
+ if ep.endpoint == endpoint {
|
|
+ self.next_endpoint_index.set((i + 1) % NUM_ENDPOINTS);
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ true
|
|
+ } else {
|
|
+ // Cannot receive now, indicate a delay to the controller.
|
|
+ s.delayed_out.set(true);
|
|
+ false
|
|
+ }
|
|
+ } else {
|
|
+ // unsupported endpoint
|
|
+ false
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /// Cancel transaction(s) in process. |endpoint| of 0 indicates all endpoints.
|
|
+ pub fn cancel_transaction(&'a self, endpoint: usize) -> bool {
|
|
+ if endpoint > 0 {
|
|
+ return self.cancel_in_transaction(endpoint) | self.cancel_out_transaction(endpoint);
|
|
+ }
|
|
+ let mut r = false;
|
|
+ for (_, s) in self.endpoints.iter().enumerate() {
|
|
+ r |= self.cancel_in_transaction(s.endpoint) | self.cancel_out_transaction(s.endpoint);
|
|
+ }
|
|
+ r
|
|
+ }
|
|
+
|
|
+ fn cancel_in_transaction(&'a self, endpoint: usize) -> bool {
|
|
+ if let Some(s) = self.get_endpoint(endpoint) {
|
|
+ s.tx_packet.take();
|
|
+ s.pending_in.take()
|
|
+ } else {
|
|
+ // Unsupported endpoint
|
|
+ false
|
|
+ }
|
|
+ }
|
|
+
|
|
+ fn cancel_out_transaction(&'a self, endpoint: usize) -> bool {
|
|
+ if let Some(_) = self.get_endpoint(endpoint) {
|
|
+ self.pending_out.take()
|
|
+ } else {
|
|
+ // Unsupported endpoint
|
|
+ false
|
|
+ }
|
|
+ }
|
|
+
|
|
+ #[inline]
|
|
+ fn controller(&'a self) -> &'a C {
|
|
+ self.client_ctrl.controller()
|
|
+ }
|
|
+}
|
|
+
|
|
+impl<'a, 'b, C: hil::usb::UsbController<'a>> hil::usb::Client<'a> for ClientCtapHID<'a, 'b, C> {
|
|
+ fn enable(&'a self) {
|
|
+ // Set up the default control endpoint
|
|
+ self.client_ctrl.enable();
|
|
+
|
|
+ // Set up the interrupt in-out endpoint(s).
|
|
+ for (i, endpoint) in ENDPOINTS.iter().enumerate() {
|
|
+ self.controller()
|
|
+ .endpoint_set_in_buffer(*endpoint, &self.endpoints[i].in_buffer.buf);
|
|
+ self.controller()
|
|
+ .endpoint_set_out_buffer(*endpoint, &self.endpoints[i].out_buffer.buf);
|
|
+ self.controller()
|
|
+ .endpoint_in_out_enable(TransferType::Interrupt, *endpoint);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ fn attach(&'a self) {
|
|
+ self.client_ctrl.attach();
|
|
+ }
|
|
+
|
|
+ fn bus_reset(&'a self) {
|
|
+ // Should the client initiate reconfiguration here?
|
|
+ // For now, the hardware layer does it.
|
|
+
|
|
+ debug!("Bus reset");
|
|
+ }
|
|
+
|
|
+ /// Handle a Control Setup transaction
|
|
+ fn ctrl_setup(&'a self, endpoint: usize) -> hil::usb::CtrlSetupResult {
|
|
+ self.client_ctrl.ctrl_setup(endpoint)
|
|
+ }
|
|
+
|
|
+ /// Handle a Control In transaction
|
|
+ fn ctrl_in(&'a self, endpoint: usize) -> hil::usb::CtrlInResult {
|
|
+ self.client_ctrl.ctrl_in(endpoint)
|
|
+ }
|
|
+
|
|
+ /// Handle a Control Out transaction
|
|
+ fn ctrl_out(&'a self, endpoint: usize, packet_bytes: u32) -> hil::usb::CtrlOutResult {
|
|
+ self.client_ctrl.ctrl_out(endpoint, packet_bytes)
|
|
+ }
|
|
+
|
|
+ fn ctrl_status(&'a self, endpoint: usize) {
|
|
+ self.client_ctrl.ctrl_status(endpoint)
|
|
+ }
|
|
+
|
|
+ /// Handle the completion of a Control transfer
|
|
+ fn ctrl_status_complete(&'a self, endpoint: usize) {
|
|
+ self.client_ctrl.ctrl_status_complete(endpoint)
|
|
+ }
|
|
+
|
|
+ /// Handle a Bulk/Interrupt IN transaction
|
|
+ fn packet_in(&'a self, transfer_type: TransferType, endpoint: usize) -> hil::usb::InResult {
|
|
+ match transfer_type {
|
|
+ TransferType::Bulk => hil::usb::InResult::Error,
|
|
+ TransferType::Interrupt => {
|
|
+ if let Some(s) = self.get_endpoint(endpoint) {
|
|
+ if let Some(packet) = s.tx_packet.take() {
|
|
+ let buf = &s.in_buffer.buf;
|
|
+ for i in 0..64 {
|
|
+ buf[i].set(packet[i]);
|
|
+ }
|
|
+ hil::usb::InResult::Packet(64)
|
|
+ } else {
|
|
+ // Nothing to send
|
|
+ hil::usb::InResult::Delay
|
|
+ }
|
|
+ } else {
|
|
+ // Unsupported endpoint
|
|
+ return hil::usb::InResult::Error;
|
|
+ }
|
|
+ }
|
|
+ TransferType::Control | TransferType::Isochronous => unreachable!(),
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /// Handle a Bulk/Interrupt OUT transaction
|
|
+ fn packet_out(
|
|
+ &'a self,
|
|
+ transfer_type: TransferType,
|
|
+ endpoint: usize,
|
|
+ packet_bytes: u32,
|
|
+ ) -> hil::usb::OutResult {
|
|
+ match transfer_type {
|
|
+ TransferType::Bulk => hil::usb::OutResult::Error,
|
|
+ TransferType::Interrupt => {
|
|
+ if endpoint == 0 || endpoint > NUM_ENDPOINTS {
|
|
+ return hil::usb::OutResult::Error;
|
|
+ }
|
|
+
|
|
+ if packet_bytes != 64 {
|
|
+ // Cannot process this packet
|
|
+ hil::usb::OutResult::Error
|
|
+ } else {
|
|
+ if self.send_packet_to_client(endpoint, None, None) {
|
|
+ hil::usb::OutResult::Ok
|
|
+ } else {
|
|
+ hil::usb::OutResult::Delay
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ TransferType::Control | TransferType::Isochronous => unreachable!(),
|
|
+ }
|
|
+ }
|
|
+
|
|
+ fn packet_transmitted(&'a self, endpoint: usize) {
|
|
+ if let Some(s) = self.get_endpoint(endpoint) {
|
|
+ if s.tx_packet.is_some() {
|
|
+ panic!("Unexpected tx_packet while a packet was being transmitted.");
|
|
+ }
|
|
+ s.pending_in.set(false);
|
|
+
|
|
+ // Clear any pending packet on the receiving side.
|
|
+ // It's up to the client to handle the transmitted packet and decide if they want to
|
|
+ // receive another packet.
|
|
+ self.cancel_out_transaction(endpoint);
|
|
+
|
|
+ // Notify the client
|
|
+ self.client.map(|client| client.packet_transmitted());
|
|
+ } else {
|
|
+ panic!("Unexpected transmission on ep {}", endpoint);
|
|
+ }
|
|
+ }
|
|
+}
|