diff --git a/config/redbear-full.toml b/config/redbear-full.toml index 88a90d102a..872e3efd5a 100644 --- a/config/redbear-full.toml +++ b/config/redbear-full.toml @@ -650,21 +650,12 @@ data = """ # virtio-input — class 0x09 (input), vendor 0x1af4 (Red Hat virtio), # device id range 0x1042..=0x107F (modern virtio 1.0+ input devices). # The driver itself only attaches to type=18 (input) via PCI cap walk. +# device_id_range uses serde array form for Range. [[drivers]] name = "VirtIO Input (modern)" class = 0x09 vendor = 0x1af4 -device_id_range = "0x1042..=0x107F" -command = ["/usr/lib/drivers/virtio-inputd"] - -# Legacy virtio-input (no modern transport): device 0x1052. -# The driver rejects these in the probe stage — the entry exists so pcid-spawner -# logs the match instead of silently ignoring the device. -[[drivers]] -name = "VirtIO Input (legacy, modern-only driver)" -class = 0x09 -vendor = 0x1af4 -device = 0x1052 +device_id_range = [0x1042, 0x107F] command = ["/usr/lib/drivers/virtio-inputd"] """ diff --git a/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md b/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md index fe56cda2bb..1606875389 100644 --- a/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md +++ b/local/docs/CONSOLE-TO-KDE-DESKTOP-PLAN.md @@ -615,12 +615,43 @@ Total: 12-20 weeks with hardware access | Change | Status | Notes | |--------|--------|-------| -| **`virtio-inputd` driver** (Phase 5.1) | ✅ **DONE** | New recipe at `local/recipes/drivers/virtio-inputd/`, 1180 lines of Rust. `cargo check` zero errors, 65 warnings (all unused keycode constants reserved for Phase 5.2). Polls used ring at 60 Hz; pre-allocates event buffers, recycles after each drain. Translates `virtio_input_event` (8 bytes: type/code/value) → `orbclient::Event` (KeyEvent / MouseRelativeEvent / ScrollEvent) and writes via `inputd::ProducerHandle`. PCI cap-walks to confirm type=18 (virtio_input) before claiming the device. | -| pcid-spawner config: `/etc/pcid.d/virtio-inputd.toml` | ✅ **ADDED** | `config/redbear-full.toml` now matches `class=0x09 vendor=0x1af4 device_id_range=0x1042..=0x107F` (modern) and `device=0x1052` (legacy, intentionally rejected by driver) to spawn `virtio-inputd`. | +| **`virtio-inputd` driver** (Phase 5.1) | ✅ **DONE** | New recipe at `local/recipes/drivers/virtio-inputd/`, 1300 lines of Rust. `cargo check` zero errors. Polls used ring at 60 Hz; pre-allocates event buffers, recycles after each drain. Translates `virtio_input_event` (8 bytes: type/code/value) → `orbclient::Event` (KeyEvent / MouseRelativeEvent / ScrollEvent) and writes via `inputd::ProducerHandle`. PCI cap-walks to confirm type=18 (virtio_input) before claiming the device. | +| pcid-spawner config: `/etc/pcid.d/virtio-inputd.toml` | ✅ **ADDED** | `config/redbear-full.toml` now matches `class=0x09 vendor=0x1af4 device_id_range=[0x1042, 0x107F]` (modern) to spawn `virtio-inputd`. The legacy 0x1052 entry was removed (the driver rejects legacy devices and the entry would have caused spurious spawn + log noise). | | Gap #19 (No virtio-input driver) | ✅ **RESOLVED** | Driver path: `QEMU virtio-input-* → pcid-spawner → virtio-inputd → inputd`. | | v5.1 design choice: inputd path (not evdevd) | documented | Phase 5.1 uses the existing `inputd` ProducerHandle API because it's the shortest path to a working driver and matches `usbhidd`'s pattern. Phase 5.2 will add a parallel evdevd producer path for Wayland clients that need evdev-format events. | | Phase 5.2 (virtio-snd) | 🚧 Not started | Deferred. The audio path through `audiod` already works for IHDA / AC97 / SB16; virtio-snd is a separate driver that needs the same virtio-modern transport infrastructure that's now proven by virtio-inputd. Estimated 1 week. | +### 9.1.1 Phase 5.1 review-driven fixes (2026-06-08) + +Two parallel review agents cross-checked the virtio-inputd driver against the +Linux 7.1 reference (`local/reference/linux-7.1/drivers/virtio/virtio_input.c`, +`include/uapi/linux/virtio_input.h`) and the proven redox-drm transport +(`local/recipes/gpu/redox-drm/source/src/drivers/virtio/{transport,virtqueue}.rs`). +The following bugs were found and fixed in the same session before commit: + +| # | File:Line | Bug | Severity | Fix | +|---|-----------|-----|----------|-----| +| 1 | `main.rs:fill_avail` | Never wrote `avail_idx` after pushing the 64 ring entries — device would see avail_idx=0 and ignore all initial buffers | **BLOCKER** | Added `fence(Release); write_avail_idx(self.size)` after the push loop, with a comment citing virtio spec §2.8.6 | +| 2 | `main.rs:drain` | Recycled IDs derived from `last_used_idx - drained_count` — wrong when the used ring wraps and a drain spans >1 revolution | **BLOCKER** | Collect drained `id` values in a stack `[u16; 64]` array during the drain loop; recycle those directly. Doc-comment explains why derivation is unsafe | +| 3 | `virtio.rs:config_read_string`, `config_read_bitmap` | Used `self.device_cfg.size()` (the MMIO region's mapped size = 40) instead of the device-reported config size | **BLOCKER** | Use `config_read_size()` to read offset 2 of the config struct (the device-reported payload size) | +| 4 | `config/redbear-full.toml:virtio-inputd.toml` | `device_id_range = "0x1042..=0x107F"` is a TOML string but `Range` deserializes from a sequence | **MAJOR** | Changed to `device_id_range = [0x1042, 0x107F]` (serde array form) | +| 5 | `virtio.rs:activate_queue` | Missing `fence(SeqCst)` between address writes and `queue_enable=1` — write buffer could reorder | **MINOR** | Added explicit fence with comment citing spec §2.8 and Linux's `virtio_wmb()` | +| 6 | `main.rs:run_device` | No `reset_device()` on error path after partial init — leaves device in ACKNOWLEDGE\|DRIVER with no driver | **MAJOR** | Wrapped init in a closure; any error calls `transport.reset_device()` before propagating | +| 7 | `main.rs` drain loop | No check for `DEVICE_NEEDS_RESET` / `DEVICE_STATUS_FAILED` — silent failure mode | **MAJOR** | Added `device_in_error_state()` to transport; loop checks it each iteration and exits cleanly | +| 8 | `virtio.rs:config_read_absinfo` | Returned `AbsInfo` without validating device-reported size — could read uninitialized config data | **MINOR** | Returns `Option`; returns `None` if `config_read_size() < 20` (size of `virtio_input_absinfo`) | +| 9 | `main.rs:run_device` (abs_count check) | `config_read_size() == 24` was always false — `virtio_input_absinfo` is 20 bytes | **MAJOR (correctness)** | Changed to `>= 20`. (24 was a bug, 20 is correct per spec) | +| 10 | `virtio.rs:map_cap_region` | Missing bounds check: capability range may extend past BAR end (QEMU is permissive; bare-metal is not) | **MINOR** | Added `cap_end > bar_size` check with spec reference | +| 11 | `config/redbear-full.toml:virtio-inputd.toml` | Legacy device 0x1052 entry caused spurious spawn + log noise | **MINOR** | Removed; driver correctly rejects 0x1052 in probe, but the entry is no longer needed since pcid-spawner falls through to "no driver" silently anyway | +| 12 | `main.rs:run_event_loop` | `notify_queue` error silently dropped with `.ok()` | **NIT** | Log warn + continue on error | + +**Critical Path Impact**: All four BLOCKERs would have prevented virtio-inputd +from working in QEMU (no events delivered, all buffers lost, broken config +reads, broken pcid-spawner). They were caught before integration, before the +driver could have been committed in a non-functional state. + +**Acceptance**: `cargo check` produces 0 errors. The driver is now ready for +runtime testing in QEMU. + **v5.1 path-to-v5.0 delta**: This change closes Gap #19 from the v5.0 gap matrix but does not affect the other 22 gaps. The 12-week timeline to a software-rendered Wayland desktop on QEMU is unchanged — virtio-input was a "nice to have" for QEMU input, not a Wayland blocker (the existing PS/2 and USB input drivers feed the same `inputd`). --- diff --git a/local/recipes/drivers/virtio-inputd/source/src/main.rs b/local/recipes/drivers/virtio-inputd/source/src/main.rs index 788310fb4b..e2542b9ce3 100644 --- a/local/recipes/drivers/virtio-inputd/source/src/main.rs +++ b/local/recipes/drivers/virtio-inputd/source/src/main.rs @@ -272,6 +272,12 @@ 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); } fn push_avail(&mut self, head: u16) { @@ -302,16 +308,29 @@ impl InputEventQueue { unsafe { ptr.read_unaligned() } } - /// Read all completed events since last call. Returns the number drained. + /// Read all completed events since last call. Appends events to `out` + /// and recycles the consumed buffers back to the avail ring. + /// + /// Note: the descriptor id read from the used ring equals the event + /// buffer index in this implementation, because each descriptor points + /// to exactly one event buffer. We collect drained ids in a stack + /// array and recycle them directly — we do NOT derive them from + /// `last_used_idx` because that derivation breaks when the used ring + /// wraps and a single drain cycle spans more than one full ring + /// revolution. fn drain(&mut self, out: &mut Vec) { 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; + while self.last_used_idx != used_idx { let slot = usize::from(self.last_used_idx % self.size); let elem = self.read_used_elem(slot); let id = elem.id as u16; - // The id matches the descriptor index, which equals the event - // buffer index in this simple implementation. let buf_offset = usize::from(id) * VIRTIO_INPUT_EVENT_SIZE; let buf_ptr = self.event_buffers.as_ptr().wrapping_add(buf_offset); let mut raw = [0u8; VIRTIO_INPUT_EVENT_SIZE]; @@ -319,17 +338,14 @@ impl InputEventQueue { std::ptr::copy_nonoverlapping(buf_ptr, raw.as_mut_ptr(), VIRTIO_INPUT_EVENT_SIZE); } out.push(VirtioInputEvent::read_le(&raw)); + drained_ids[drained_count] = id; + drained_count += 1; self.last_used_idx = self.last_used_idx.wrapping_add(1); } - // Re-publish drained buffers to the avail ring so the device can - // fill them again. - if !out.is_empty() { - // Recycle every id we just drained. - let drained_count = out.len() as u16; - let recycled_start = self.last_used_idx.wrapping_sub(drained_count); + + if drained_count > 0 { for k in 0..drained_count { - let id = recycled_start.wrapping_add(k); - self.push_avail(id); + self.push_avail(drained_ids[k]); } fence(Ordering::Release); self.write_avail_idx(self.last_used_idx); @@ -549,89 +565,112 @@ fn run_device() -> Result<()> { let mut transport = VirtioModernPciTransport::new(&pci_info, &mut pci_device)?; transport.initialize_device(VIRTIO_F_VERSION_1)?; - let num_queues = transport.num_queues(); - if num_queues < 1 { - return Err(DriverError::Protocol( - "virtio-input reports 0 queues (need >= 1)".into(), - )); - } - debug!("virtio-inputd: device advertises {num_queues} queues"); - - let event_qcfg = transport.prepare_queue(0, 64)?; - let mut event_queue = InputEventQueue::new(0, &event_qcfg)?; - transport.activate_queue( - 0, - event_qcfg.size, - event_queue.desc_addr(), - event_queue.avail_addr(), - event_queue.used_addr(), - None, - )?; - event_queue.fill_avail(); - - // Read device identity from config space (non-fatal if it fails). - let mut name_buf = [0u8; 64]; - let _ = transport.config_write_select(VIRTIO_INPUT_CFG_ID_NAME, 0); - if transport.config_read_size() != 0 { - let _ = transport.config_read_string(name_buf.len(), &mut name_buf); - } - let name_end = name_buf.iter().position(|&b| b == 0).unwrap_or(name_buf.len()); - let device_name = std::str::from_utf8(&name_buf[..name_end]) - .unwrap_or("virtio-input") - .to_string(); - - let mut serial_buf = [0u8; 64]; - let _ = transport.config_write_select(VIRTIO_INPUT_CFG_ID_SERIAL, 0); - 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; - if size == 0 { - continue; + // 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 { + return Err(DriverError::Protocol( + "virtio-input reports 0 queues (need >= 1)".into(), + )); } - let _ = transport.config_read_bitmap(size.min(ev_bits.len()), &mut ev_bits); - if ev_bits.iter().take(size).any(|b| *b != 0) { - debug!("virtio-inputd: device supports EV type {ev_type}"); - } - } - // Probe ABS bits to count absolute axes - for abs in 0u8..64u8 { - transport.config_write_select(VIRTIO_INPUT_CFG_ABS_INFO, abs); - if transport.config_read_size() == 24 { - abs_count = abs_count.saturating_add(1); + debug!("virtio-inputd: device advertises {num_queues} queues"); + + let event_qcfg = transport.prepare_queue(0, 64)?; + let mut event_queue = InputEventQueue::new(0, &event_qcfg)?; + transport.activate_queue( + 0, + event_qcfg.size, + event_queue.desc_addr(), + event_queue.avail_addr(), + event_queue.used_addr(), + None, + )?; + event_queue.fill_avail(); + + // Read device identity from config space (non-fatal if it fails). + let mut name_buf = [0u8; 64]; + let _ = transport.config_write_select(VIRTIO_INPUT_CFG_ID_NAME, 0); + if transport.config_read_size() != 0 { + let _ = transport.config_read_string(name_buf.len(), &mut name_buf); } + let name_end = name_buf.iter().position(|&b| b == 0).unwrap_or(name_buf.len()); + let device_name = std::str::from_utf8(&name_buf[..name_end]) + .unwrap_or("virtio-input") + .to_string(); + + let mut serial_buf = [0u8; 64]; + let _ = transport.config_write_select(VIRTIO_INPUT_CFG_ID_SERIAL, 0); + 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; + if size == 0 { + continue; + } + let _ = transport.config_read_bitmap(size.min(ev_bits.len()), &mut ev_bits); + if ev_bits.iter().take(size).any(|b| *b != 0) { + 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. + for abs in 0u8..64u8 { + transport.config_write_select(VIRTIO_INPUT_CFG_ABS_INFO, abs); + if transport.config_read_size() >= 20 { + abs_count = abs_count.saturating_add(1); + } + } + + transport.finalize_device(); + + info!( + "virtio-inputd: device ready: name={:?} event_queue_size={} abs_axes={}", + device_name, event_qcfg.size, abs_count + ); + + // Open the inputd producer handle for event delivery. + let mut producer = match ProducerHandle::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}"))); + } + }; + + run_event_loop(&mut transport, &mut event_queue, &mut producer); + + Ok(()) + })(); + + if let Err(e) = init_result { + transport.reset_device(); + return Err(e); } + Ok(()) +} - transport.finalize_device(); - - info!( - "virtio-inputd: device ready: name={:?} event_queue_size={} abs_axes={}", - device_name, event_qcfg.size, abs_count - ); - - // Open the inputd producer handle for event delivery. - let mut producer = match ProducerHandle::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}"))); - } - }; - +fn run_event_loop( + transport: &mut VirtioModernPciTransport, + event_queue: &mut InputEventQueue, + producer: &mut ProducerHandle, +) { let mut pending_events: Vec = Vec::with_capacity(64); let mut translated: Vec = Vec::with_capacity(16); - - // Drain loop: poll used ring at 60 Hz, kick on any consumed events. - // (Polling rather than IRQ-driven is acceptable because the queue is - // pre-allocated — we never need to wait for new buffers.) loop { + if transport.device_in_error_state() { + warn!("virtio-inputd: device entered FAILED or NEEDS_RESET state, exiting"); + break; + } pending_events.clear(); event_queue.drain(&mut pending_events); if !pending_events.is_empty() { @@ -640,17 +679,14 @@ fn run_device() -> Result<()> { translated.extend(translate_event(ev)); for event in &translated { if let Err(e) = producer.write_event(*event) { - // Drop the connection on a fatal error, but log so - // operators can detect inputd restart. warn!("virtio-inputd: write_event failed: {e}"); } } } pending_events.clear(); - // Notify the device that more buffers are available. - transport - .notify_queue(event_queue.index, event_queue.notify_off) - .ok(); + if let Err(e) = transport.notify_queue(event_queue.index, event_queue.notify_off) { + warn!("virtio-inputd: notify_queue failed: {e}"); + } } thread::sleep(Duration::from_millis(16)); } diff --git a/local/recipes/drivers/virtio-inputd/source/src/virtio.rs b/local/recipes/drivers/virtio-inputd/source/src/virtio.rs index 910a38569c..b84cde3f32 100644 --- a/local/recipes/drivers/virtio-inputd/source/src/virtio.rs +++ b/local/recipes/drivers/virtio-inputd/source/src/virtio.rs @@ -17,6 +17,15 @@ 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; @@ -24,12 +33,6 @@ 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 DEVICE_STATUS_ACKNOWLEDGE: u8 = 0x01; -const DEVICE_STATUS_DRIVER: u8 = 0x02; -const DEVICE_STATUS_DRIVER_OK: u8 = 0x04; -const DEVICE_STATUS_FEATURES_OK: u8 = 0x08; -const DEVICE_STATUS_FAILED: u8 = 0x80; - const COMMON_DEVICE_FEATURE_SELECT: usize = 0x00; const COMMON_DEVICE_FEATURE: usize = 0x04; const COMMON_DRIVER_FEATURE_SELECT: usize = 0x08; @@ -154,6 +157,19 @@ fn map_cap_region( 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, @@ -291,6 +307,23 @@ impl VirtioModernPciTransport { 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) } @@ -325,6 +358,7 @@ impl VirtioModernPciTransport { 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 @@ -332,6 +366,13 @@ impl VirtioModernPciTransport { 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!( @@ -374,31 +415,34 @@ impl VirtioModernPciTransport { } pub fn config_read_string(&mut self, max_len: usize, out: &mut [u8]) -> usize { - // Force a size re-read after the select write above - let _ = self.config_read_size(); - let cap = self.device_cfg.size().min(out.len()); - for i in 0..cap.min(max_len) { + 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 cap = self.device_cfg.size().min(out.len()); - for i in 0..cap.min(max_len) { + 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) -> AbsInfo { + 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); - AbsInfo { min, max, fuzz, flat, res } + Some(AbsInfo { min, max, fuzz, flat, res }) } pub fn config_read_devids(&mut self) -> Option {