initial stab at a usb-cramium implementation

The approach here turns the usb-device crate "inside out". The
working hypothesis for the unreliability of the previous implementation
is that we try to do all the IRQ-handling in userspace, where
the operation can be pre-empted. Unfortunately, this doesn't set well
with the corigine USB hardware driver, as it assumes that it has
the full CPU attention while its state is being manipulated (and
some things must be responded to in basically real time otherwise
you get protocol inconsistencies).

In this implementation, we use the Xous subsystem to do the
API-level exchanges of information to a USB handler that, for the
most part, runs directly inside an IRQ context.

There's some trickiness in terms of sharing the hardware state
across the boundary between IRQ and userland, but most of it
got handled I think fairly cleanly in this implemnetation by
using the xous-native server queuing mechanism to shuttle data
in between the two domains. The main spot where I think panics
can creep in is in data transmission, where the Tx data is
shoved into a RefCell and then the IRQ is invoked to pull the
data from a RefCell. As long as we don't invoke the IRQ to
pull data from the RefCell while it's borrowed mutably, we should
be OK.

Wrapping the structure in a Mutex actually doesn't help here
because the IRQ context can't do a "wait" to get a lock - it
would never get it, because it's in an uninterruptable context.
Thus we skip the complexity of a Mutex and let Rust panic if
we mesesd up the data hand-off.
This commit is contained in:
bunnie 2024-12-16 17:56:05 +08:00
parent 5d35dee4f6
commit 9a0bbbd2fb
7 changed files with 1377 additions and 3 deletions

View file

@ -22,6 +22,7 @@ rkyv = { version = "0.8.8", default-features = false, features = [
] }
packed_struct = { version = "0.10", default-features = false } # used by the xous-usb-hid crate
locales = { path = "../../locales" }
frunk_core = "0.4.2"
[dependencies.usb-device]
# see top level Cargo.toml for patch.crates-io directive to help with dev work
@ -36,4 +37,10 @@ precursor = ["utralib/precursor", "utralib/std"]
hosted = ["utralib/hosted", "utralib/std"]
renode = ["utralib/renode", "utralib/std"]
mass-storage = []
default = ["mass-storage"]
debug-print = [
] # mutually exclusive feature with other crates that use the app UART
prototyping = [
] # temporary feature-out for figuring out the code; remove this if you don't recognize it anymore
default = ["mass-storage", "debug-print"]

View file

@ -57,6 +57,9 @@ pub(crate) enum Opcode {
/// TRNG send poll
SerialTrngPoll = 518,
/// Interrupt-context USB stack messages
IrqFidoRx = 768,
#[cfg(feature = "mass-storage")]
SetBlockDevice = 1024,
#[cfg(feature = "mass-storage")]
@ -84,6 +87,8 @@ pub(crate) enum Opcode {
SuspendResume = 2049,
/// Exits the server
Quit = 4096,
/// Invalid opcode gutter
InvalidCall = 4097,
/// API used by the logging crate. The number is hard-coded; don't change it.
LogString = 8192,

View file

@ -0,0 +1,68 @@
use core::fmt::{Error, Write};
// manually sync'd with constant in loader/src/swap.rs, which is where the App UART
// is initially setup because a swapper can't set up its UART any other way (it needs
// to have its v-pages mapped by the loader as it is possible for the first v-page
// mapping to page fault into a swapped region)
pub const SWAP_APP_UART_VADDR: usize = 0xE180_0000;
pub const SWAP_APP_UART_IFRAM_VADDR: usize = 0xE180_1000;
pub struct DebugUart {}
impl DebugUart {
#[cfg(feature = "debug-print")]
pub fn putc(&mut self, c: u8) {
use cramium_hal::udma;
// safety: safe to call as long as the raw parts are initialized and we exclusively
// own it; and the UART has been initialized. For this peripheral, initialization
// is handled by the loader and tossed to us, and exclusivity of access is something
// we just have to not bungle.
let mut uart = unsafe {
udma::Uart::get_handle(
SWAP_APP_UART_VADDR as usize,
cramium_hal::board::APP_UART_IFRAM_ADDR as usize,
SWAP_APP_UART_IFRAM_VADDR as usize,
)
};
// enqueue our character to send via DMA
uart.write(&[c]);
}
// when debug-print is off, the print call code still exist, but the output step is skipped
#[cfg(not(feature = "debug-print"))]
pub fn putc(&self, _c: u8) {}
}
impl Write for DebugUart {
fn write_str(&mut self, s: &str) -> Result<(), Error> {
for c in s.bytes() {
self.putc(c);
}
Ok(())
}
}
#[macro_use]
pub mod debug_print_hardware {
#[macro_export]
macro_rules! print
{
($($args:tt)+) => ({
use core::fmt::Write;
let _ = write!(crate::debug::DebugUart {}, $($args)+);
});
}
}
#[macro_export]
macro_rules! println
{
() => ({
$crate::print!("\r\n")
});
($fmt:expr) => ({
$crate::print!(concat!($fmt, "\r\n"))
});
($fmt:expr, $($args:tt)+) => ({
$crate::print!(concat!($fmt, "\r\n"), $($args)+)
});
}

View file

@ -0,0 +1,287 @@
use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::sync::atomic::compiler_fence;
use std::sync::atomic::{AtomicPtr, Ordering};
use cramium_hal::usb::driver::{CRG_UDC_ERDPLO_EHB, CorigineWrapper, EventTrbS};
use cramium_hal::usb::utra::*;
use num_traits::*;
use usb_cramium::HIDReport;
use usb_device::class_prelude::*;
use usb_device::device::UsbDevice;
use usb_device::prelude::*;
use utralib::AtomicCsr;
use xous_ipc::Buffer;
use xous_usb_hid::device::DeviceClass;
use xous_usb_hid::device::fido::RawFido;
use xous_usb_hid::device::fido::RawFidoConfig;
use xous_usb_hid::device::fido::RawFidoReport;
use xous_usb_hid::device::keyboard::{NKROBootKeyboard, NKROBootKeyboardConfig};
use xous_usb_hid::page::Keyboard;
use xous_usb_hid::prelude::UsbHidClass;
use xous_usb_hid::prelude::*;
use crate::Opcode;
/*
1. interrupt enters
2. poll() is called in a loop
- this loop is the process_event_ring() "loop"
- the result of poll() is immediately passed on to the Class() handler *before* the Event is dequeued
- the read()/write() functions calls that would interact with the Transfer TRBs should all be called by now
- (so, the events are handled one-by-one even though "poll" natively wants to aggregate all simultaneous events)
3. only after the loop is done, do we execute the post-amble Event DeQueue pointer update
4. EP allocation simply manages some bookkeeping that allows the EpAddress to be passed on to read()/write()
call such that the handlers line up with the packet type. This virtually implements the function-pointer immediate
decode & dispatch of handlers within the framework of the USB crate
5. interrupt exits
I think this succeeds where the previous version fails because:
- the EventTRB is only dequeued after all the transfer pending data is handled - previously we would
dequeue the EventTRB, then potentially take arbitrarily long (due to interruptability) to handle the
TransferTRB, which could allow the TransferTRB to be overwritten because the existence of the EventTRB
is more of what stalls the packet engine
- The actual "set stall" handler thus might actually be nil, because the stall I believe is automatically
originated within the USB hardware itself, as it knows that a packet is pending and will NAK the packets.
The clearing of the stall is the only thing that might need an implementation, and that is equivalently
the enqueue ZLP function but we need to revisit this on a case by case basis - it might make sense for
the system to unstall right away based on the read of the TransferTRB instead of trying to split the
unstall call to work with the usb-device stack.
Another option would be to just figure out how to abuse the usb-device stack to simply generate
a formatted device descriptor that we can feed into the state-machine based implementation provided
by the vendor as a reference.
*/
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum UsbIrqReq {
FidoTx,
KbdTx,
}
pub const CORIGINE_IRQ_MASK: u32 = 0x1;
pub const SW_IRQ_MASK: u32 = 0x2;
pub(crate) fn composite_handler(_irq_no: usize, arg: *mut usize) {
let usb = unsafe { &mut *(arg as *mut CramiumUsb) };
// immediately clear the interrupt and re-enable it so we can catch an interrupt
// that is generated while we are handling the interrupt.
let pending = usb.irq_csr.r(utralib::utra::irqarray1::EV_PENDING);
// clear pending
usb.irq_csr.wo(utralib::utra::irqarray1::EV_PENDING, pending);
// re-enable interrupts
usb.irq_csr.wfo(utralib::utra::irqarray1::EV_ENABLE_USBC_DUPE, 3);
if (pending & CORIGINE_IRQ_MASK) != 0 {
let status = usb.csr.r(USBSTS);
// usb.print_status(status);
if (status & usb.csr.ms(USBSTS_SYSTEM_ERR, 1)) != 0 {
crate::println!("System error");
usb.csr.wfo(USBSTS_SYSTEM_ERR, 1);
crate::println!("USBCMD: {:x}", usb.csr.r(USBCMD));
} else {
if (status & usb.csr.ms(USBSTS_EINT, 1)) != 0 {
let status = usb.csr.r(USBSTS);
// self.print_status(status);
if (status & usb.csr.ms(USBSTS_SYSTEM_ERR, 1)) != 0 {
crate::println!("System error");
usb.csr.wfo(USBSTS_SYSTEM_ERR, 1);
crate::println!("USBCMD: {:x}", usb.csr.r(USBCMD));
} else {
if (status & usb.csr.ms(USBSTS_EINT, 1)) != 0 {
usb.csr.wfo(USBSTS_EINT, 1);
// clear IP
usb.csr.rmwf(IMAN_IP, 1);
loop {
{
// scoping on the hardware lock to manipulate pointer states
let hw_lock = usb.wrapper.hw.borrow_mut().lock().unwrap();
let event = {
if hw_lock.udc_event.evt_dq_pt.load(Ordering::SeqCst).is_null() {
// break;
crate::println!("null pointer in process_event_ring");
break;
}
let event_ptr =
hw_lock.udc_event.evt_dq_pt.load(Ordering::SeqCst) as usize;
unsafe {
(event_ptr as *mut EventTrbS)
.as_mut()
.expect("couldn't deref pointer")
}
};
if event.dw3.cycle_bit() != hw_lock.udc_event.ccs {
break;
}
}
let device = usb.device.borrow_mut();
let class = usb.class.borrow_mut();
if device.poll(&mut [class]) {
// keyboard report handler
match class.device::<NKROBootKeyboard<_>, _>().read_report() {
Ok(l) => {
// for now all we do is just print this, we don't
// actually store the data or pass it on to userspace
crate::println!("keyboard LEDs: {:?}", l);
}
Err(e) => crate::println!("KEYB ERR: {:?}", e),
};
// u2f report handler
match class.device::<RawFido<'_, _>, _>().read_report() {
Ok(u2f_report) => {
let mut rx_to_userspace = HIDReport::default();
rx_to_userspace.0.copy_from_slice(&u2f_report.packet);
let buf = Buffer::into_buf(rx_to_userspace)
.expect("couldn't transform rx packet");
// this will panic if the server queue is full. For now, this
// is what we want to do because (a) I suspect this condition
// doesn't happen and (b) if it does it means we have to write
// code that puts it into an IRQ-handler side queue, along
// with a mechanism that sends a message to userspace that
// initiates a retry timer in an interruptable context (so
// that the queue can empty) and then re-enters the interrupt
// context via a software interrupt to retry the send.
buf.try_send(usb.conn, Opcode::IrqFidoRx.to_u32().unwrap())
.expect("couldn't send FIDO packet to userspace: maybe we need to implement a timeout/retry mechanism?");
}
Err(e) => crate::println!("U2F ERR: {:?}", e),
}
}
{
// scoping on the hardware lock to manipulate pointer states
let mut hw_lock = usb.wrapper.hw.borrow_mut().lock().unwrap();
if hw_lock.udc_event.evt_dq_pt.load(Ordering::SeqCst)
== hw_lock.udc_event.evt_seg0_last_trb.load(Ordering::SeqCst)
{
crate::println!(
" evt_last_trb {:x}",
hw_lock.udc_event.evt_seg0_last_trb.load(Ordering::SeqCst) as usize
);
hw_lock.udc_event.ccs = !hw_lock.udc_event.ccs;
// does this...go to null to end the transfer??
hw_lock.udc_event.evt_dq_pt = AtomicPtr::new(
hw_lock.udc_event.event_ring.vaddr.load(Ordering::SeqCst)
as *mut EventTrbS,
);
} else {
hw_lock.udc_event.evt_dq_pt = AtomicPtr::new(unsafe {
hw_lock.udc_event.evt_dq_pt.load(Ordering::SeqCst).add(1)
});
}
}
}
// update dequeue pointer
usb.csr.wo(ERDPHI, 0);
usb.csr.wo(
ERDPLO,
(usb.wrapper
.hw
.borrow_mut()
.lock()
.unwrap()
.udc_event
.evt_dq_pt
.load(Ordering::SeqCst) as u32
& 0xFFFF_FFF0)
| CRG_UDC_ERDPLO_EHB,
);
compiler_fence(Ordering::SeqCst);
}
};
}
if usb.csr.rf(IMAN_IE) != 0 {
usb.csr.wo(IMAN, usb.csr.ms(IMAN_IE, 1) | usb.csr.ms(IMAN_IP, 1));
}
}
} else if (pending & SW_IRQ_MASK) != 0 {
let composite = usb.class.borrow_mut();
match usb.irq_req.take() {
Some(UsbIrqReq::FidoTx) => {
let u2f = composite.device::<RawFido<'_, _>, _>();
// you know, I'm not 100% sure we *can* write multiple reports without taking
// another interrupt. But, in practice, we /should/ only ever get one event
// and the VecDequeue structure lays the ground work to extend this to something
// more flexible if we decide we need to handle multiple Tx queued events in a single
// IRQ trigger.
while let Some(u2f_msg) = usb.fido_tx_queue.borrow_mut().pop_front() {
u2f.write_report(&u2f_msg).ok();
}
}
Some(UsbIrqReq::KbdTx) => {
let keyboard = composite.device::<NKROBootKeyboard<'_, _>, _>();
usb.kbd_tx_queue.borrow_mut().make_contiguous();
let kbd_events = usb.kbd_tx_queue.borrow().as_slices().0.to_vec();
keyboard.write_report(kbd_events).ok();
usb.kbd_tx_queue.borrow_mut().clear();
keyboard.tick().ok();
}
None => (),
}
}
}
pub struct CramiumUsb<'a> {
pub conn: xous::CID,
pub wrapper: CorigineWrapper,
pub device: UsbDevice<'a, CorigineWrapper>,
pub class: UsbHidClass<
'a,
CorigineWrapper,
frunk_core::hlist::HCons<
RawFido<'a, CorigineWrapper>,
frunk_core::hlist::HCons<NKROBootKeyboard<'a, CorigineWrapper>, frunk_core::hlist::HNil>,
>,
>,
pub csr: AtomicCsr<u32>,
pub irq_csr: AtomicCsr<u32>,
pub fido_tx_queue: RefCell<VecDeque<RawFidoReport>>,
pub kbd_tx_queue: RefCell<VecDeque<Keyboard>>,
pub irq_req: Option<UsbIrqReq>,
}
impl<'a> CramiumUsb<'a> {
pub fn new(
cid: xous::CID,
cw: CorigineWrapper,
usb_alloc: &'a UsbBusAllocator<CorigineWrapper>,
csr: AtomicCsr<u32>,
irq_csr: AtomicCsr<u32>,
serial_number: &'a String,
) -> Self {
let class = UsbHidClassBuilder::new()
.add_device(NKROBootKeyboardConfig::default())
.add_device(RawFidoConfig::default())
.build(&usb_alloc);
let device = UsbDeviceBuilder::new(&usb_alloc, UsbVidPid(0x1209, 0x3613))
.manufacturer("Kosagi")
.product("Precursor")
.serial_number(&serial_number)
.build();
CramiumUsb {
conn: cid,
// safety: we created iframrange to have the exact same P&V mappings
wrapper: cw,
device,
class,
csr,
irq_csr,
fido_tx_queue: RefCell::new(VecDeque::new()),
kbd_tx_queue: RefCell::new(VecDeque::new()),
irq_req: None,
}
}
pub fn sw_irq(&mut self, request_type: UsbIrqReq) {
self.irq_req = Some(request_type);
self.irq_csr.wfo(utralib::utra::irqarray1::EV_SOFT_TRIGGER, SW_IRQ_MASK);
}
}

View file

@ -1,3 +1,483 @@
fn main() {
println!("Hello, world!");
mod api;
mod debug;
mod hw;
#[cfg(not(target_os = "xous"))]
mod main_hosted;
mod mappings;
use core::convert::TryFrom;
use core::num::NonZeroU8;
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use std::collections::VecDeque;
use std::sync::Arc;
use api::{HIDReport, LogLevel, Opcode, U2fCode, U2fMsgIpc, UsbListenerRegistration};
use cram_hal_service::api::KeyMap;
use cramium_hal::usb::driver::{CorigineUsb, CorigineWrapper};
use hw::CramiumUsb;
use hw::UsbIrqReq;
use num_traits::*;
use usb_device::class_prelude::*;
use utralib::AtomicCsr;
use xous::{msg_blocking_scalar_unpack, msg_scalar_unpack};
use xous_ipc::Buffer;
use xous_usb_hid::device::fido::RawFidoReport;
use xous_usb_hid::page::Keyboard;
#[derive(num_derive::FromPrimitive, num_derive::ToPrimitive, Debug)]
enum TimeoutOp {
Pump,
InvalidCall,
Quit,
}
fn main() -> ! {
#[cfg(target_os = "xous")]
main_hw();
#[cfg(not(target_os = "xous"))]
main_hosted::main_hosted();
}
pub(crate) fn main_hw() -> ! {
log_server::init_wait().unwrap();
log::set_max_level(log::LevelFilter::Info);
log::info!("my PID is {}", xous::process::id());
let xns = xous_names::XousNames::new().unwrap();
let usbdev_sid = xns.register_name(api::SERVER_NAME_USB_DEVICE, None).expect("can't register server");
let cid = xous::connect(usbdev_sid).expect("couldn't create suspend callback connection");
log::trace!("registered with NS -- {:?}", usbdev_sid);
let tt = ticktimer::Ticktimer::new().unwrap();
let serial_number = format!("TODO!!"); // implement in cramium-hal once we have a serial number API
let native_kbd =
cram_hal_service::keyboard::Keyboard::new(&xns).expect("couldn't connect to keyboard service");
let usb_mapping = xous::syscall::map_memory(
xous::MemoryAddress::new(cramium_hal::usb::utra::CORIGINE_USB_BASE),
None,
cramium_hal::usb::utra::CORIGINE_USB_LEN,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't reserve register pages");
let ifram_range = xous::syscall::map_memory(
xous::MemoryAddress::new(cramium_hal::usb::driver::CRG_UDC_MEMBASE),
xous::MemoryAddress::new(cramium_hal::usb::driver::CRG_UDC_MEMBASE), /* make P & V addresses
* line up */
cramium_hal::usb::driver::CRG_IFRAM_PAGES * 0x1000,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't allocate IFRAM pages");
assert!(
cramium_hal::usb::driver::CRG_UDC_TOTAL_MEM_LEN <= cramium_hal::usb::driver::CRG_IFRAM_PAGES * 0x1000
);
log::info!(
"total memory len: {:x}, allocated: {:x}",
cramium_hal::usb::driver::CRG_UDC_TOTAL_MEM_LEN,
cramium_hal::usb::driver::CRG_IFRAM_PAGES * 0x1000
);
let irq_range = xous::syscall::map_memory(
xous::MemoryAddress::new(utralib::utra::irqarray1::HW_IRQARRAY1_BASE),
None,
0x1000,
xous::MemoryFlags::R | xous::MemoryFlags::W,
)
.expect("couldn't allocate IFRAM pages");
let usb = AtomicCsr::new(usb_mapping.as_ptr() as *mut u32);
let irq_csr = AtomicCsr::new(irq_range.as_ptr() as *mut u32);
let h_op: usize = Opcode::UsbIrqHandler.to_usize().unwrap();
let corigine_usb =
unsafe { CorigineUsb::new(cid, h_op, ifram_range.as_ptr() as usize, usb.clone(), irq_csr.clone()) };
// safety: this is only safe because we will actually claim the IRQ after all the initializations are
// done, and we promise not to enable interrupts until that time, either.
unsafe {
corigine_usb.irq_claimed();
}
let cw = CorigineWrapper::new(corigine_usb);
let usb_alloc = UsbBusAllocator::new(cw.clone());
let mut cu = Box::new(CramiumUsb::new(cid, cw, &usb_alloc, usb.clone(), irq_csr.clone(), &serial_number));
// this has to be done in `main` because we're passing a pointer to the Box'd structure, which
// the IRQ handler can freely and safely manipulate
xous::claim_interrupt(
utralib::utra::irqarray1::IRQARRAY1_IRQ,
hw::composite_handler,
&mut cu as *mut Box<CramiumUsb> as *mut usize,
)
.expect("couldn't claim irq");
// enable both the corigine core IRQ and the software IRQ bit
// software IRQ is used to initiate send/receive from software to the interrupt context
irq_csr.wo(utralib::utra::irqarray1::EV_ENABLE, hw::CORIGINE_IRQ_MASK | hw::SW_IRQ_MASK);
// under the theory that PIDs cannot be forged.
// also if someone commandeers a process, all bets are off within that process (this is a general
// statement)
let mut fido_listener_pid: Option<NonZeroU8> = None;
let mut fido_listener: Option<xous::MessageEnvelope> = None;
let mut fido_rx_queue: VecDeque<[u8; 64]> = VecDeque::new();
let mut autotype_delay_ms = 30;
// event observer connection
let mut observer_conn: Option<xous::CID> = None;
let mut observer_op: Option<usize> = None;
// manage FIDO Rx timeouts -- not tested yet
let to_server = xous::create_server().unwrap();
let to_conn = xous::connect(to_server).unwrap();
// we don't have AtomicU64 on this platform, so we suffer from a rollover condition in timeouts once every
// 46 days this manifests as a timeout that happens to be scheduled on the rollover being rounded to a
// max limit timeout of 5 seconds, and/or an immediate timeout happening during the 5 seconds before
// the 46-day limit
let target_time_lsb = Arc::new(AtomicU32::new(0));
let to_run = Arc::new(AtomicBool::new(false));
const MAX_TIMEOUT_LIMIT_MS: u32 = 5000;
const POLL_INTERVAL_MS: u64 = 50;
std::thread::spawn({
let cid = cid;
let to_conn = to_conn;
let target_time_lsb = target_time_lsb.clone();
let to_run = to_run.clone();
move || {
let tt = ticktimer::Ticktimer::new().unwrap();
let mut msg_opt = None;
let mut return_type = 0;
let mut next_wake = tt.elapsed_ms();
loop {
xous::reply_and_receive_next_legacy(to_server, &mut msg_opt, &mut return_type).unwrap();
let msg = msg_opt.as_mut().unwrap();
// loop only consumes CPU time when a timeout is active. Once it has timed out,
// it will wait for a new pump call.
let now = tt.elapsed_ms();
match num_traits::FromPrimitive::from_usize(msg.body.id()).unwrap_or(TimeoutOp::InvalidCall) {
TimeoutOp::Pump => {
if to_run.load(Ordering::SeqCst) {
let tt_lsb = target_time_lsb.load(Ordering::SeqCst);
if tt_lsb >= (now as u32) || (now as u32) - tt_lsb > MAX_TIMEOUT_LIMIT_MS
// limits rollover case
{
xous::try_send_message(
cid,
xous::Message::new_scalar(
Opcode::U2fRxTimeout.to_usize().unwrap(),
0,
0,
0,
0,
),
)
.ok();
// no need to set to_run to `false` because a Pump message isn't initiated;
// the loop de-facto stops from a lack of new Pump messages
} else {
if next_wake <= now {
next_wake = now + POLL_INTERVAL_MS;
tt.sleep_ms(POLL_INTERVAL_MS as usize).ok();
xous::try_send_message(
to_conn,
xous::Message::new_scalar(
TimeoutOp::Pump.to_usize().unwrap(),
0,
0,
0,
0,
),
)
.ok();
} else {
// don't issue more wakeups if we already have a wakeup scheduled
}
}
}
}
TimeoutOp::Quit => {
if let Some(scalar) = msg.body.scalar_message_mut() {
scalar.id = 0;
scalar.arg1 = 1;
break;
}
}
TimeoutOp::InvalidCall => {
log::error!(
"Unknown opcode received in FIDO Rx timeout handler: {:?}",
msg.body.id()
);
}
}
}
xous::destroy_server(to_server).unwrap();
}
});
let mut msg_opt = None;
loop {
xous::reply_and_receive_next(usbdev_sid, &mut msg_opt).unwrap();
let msg = msg_opt.as_mut().unwrap();
let opcode = num_traits::FromPrimitive::from_usize(msg.body.id()).unwrap_or(Opcode::InvalidCall);
log::debug!("{:?}", opcode);
match opcode {
Opcode::U2fRxDeferred => {
// notify the event listener, if any
if observer_conn.is_some() && observer_op.is_some() {
xous::try_send_message(
observer_conn.unwrap(),
xous::Message::new_scalar(observer_op.unwrap(), 0, 0, 0, 0),
)
.ok();
}
if fido_listener_pid.is_none() {
fido_listener_pid = msg.sender.pid();
}
if fido_listener.is_some() {
log::error!(
"Double-listener request detected. There should only ever by one registered listener at a time."
);
log::error!(
"This will cause an upstream server to misbehave, but not panicing so the problem can be debugged."
);
// the receiver will get a response with the `code` field still in the `RxWait` state to
// indicate the problem
}
if fido_listener_pid == msg.sender.pid() {
// preferentially pull from the rx queue if it has elements
if let Some(data) = fido_rx_queue.pop_front() {
log::debug!(
"no deferral: ret queued data: {:?} queue len: {}",
&data[..8],
fido_rx_queue.len() + 1
);
let mut response = unsafe {
Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())
};
let mut buf = response.to_original::<U2fMsgIpc, _>().unwrap();
assert_eq!(buf.code, U2fCode::RxWait, "Expected U2fcode::RxWait in wrapper");
buf.data.copy_from_slice(&data);
buf.code = U2fCode::RxAck;
response.replace(buf).unwrap();
} else {
log::trace!("registering deferred listener");
{
// not tested
let spec = unsafe {
Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())
};
if let Some(to) = spec.to_original::<U2fMsgIpc, _>().unwrap().timeout_ms {
let target = tt.elapsed_ms() + to;
target_time_lsb.store(target as u32, Ordering::SeqCst); // this will keep updating the target time later and later
// run must always be set *after* target time is updated, because there is
// always a chance we timed out and checked
// target time between these two steps
to_run.store(true, Ordering::SeqCst);
xous::try_send_message(
to_conn,
xous::Message::new_scalar(
TimeoutOp::Pump.to_usize().unwrap(),
0,
0,
0,
0,
),
)
.ok();
}
};
fido_listener = msg_opt.take();
}
} else {
log::warn!(
"U2F interface capability is locked on first use; additional servers are ignored: {:?}",
msg.sender
);
let mut buffer =
unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) };
let mut u2f_ipc = buffer.to_original::<U2fMsgIpc, _>().unwrap();
u2f_ipc.code = U2fCode::Denied;
buffer.replace(u2f_ipc).unwrap();
}
}
// Note: not tested
Opcode::U2fRxTimeout => {
// put the lock in its own scope so we release it as soon as we've taken the message
let maybe_listener = { fido_listener.take() };
if let Some(mut listener) = maybe_listener {
let mut response = unsafe {
Buffer::from_memory_message_mut(listener.body.memory_message_mut().unwrap())
};
let mut buf = response.to_original::<U2fMsgIpc, _>().unwrap();
assert_eq!(buf.code, U2fCode::RxWait, "Expected U2fcode::RxWait in wrapper");
buf.code = U2fCode::RxTimeout;
response.replace(buf).unwrap();
}
}
Opcode::IrqFidoRx => {
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
let u2f_report = buffer.to_original::<HIDReport, _>().unwrap();
if let Some(mut listener) = fido_listener.take() {
let mut response = unsafe {
Buffer::from_memory_message_mut(listener.body.memory_message_mut().unwrap())
};
let mut deferred_buf = response.to_original::<U2fMsgIpc, _>().unwrap();
deferred_buf.data.copy_from_slice(&u2f_report.0);
log::trace!("ret deferred data {:x?}", &u2f_report.0[..8]);
deferred_buf.code = U2fCode::RxAck;
response.replace(deferred_buf).unwrap();
} else {
crate::println!("Got U2F packet, but no server to respond...queuing.");
fido_rx_queue.push_back(u2f_report.0);
}
}
Opcode::U2fTx => {
if fido_listener_pid.is_none() {
fido_listener_pid = msg.sender.pid();
}
let mut buffer =
unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) };
let mut u2f_ipc = buffer.to_original::<U2fMsgIpc, _>().unwrap();
if fido_listener_pid == msg.sender.pid() {
let mut u2f_msg = RawFidoReport::default();
assert_eq!(u2f_ipc.code, U2fCode::Tx, "Expected U2fCode::Tx in wrapper");
u2f_msg.packet.copy_from_slice(&u2f_ipc.data);
{
// scope this so the borrow is released before the IRQ is tripped
cu.fido_tx_queue.borrow_mut().push_back(u2f_msg);
}
cu.sw_irq(UsbIrqReq::FidoTx);
log::debug!("enqueued U2F packet {:x?}", u2f_ipc.data);
u2f_ipc.code = U2fCode::TxAck;
} else {
u2f_ipc.code = U2fCode::Denied;
}
buffer.replace(u2f_ipc).unwrap();
}
Opcode::SendKeyCode => msg_blocking_scalar_unpack!(msg, code0, code1, code2, autoup, {
let native_map = native_kbd.get_keymap().unwrap();
if code0 != 0 {
cu.kbd_tx_queue.borrow_mut().push_back(match native_map {
KeyMap::Dvorak => mappings::char_to_hid_code_dvorak(code0 as u8 as char)[0],
_ => mappings::char_to_hid_code_us101(code0 as u8 as char)[0],
});
}
if code1 != 0 {
cu.kbd_tx_queue.borrow_mut().push_back(match native_map {
KeyMap::Dvorak => mappings::char_to_hid_code_dvorak(code1 as u8 as char)[0],
_ => mappings::char_to_hid_code_us101(code1 as u8 as char)[0],
});
}
if code2 != 0 {
cu.kbd_tx_queue.borrow_mut().push_back(match native_map {
KeyMap::Dvorak => mappings::char_to_hid_code_dvorak(code2 as u8 as char)[0],
_ => mappings::char_to_hid_code_us101(code2 as u8 as char)[0],
});
}
let auto_up = if autoup == 1 { true } else { false };
// kbd_tx_queue borrow_mut() should be out of scope before the IRQ is fired
cu.sw_irq(UsbIrqReq::KbdTx);
tt.sleep_ms(autotype_delay_ms).ok();
if auto_up {
{
// ensure borrow_mut() is scoped out before IRQ is fired
cu.kbd_tx_queue.borrow_mut().push_back(Keyboard::NoEventIndicated);
}
cu.sw_irq(UsbIrqReq::KbdTx);
tt.sleep_ms(autotype_delay_ms).ok();
}
xous::return_scalar(msg.sender, 0).unwrap();
}),
Opcode::SendString => {
let mut buffer =
unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) };
let mut usb_send = buffer.to_original::<api::UsbString, _>().unwrap();
let mut sent = 0;
// check keymap on every call because we may need to toggle this for e.g. plugging
// into a new host with a different map
let native_map = native_kbd.get_keymap().unwrap();
for ch in usb_send.s.as_str().chars() {
// ASSUME: user's keyboard type matches the preference on their Precursor device.
let codes = match native_map {
KeyMap::Dvorak => mappings::char_to_hid_code_dvorak(ch),
_ => mappings::char_to_hid_code_us101(ch),
};
for code in codes {
cu.kbd_tx_queue.borrow_mut().push_back(code);
}
// key down
cu.sw_irq(UsbIrqReq::KbdTx);
tt.sleep_ms(autotype_delay_ms).ok();
// key up
{
// ensure that borrow_mut() is scoped out before IRQ is fired
cu.kbd_tx_queue.borrow_mut().push_back(Keyboard::NoEventIndicated);
}
cu.sw_irq(UsbIrqReq::KbdTx);
tt.sleep_ms(autotype_delay_ms).ok();
sent += 1;
}
usb_send.sent = Some(sent as _);
buffer.replace(usb_send).unwrap();
}
Opcode::RegisterUsbObserver => {
let buffer = unsafe { Buffer::from_memory_message(msg.body.memory_message().unwrap()) };
let ur = buffer.as_flat::<UsbListenerRegistration, _>().unwrap();
if observer_conn.is_none() {
match xns.request_connection_blocking(ur.server_name.as_str()) {
Ok(cid) => {
observer_conn = Some(cid);
observer_op = Some(<u32 as From<u32>>::from(ur.listener_op_id.into()) as usize);
}
Err(e) => {
log::error!("couldn't connect to observer: {:?}", e);
observer_conn = None;
observer_op = None;
}
}
}
}
Opcode::SetAutotypeRate => msg_scalar_unpack!(msg, rate, _, _, _, {
// limit rate to 0.5s delay. Even then, this will probably cause repeated characters because
// it also adjusts keydown delays
let checked_rate = if rate > 500 { 500 } else { rate };
// there is no limit on the minimum rate. good luck if you set it to 0!
autotype_delay_ms = checked_rate;
}),
Opcode::SetLogLevel => msg_scalar_unpack!(msg, level_code, _, _, _, {
let level = LogLevel::try_from(level_code).unwrap_or(LogLevel::Info);
match level {
LogLevel::Trace => log::set_max_level(log::LevelFilter::Trace),
LogLevel::Info => log::set_max_level(log::LevelFilter::Info),
LogLevel::Debug => log::set_max_level(log::LevelFilter::Debug),
LogLevel::Warn => log::set_max_level(log::LevelFilter::Warn),
LogLevel::Err => log::set_max_level(log::LevelFilter::Error),
}
}),
Opcode::InvalidCall => {
log::warn!("Illegal opcode received {:?}", msg);
}
Opcode::Quit => {
log::warn!("Quit received, goodbye world!");
break;
}
_ => {
unimplemented!(
"Opcode {:?} not implemented for this version of the stack: {:?}",
opcode,
msg
);
}
}
}
// clean up our program
log::trace!("main loop exit, destroying servers");
xns.unregister_server(usbdev_sid).unwrap();
xous::destroy_server(usbdev_sid).unwrap();
log::trace!("quitting");
xous::terminate_process(0)
}

View file

@ -0,0 +1,298 @@
use core::num::NonZeroU8;
use core::sync::atomic::AtomicUsize;
use std::collections::VecDeque;
use std::sync::Arc;
use num_traits::*;
use xous::{msg_blocking_scalar_unpack, msg_scalar_unpack};
use xous_ipc::Buffer;
use xous_semver::SemVer;
use xous_usb_hid::device::fido::RawFidoReport;
use crate::*;
pub(crate) fn main_hosted() -> ! {
log_server::init_wait().unwrap();
log::set_max_level(log::LevelFilter::Info);
log::info!("my PID is {}", xous::process::id());
let xns = xous_names::XousNames::new().unwrap();
let usbdev_sid = xns.register_name(api::SERVER_NAME_USB_DEVICE, None).expect("can't register server");
log::trace!("registered with NS -- {:?}", usbdev_sid);
let llio = llio::Llio::new(&xns);
let tt = ticktimer_server::Ticktimer::new().unwrap();
let minimum_ver = SemVer { maj: 0, min: 9, rev: 8, extra: 20, commit: None };
let soc_ver = llio.soc_gitrev().unwrap();
if soc_ver < minimum_ver {
if soc_ver.min != 0 {
// don't show during hosted mode, which reports 0.0.0+0
tt.sleep_ms(1500).ok(); // wait for some system boot to happen before popping up the modal
let modals = modals::Modals::new(&xns).unwrap();
modals.show_notification(
&format!("SoC version >= 0.9.8+20 required for USB HID. Detected rev: {}. Refusing to start USB driver.",
soc_ver.to_string()
),
None
).unwrap();
}
let mut fido_listener: Option<xous::MessageEnvelope> = None;
loop {
let msg = xous::receive_message(usbdev_sid).unwrap();
match FromPrimitive::from_usize(msg.body.id()) {
Some(Opcode::DebugUsbOp) => {
msg_blocking_scalar_unpack!(msg, _update_req, _new_state, _, _, {
xous::return_scalar2(msg.sender, 0, 1).expect("couldn't return status");
})
}
Some(Opcode::U2fRxDeferred) => {
// block any rx requests forever
fido_listener = Some(msg);
}
Some(Opcode::IsSocCompatible) => msg_blocking_scalar_unpack!(msg, _, _, _, _, {
xous::return_scalar(msg.sender, 0).expect("couldn't return compatibility status")
}),
Some(Opcode::Quit) => {
break;
}
_ => {
log::warn!("SoC not compatible with HID, ignoring USB message: {:?}", msg);
// make it so blocking scalars don't block
if let xous::Message::BlockingScalar(xous::ScalarMessage {
id: _,
arg1: _,
arg2: _,
arg3: _,
arg4: _,
}) = msg.body
{
log::warn!("Returning bogus result");
xous::return_scalar(msg.sender, 0).unwrap();
}
}
}
}
log::info!("consuming listener: {:?}", fido_listener);
}
let view = Arc::new(AtomicUsize::new(0));
let usbdev = SpinalUsbDevice::new(usbdev_sid, view.clone());
let mut usbmgmt = usbdev.get_iface();
// register a suspend/resume listener
let cid = xous::connect(usbdev_sid).expect("couldn't create suspend callback connection");
let mut susres = susres::Susres::new(None, &xns, api::Opcode::SuspendResume as u32, cid)
.expect("couldn't create suspend/resume object");
let mut fido_listener: Option<xous::MessageEnvelope> = None;
// under the theory that PIDs are unforgeable. TODO: check that PIDs are unforgeable.
// also if someone commandeers a process, all bets are off within that process (this is a general
// statement)
let mut fido_listener_pid: Option<NonZeroU8> = None;
let mut fido_rx_queue = VecDeque::<[u8; 64]>::new();
let mut lockstatus_force_update = true; // some state to track if we've been through a susupend/resume, to help out the status thread with its UX update after a restart-from-cold
loop {
let mut msg = xous::receive_message(usbdev_sid).unwrap();
let opcode: Option<Opcode> = FromPrimitive::from_usize(msg.body.id());
log::debug!("{:?}", opcode);
match opcode {
#[cfg(feature = "mass-storage")]
Some(Opcode::SetBlockDevice) => {
log::info!("ignoring SetBlockDevice in hosted mode");
}
#[cfg(feature = "mass-storage")]
Some(Opcode::SetBlockDeviceSID) => {
log::info!("ignoring SetBlockDeviceSID in hosted mode");
}
#[cfg(feature = "mass-storage")]
Some(Opcode::ResetBlockDevice) => {
log::info!("ignoring ResetBlockDevice in hosted mode");
}
Some(Opcode::SuspendResume) => msg_scalar_unpack!(msg, token, _, _, _, {
usbmgmt.xous_suspend();
susres.suspend_until_resume(token).expect("couldn't execute suspend/resume");
// resume1 + reset brings us to an initialized state
usbmgmt.xous_resume1();
// resume2 brings us to our last application state
usbmgmt.xous_resume2();
lockstatus_force_update = true; // notify the status bar that yes, it does need to redraw the lock status, even if the value hasn't changed since the last read
}),
Some(Opcode::IsSocCompatible) => msg_blocking_scalar_unpack!(msg, _, _, _, _, {
xous::return_scalar(msg.sender, 1).expect("couldn't return compatibility status")
}),
Some(Opcode::U2fRxDeferred) => {
if fido_listener_pid.is_none() {
fido_listener_pid = msg.sender.pid();
}
if fido_listener.is_some() {
log::error!(
"Double-listener request detected. There should only ever by one registered listener at a time."
);
log::error!(
"This will cause an upstream server to misbehave, but not panicing so the problem can be debugged."
);
// the receiver will get a response with the `code` field still in the `RxWait` state to
// indicate the problem
}
if fido_listener_pid == msg.sender.pid() {
// preferentially pull from the rx queue if it has elements
if let Some(data) = fido_rx_queue.pop_front() {
log::debug!(
"no deferral: ret queued data: {:?} queue len: {}",
&data[..8],
fido_rx_queue.len() + 1
);
let mut response = unsafe {
Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap())
};
let mut buf = response.to_original::<U2fMsgIpc, _>().unwrap();
assert_eq!(buf.code, U2fCode::RxWait, "Expected U2fcode::RxWait in wrapper");
buf.data.copy_from_slice(&data);
buf.code = U2fCode::RxAck;
response.replace(buf).unwrap();
} else {
log::trace!("registering deferred listener");
fido_listener = Some(msg);
}
} else {
log::warn!(
"U2F interface capability is locked on first use; additional servers are ignored: {:?}",
msg.sender
);
let mut buffer =
unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) };
let mut u2f_ipc = buffer.to_original::<U2fMsgIpc, _>().unwrap();
u2f_ipc.code = U2fCode::Denied;
buffer.replace(u2f_ipc).unwrap();
}
}
Some(Opcode::U2fRxTimeout) => {
if let Some(mut listener) = fido_listener.take() {
let mut response = unsafe {
Buffer::from_memory_message_mut(listener.body.memory_message_mut().unwrap())
};
let mut buf = response.to_original::<U2fMsgIpc, _>().unwrap();
assert_eq!(buf.code, U2fCode::RxWait, "Expected U2fcode::RxWait in wrapper");
buf.code = U2fCode::RxTimeout;
response.replace(buf).unwrap();
}
}
Some(Opcode::U2fTx) => {
if fido_listener_pid.is_none() {
fido_listener_pid = msg.sender.pid();
}
let mut buffer =
unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) };
let mut u2f_ipc = buffer.to_original::<U2fMsgIpc, _>().unwrap();
if fido_listener_pid == msg.sender.pid() {
let mut u2f_msg = RawFidoReport::default();
assert_eq!(u2f_ipc.code, U2fCode::Tx, "Expected U2fCode::Tx in wrapper");
u2f_msg.packet.copy_from_slice(&u2f_ipc.data);
log::debug!("sent U2F packet {:x?}", u2f_ipc.data);
u2f_ipc.code = U2fCode::TxAck;
} else {
u2f_ipc.code = U2fCode::Denied;
}
buffer.replace(u2f_ipc).unwrap();
}
Some(Opcode::UsbIrqHandler) => {}
Some(Opcode::SwitchCores) => msg_blocking_scalar_unpack!(msg, core, _, _, _, {
if core == 1 {
log::info!("Connecting USB device core; disconnecting debug USB core");
usbmgmt.connect_device_core(true);
} else {
log::info!("Connecting debug core; disconnecting USB device core");
usbmgmt.connect_device_core(false);
}
xous::return_scalar(msg.sender, 0).unwrap();
}),
Some(Opcode::EnsureCore) => msg_blocking_scalar_unpack!(msg, core, _, _, _, {
if core == 1 {
if !usbmgmt.is_device_connected() {
log::info!("Connecting USB device core; disconnecting debug USB core");
usbmgmt.connect_device_core(true);
}
} else {
if usbmgmt.is_device_connected() {
log::info!("Connecting debug core; disconnecting USB device core");
usbmgmt.connect_device_core(false);
}
}
xous::return_scalar(msg.sender, 0).unwrap();
}),
Some(Opcode::WhichCore) => msg_blocking_scalar_unpack!(msg, _, _, _, _, {
if usbmgmt.is_device_connected() {
xous::return_scalar(msg.sender, 1).unwrap();
} else {
xous::return_scalar(msg.sender, 0).unwrap();
}
}),
Some(Opcode::RestrictDebugAccess) => msg_scalar_unpack!(msg, restrict, _, _, _, {
if restrict == 0 {
usbmgmt.disable_debug(false);
} else {
usbmgmt.disable_debug(true);
}
}),
Some(Opcode::IsRestricted) => msg_blocking_scalar_unpack!(msg, _, _, _, _, {
if usbmgmt.get_disable_debug() {
xous::return_scalar(msg.sender, 1).unwrap();
} else {
xous::return_scalar(msg.sender, 0).unwrap();
}
}),
Some(Opcode::DebugUsbOp) => msg_blocking_scalar_unpack!(msg, update_req, new_state, _, _, {
if update_req != 0 {
// if new_state is true (not 0), then try to lock the USB port
// if false, try to unlock the USB port
if new_state != 0 {
usbmgmt.disable_debug(true);
} else {
usbmgmt.disable_debug(false);
}
}
// at this point, *read back* the new state -- don't assume it "took". The readback is always
// based on a real hardware value and not the requested value. for now, always
// false.
let is_locked = if usbmgmt.get_disable_debug() { 1 } else { 0 };
// this is a performance optimization. we could always redraw the status, but, instead we only
// redraw when the status has changed. However, there is an edge case: on a
// resume from suspend, the status needs a redraw, even if nothing has
// changed. Thus, we have this separate boolean we send back to force an update in the
// case that we have just come out of a suspend.
let force_update = if lockstatus_force_update { 1 } else { 0 };
xous::return_scalar2(msg.sender, is_locked, force_update).expect("couldn't return status");
lockstatus_force_update = false;
}),
Some(Opcode::LinkStatus) => msg_blocking_scalar_unpack!(msg, _, _, _, _, {
xous::return_scalar(msg.sender, 0).unwrap();
}),
Some(Opcode::SendKeyCode) => {
xous::return_scalar(msg.sender, 1).unwrap();
}
Some(Opcode::SendString) => {
let mut buffer =
unsafe { Buffer::from_memory_message_mut(msg.body.memory_message_mut().unwrap()) };
let usb_send = buffer.to_original::<api::UsbString, _>().unwrap(); // suppress mut warning on hosted mode
buffer.replace(usb_send).unwrap();
}
Some(Opcode::GetLedState) => {
xous::return_scalar(msg.sender, 0).unwrap();
}
Some(Opcode::Quit) => {
log::warn!("Quit received, goodbye world!");
break;
}
_ => log::warn!("Opcode not supported: {:?}", msg),
}
}
// clean up our program
log::trace!("main loop exit, destroying servers");
xns.unregister_server(usbdev_sid).unwrap();
xous::destroy_server(usbdev_sid).unwrap();
log::trace!("quitting");
xous::terminate_process(0)
}

View file

@ -0,0 +1,229 @@
#![allow(unused_imports)]
pub use xous_usb_hid::page::Keyboard as UsbKeyCode;
#[cfg(any(feature = "precursor", feature = "renode", feature = "cramium-soc"))]
#[rustfmt::skip]
pub fn char_to_hid_code_us101(key: char) -> Vec<UsbKeyCode> {
let mut code = vec![];
match key {
'a' => code.push(UsbKeyCode::A),
'b' => code.push(UsbKeyCode::B),
'c' => code.push(UsbKeyCode::C),
'd' => code.push(UsbKeyCode::D),
'e' => code.push(UsbKeyCode::E),
'f' => code.push(UsbKeyCode::F),
'g' => code.push(UsbKeyCode::G),
'h' => code.push(UsbKeyCode::H),
'i' => code.push(UsbKeyCode::I),
'j' => code.push(UsbKeyCode::J),
'k' => code.push(UsbKeyCode::K),
'l' => code.push(UsbKeyCode::L),
'm' => code.push(UsbKeyCode::M),
'n' => code.push(UsbKeyCode::N),
'o' => code.push(UsbKeyCode::O),
'p' => code.push(UsbKeyCode::P),
'q' => code.push(UsbKeyCode::Q),
'r' => code.push(UsbKeyCode::R),
's' => code.push(UsbKeyCode::S),
't' => code.push(UsbKeyCode::T),
'u' => code.push(UsbKeyCode::U),
'v' => code.push(UsbKeyCode::V),
'w' => code.push(UsbKeyCode::W),
'x' => code.push(UsbKeyCode::X),
'y' => code.push(UsbKeyCode::Y),
'z' => code.push(UsbKeyCode::Z),
'A' => {code.push(UsbKeyCode::A); code.push(UsbKeyCode::LeftShift)},
'B' => {code.push(UsbKeyCode::B); code.push(UsbKeyCode::LeftShift)},
'C' => {code.push(UsbKeyCode::C); code.push(UsbKeyCode::LeftShift)},
'D' => {code.push(UsbKeyCode::D); code.push(UsbKeyCode::LeftShift)},
'E' => {code.push(UsbKeyCode::E); code.push(UsbKeyCode::LeftShift)},
'F' => {code.push(UsbKeyCode::F); code.push(UsbKeyCode::LeftShift)},
'G' => {code.push(UsbKeyCode::G); code.push(UsbKeyCode::LeftShift)},
'H' => {code.push(UsbKeyCode::H); code.push(UsbKeyCode::LeftShift)},
'I' => {code.push(UsbKeyCode::I); code.push(UsbKeyCode::LeftShift)},
'J' => {code.push(UsbKeyCode::J); code.push(UsbKeyCode::LeftShift)},
'K' => {code.push(UsbKeyCode::K); code.push(UsbKeyCode::LeftShift)},
'L' => {code.push(UsbKeyCode::L); code.push(UsbKeyCode::LeftShift)},
'M' => {code.push(UsbKeyCode::M); code.push(UsbKeyCode::LeftShift)},
'N' => {code.push(UsbKeyCode::N); code.push(UsbKeyCode::LeftShift)},
'O' => {code.push(UsbKeyCode::O); code.push(UsbKeyCode::LeftShift)},
'P' => {code.push(UsbKeyCode::P); code.push(UsbKeyCode::LeftShift)},
'Q' => {code.push(UsbKeyCode::Q); code.push(UsbKeyCode::LeftShift)},
'R' => {code.push(UsbKeyCode::R); code.push(UsbKeyCode::LeftShift)},
'S' => {code.push(UsbKeyCode::S); code.push(UsbKeyCode::LeftShift)},
'T' => {code.push(UsbKeyCode::T); code.push(UsbKeyCode::LeftShift)},
'U' => {code.push(UsbKeyCode::U); code.push(UsbKeyCode::LeftShift)},
'V' => {code.push(UsbKeyCode::V); code.push(UsbKeyCode::LeftShift)},
'W' => {code.push(UsbKeyCode::W); code.push(UsbKeyCode::LeftShift)},
'X' => {code.push(UsbKeyCode::X); code.push(UsbKeyCode::LeftShift)},
'Y' => {code.push(UsbKeyCode::Y); code.push(UsbKeyCode::LeftShift)},
'Z' => {code.push(UsbKeyCode::Z); code.push(UsbKeyCode::LeftShift)},
'0' => code.push(UsbKeyCode::Keyboard0),
'1' => code.push(UsbKeyCode::Keyboard1),
'2' => code.push(UsbKeyCode::Keyboard2),
'3' => code.push(UsbKeyCode::Keyboard3),
'4' => code.push(UsbKeyCode::Keyboard4),
'5' => code.push(UsbKeyCode::Keyboard5),
'6' => code.push(UsbKeyCode::Keyboard6),
'7' => code.push(UsbKeyCode::Keyboard7),
'8' => code.push(UsbKeyCode::Keyboard8),
'9' => code.push(UsbKeyCode::Keyboard9),
'!' => {code.push(UsbKeyCode::Keyboard1); code.push(UsbKeyCode::LeftShift)},
'@' => {code.push(UsbKeyCode::Keyboard2); code.push(UsbKeyCode::LeftShift)},
'#' => {code.push(UsbKeyCode::Keyboard3); code.push(UsbKeyCode::LeftShift)},
'$' => {code.push(UsbKeyCode::Keyboard4); code.push(UsbKeyCode::LeftShift)},
'%' => {code.push(UsbKeyCode::Keyboard5); code.push(UsbKeyCode::LeftShift)},
'^' => {code.push(UsbKeyCode::Keyboard6); code.push(UsbKeyCode::LeftShift)},
'&' => {code.push(UsbKeyCode::Keyboard7); code.push(UsbKeyCode::LeftShift)},
'*' => {code.push(UsbKeyCode::Keyboard8); code.push(UsbKeyCode::LeftShift)},
'(' => {code.push(UsbKeyCode::Keyboard9); code.push(UsbKeyCode::LeftShift)},
')' => {code.push(UsbKeyCode::Keyboard0); code.push(UsbKeyCode::LeftShift)},
'-' => code.push(UsbKeyCode::Minus),
'_' => {code.push(UsbKeyCode::Minus); code.push(UsbKeyCode::LeftShift)},
'[' => code.push(UsbKeyCode::LeftBrace),
'{' => {code.push(UsbKeyCode::LeftBrace); code.push(UsbKeyCode::LeftShift)},
']' => code.push(UsbKeyCode::RightBrace),
'}' => {code.push(UsbKeyCode::RightBrace); code.push(UsbKeyCode::LeftShift)},
'/' => code.push(UsbKeyCode::ForwardSlash),
'?' => {code.push(UsbKeyCode::ForwardSlash); code.push(UsbKeyCode::LeftShift)},
'\\' => code.push(UsbKeyCode::Backslash),
'|' => {code.push(UsbKeyCode::Backslash); code.push(UsbKeyCode::LeftShift)},
'=' => code.push(UsbKeyCode::Equal),
'+' => {code.push(UsbKeyCode::Equal); code.push(UsbKeyCode::LeftShift)},
'\'' => code.push(UsbKeyCode::Apostrophe),
'"' => {code.push(UsbKeyCode::Apostrophe); code.push(UsbKeyCode::LeftShift)},
';' => {code.push(UsbKeyCode::Semicolon)},
':' => {code.push(UsbKeyCode::Semicolon); code.push(UsbKeyCode::LeftShift)},
'`' => {code.push(UsbKeyCode::Grave)},
'~' => {code.push(UsbKeyCode::Grave); code.push(UsbKeyCode::LeftShift)},
'←' => code.push(UsbKeyCode::LeftArrow),
'→' => code.push(UsbKeyCode::RightArrow),
'↑' => code.push(UsbKeyCode::UpArrow),
'↓' => code.push(UsbKeyCode::DownArrow),
',' => code.push(UsbKeyCode::Comma),
'<' => {code.push(UsbKeyCode::Comma); code.push(UsbKeyCode::LeftShift)},
'.' => code.push(UsbKeyCode::Dot),
'>' => {code.push(UsbKeyCode::Dot); code.push(UsbKeyCode::LeftShift)},
'\u{000d}' => {}, // ignore CR
'\u{000a}' => code.push(UsbKeyCode::ReturnEnter), // turn LF ('\n') into enter
' ' => {code.push(UsbKeyCode::Space); },
'\u{0008}' => code.push(UsbKeyCode::DeleteBackspace),
_ => log::warn!("Ignoring unhandled character: {}", key),
};
code
}
/// auto-generated using tools/kbd_layout.py + `usb kbdtest` on device for dvorak layout on US101
#[cfg(any(feature = "precursor", feature = "renode", feature = "cramium-soc"))]
#[rustfmt::skip]
pub fn char_to_hid_code_dvorak(key: char) -> Vec<UsbKeyCode> {
let mut code = vec![];
match key {
' ' => {code.push(UsbKeyCode::Space); },
'!' => {code.push(UsbKeyCode::Keyboard1); code.push(UsbKeyCode::LeftShift); },
'"' => {code.push(UsbKeyCode::Q); code.push(UsbKeyCode::LeftShift); },
'#' => {code.push(UsbKeyCode::Keyboard3); code.push(UsbKeyCode::LeftShift); },
'$' => {code.push(UsbKeyCode::Keyboard4); code.push(UsbKeyCode::LeftShift); },
'%' => {code.push(UsbKeyCode::Keyboard5); code.push(UsbKeyCode::LeftShift); },
'&' => {code.push(UsbKeyCode::Keyboard7); code.push(UsbKeyCode::LeftShift); },
'\'' => {code.push(UsbKeyCode::Q); },
'(' => {code.push(UsbKeyCode::Keyboard9); code.push(UsbKeyCode::LeftShift); },
')' => {code.push(UsbKeyCode::Keyboard0); code.push(UsbKeyCode::LeftShift); },
'*' => {code.push(UsbKeyCode::Keyboard8); code.push(UsbKeyCode::LeftShift); },
'+' => {code.push(UsbKeyCode::RightBrace); code.push(UsbKeyCode::LeftShift); },
',' => {code.push(UsbKeyCode::W); },
'-' => {code.push(UsbKeyCode::Apostrophe); },
'.' => {code.push(UsbKeyCode::E); },
'/' => {code.push(UsbKeyCode::LeftBrace); },
'0' => {code.push(UsbKeyCode::Keyboard0); },
'1' => {code.push(UsbKeyCode::Keyboard1); },
'2' => {code.push(UsbKeyCode::Keyboard2); },
'3' => {code.push(UsbKeyCode::Keyboard3); },
'4' => {code.push(UsbKeyCode::Keyboard4); },
'5' => {code.push(UsbKeyCode::Keyboard5); },
'6' => {code.push(UsbKeyCode::Keyboard6); },
'7' => {code.push(UsbKeyCode::Keyboard7); },
'8' => {code.push(UsbKeyCode::Keyboard8); },
'9' => {code.push(UsbKeyCode::Keyboard9); },
':' => {code.push(UsbKeyCode::Z); code.push(UsbKeyCode::LeftShift); },
';' => {code.push(UsbKeyCode::Z); },
'<' => {code.push(UsbKeyCode::W); code.push(UsbKeyCode::LeftShift); },
'=' => {code.push(UsbKeyCode::RightBrace); },
'>' => {code.push(UsbKeyCode::E); code.push(UsbKeyCode::LeftShift); },
'?' => {code.push(UsbKeyCode::LeftBrace); code.push(UsbKeyCode::LeftShift); },
'@' => {code.push(UsbKeyCode::Keyboard2); code.push(UsbKeyCode::LeftShift); },
'A' => {code.push(UsbKeyCode::A); code.push(UsbKeyCode::LeftShift); },
'B' => {code.push(UsbKeyCode::N); code.push(UsbKeyCode::LeftShift); },
'C' => {code.push(UsbKeyCode::I); code.push(UsbKeyCode::LeftShift); },
'D' => {code.push(UsbKeyCode::H); code.push(UsbKeyCode::LeftShift); },
'E' => {code.push(UsbKeyCode::D); code.push(UsbKeyCode::LeftShift); },
'F' => {code.push(UsbKeyCode::Y); code.push(UsbKeyCode::LeftShift); },
'G' => {code.push(UsbKeyCode::U); code.push(UsbKeyCode::LeftShift); },
'H' => {code.push(UsbKeyCode::J); code.push(UsbKeyCode::LeftShift); },
'I' => {code.push(UsbKeyCode::G); code.push(UsbKeyCode::LeftShift); },
'J' => {code.push(UsbKeyCode::C); code.push(UsbKeyCode::LeftShift); },
'K' => {code.push(UsbKeyCode::V); code.push(UsbKeyCode::LeftShift); },
'L' => {code.push(UsbKeyCode::P); code.push(UsbKeyCode::LeftShift); },
'M' => {code.push(UsbKeyCode::M); code.push(UsbKeyCode::LeftShift); },
'N' => {code.push(UsbKeyCode::L); code.push(UsbKeyCode::LeftShift); },
'O' => {code.push(UsbKeyCode::S); code.push(UsbKeyCode::LeftShift); },
'P' => {code.push(UsbKeyCode::R); code.push(UsbKeyCode::LeftShift); },
'Q' => {code.push(UsbKeyCode::X); code.push(UsbKeyCode::LeftShift); },
'R' => {code.push(UsbKeyCode::O); code.push(UsbKeyCode::LeftShift); },
'S' => {code.push(UsbKeyCode::Semicolon); code.push(UsbKeyCode::LeftShift); },
'T' => {code.push(UsbKeyCode::K); code.push(UsbKeyCode::LeftShift); },
'U' => {code.push(UsbKeyCode::F); code.push(UsbKeyCode::LeftShift); },
'V' => {code.push(UsbKeyCode::Dot); code.push(UsbKeyCode::LeftShift); },
'W' => {code.push(UsbKeyCode::Comma); code.push(UsbKeyCode::LeftShift); },
'X' => {code.push(UsbKeyCode::B); code.push(UsbKeyCode::LeftShift); },
'Y' => {code.push(UsbKeyCode::T); code.push(UsbKeyCode::LeftShift); },
'Z' => {code.push(UsbKeyCode::ForwardSlash); code.push(UsbKeyCode::LeftShift); },
'[' => {code.push(UsbKeyCode::Minus); },
'\\' => {code.push(UsbKeyCode::Backslash); },
']' => {code.push(UsbKeyCode::Equal); },
'^' => {code.push(UsbKeyCode::Keyboard6); code.push(UsbKeyCode::LeftShift); },
'_' => {code.push(UsbKeyCode::Apostrophe); code.push(UsbKeyCode::LeftShift); },
'`' => {code.push(UsbKeyCode::Grave); },
'a' => {code.push(UsbKeyCode::A); },
'b' => {code.push(UsbKeyCode::N); },
'c' => {code.push(UsbKeyCode::I); },
'd' => {code.push(UsbKeyCode::H); },
'e' => {code.push(UsbKeyCode::D); },
'f' => {code.push(UsbKeyCode::Y); },
'g' => {code.push(UsbKeyCode::U); },
'h' => {code.push(UsbKeyCode::J); },
'i' => {code.push(UsbKeyCode::G); },
'j' => {code.push(UsbKeyCode::C); },
'k' => {code.push(UsbKeyCode::V); },
'l' => {code.push(UsbKeyCode::P); },
'm' => {code.push(UsbKeyCode::M); },
'n' => {code.push(UsbKeyCode::L); },
'o' => {code.push(UsbKeyCode::S); },
'p' => {code.push(UsbKeyCode::R); },
'q' => {code.push(UsbKeyCode::X); },
'r' => {code.push(UsbKeyCode::O); },
's' => {code.push(UsbKeyCode::Semicolon); },
't' => {code.push(UsbKeyCode::K); },
'u' => {code.push(UsbKeyCode::F); },
'v' => {code.push(UsbKeyCode::Dot); },
'w' => {code.push(UsbKeyCode::Comma); },
'x' => {code.push(UsbKeyCode::B); },
'y' => {code.push(UsbKeyCode::T); },
'z' => {code.push(UsbKeyCode::ForwardSlash); },
'{' => {code.push(UsbKeyCode::Minus); code.push(UsbKeyCode::LeftShift); },
'|' => {code.push(UsbKeyCode::Backslash); code.push(UsbKeyCode::LeftShift); },
'}' => {code.push(UsbKeyCode::Equal); code.push(UsbKeyCode::LeftShift); },
'~' => {code.push(UsbKeyCode::Grave); code.push(UsbKeyCode::LeftShift); },
'\u{000d}' => {}, // ignore CR
'\u{000a}' => code.push(UsbKeyCode::ReturnEnter), // turn LF ('\n') into enter
'\u{0008}' => code.push(UsbKeyCode::DeleteBackspace),
_ => log::warn!("Ignoring unhandled character: {}", key),
};
code
}