diff --git a/boards/components/src/lib.rs b/boards/components/src/lib.rs index dc56c94d2..917497af4 100644 --- a/boards/components/src/lib.rs +++ b/boards/components/src/lib.rs @@ -36,3 +36,4 @@ pub mod spi; pub mod st7735; pub mod temperature; pub mod touch; +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..69e95c3c7 --- /dev/null +++ b/boards/components/src/usb_ctap.rs @@ -0,0 +1,88 @@ +//! 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; +use kernel::static_init_half; + +// Setup static space for the objects. +#[macro_export] +macro_rules! usb_ctap_component_buf { + ($C:ty) => {{ + use capsules::usb::usb_ctap::CtapUsbSyscallDriver; + use capsules::usb::usbc_ctap_hid::ClientCtapHID; + use core::mem::MaybeUninit; + static mut BUF1: MaybeUninit> = MaybeUninit::uninit(); + static mut BUF2: MaybeUninit> = + MaybeUninit::uninit(); + (&mut BUF1, &mut BUF2) + };}; +} + +pub struct UsbCtapComponent> { + board_kernel: &'static kernel::Kernel, + controller: &'static C, + max_ctrl_packet_size: u8, + vendor_id: u16, + product_id: u16, + strings: &'static [&'static str], +} + +impl> UsbCtapComponent { + pub fn new( + board_kernel: &'static kernel::Kernel, + controller: &'static C, + max_ctrl_packet_size: u8, + vendor_id: u16, + product_id: u16, + strings: &'static [&'static str], + ) -> Self { + Self { + board_kernel, + controller, + max_ctrl_packet_size, + vendor_id, + product_id, + strings, + } + } +} + +impl> Component for UsbCtapComponent { + type StaticInput = ( + &'static mut MaybeUninit>, + &'static mut MaybeUninit>, + ); + type Output = &'static CtapUsbSyscallDriver<'static, 'static, C>; + + unsafe fn finalize(self, static_buffer: Self::StaticInput) -> Self::Output { + let grant_cap = create_capability!(capabilities::MemoryAllocationCapability); + + let usb_ctap = static_init_half!( + static_buffer.0, + ClientCtapHID<'static, 'static, C>, + 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 = static_init_half!( + static_buffer.1, + CtapUsbSyscallDriver<'static, 'static, C>, + CtapUsbSyscallDriver::new(usb_ctap, self.board_kernel.create_grant(&grant_cap)) + ); + usb_ctap.set_client(usb_driver); + + usb_driver + } +} diff --git a/boards/nordic/nrf52840_dongle_opensk/src/main.rs b/boards/nordic/nrf52840_dongle_opensk/src/main.rs index b485a0997..f5d29d025 100644 --- a/boards/nordic/nrf52840_dongle_opensk/src/main.rs +++ b/boards/nordic/nrf52840_dongle_opensk/src/main.rs @@ -40,6 +40,17 @@ const _SPI_CLK: Pin = Pin::P1_04; /// UART Writer pub mod io; +const VENDOR_ID: u16 = 0x1915; // Nordic Semiconductor +const PRODUCT_ID: u16 = 0x521f; // nRF52840 Dongle (PCA10059) +static STRINGS: &'static [&'static str] = &[ + // Manufacturer + "Nordic Semiconductor ASA", + // Product + "OpenSK", + // Serial number + "v1.0", +]; + // State for loading and holding applications. // How should the kernel respond when a process faults. const FAULT_RESPONSE: kernel::procs::FaultResponse = kernel::procs::FaultResponse::Panic; @@ -84,6 +95,11 @@ pub struct Platform { capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, >, nvmc: &'static nrf52840::nvmc::SyscallDriver, + usb: &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver< + 'static, + 'static, + nrf52840::usbd::Usbd<'static>, + >, } impl kernel::Platform for Platform { @@ -100,6 +116,7 @@ impl kernel::Platform for Platform { capsules::rng::DRIVER_NUM => f(Some(self.rng)), capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)), + capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -286,6 +303,21 @@ pub unsafe fn reset_handler() { ) ); + // Enable power events to be sent to USB controller + nrf52840::power::POWER.set_usb_client(&nrf52840::usbd::USBD); + nrf52840::power::POWER.enable_interrupts(); + + // Configure USB controller + let usb = components::usb_ctap::UsbCtapComponent::new( + board_kernel, + &nrf52840::usbd::USBD, + capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_NRF52840, + VENDOR_ID, + PRODUCT_ID, + STRINGS, + ) + .finalize(components::usb_ctap_component_buf!(nrf52840::usbd::Usbd)); + nrf52_components::NrfClockComponent::new().finalize(()); let platform = Platform { @@ -298,6 +330,7 @@ pub unsafe fn reset_handler() { alarm, analog_comparator, nvmc, + usb, ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability), }; diff --git a/boards/nordic/nrf52840dk_opensk/src/main.rs b/boards/nordic/nrf52840dk_opensk/src/main.rs index c80732f8d..41047a390 100644 --- a/boards/nordic/nrf52840dk_opensk/src/main.rs +++ b/boards/nordic/nrf52840dk_opensk/src/main.rs @@ -104,6 +104,17 @@ pub mod io; // - Set to true to use Segger RTT over USB. const USB_DEBUGGING: bool = true; +const VENDOR_ID: u16 = 0x1915; // Nordic Semiconductor +const PRODUCT_ID: u16 = 0x521f; // nRF52840 Dongle (PCA10059) +static STRINGS: &'static [&'static str] = &[ + // Manufacturer + "Nordic Semiconductor ASA", + // Product + "OpenSK", + // Serial number + "v1.0", +]; + // State for loading and holding applications. // How should the kernel respond when a process faults. const FAULT_RESPONSE: kernel::procs::FaultResponse = kernel::procs::FaultResponse::Panic; @@ -147,6 +158,11 @@ pub struct Platform { capsules::virtual_alarm::VirtualMuxAlarm<'static, nrf52840::rtc::Rtc<'static>>, >, nvmc: &'static nrf52840::nvmc::SyscallDriver, + usb: &'static capsules::usb::usb_ctap::CtapUsbSyscallDriver< + 'static, + 'static, + nrf52840::usbd::Usbd<'static>, + >, } impl kernel::Platform for Platform { @@ -163,6 +179,7 @@ impl kernel::Platform for Platform { capsules::rng::DRIVER_NUM => f(Some(self.rng)), capsules::analog_comparator::DRIVER_NUM => f(Some(self.analog_comparator)), nrf52840::nvmc::DRIVER_NUM => f(Some(self.nvmc)), + capsules::usb::usb_ctap::DRIVER_NUM => f(Some(self.usb)), kernel::ipc::DRIVER_NUM => f(Some(&self.ipc)), _ => f(None), } @@ -377,6 +394,21 @@ pub unsafe fn reset_handler() { .expect("no deferred call slot available for nvmc"), ); + // Enable power events to be sent to USB controller + nrf52840::power::POWER.set_usb_client(&nrf52840::usbd::USBD); + nrf52840::power::POWER.enable_interrupts(); + + // Configure USB controller + let usb = components::usb_ctap::UsbCtapComponent::new( + board_kernel, + &nrf52840::usbd::USBD, + capsules::usb::usbc_client::MAX_CTRL_PACKET_SIZE_NRF52840, + VENDOR_ID, + PRODUCT_ID, + STRINGS, + ) + .finalize(components::usb_ctap_component_buf!(nrf52840::usbd::Usbd)); + nrf52_components::NrfClockComponent::new().finalize(()); let platform = Platform { @@ -389,6 +421,7 @@ pub unsafe fn reset_handler() { alarm, analog_comparator, nvmc, + usb, ipc: kernel::ipc::IPC::new(board_kernel, &memory_allocation_capability), }; diff --git a/capsules/src/driver.rs b/capsules/src/driver.rs index 256fc0e9d..ae458b309 100644 --- a/capsules/src/driver.rs +++ b/capsules/src/driver.rs @@ -26,6 +26,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 767f5de83..3f3a4f646 100644 --- a/capsules/src/usb/mod.rs +++ b/capsules/src/usb/mod.rs @@ -1,5 +1,7 @@ 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..da3d16d85 --- /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, + callback: Option, + buffer: Option>, + // 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, +} + +impl<'a, 'b, C: hil::usb::UsbController<'a>> CtapUsbSyscallDriver<'a, 'b, C> { + pub fn new(usb_client: &'a ClientCtapHID<'a, 'b, C>, apps: Grant) -> 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>, + ) -> 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, + 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 000000000..642039120 --- /dev/null +++ b/capsules/src/usb/usbc_ctap_hid.rs @@ -0,0 +1,369 @@ +//! A USB HID client of the USB hardware interface + +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::common::cells::OptionalCell; +use kernel::debug; +use kernel::hil; +use kernel::hil::usb::TransferType; + +static LANGUAGES: &'static [u16; 1] = &[ + 0x0409, // English (United States) +]; + +const ENDPOINT_NUM: usize = 1; + +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, + pending_out: Cell, + delayed_out: Cell, +} + +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 { + let interfaces: &mut [InterfaceDescriptor] = &mut [ + // Interface declared in the FIDO2 specification, section 8.1.8.1 + InterfaceDescriptor { + interface_class: 0x03, // HID + interface_subclass: 0x00, + interface_protocol: 0x00, + ..InterfaceDescriptor::default() + }, + ]; + + let endpoints: &[&[EndpointDescriptor]] = &[&[ + 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, + }, + ]]; + + 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, + max_packet_size_ep0: max_ctrl_packet_size, + ..descriptors::DeviceDescriptor::default() + }, + descriptors::ConfigurationDescriptor { + configuration_value: 1, + ..descriptors::ConfigurationDescriptor::default() + }, + interfaces, + endpoints, + Some(&HID), + None, // No CDC descriptor array + ); + + ClientCtapHID { + client_ctrl: ClientCtrl::new( + controller, + device_descriptor_buffer, + other_descriptor_buffer, + Some(&HID), + Some(&CTAP_REPORT), + LANGUAGES, + strings, + ), + in_buffer: Buffer64::default(), + out_buffer: Buffer64::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()); + } +} diff --git a/chips/nrf52840/src/lib.rs b/chips/nrf52840/src/lib.rs index 942d0288f..ce73e1f82 100644 --- a/chips/nrf52840/src/lib.rs +++ b/chips/nrf52840/src/lib.rs @@ -2,7 +2,7 @@ pub use nrf52::{ acomp, adc, aes, ble_radio, clock, constants, crt1, ficr, i2c, ieee802154_radio, init, nvmc, - pinmux, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd, + pinmux, power, ppi, pwm, rtc, spi, temperature, timer, trng, uart, uicr, usbd, }; pub mod chip; pub mod gpio;