diff --git a/services/usb-cramium/Cargo.toml b/services/usb-cramium/Cargo.toml index eb62a6640..faf145dd1 100644 --- a/services/usb-cramium/Cargo.toml +++ b/services/usb-cramium/Cargo.toml @@ -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"] diff --git a/services/usb-cramium/src/api.rs b/services/usb-cramium/src/api.rs index 10cbc4e0b..753704d04 100644 --- a/services/usb-cramium/src/api.rs +++ b/services/usb-cramium/src/api.rs @@ -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, diff --git a/services/usb-cramium/src/debug.rs b/services/usb-cramium/src/debug.rs new file mode 100644 index 000000000..f4dccbd37 --- /dev/null +++ b/services/usb-cramium/src/debug.rs @@ -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)+) + }); +} diff --git a/services/usb-cramium/src/hw.rs b/services/usb-cramium/src/hw.rs new file mode 100644 index 000000000..4af368170 --- /dev/null +++ b/services/usb-cramium/src/hw.rs @@ -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::, _>().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::, _>().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::, _>(); + // 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::, _>(); + 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, frunk_core::hlist::HNil>, + >, + >, + pub csr: AtomicCsr, + pub irq_csr: AtomicCsr, + pub fido_tx_queue: RefCell>, + pub kbd_tx_queue: RefCell>, + pub irq_req: Option, +} + +impl<'a> CramiumUsb<'a> { + pub fn new( + cid: xous::CID, + cw: CorigineWrapper, + usb_alloc: &'a UsbBusAllocator, + csr: AtomicCsr, + irq_csr: AtomicCsr, + 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); + } +} diff --git a/services/usb-cramium/src/main.rs b/services/usb-cramium/src/main.rs index e7a11a969..563279873 100644 --- a/services/usb-cramium/src/main.rs +++ b/services/usb-cramium/src/main.rs @@ -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 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 = None; + let mut fido_listener: Option = 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 = None; + let mut observer_op: Option = 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::().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::().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::().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::().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::().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::().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::().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::().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::().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(>::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) } diff --git a/services/usb-cramium/src/main_hosted.rs b/services/usb-cramium/src/main_hosted.rs new file mode 100644 index 000000000..17e6f4194 --- /dev/null +++ b/services/usb-cramium/src/main_hosted.rs @@ -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 = 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 = 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 = 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 = 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::().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::().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::().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::().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::().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) +} diff --git a/services/usb-cramium/src/mappings.rs b/services/usb-cramium/src/mappings.rs new file mode 100644 index 000000000..6a565948d --- /dev/null +++ b/services/usb-cramium/src/mappings.rs @@ -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 { + 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 { + 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 +}