diff --git a/local/recipes/drivers/virtio-inputd/source/Cargo.toml b/local/recipes/drivers/virtio-inputd/source/Cargo.toml index 3716e4aec1..76d7c02b0c 100644 --- a/local/recipes/drivers/virtio-inputd/source/Cargo.toml +++ b/local/recipes/drivers/virtio-inputd/source/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "virtio-inputd" -version = "0.2.0" +version = "0.2.3" edition = "2024" -description = "VirtIO input device driver for Red Bear OS (v6.0: writes Linux evdev events to /scheme/input/evdev). Handles QEMU virtio-input-host-pci, virtio-input-keyboard, virtio-input-mouse, virtio-input-tablet." +description = "virtio-input daemon v6.0 2026: reads virtio-input PCI events and writes Linux evdev events to /scheme/input/evdev" [[bin]] name = "virtio-inputd" diff --git a/local/recipes/drivers/virtio-inputd/source/src/main.rs b/local/recipes/drivers/virtio-inputd/source/src/main.rs index e2542b9ce3..38d3142d65 100644 --- a/local/recipes/drivers/virtio-inputd/source/src/main.rs +++ b/local/recipes/drivers/virtio-inputd/source/src/main.rs @@ -1,6 +1,6 @@ -//! virtio-inputd — VirtIO input device driver for Red Bear OS +//! virtio-inputd v6.0 — VirtIO input device driver for Red Bear OS //! -//! Handles the QEMU `virtio-input-*` paravirt input devices: +//! Drives QEMU `virtio-input-*` paravirt input devices: //! - `-device virtio-input-host-pci` (host passthrough) //! - `-device virtio-input-keyboard` //! - `-device virtio-input-mouse` @@ -8,27 +8,21 @@ //! //! ## Pipeline //! -//! 1. PCI probe for `vendor=0x1AF4 device=0x1052` (legacy virtio-input) or -//! `vendor=0x1AF4 device=0x1042+` (modern virtio 1.0, type=18). One daemon -//! per device — pcid-spawner launches us. -//! 2. Negotiate `VIRTIO_F_VERSION_1` (only feature we need). -//! 3. Set up one event virtqueue and pre-fill the available ring with 8 KiB -//! of DMA-allocated event buffers. -//! 4. Wait for `used_idx` to advance via IRQ. Drain used buffers, decode -//! virtio_input_event (type/code/value), translate to orbclient event, -//! write to inputd via ProducerHandle. -//! 5. Re-cycle drained buffers back to the avail ring and kick. +//! 1. PCI probe for modern virtio-input (vendor=0x1AF4, device=0x1042+). +//! pcid-spawner launches us with the PCI location as argument. +//! 2. Negotiate `VIRTIO_F_VERSION_1`. +//! 3. Set up one event virtqueue with 64 DMA-allocated event buffers. +//! 4. Drain used buffers, decode `VirtioInputEvent`, write Linux evdev events +//! to `/scheme/input/evdev` via `EvdevProducerHandle`. +//! 5. Recycle drained buffers to the avail ring and kick. //! -//! ## Event Translation +//! ## Wire format //! -//! virtio_input_event types map to orbclient events: -//! - EV_KEY (1) → KeyEvent (key press / release via value 0/1) -//! - EV_REL (2) → MouseRelativeEvent (dx, dy from REL_X, REL_Y) -//! - EV_SYN (0) → dropped (inputd multiplexes itself) -//! - EV_ABS / MSC / LED → dropped for now (Phase 5.2 expansion) -//! -//! Phase 5.2 (follow-up): add evdevd producer path in parallel with inputd. -//! See local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md §5. +//! virtio-input sends `VirtioInputEvent { type: u8, code: u8, value: u32 }`. +//! We translate to Linux evdev `EvdevEvent { event_type: u16, code: u16, value: i32 }`: +//! - virtio `type` (u8) → evdev `event_type` (u16) — direct map, EV_SYN/EV_KEY/EV_REL/EV_ABS match +//! - virtio `code` (u8) → evdev `code` (u16) — zero-extend +//! - virtio `value` (u32) → evdev `value` (i32) — sign-extend for ABS, cast for others #![forbid(unsafe_op_in_unsafe_fn)] @@ -39,9 +33,8 @@ use std::sync::atomic::{Ordering, fence}; use std::thread; use std::time::Duration; -use inputd::ProducerHandle; +use inputd::{EvdevProducerHandle, EV_ABS, EV_KEY, EV_REL, EV_SYN}; use log::{debug, error, info, warn}; -use orbclient::{Event, KeyEvent, MouseRelativeEvent, ScrollEvent}; use redox_driver_sys::dma::DmaBuffer; use redox_driver_sys::pcid_client::PcidClient; use redox_driver_sys::pci::{PciDevice, PciDeviceInfo, PCI_CAP_ID_VNDR}; @@ -50,117 +43,12 @@ mod virtio; use virtio::{ QueueConfig, VirtioInputEvent, VirtioModernPciTransport, VIRTIO_INPUT_EVENT_SIZE, VIRTIO_INPUT_CFG_EV_BITS, VIRTIO_INPUT_CFG_ABS_INFO, VIRTIO_INPUT_CFG_ID_NAME, - VIRTIO_INPUT_CFG_ID_SERIAL, VIRTIO_INPUT_CFG_ID_DEVIDS, VIRTIO_INPUT_CFG_PROP_BITS, + VIRTIO_INPUT_CFG_ID_SERIAL, VIRTIO_F_VERSION_1, }; -// Linux input-event-codes.h (subset we care about) -const EV_SYN: u16 = 0x00; -const EV_KEY: u16 = 0x01; -const EV_REL: u16 = 0x02; -const EV_ABS: u16 = 0x03; -const EV_MSC: u16 = 0x04; -const EV_SW: u16 = 0x05; -const EV_LED: u16 = 0x11; -const EV_SND: u16 = 0x12; -const EV_REP: u16 = 0x14; - -const SYN_REPORT: u16 = 0; -const SYN_DROPPED: u16 = 3; - -// REL_* -const REL_X: u16 = 0x00; -const REL_Y: u16 = 0x01; -const REL_WHEEL: u16 = 0x08; -const REL_HWHEEL: u16 = 0x06; - -// KEY_* -const KEY_ESC: u16 = 1; -const KEY_1: u16 = 2; -const KEY_0: u16 = 11; -const KEY_Q: u16 = 16; -const KEY_P: u16 = 25; -const KEY_A: u16 = 30; -const KEY_L: u16 = 38; -const KEY_Z: u16 = 44; -const KEY_M: u16 = 50; -const KEY_F1: u16 = 59; -const KEY_F12: u16 = 68; -const KEY_LEFTCTRL: u16 = 29; -const KEY_LEFTALT: u16 = 56; -const KEY_LEFTSHIFT: u16 = 42; -const KEY_RIGHTSHIFT: u16 = 54; -const KEY_LEFTMETA: u16 = 91; -const KEY_RIGHTMETA: u16 = 92; -const KEY_KPENTER: u16 = 96; -const KEY_KPSLASH: u16 = 95; -const KEY_SPACE: u16 = 57; -const KEY_CAPSLOCK: u16 = 58; -const KEY_NUMLOCK: u16 = 69; -const KEY_SCROLLLOCK: u16 = 70; -const KEY_MINUS: u16 = 12; -const KEY_EQUAL: u16 = 13; -const KEY_TAB: u16 = 15; -const KEY_ENTER: u16 = 28; -const KEY_SEMICOLON: u16 = 39; -const KEY_APOSTROPHE: u16 = 40; -const KEY_GRAVE: u16 = 41; -const KEY_BACKSLASH: u16 = 43; -const KEY_COMMA: u16 = 51; -const KEY_DOT: u16 = 52; -const KEY_SLASH: u16 = 53; -const KEY_LEFTBRACE: u16 = 26; -const KEY_RIGHTBRACE: u16 = 27; -const KEY_BACKSPACE: u16 = 14; -const KEY_102ND: u16 = 86; -const KEY_RO: u16 = 89; -const KEY_KATAKANAHIRAGANA: u16 = 93; -const KEY_HENKAN: u16 = 92; -const KEY_MUHENKAN: u16 = 94; -const KEY_KPJPCOMMA: u16 = 95; -const KEY_KP7: u16 = 71; -const KEY_KP8: u16 = 72; -const KEY_KP9: u16 = 73; -const KEY_KPMINUS: u16 = 74; -const KEY_KP4: u16 = 75; -const KEY_KP5: u16 = 76; -const KEY_KP6: u16 = 77; -const KEY_KPPLUS: u16 = 78; -const KEY_KP1: u16 = 79; -const KEY_KP2: u16 = 80; -const KEY_KP3: u16 = 81; -const KEY_KP0: u16 = 82; -const KEY_KPDOT: u16 = 83; -const KEY_KPASTERISK: u16 = 55; -const KEY_KPEQUAL: u16 = 117; -const KEY_F2: u16 = 60; -const KEY_F3: u16 = 61; -const KEY_F4: u16 = 62; -const KEY_F5: u16 = 63; -const KEY_F6: u16 = 64; -const KEY_F7: u16 = 65; -const KEY_F8: u16 = 66; -const KEY_F9: u16 = 67; -const KEY_F10: u16 = 68; -const KEY_F11: u16 = 69; -const KEY_PRINT: u16 = 70; -const KEY_SCROLL: u16 = 70; -const KEY_PAUSE: u16 = 119; -const KEY_INSERT: u16 = 110; -const KEY_HOME: u16 = 102; -const KEY_PAGEUP: u16 = 104; -const KEY_DELETE: u16 = 111; -const KEY_END: u16 = 107; -const KEY_PAGEDOWN: u16 = 109; -const KEY_RIGHT: u16 = 106; -const KEY_LEFT: u16 = 105; -const KEY_DOWN: u16 = 108; -const KEY_UP: u16 = 103; - const VIRTQ_DESC_F_NEXT: u16 = 1; const VIRTQ_DESC_F_WRITE: u16 = 2; -const VIRTQ_DESC_F_AVAIL: u16 = 4; -const VIRTQ_DESC_F_USED: u16 = 8; #[derive(Debug)] pub enum DriverError { @@ -211,7 +99,7 @@ struct VirtqUsedElem { /// /// virtio-input only needs to receive event buffers from the device, so this /// implementation pre-allocates a ring of `size` buffers at startup. Each -/// buffer is exactly one `virtio_input_event` (8 bytes). Buffers are cycled +/// buffer is exactly one `VirtioInputEvent` (8 bytes). Buffers are cycled /// back to the avail ring after the device has consumed them. struct InputEventQueue { index: u16, @@ -272,10 +160,6 @@ impl InputEventQueue { for i in 0..self.size { self.push_avail(i); } - // The device reads avail_ring[avail_idx % size] to discover new buffers. - // After writing all `size` ring slots, we MUST publish the new - // avail_idx = size, or the device will not see any of them. - // See virtio spec §2.8.6 "Publishing the used ring". fence(Ordering::Release); self.write_avail_idx(self.size); } @@ -322,8 +206,6 @@ impl InputEventQueue { fence(Ordering::SeqCst); let used_idx = self.read_used_idx(); - // 64 is the maximum queue size we accept, so the stack array is - // always large enough for a single drain cycle. let mut drained_ids: [u16; 64] = [0u16; 64]; let mut drained_count: usize = 0; @@ -363,103 +245,50 @@ impl InputEventQueue { } } -/// Map a Linux evdev keycode to the closest character (US QWERTY layout). +/// Translate a virtio input event to evdev and write to the producer. /// -/// This is a very small subset — sufficient for QEMU virtio-input-keyboard -/// to produce useful KeyEvent::character values. Real evdev has a complex -/// keymap model; we accept the simplification that Phase 5.2 will replace -/// with the evdevd keymap bridge. -fn keycode_to_char(code: u16) -> char { - match code { - KEY_ESC => '\u{1B}', - KEY_1 => '1', - KEY_0 => '0', - KEY_Q => 'q', - KEY_P => 'p', - KEY_A => 'a', - KEY_L => 'l', - KEY_Z => 'z', - KEY_M => 'm', - KEY_MINUS => '-', - KEY_EQUAL => '=', - KEY_TAB => '\t', - KEY_SPACE => ' ', - KEY_LEFTBRACE => '[', - KEY_RIGHTBRACE => ']', - KEY_BACKSLASH => '\\', - KEY_SEMICOLON => ';', - KEY_APOSTROPHE => '\'', - KEY_GRAVE => '`', - KEY_COMMA => ',', - KEY_DOT => '.', - KEY_SLASH => '/', - KEY_ENTER => '\n', - KEY_BACKSPACE => '\u{08}', - KEY_KP0 => '0', - KEY_KP1 => '1', - KEY_KP2 => '2', - KEY_KP3 => '3', - KEY_KP4 => '4', - KEY_KP5 => '5', - KEY_KP6 => '6', - KEY_KP7 => '7', - KEY_KP8 => '8', - KEY_KP9 => '9', - KEY_KPMINUS => '-', - KEY_KPPLUS => '+', - KEY_KPDOT => '.', - KEY_KPASTERISK => '*', - KEY_KPSLASH => '/', - KEY_KPENTER => '\n', - KEY_KPEQUAL => '=', - _ => '\0', - } -} +/// virtio type (u8) → evdev event_type (u16): direct map, EV_SYN=0, EV_KEY=1, EV_REL=2, EV_ABS=3 +/// virtio code (u8) → evdev code (u16): zero-extend +/// virtio value (u32) → evdev value (i32): cast (ABS sign-extends via i32) +fn write_evdev_event(producer: &mut EvdevProducerHandle, ev: &VirtioInputEvent) { + let event_type = ev.event_type as u16; + let code = ev.code as u16; + let value = ev.value as i32; -/// Translate a virtio_input_event into one or more orbclient events. -/// -/// Multiple events are batched in the output vector so the caller can write -/// them all in one syscall. -fn translate_event(ev: &VirtioInputEvent) -> Vec { - match ev.event_type { - EV_SYN => Vec::new(), - EV_KEY => { - let pressed = ev.value != 0; - let character = keycode_to_char(ev.code); - vec![KeyEvent { character, scancode: ev.code as u8, pressed }.to_event()] - } - EV_REL => { - // REL_WHEEL: value is delta in 120ths of a notch; orbclient uses - // raw pixels. Clamp small positive/negative to +/-1. - match ev.code { - REL_X | REL_Y => { - vec![MouseRelativeEvent { - dx: if ev.code == REL_X { ev.value } else { 0 }, - dy: if ev.code == REL_Y { ev.value } else { 0 }, - } - .to_event()] - } - REL_WHEEL | REL_HWHEEL => { - let clicks = if ev.value == 0 { - 0 - } else if ev.value > 0 { - 1 - } else { - -1 - }; - vec![ScrollEvent { - x: if ev.code == REL_HWHEEL { clicks } else { 0 }, - y: if ev.code == REL_WHEEL { clicks } else { 0 }, - } - .to_event()] - } - _ => Vec::new(), + match event_type { + EV_SYN => { + if let Err(e) = producer.write_syn_report() { + warn!("virtio-inputd: write_syn_report failed: {e}"); } } - // EV_ABS / EV_MSC / EV_LED / EV_REP / EV_SND / EV_SW are not yet - // translated — Phase 5.2 expansion. For now they are dropped, which - // is acceptable for QEMU keyboard + mouse + basic tablet. - _ => Vec::new(), + EV_KEY => { + if let Err(e) = producer.write_key(code, value != 0) { + warn!("virtio-inputd: write_key failed: {e}"); + } + if let Err(e) = producer.write_syn_report() { + warn!("virtio-inputd: write_syn_report (after key) failed: {e}"); + } + } + EV_REL => { + if let Err(e) = producer.write_rel(code, value) { + warn!("virtio-inputd: write_rel failed: {e}"); + } + if let Err(e) = producer.write_syn_report() { + warn!("virtio-inputd: write_syn_report (after rel) failed: {e}"); + } + } + EV_ABS => { + if let Err(e) = producer.write_abs(code, value) { + warn!("virtio-inputd: write_abs failed: {e}"); + } + if let Err(e) = producer.write_syn_report() { + warn!("virtio-inputd: write_syn_report (after abs) failed: {e}"); + } + } + // EV_MSC, EV_LED, EV_SND, EV_REP, EV_SW — dropped; not used by virtio-input devices + _ => { + debug!("virtio-inputd: dropped evdev event type={event_type} code={code} value={value}"); + } } } @@ -492,11 +321,8 @@ fn virtio_input_probe(pci: &mut PciDevice) -> Result { let cap_id = pci.read_config_byte(offset as u64)?; let cap_next = pci.read_config_byte(offset as u64 + 1)?; if cap_id == PCI_CAP_ID_VNDR { - // cap.cfg_type is at offset 3 of the capability. let cfg_type = pci.read_config_byte(offset as u64 + 3)?; if cfg_type == 4 { - // cap.id is at offset 5 — for device_cfg, this is the - // virtio device type. let dev_type = pci.read_config_byte(offset as u64 + 5)?; if dev_type == 18 { return Ok(true); @@ -509,15 +335,12 @@ fn virtio_input_probe(pci: &mut PciDevice) -> Result { } fn run_device() -> Result<()> { - // Connect to pcid via the env var it provides. If absent, we cannot run. if PcidClient::connect_default().is_none() { return Err(DriverError::Initialization( "virtio-inputd: not launched by pcid-spawner (PCID_CLIENT_CHANNEL unset)".into(), )); } - // The pcid-spawner also passes the PCI location as a positional argument - // before PCID_CLIENT_CHANNEL. Format: "segment:bus:device.function". let args: Vec = env::args().skip(1).collect(); let loc_str = args.first().ok_or_else(|| { DriverError::Initialization( @@ -565,10 +388,6 @@ fn run_device() -> Result<()> { let mut transport = VirtioModernPciTransport::new(&pci_info, &mut pci_device)?; transport.initialize_device(VIRTIO_F_VERSION_1)?; - // Wrap remaining init in a closure so any error resets the device - // to a clean state. virtio 1.0 §2.1.6: a driver that fails to - // complete initialization MUST reset the device so a future - // attach (e.g. after driver-manager restart) starts cleanly. let init_result = (|| -> Result<()> { let num_queues = transport.num_queues(); if num_queues < 1 { @@ -606,11 +425,9 @@ fn run_device() -> Result<()> { if transport.config_read_size() != 0 { let _ = transport.config_read_string(serial_buf.len(), &mut serial_buf); } - let _ = std::str::from_utf8(&serial_buf[..]); // Probe EV bits to log a summary let mut ev_bits = [0u8; 16]; - let mut abs_count = 0u8; for ev_type in 0u8..16u8 { transport.config_write_select(VIRTIO_INPUT_CFG_EV_BITS, ev_type); let size = transport.config_read_size() as usize; @@ -622,8 +439,8 @@ fn run_device() -> Result<()> { debug!("virtio-inputd: device supports EV type {ev_type}"); } } - // Probe ABS bits to count absolute axes. absinfo is 5*u32=20 bytes; - // a non-zero size response indicates the axis is supported. + // Probe ABS bits to count absolute axes. + let mut abs_count = 0u8; for abs in 0u8..64u8 { transport.config_write_select(VIRTIO_INPUT_CFG_ABS_INFO, abs); if transport.config_read_size() >= 20 { @@ -638,12 +455,12 @@ fn run_device() -> Result<()> { device_name, event_qcfg.size, abs_count ); - // Open the inputd producer handle for event delivery. - let mut producer = match ProducerHandle::new() { + // Open the v6.0 evdev producer handle for event delivery. + let mut producer = match EvdevProducerHandle::new() { Ok(p) => p, Err(e) => { - warn!("virtio-inputd: failed to open /scheme/input/producer: {e} — events will be dropped"); - return Err(DriverError::Io(format!("inputd producer unavailable: {e}"))); + warn!("virtio-inputd: failed to open /scheme/input/evdev: {e} — events will be dropped"); + return Err(DriverError::Io(format!("evdev producer unavailable: {e}"))); } }; @@ -662,10 +479,9 @@ fn run_device() -> Result<()> { fn run_event_loop( transport: &mut VirtioModernPciTransport, event_queue: &mut InputEventQueue, - producer: &mut ProducerHandle, + producer: &mut EvdevProducerHandle, ) { let mut pending_events: Vec = Vec::with_capacity(64); - let mut translated: Vec = Vec::with_capacity(16); loop { if transport.device_in_error_state() { warn!("virtio-inputd: device entered FAILED or NEEDS_RESET state, exiting"); @@ -675,15 +491,8 @@ fn run_event_loop( event_queue.drain(&mut pending_events); if !pending_events.is_empty() { for ev in &pending_events { - translated.clear(); - translated.extend(translate_event(ev)); - for event in &translated { - if let Err(e) = producer.write_event(*event) { - warn!("virtio-inputd: write_event failed: {e}"); - } - } + write_evdev_event(producer, ev); } - pending_events.clear(); if let Err(e) = transport.notify_queue(event_queue.index, event_queue.notify_off) { warn!("virtio-inputd: notify_queue failed: {e}"); } @@ -704,4 +513,4 @@ fn main() { error!("virtio-inputd: fatal: {e:?}"); process::exit(1); } -} +} \ No newline at end of file