Files
RedBear-OS/local/recipes/drivers/virtio-inputd/source/src/virtio.rs
T
vasilito dc68054305 restore lost packages from 0.2.3 + fix overwritten 0.2.4 files
- Restore 29 recipe symlinks (libdrm, qtbase, dbus, sddm, pipewire, etc.)
- Restore 33 patches (KDE, libdrm, mesa, pipewire, sddm, wireplumber)
- Restore 20+ local/scripts (audit, lint, test, build helpers)
- Restore src/cook/scheduler.rs, status.rs, gnu-config/
- Restore scripts/patch-inclusion-gate.sh, run_mini1.sh, validate-collision-log.sh
- Recover TLC source from HEAD (was overwritten by 0.2.3 checkout)
- Recover 11 local/docs plans from HEAD (were overwritten)
- Recover qt6-wayland-smoke symlink from HEAD
- Fix MOTD: remove garbled ASCII art, use clean text
- Update version: 0.2.0 -> 0.2.4 in os-release, motd, config
- Reduce filesystem_size: 1536 -> 512 MiB
- Add ABSOLUTE RULE to AGENTS.md: never delete/ignore packages
- Reduce pcid scheme log verbosity: info -> debug
2026-06-19 12:39:14 +03:00

554 lines
20 KiB
Rust

//! 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::<u32>();
const ISR_STATUS_OFFSET: usize = 0;
const ISR_CFG_REQUIRED_BYTES: usize = ISR_STATUS_OFFSET + core::mem::size_of::<u8>();
const NOTIFY_CFG_REQUIRED_BYTES: usize = core::mem::size_of::<u16>();
// 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<VirtioPciCap> {
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<VirtioPciNotifyCap> {
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<MmioRegion> {
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<Self> {
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,
&notify_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<u64> {
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<QueueConfig> {
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<u16>,
) -> 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<u16>) {
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::<u16>())
.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<AbsInfo> {
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<DevIds> {
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<T>(&mut self, reason: String) -> Result<T> {
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])
}