mirror of
https://github.com/saymrwulf/xous-core.git
synced 2026-11-15 08:30:02 +00:00
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:
parent
5d35dee4f6
commit
9a0bbbd2fb
7 changed files with 1377 additions and 3 deletions
|
|
@ -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"]
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
68
services/usb-cramium/src/debug.rs
Normal file
68
services/usb-cramium/src/debug.rs
Normal 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)+)
|
||||
});
|
||||
}
|
||||
287
services/usb-cramium/src/hw.rs
Normal file
287
services/usb-cramium/src/hw.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
298
services/usb-cramium/src/main_hosted.rs
Normal file
298
services/usb-cramium/src/main_hosted.rs
Normal 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)
|
||||
}
|
||||
229
services/usb-cramium/src/mappings.rs
Normal file
229
services/usb-cramium/src/mappings.rs
Normal 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
|
||||
}
|
||||
Loading…
Reference in a new issue