-
Guillaume Endignoux authoredGuillaume Endignoux authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
02-usb.patch 30.96 KiB
diff --git a/boards/nordic/nrf52840_dongle/src/main.rs b/boards/nordic/nrf52840_dongle/src/main.rs
index 9a8dccfd..ad3e69b8 100644
--- a/boards/nordic/nrf52840_dongle/src/main.rs
+++ b/boards/nordic/nrf52840_dongle/src/main.rs
@@ -144,6 +144,7 @@ pub unsafe fn reset_handler() {
FAULT_RESPONSE,
nrf52840::uicr::Regulator0Output::V3_0,
false,
+ &Some(&nrf52840::usbd::USBD),
chip,
);
}
diff --git a/boards/nordic/nrf52840dk/src/main.rs b/boards/nordic/nrf52840dk/src/main.rs
index 127c4f2f..a5847805 100644
--- a/boards/nordic/nrf52840dk/src/main.rs
+++ b/boards/nordic/nrf52840dk/src/main.rs
@@ -236,6 +236,7 @@ pub unsafe fn reset_handler() {
FAULT_RESPONSE,
nrf52840::uicr::Regulator0Output::DEFAULT,
false,
+ &Some(&nrf52840::usbd::USBD),
chip,
);
}
diff --git a/boards/nordic/nrf52dk/src/main.rs b/boards/nordic/nrf52dk/src/main.rs
index d67ac695..b0bd8bf1 100644
--- a/boards/nordic/nrf52dk/src/main.rs
+++ b/boards/nordic/nrf52dk/src/main.rs
@@ -213,6 +213,7 @@ pub unsafe fn reset_handler() {
FAULT_RESPONSE,
nrf52832::uicr::Regulator0Output::DEFAULT,
false,
+ &None,
chip,
);
}
diff --git a/boards/nordic/nrf52dk_base/src/lib.rs b/boards/nordic/nrf52dk_base/src/lib.rs
index 105f7120..535e5cd8 100644
--- a/boards/nordic/nrf52dk_base/src/lib.rs
+++ b/boards/nordic/nrf52dk_base/src/lib.rs
@@ -101,6 +101,13 @@ pub struct Platform {
'static,
capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52::rtc::Rtc<'static>>,
>,
+ usb: Option<
+ &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver<
+ 'static,
+ 'static,
+ nrf52::usbd::Usbd<'static>,
+ >,
+ >,
// The nRF52dk does not have the flash chip on it, so we make this optional.
nonvolatile_storage:
Option<&'static capsules::nonvolatile_storage_driver::NonvolatileStorage<'static>>,
@@ -130,6 +137,9 @@ impl kernel::Platform for Platform {
f(self.nonvolatile_storage.map_or(None, |nv| Some(nv)))
}
nrf52::nvmc::DRIVER_NUM => f(Some(self.nvmc)),
+ capsules::usb::usb_ctap::DRIVER_NUM => {
+ f(self.usb.map(|ctap| ctap as &dyn kernel::Driver))
+ }
kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)),
_ => f(None),
}
@@ -157,6 +167,7 @@ pub unsafe fn setup_board<I: nrf52::interrupt_service::InterruptService>(
app_fault_response: kernel::procs::FaultResponse,
reg_vout: Regulator0Output,
nfc_as_gpios: bool,
+ usb: &Option<&'static nrf52::usbd::Usbd<'static>>,
chip: &'static nrf52::chip::NRF52<I>,
) {
// Make non-volatile memory writable and activate the reset button
@@ -415,6 +426,44 @@ pub unsafe fn setup_board<I: nrf52::interrupt_service::InterruptService>(
)
);
+ // Configure USB controller if supported
+ let usb_driver: Option<
+ &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver<
+ 'static,
+ 'static,
+ nrf52::usbd::Usbd<'static>,
+ >,
+ > = usb.map(|driver| {
+ let usb_ctap = static_init!(
+ capsules::usb::usbc_ctap_hid::ClientCtapHID<
+ 'static,
+ 'static,
+ nrf52::usbd::Usbd<'static>,
+ >,
+ capsules::usb::usbc_ctap_hid::ClientCtapHID::new(driver)
+ );
+ driver.set_client(usb_ctap);
+
+ // Enable power events to be sent to USB controller
+ nrf52::power::POWER.set_usb_client(driver);
+ nrf52::power::POWER.enable_interrupts();
+
+ // Configure the USB userspace driver
+ let usb_driver = static_init!(
+ capsules::usb::usb_ctap::CtapUsbSyscallDriver<
+ 'static,
+ 'static,
+ nrf52::usbd::Usbd<'static>,
+ >,
+ capsules::usb::usb_ctap::CtapUsbSyscallDriver::new(
+ usb_ctap,
+ board_kernel.create_grant(&memory_allocation_capability)
+ )
+ );
+ usb_ctap.set_client(usb_driver);
+ usb_driver as &'static _
+ });
+
// Start all of the clocks. Low power operation will require a better
// approach than this.
nrf52::clock::CLOCK.low_stop();
@@ -447,6 +496,7 @@ pub unsafe fn setup_board<I: nrf52::interrupt_service::InterruptService>(
temp: temp,
alarm: alarm,
analog_comparator: analog_comparator,
+ usb: usb_driver,
nonvolatile_storage: nonvolatile_storage,
ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability),
nvmc: nvmc,
diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs
index bfc06429..5858d352 100644
--- a/capsules/src/driver.rs
+++ b/capsules/src/driver.rs
@@ -25,6 +25,7 @@ pub enum NUM {
I2cMaster = 0x20003,
UsbUser = 0x20005,
I2cMasterSlave = 0x20006,
+ UsbCtap = 0x20009,
// Radio
BleAdvertising = 0x30000,
diff --git a/capsules/src/usb/mod.rs b/capsules/src/usb/mod.rs
index e5c8d6ad..7af3da2e 100644
--- a/capsules/src/usb/mod.rs
+++ b/capsules/src/usb/mod.rs
@@ -1,4 +1,6 @@
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 00000000..da3d16d8
--- /dev/null
+++ b/capsules/src/usb/usb_ctap.rs
@@ -0,0 +1,355 @@
+use super::usbc_ctap_hid::ClientCtapHID;
+use kernel::hil;
+use kernel::hil::usb::Client;
+use kernel::{AppId, AppSlice, Callback, Driver, Grant, ReturnCode, Shared};
+
+/// 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;
+
+pub const CTAP_ALLOW_TRANSMIT: usize = 1;
+pub const CTAP_ALLOW_RECEIVE: usize = 2;
+pub const CTAP_ALLOW_TRANSMIT_OR_RECEIVE: usize = 3;
+
+pub const CTAP_SUBSCRIBE_TRANSMIT: usize = 1;
+pub const CTAP_SUBSCRIBE_RECEIVE: usize = 2;
+pub const CTAP_SUBSCRIBE_TRANSMIT_OR_RECEIVE: usize = 3;
+
+pub const CTAP_CALLBACK_TRANSMITED: usize = 1;
+pub const CTAP_CALLBACK_RECEIVED: usize = 2;
+
+#[derive(Clone, Copy, PartialEq, Eq)]
+enum Side {
+ Transmit,
+ Receive,
+ TransmitOrReceive,
+}
+
+impl Side {
+ fn can_transmit(&self) -> bool {
+ match self {
+ Side::Transmit | Side::TransmitOrReceive => true,
+ Side::Receive => false,
+ }
+ }
+
+ 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.
+ 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.
+ side: Option<Side>,
+ callback: Option<Callback>,
+ buffer: Option<AppSlice<Shared, u8>>,
+ // Whether the app is waiting for the kernel signaling a packet transfer.
+ waiting: bool,
+}
+
+impl App {
+ fn check_side(&mut self) {
+ if self.callback.is_none() && self.buffer.is_none() && !self.waiting {
+ self.side = None;
+ }
+ }
+
+ fn set_side(&mut self, side: Side) -> bool {
+ match self.side {
+ None => {
+ self.side = Some(side);
+ true
+ }
+ Some(app_side) => side == app_side,
+ }
+ }
+
+ fn is_ready_for_command(&self, side: Side) -> bool {
+ self.buffer.is_some() && self.callback.is_some() && self.side == Some(side)
+ }
+}
+
+pub trait CtapUsbClient {
+ // Whether this client is ready to receive a packet. This must be checked before calling
+ // packet_received().
+ fn can_receive_packet(&self) -> bool;
+
+ // Signal to the client that a packet has been received.
+ fn packet_received(&self, packet: &[u8; 64]);
+
+ // 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: Grant<App>,
+}
+
+impl<'a, 'b, C: hil::usb::UsbController<'a>> CtapUsbSyscallDriver<'a, 'b, C> {
+ pub fn new(usb_client: &'a ClientCtapHID<'a, 'b, C>, apps: Grant<App>) -> Self {
+ CtapUsbSyscallDriver { usb_client, apps }
+ }
+}
+
+impl<'a, 'b, C: hil::usb::UsbController<'a>> CtapUsbClient for CtapUsbSyscallDriver<'a, 'b, C> {
+ fn can_receive_packet(&self) -> bool {
+ let mut result = false;
+ for app in self.apps.iter() {
+ app.enter(|app, _| {
+ if app.connected {
+ result = app.waiting
+ && app.side.map_or(false, |side| side.can_receive())
+ && app.buffer.is_some();
+ }
+ });
+ }
+ result
+ }
+
+ fn packet_received(&self, packet: &[u8; 64]) {
+ for app in self.apps.iter() {
+ app.enter(|app, _| {
+ if app.connected && app.waiting && app.side.map_or(false, |side| side.can_receive())
+ {
+ if let Some(buf) = &mut app.buffer {
+ // Copy the packet to the app's allowed buffer.
+ buf.as_mut().copy_from_slice(packet);
+ app.waiting = false;
+ // Signal to the app that a packet is ready.
+ app.callback
+ .map(|mut cb| cb.schedule(CTAP_CALLBACK_RECEIVED, 0, 0));
+ }
+ }
+ });
+ }
+ }
+
+ fn packet_transmitted(&self) {
+ for app in self.apps.iter() {
+ app.enter(|app, _| {
+ if app.connected
+ && app.waiting
+ && app.side.map_or(false, |side| side.can_transmit())
+ {
+ app.waiting = false;
+ // Signal to the app that the packet was sent.
+ app.callback
+ .map(|mut cb| cb.schedule(CTAP_CALLBACK_TRANSMITED, 0, 0));
+ }
+ });
+ }
+ }
+}
+
+impl<'a, 'b, C: hil::usb::UsbController<'a>> Driver for CtapUsbSyscallDriver<'a, 'b, C> {
+ fn allow(
+ &self,
+ appid: AppId,
+ allow_num: usize,
+ slice: Option<AppSlice<Shared, u8>>,
+ ) -> ReturnCode {
+ let side = match allow_num {
+ CTAP_ALLOW_TRANSMIT => Side::Transmit,
+ CTAP_ALLOW_RECEIVE => Side::Receive,
+ CTAP_ALLOW_TRANSMIT_OR_RECEIVE => Side::TransmitOrReceive,
+ _ => return ReturnCode::ENOSUPPORT,
+ };
+ self.apps
+ .enter(appid, |app, _| {
+ if !app.connected {
+ ReturnCode::ERESERVE
+ } else {
+ if let Some(buf) = &slice {
+ if buf.len() != 64 {
+ return ReturnCode::EINVAL;
+ }
+ }
+ if !app.set_side(side) {
+ return ReturnCode::EALREADY;
+ }
+ app.buffer = slice;
+ app.check_side();
+ ReturnCode::SUCCESS
+ }
+ })
+ .unwrap_or_else(|err| err.into())
+ }
+
+ fn subscribe(
+ &self,
+ subscribe_num: usize,
+ callback: Option<Callback>,
+ appid: AppId,
+ ) -> ReturnCode {
+ let side = match subscribe_num {
+ CTAP_SUBSCRIBE_TRANSMIT => Side::Transmit,
+ CTAP_SUBSCRIBE_RECEIVE => Side::Receive,
+ CTAP_SUBSCRIBE_TRANSMIT_OR_RECEIVE => Side::TransmitOrReceive,
+ _ => return ReturnCode::ENOSUPPORT,
+ };
+ self.apps
+ .enter(appid, |app, _| {
+ if !app.connected {
+ ReturnCode::ERESERVE
+ } else {
+ if !app.set_side(side) {
+ return ReturnCode::EALREADY;
+ }
+ app.callback = callback;
+ app.check_side();
+ ReturnCode::SUCCESS
+ }
+ })
+ .unwrap_or_else(|err| err.into())
+ }
+
+ fn command(&self, cmd_num: usize, _arg1: usize, _arg2: usize, appid: AppId) -> ReturnCode {
+ match cmd_num {
+ CTAP_CMD_CHECK => ReturnCode::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(appid, |app, _| {
+ if app.connected {
+ ReturnCode::EALREADY
+ } else if busy {
+ ReturnCode::EBUSY
+ } else {
+ self.usb_client.enable();
+ self.usb_client.attach();
+ app.connected = true;
+ ReturnCode::SUCCESS
+ }
+ })
+ .unwrap_or_else(|err| err.into())
+ }
+ CTAP_CMD_TRANSMIT => self
+ .apps
+ .enter(appid, |app, _| {
+ if !app.connected {
+ ReturnCode::ERESERVE
+ } else {
+ if app.is_ready_for_command(Side::Transmit) {
+ if app.waiting {
+ ReturnCode::EALREADY
+ } else if self
+ .usb_client
+ .transmit_packet(app.buffer.as_ref().unwrap().as_ref())
+ {
+ app.waiting = true;
+ ReturnCode::SUCCESS
+ } else {
+ ReturnCode::EBUSY
+ }
+ } else {
+ ReturnCode::EINVAL
+ }
+ }
+ })
+ .unwrap_or_else(|err| err.into()),
+ CTAP_CMD_RECEIVE => self
+ .apps
+ .enter(appid, |app, _| {
+ if !app.connected {
+ ReturnCode::ERESERVE
+ } else {
+ if app.is_ready_for_command(Side::Receive) {
+ if app.waiting {
+ ReturnCode::EALREADY
+ } else {
+ app.waiting = true;
+ self.usb_client.receive_packet();
+ ReturnCode::SUCCESS
+ }
+ } else {
+ ReturnCode::EINVAL
+ }
+ }
+ })
+ .unwrap_or_else(|err| err.into()),
+ CTAP_CMD_TRANSMIT_OR_RECEIVE => self
+ .apps
+ .enter(appid, |app, _| {
+ if !app.connected {
+ ReturnCode::ERESERVE
+ } else {
+ if app.is_ready_for_command(Side::TransmitOrReceive) {
+ if app.waiting {
+ ReturnCode::EALREADY
+ } else {
+ // Indicates to the driver that we can receive any pending packet.
+ app.waiting = true;
+ self.usb_client.receive_packet();
+
+ if !app.waiting {
+ // The call to receive_packet() collected a pending packet.
+ ReturnCode::SUCCESS
+ } else {
+ // Indicates to the driver that we have a packet to send.
+ if self
+ .usb_client
+ .transmit_packet(app.buffer.as_ref().unwrap().as_ref())
+ {
+ ReturnCode::SUCCESS
+ } else {
+ ReturnCode::EBUSY
+ }
+ }
+ }
+ } else {
+ ReturnCode::EINVAL
+ }
+ }
+ })
+ .unwrap_or_else(|err| err.into()),
+ CTAP_CMD_CANCEL => self
+ .apps
+ .enter(appid, |app, _| {
+ if !app.connected {
+ ReturnCode::ERESERVE
+ } else {
+ if app.waiting {
+ // FIXME: if cancellation failed, the app should still wait. But that
+ // doesn't work yet.
+ app.waiting = false;
+ if self.usb_client.cancel_transaction() {
+ ReturnCode::SUCCESS
+ } else {
+ // Cannot cancel now because the transaction is already in process.
+ // The app should wait for the callback instead.
+ ReturnCode::EBUSY
+ }
+ } else {
+ ReturnCode::EALREADY
+ }
+ }
+ })
+ .unwrap_or_else(|err| err.into()),
+ _ => ReturnCode::ENOSUPPORT,
+ }
+ }
+}
diff --git a/capsules/src/usb/usbc_ctap_hid.rs b/capsules/src/usb/usbc_ctap_hid.rs
new file mode 100644
index 00000000..4b1916cf
--- /dev/null
+++ b/capsules/src/usb/usbc_ctap_hid.rs
@@ -0,0 +1,359 @@
+//! A USB HID client of the USB hardware interface
+
+use super::descriptors::Buffer64;
+use super::descriptors::ConfigurationDescriptor;
+use super::descriptors::DescriptorType;
+use super::descriptors::DeviceDescriptor;
+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::common::cells::OptionalCell;
+use kernel::debug;
+use kernel::hil;
+use kernel::hil::usb::TransferType;
+
+const VENDOR_ID: u16 = 0x1915; // Nordic Semiconductor
+const PRODUCT_ID: u16 = 0x521f; // nRF52840 Dongle (PCA10059)
+
+static LANGUAGES: &'static [u16; 1] = &[
+ 0x0409, // English (United States)
+];
+
+static STRINGS: &'static [&'static str] = &[
+ // Manufacturer
+ "Nordic Semiconductor ASA",
+ // Product
+ "OpenSK",
+ // Serial number
+ "v0.1",
+];
+
+static ENDPOINTS: &'static [EndpointDescriptor] = &[
+ EndpointDescriptor {
+ endpoint_address: EndpointAddress::new_const(1, TransferDirection::HostToDevice),
+ transfer_type: TransferType::Interrupt,
+ max_packet_size: 64,
+ interval: 5,
+ },
+ EndpointDescriptor {
+ endpoint_address: EndpointAddress::new_const(1, TransferDirection::DeviceToHost),
+ transfer_type: TransferType::Interrupt,
+ max_packet_size: 64,
+ interval: 5,
+ },
+];
+
+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
+];
+
+static CTAP_REPORT: ReportDescriptor<'static> = ReportDescriptor {
+ desc: CTAP_REPORT_DESCRIPTOR,
+};
+
+static HID_SUB_DESCRIPTORS: &'static [HIDSubordinateDescriptor] = &[HIDSubordinateDescriptor {
+ typ: DescriptorType::Report,
+ len: CTAP_REPORT_DESCRIPTOR.len() as u16,
+}];
+
+static HID: HIDDescriptor<'static> = HIDDescriptor {
+ hid_class: 0x0110,
+ country_code: HIDCountryCode::NotSupported,
+ sub_descriptors: HID_SUB_DESCRIPTORS,
+};
+
+pub struct ClientCtapHID<'a, 'b, C: 'a> {
+ client_ctrl: ClientCtrl<'a, 'static, C>,
+
+ // 64-byte buffers for the endpoint
+ in_buffer: Buffer64,
+ out_buffer: Buffer64,
+
+ // Interaction with the client
+ client: OptionalCell<&'b dyn CtapUsbClient>,
+ tx_packet: OptionalCell<[u8; 64]>,
+ pending_in: Cell<bool>,
+ pending_out: Cell<bool>,
+ delayed_out: Cell<bool>,
+}
+
+impl<'a, 'b, C: hil::usb::UsbController<'a>> ClientCtapHID<'a, 'b, C> {
+ pub fn new(controller: &'a C) -> Self {
+ ClientCtapHID {
+ client_ctrl: ClientCtrl::new(
+ controller,
+ DeviceDescriptor {
+ // TODO: set this field at the board level.
+ max_packet_size_ep0: 64,
+ vendor_id: VENDOR_ID,
+ product_id: PRODUCT_ID,
+ manufacturer_string: 1,
+ product_string: 2,
+ serial_number_string: 3,
+ ..Default::default()
+ },
+ ConfigurationDescriptor {
+ // Must be non-zero, otherwise dmesg prints the following error:
+ // [...] usb 2-3: config 0 descriptor??
+ configuration_value: 1,
+ ..Default::default()
+ },
+ // Interface declared in the FIDO2 specification, section 8.1.8.1
+ InterfaceDescriptor {
+ interface_class: 0x03, // HID
+ interface_subclass: 0x00,
+ interface_protocol: 0x00,
+ ..Default::default()
+ },
+ ENDPOINTS,
+ Some(&HID),
+ Some(&CTAP_REPORT),
+ LANGUAGES,
+ STRINGS,
+ ),
+ in_buffer: Default::default(),
+ out_buffer: Default::default(),
+ client: OptionalCell::empty(),
+ tx_packet: OptionalCell::empty(),
+ pending_in: Cell::new(false),
+ pending_out: Cell::new(false),
+ delayed_out: Cell::new(false),
+ }
+ }
+
+ pub fn set_client(&'a self, client: &'b dyn CtapUsbClient) {
+ self.client.set(client);
+ }
+
+ pub fn transmit_packet(&'a self, packet: &[u8]) -> bool {
+ if self.pending_in.get() {
+ // The previous packet has not yet been transmitted, reject the new one.
+ false
+ } else {
+ self.pending_in.set(true);
+ let mut buf: [u8; 64] = [0; 64];
+ buf.copy_from_slice(packet);
+ self.tx_packet.set(buf);
+ // Alert the controller that we now have data to send on the Interrupt IN endpoint.
+ self.controller().endpoint_resume_in(1);
+ true
+ }
+ }
+
+ pub fn receive_packet(&'a self) -> bool {
+ if self.pending_out.get() {
+ // The previous packet has not yet been received, reject the new one.
+ false
+ } else {
+ self.pending_out.set(true);
+ // 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 self.delayed_out.take() {
+ if self.send_packet_to_client() {
+ // If that succeeds, alert the controller that we can now
+ // receive data on the Interrupt OUT endpoint.
+ self.controller().endpoint_resume_out(1);
+ }
+ }
+ true
+ }
+ }
+
+ // 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) -> bool {
+ // Copy the packet into a buffer to send to the client.
+ let mut buf: [u8; 64] = [0; 64];
+ for (i, x) in self.out_buffer.buf.iter().enumerate() {
+ buf[i] = x.get();
+ }
+
+ assert!(!self.delayed_out.get());
+
+ // Notify the client
+ if self
+ .client
+ .map_or(false, |client| client.can_receive_packet())
+ {
+ 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();
+
+ self.client.map(|client| client.packet_received(&buf));
+ true
+ } else {
+ // Cannot receive now, indicate a delay to the controller.
+ self.delayed_out.set(true);
+ false
+ }
+ }
+
+ pub fn cancel_transaction(&'a self) -> bool {
+ self.cancel_in_transaction() | self.cancel_out_transaction()
+ }
+
+ fn cancel_in_transaction(&'a self) -> bool {
+ self.tx_packet.take();
+ self.pending_in.take()
+ }
+
+ fn cancel_out_transaction(&'a self) -> bool {
+ self.pending_out.take()
+ }
+
+ #[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
+ self.controller()
+ .endpoint_set_in_buffer(1, &self.in_buffer.buf);
+ self.controller()
+ .endpoint_set_out_buffer(1, &self.out_buffer.buf);
+ self.controller()
+ .endpoint_in_out_enable(TransferType::Interrupt, 1);
+ }
+
+ 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 endpoint != 1 {
+ return hil::usb::InResult::Error;
+ }
+
+ if let Some(packet) = self.tx_packet.take() {
+ let buf = &self.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
+ }
+ }
+ 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 != 1 {
+ return hil::usb::OutResult::Error;
+ }
+
+ if packet_bytes != 64 {
+ // Cannot process this packet
+ hil::usb::OutResult::Error
+ } else {
+ if self.send_packet_to_client() {
+ hil::usb::OutResult::Ok
+ } else {
+ hil::usb::OutResult::Delay
+ }
+ }
+ }
+ TransferType::Control | TransferType::Isochronous => unreachable!(),
+ }
+ }
+
+ fn packet_transmitted(&'a self, endpoint: usize) {
+ if endpoint != 1 {
+ panic!("Unexpected transmission on ep {}", endpoint);
+ }
+
+ if self.tx_packet.is_some() {
+ panic!("Unexpected tx_packet while a packet was being transmitted.");
+ }
+ self.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();
+
+ // Notify the client
+ self.client.map(|client| client.packet_transmitted());
+ }
+}