//! VirtIO input device protocol definitions and modern PCI transport. //! //! Reference: Linux 7.1 drivers/virtio/virtio_input.c //! Linux 7.1 include/uapi/linux/virtio_input.h //! //! virtio-input is a paravirt input device used by QEMU. QEMU options: //! -device virtio-input-host-pci (passthrough host input) //! -device virtio-input-keyboard //! -device virtio-input-mouse //! -device virtio-input-tablet //! //! The device uses a single event virtqueue (no status queue) and config-space //! introspection to advertise supported event types and absolute axis ranges. use log::{debug, info}; use redox_driver_sys::memory::{CacheType, MmioProt, MmioRegion}; use redox_driver_sys::pci::{PciDevice, PciDeviceInfo, PCI_CAP_ID_VNDR}; use crate::DriverError; // virtio 1.0 §2.1 — device status register bits pub const DEVICE_STATUS_RESET: u8 = 0x00; pub const DEVICE_STATUS_ACKNOWLEDGE: u8 = 0x01; pub const DEVICE_STATUS_DRIVER: u8 = 0x02; pub const DEVICE_STATUS_DRIVER_OK: u8 = 0x04; pub const DEVICE_STATUS_FEATURES_OK: u8 = 0x08; pub const DEVICE_STATUS_NEEDS_RESET: u8 = 0x40; pub const DEVICE_STATUS_FAILED: u8 = 0x80; use crate::Result; const VIRTIO_PCI_CAP_COMMON_CFG: u8 = 1; const VIRTIO_PCI_CAP_NOTIFY_CFG: u8 = 2; const VIRTIO_PCI_CAP_ISR_CFG: u8 = 3; const VIRTIO_PCI_CAP_DEVICE_CFG: u8 = 4; const COMMON_DEVICE_FEATURE_SELECT: usize = 0x00; const COMMON_DEVICE_FEATURE: usize = 0x04; const COMMON_DRIVER_FEATURE_SELECT: usize = 0x08; const COMMON_DRIVER_FEATURE: usize = 0x0C; const COMMON_MSIX_CONFIG: usize = 0x10; const COMMON_NUM_QUEUES: usize = 0x12; const COMMON_DEVICE_STATUS: usize = 0x14; const COMMON_QUEUE_SELECT: usize = 0x16; const COMMON_QUEUE_SIZE: usize = 0x18; const COMMON_QUEUE_MSIX_VECTOR: usize = 0x1A; const COMMON_QUEUE_ENABLE: usize = 0x1C; const COMMON_QUEUE_NOTIFY_OFF: usize = 0x1E; const COMMON_QUEUE_DESC_LO: usize = 0x20; const COMMON_QUEUE_DESC_HI: usize = 0x24; const COMMON_QUEUE_AVAIL_LO: usize = 0x28; const COMMON_QUEUE_AVAIL_HI: usize = 0x2C; const COMMON_QUEUE_USED_LO: usize = 0x30; const COMMON_QUEUE_USED_HI: usize = 0x34; const COMMON_CFG_REQUIRED_BYTES: usize = COMMON_QUEUE_USED_HI + core::mem::size_of::(); const ISR_STATUS_OFFSET: usize = 0; const ISR_CFG_REQUIRED_BYTES: usize = ISR_STATUS_OFFSET + core::mem::size_of::(); const NOTIFY_CFG_REQUIRED_BYTES: usize = core::mem::size_of::(); // virtio_input.h enums pub const VIRTIO_INPUT_CFG_UNSET: u8 = 0x00; pub const VIRTIO_INPUT_CFG_ID_NAME: u8 = 0x01; pub const VIRTIO_INPUT_CFG_ID_SERIAL: u8 = 0x02; pub const VIRTIO_INPUT_CFG_ID_DEVIDS: u8 = 0x03; pub const VIRTIO_INPUT_CFG_PROP_BITS: u8 = 0x10; pub const VIRTIO_INPUT_CFG_EV_BITS: u8 = 0x11; pub const VIRTIO_INPUT_CFG_ABS_INFO: u8 = 0x12; // virtio_input_event is 8 bytes (type: u16, code: u16, value: u32) pub const VIRTIO_INPUT_EVENT_SIZE: usize = 8; pub const VIRTIO_INPUT_CONFIG_SIZE: usize = 40; // select(1) + subsel(1) + size(1) + reserved(5) + payload(32) = 40 /// Required feature bit: VIRTIO_F_VERSION_1 (bit 32). pub const VIRTIO_F_VERSION_1: u64 = 1u64 << 32; #[repr(C)] #[derive(Clone, Copy, Debug, Default)] struct VirtioPciCap { cap_vndr: u8, cap_next: u8, cap_len: u8, cfg_type: u8, bar: u8, id: u8, padding: [u8; 2], offset: u32, length: u32, } #[repr(C)] #[derive(Clone, Copy, Debug, Default)] struct VirtioPciNotifyCap { cap: VirtioPciCap, notify_off_multiplier: u32, } fn pci_error(e: redox_driver_sys::DriverError) -> DriverError { DriverError::Pci(format!("{e}")) } fn read_pci_cap(pci: &mut PciDevice, offset: u8) -> Result { let mut raw = [0u8; 16]; for (i, byte) in raw.iter_mut().enumerate() { *byte = pci.read_config_byte(offset as u64 + i as u64)?; } Ok(VirtioPciCap { cap_vndr: raw[0], cap_next: raw[1], cap_len: raw[2], cfg_type: raw[3], bar: raw[4], id: raw[5], padding: [raw[6], raw[7]], offset: u32::from_le_bytes([raw[8], raw[9], raw[10], raw[11]]), length: u32::from_le_bytes([raw[12], raw[13], raw[14], raw[15]]), }) } fn read_notify_cap(pci: &mut PciDevice, offset: u8) -> Result { let mut raw = [0u8; 20]; for (i, byte) in raw.iter_mut().enumerate() { *byte = pci.read_config_byte(offset as u64 + i as u64)?; } let cap = VirtioPciCap { cap_vndr: raw[0], cap_next: raw[1], cap_len: raw[2], cfg_type: raw[3], bar: raw[4], id: raw[5], padding: [raw[6], raw[7]], offset: u32::from_le_bytes([raw[8], raw[9], raw[10], raw[11]]), length: u32::from_le_bytes([raw[12], raw[13], raw[14], raw[15]]), }; let notify_off_multiplier = u32::from_le_bytes([raw[16], raw[17], raw[18], raw[19]]); Ok(VirtioPciNotifyCap { cap, notify_off_multiplier }) } fn map_cap_region( info: &PciDeviceInfo, cap: &VirtioPciCap, label: &'static str, min_bytes: usize, ) -> Result { if cap.length < min_bytes as u32 { return Err(DriverError::Initialization(format!( "VirtIO input {label} cap length {min_bytes} required, got {}", cap.length ))); } let bar = info.bars.get(cap.bar as usize).ok_or_else(|| { DriverError::Pci(format!( "VirtIO input {label}: BAR index {} out of range", cap.bar )) })?; let (phys_addr, bar_size) = bar .memory_info() .ok_or_else(|| DriverError::Pci(format!("VirtIO input {label}: BAR not memory")))?; // Verify the capability range fits within the BAR before mapping. // This prevents the MMIO mapping from extending past the BAR's // actual physical extent on a real device. (QEMU is permissive // and would not catch this; bare-metal hardware would.) let cap_end = u64::from(cap.offset) .checked_add(u64::from(cap.length)) .ok_or_else(|| DriverError::Pci(format!("VirtIO input {label} capability range overflow")))?; if cap_end > bar_size as u64 { return Err(DriverError::Pci(format!( "VirtIO input {label} capability range [{:#x}, {:#x}) exceeds BAR{} size {:#x}", cap.offset, cap_end, cap.bar, bar_size ))); } MmioRegion::map( phys_addr + cap.offset as u64, cap.length as usize, CacheType::Uncacheable, MmioProt::READ_WRITE, ) .map_err(|e| DriverError::Mmio(format!("virtio-inputd: failed to map {label}: {e}"))) } #[derive(Debug)] pub struct QueueConfig { pub index: u16, pub size: u16, pub notify_off: u16, } pub struct VirtioModernPciTransport { common_cfg: MmioRegion, notify_cfg: MmioRegion, isr_cfg: MmioRegion, device_cfg: MmioRegion, notify_off_multiplier: u32, } impl VirtioModernPciTransport { pub fn new(info: &PciDeviceInfo, pci: &mut PciDevice) -> Result { let mut common_cap = None; let mut notify_cap = None; let mut isr_cap = None; let mut device_cap = None; let cap_ptr = pci.read_config_byte(0x34)?; if cap_ptr == 0 { return Err(DriverError::Initialization( "VirtIO input has no PCI capabilities".into(), )); } let mut offset = cap_ptr; let mut visited = 0u8; const MAX_CAPS: u8 = 48; while offset != 0 && visited < MAX_CAPS { visited += 1; 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 { let raw = read_pci_cap(pci, offset)?; match raw.cfg_type { VIRTIO_PCI_CAP_COMMON_CFG => common_cap = Some(raw), VIRTIO_PCI_CAP_NOTIFY_CFG => notify_cap = Some(read_notify_cap(pci, offset)?), VIRTIO_PCI_CAP_ISR_CFG => isr_cap = Some(raw), VIRTIO_PCI_CAP_DEVICE_CFG => device_cap = Some(raw), _ => {} } } offset = cap_next; } info!( "virtio-inputd: VirtIO PCI capability scan found {} caps, common={} notify={} isr={} device={}", visited, common_cap.is_some(), notify_cap.is_some(), isr_cap.is_some(), device_cap.is_some(), ); let common_cap = common_cap .ok_or_else(|| DriverError::Initialization("VirtIO input missing common_cfg".into()))?; let notify_cap = notify_cap .ok_or_else(|| DriverError::Initialization("VirtIO input missing notify_cfg".into()))?; let isr_cap = isr_cap .ok_or_else(|| DriverError::Initialization("VirtIO input missing isr_cfg".into()))?; let device_cap = device_cap .ok_or_else(|| DriverError::Initialization("VirtIO input missing device_cfg".into()))?; let common_cfg = map_cap_region(info, &common_cap, "common_cfg", COMMON_CFG_REQUIRED_BYTES)?; let notify_cfg = map_cap_region( info, ¬ify_cap.cap, "notify_cfg", NOTIFY_CFG_REQUIRED_BYTES, )?; let isr_cfg = map_cap_region(info, &isr_cap, "isr_cfg", ISR_CFG_REQUIRED_BYTES)?; let device_cfg = map_cap_region(info, &device_cap, "device_cfg", VIRTIO_INPUT_CONFIG_SIZE)?; info!( "virtio-inputd: VirtIO PCI transport mapped for {} (notify multiplier {})", info.location, notify_cap.notify_off_multiplier ); Ok(Self { common_cfg, notify_cfg, isr_cfg, device_cfg, notify_off_multiplier: notify_cap.notify_off_multiplier, }) } pub fn initialize_device(&mut self, requested_features: u64) -> Result { debug!("virtio-inputd: VirtIO reset device"); self.write_device_status(0); self.write_device_status(DEVICE_STATUS_ACKNOWLEDGE); self.write_device_status(DEVICE_STATUS_ACKNOWLEDGE | DEVICE_STATUS_DRIVER); let available = self.read_device_features(); if (available & requested_features) & VIRTIO_F_VERSION_1 == 0 { self.fail(format!( "VirtIO input missing VIRTIO_F_VERSION_1 (device features={available:#x})" ))?; } let negotiated = available & requested_features; self.write_driver_features(negotiated); let mut status = self.device_status(); status |= DEVICE_STATUS_FEATURES_OK; self.write_device_status(status); if self.device_status() & DEVICE_STATUS_FEATURES_OK == 0 { self.fail("VirtIO input rejected FEATURES_OK during negotiation".into())?; } info!("virtio-inputd: VirtIO negotiated features device={available:#x} driver={negotiated:#x}"); Ok(negotiated) } pub fn finalize_device(&mut self) { let status = self.device_status() | DEVICE_STATUS_DRIVER_OK; self.write_device_status(status); } pub fn device_status(&self) -> u8 { self.common_cfg.read8(COMMON_DEVICE_STATUS) } /// Returns true if the device has signalled FAILED or NEEDS_RESET /// since the last `finalize_device` call. The drain loop should /// check this on each iteration to detect a virtio-input device /// that has entered an unrecoverable state and bail out cleanly /// (virtio 1.0 §2.1.4 / §2.1.5). pub fn device_in_error_state(&self) -> bool { let s = self.device_status(); (s & DEVICE_STATUS_FAILED) != 0 || (s & DEVICE_STATUS_NEEDS_RESET) != 0 } /// Reset the device to a clean state. Called on probe failure paths /// after a partial `initialize_device` to avoid leaving the device /// in ACKNOWLEDGE | DRIVER with no driver active. pub fn reset_device(&mut self) { self.write_device_status(DEVICE_STATUS_RESET); } pub fn read_isr_status(&mut self) -> u8 { self.isr_cfg.read8(ISR_STATUS_OFFSET) } pub fn num_queues(&self) -> u16 { self.common_cfg.read16(COMMON_NUM_QUEUES) } pub fn prepare_queue(&self, index: u16, requested_size: u16) -> Result { self.select_queue(index); let device_size = self.common_cfg.read16(COMMON_QUEUE_SIZE); if device_size == 0 { return Err(DriverError::Initialization(format!( "VirtIO input queue {index} reports size 0" ))); } let size = device_size.min(requested_size); let notify_off = self.common_cfg.read16(COMMON_QUEUE_NOTIFY_OFF); Ok(QueueConfig { index, size, notify_off, }) } pub fn activate_queue( &self, index: u16, size: u16, desc_addr: u64, avail_addr: u64, used_addr: u64, msix_vector: Option, ) -> Result<()> { use std::sync::atomic::{fence, Ordering}; self.select_queue(index); self.common_cfg.write16(COMMON_QUEUE_SIZE, size); self.common_cfg .write16(COMMON_QUEUE_MSIX_VECTOR, msix_vector.unwrap_or(u16::MAX)); self.write_u64_pair(COMMON_QUEUE_DESC_LO, COMMON_QUEUE_DESC_HI, desc_addr); self.write_u64_pair(COMMON_QUEUE_AVAIL_LO, COMMON_QUEUE_AVAIL_HI, avail_addr); self.write_u64_pair(COMMON_QUEUE_USED_LO, COMMON_QUEUE_USED_HI, used_addr); // virtio spec §2.8: the queue configuration (addresses, MSIX vector) // must be visible to the device before queue_enable transitions // to 1. The MMIO region is uncacheable, but a CPU write buffer // may still reorder writes to distinct MMIO addresses. An explicit // full barrier is the documented hardening — Linux uses // `virtio_wmb()` here for the same reason. fence(Ordering::SeqCst); self.common_cfg.write16(COMMON_QUEUE_ENABLE, 1); if self.common_cfg.read16(COMMON_QUEUE_ENABLE) != 1 { return Err(DriverError::Initialization(format!( "VirtIO input queue {index} refused queue_enable" ))); } Ok(()) } pub fn set_config_msix_vector(&self, vector: Option) { self.common_cfg .write16(COMMON_MSIX_CONFIG, vector.unwrap_or(u16::MAX)); } pub fn notify_queue(&self, queue_index: u16, notify_off: u16) -> Result<()> { let byte_offset = usize::from(notify_off) .checked_mul(self.notify_off_multiplier as usize) .ok_or_else(|| DriverError::Mmio("VirtIO notify offset overflow".into()))?; let end = byte_offset .checked_add(core::mem::size_of::()) .ok_or_else(|| DriverError::Mmio("VirtIO notify MMIO overflow".into()))?; if end > self.notify_cfg.size() { return Err(DriverError::Mmio(format!( "VirtIO input queue notify outside notify_cfg window: end={end:#x} size={:#x}", self.notify_cfg.size() ))); } self.notify_cfg.write16(byte_offset, queue_index); Ok(()) } // Config-space read helpers (used to enumerate device capabilities) pub fn config_write_select(&mut self, select: u8, subsel: u8) { self.device_cfg.write8(0, select); self.device_cfg.write8(1, subsel); } pub fn config_read_size(&self) -> u8 { self.device_cfg.read8(2) } pub fn config_read_string(&mut self, max_len: usize, out: &mut [u8]) -> usize { let reported = self.config_read_size() as usize; let cap = reported.min(out.len()).min(max_len); for i in 0..cap { out[i] = self.device_cfg.read8(8 + i); } cap } pub fn config_read_bitmap(&mut self, max_len: usize, out: &mut [u8]) -> usize { let reported = self.config_read_size() as usize; let cap = reported.min(out.len()).min(max_len); for i in 0..cap { out[i] = self.device_cfg.read8(8 + i); } cap } pub fn config_read_absinfo(&mut self, abs_code: u8) -> Option { self.config_write_select(VIRTIO_INPUT_CFG_ABS_INFO, abs_code); if self.config_read_size() < 20 { return None; } let min = read_le32(&mut self.device_cfg, 8); let max = read_le32(&mut self.device_cfg, 12); let fuzz = read_le32(&mut self.device_cfg, 16); let flat = read_le32(&mut self.device_cfg, 20); let res = read_le32(&mut self.device_cfg, 24); Some(AbsInfo { min, max, fuzz, flat, res }) } pub fn config_read_devids(&mut self) -> Option { self.config_write_select(VIRTIO_INPUT_CFG_ID_DEVIDS, 0); if self.config_read_size() < 8 { return None; } let bustype = read_le16(&mut self.device_cfg, 8); let vendor = read_le16(&mut self.device_cfg, 10); let product = read_le16(&mut self.device_cfg, 12); let version = read_le16(&mut self.device_cfg, 14); Some(DevIds { bustype, vendor, product, version }) } fn fail(&mut self, reason: String) -> Result { let status = self.device_status() | DEVICE_STATUS_FAILED; self.write_device_status(status); Err(DriverError::Initialization(reason)) } fn read_device_features(&self) -> u64 { self.common_cfg.write32(COMMON_DEVICE_FEATURE_SELECT, 0); let low = self.common_cfg.read32(COMMON_DEVICE_FEATURE) as u64; self.common_cfg.write32(COMMON_DEVICE_FEATURE_SELECT, 1); let high = self.common_cfg.read32(COMMON_DEVICE_FEATURE) as u64; low | (high << 32) } fn write_driver_features(&self, features: u64) { self.common_cfg.write32(COMMON_DRIVER_FEATURE_SELECT, 0); self.common_cfg .write32(COMMON_DRIVER_FEATURE, features as u32); self.common_cfg.write32(COMMON_DRIVER_FEATURE_SELECT, 1); self.common_cfg .write32(COMMON_DRIVER_FEATURE, (features >> 32) as u32); } fn write_device_status(&mut self, status: u8) { self.common_cfg.write8(COMMON_DEVICE_STATUS, status); } fn select_queue(&self, index: u16) { self.common_cfg.write16(COMMON_QUEUE_SELECT, index); } fn write_u64_pair(&self, lo: usize, hi: usize, value: u64) { self.common_cfg.write32(lo, value as u32); self.common_cfg.write32(hi, (value >> 32) as u32); } } /// virtio_input_absinfo (Linux include/uapi/linux/virtio_input.h) #[derive(Clone, Copy, Debug, Default)] pub struct AbsInfo { pub min: u32, pub max: u32, pub fuzz: u32, pub flat: u32, pub res: u32, } /// virtio_input_devids #[derive(Clone, Copy, Debug, Default)] pub struct DevIds { pub bustype: u16, pub vendor: u16, pub product: u16, pub version: u16, } /// A decoded virtio_input_event (8 bytes from the wire). /// /// Wire layout per Linux include/uapi/linux/virtio_input.h: /// struct virtio_input_event { /// __le16 type; /// __le16 code; /// __le32 value; /// }; #[derive(Clone, Copy, Debug, Default)] pub struct VirtioInputEvent { pub event_type: u16, pub code: u16, pub value: i32, } impl VirtioInputEvent { pub fn read_le(buf: &[u8; VIRTIO_INPUT_EVENT_SIZE]) -> Self { Self { event_type: u16::from_le_bytes([buf[0], buf[1]]), code: u16::from_le_bytes([buf[2], buf[3]]), value: i32::from_le_bytes([buf[4], buf[5], buf[6], buf[7]]), } } } fn read_le16(mmio: &mut MmioRegion, offset: usize) -> u16 { let b0 = mmio.read8(offset); let b1 = mmio.read8(offset + 1); u16::from_le_bytes([b0, b1]) } fn read_le32(mmio: &mut MmioRegion, offset: usize) -> u32 { let b0 = mmio.read8(offset); let b1 = mmio.read8(offset + 1); let b2 = mmio.read8(offset + 2); let b3 = mmio.read8(offset + 3); u32::from_le_bytes([b0, b1, b2, b3]) }