dc68054305
- 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
554 lines
20 KiB
Rust
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,
|
|
¬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<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])
|
|
}
|