virtio-inputd: implement Phase 5.1 virtio-input driver
Add a real, QEMU-targeted virtio-input driver as a new Red Bear recipe at local/recipes/drivers/virtio-inputd/. The driver handles virtio-input-host-pci, virtio-input-keyboard, virtio-input-mouse, and virtio-input-tablet devices and closes Gap #19 of the v5.0 desktop plan. The driver: * Walks the PCI capability list to find the modern virtio 1.0 capability block (common_cfg, notify_cfg, isr_cfg, device_cfg) using MmioRegion mappings via redox-driver-sys. Rejects legacy virtio-input (device 0x1052) which lacks the modern transport. * Negotiates VIRTIO_F_VERSION_1 only (the only required feature). * Allocates one event virtqueue (size up to 64) backed by four DMA buffers (desc, avail, used, event_buffers) and pre-fills the avail ring. * Polls the used ring at 60 Hz, drains completed events, decodes each virtio_input_event (8-byte type/code/value), and recycles drained buffers back to the avail ring. * Translates events to orbclient format and pushes them to inputd via ProducerHandle (Orbital path): - EV_KEY -> KeyEvent (with US-QWERTY character mapping) - EV_REL -> MouseRelativeEvent (REL_X/REL_Y) or ScrollEvent (REL_WHEEL) - EV_SYN -> dropped (inputd multiplexes) - Other -> dropped (Phase 5.2 will add evdevd path) Probe-time checks: * Vendor 0x1AF4, device_id >= 0x1042, revision >= 1 * Caps include a device_cfg block with virtio type == 18 (virtio_input) Configuration: a pcid-spawner fragment is added to config/redbear-full.toml under /etc/pcid.d/virtio-inputd.toml matching class=0x09 vendor=0x1AF4 with device_id_range 0x1042..=0x107F (and a separate 0x1052 entry that the driver intentionally rejects). Verification: cargo check produces 0 errors and 65 warnings, all of which are unused input-event-codes.h constants reserved for the Phase 5.2 expansion. Linking the binary requires the Redox cross-toolchain (relibc provides redox_sys_call_v0); this is provided by the build system, not the host toolchain. Plan: this is Phase 5.1 of CONSOLE-TO-KDE-DESKTOP-PLAN.md v5.0. The plan is updated to v5.1 with: (a) a 'What Changed Since v5.0' section, (b) Gap #19 marked DONE, (c) Phase 5 row marked DONE with sub-task status, (d) Gate E updated, (e) Input pipeline section updated to reflect the (c) path is now implemented. Phase 5.2 (evdevd producer path + virtio-snd) is documented as the next planned work but not yet implemented.
This commit is contained in:
@@ -644,6 +644,30 @@ device = 0x1050
|
||||
command = ["/usr/bin/redox-drm"]
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/pcid.d/virtio-inputd.toml"
|
||||
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.
|
||||
[[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
|
||||
command = ["/usr/lib/drivers/virtio-inputd"]
|
||||
"""
|
||||
|
||||
[[files]]
|
||||
path = "/etc/environment.d/90-dbus.conf"
|
||||
data = """
|
||||
|
||||
@@ -296,7 +296,7 @@ Remaining gaps:
|
||||
| `udev-shim` | ✅ Real | `local/recipes/system/udev-shim/source/src/` | `/dev/input/*` node creation |
|
||||
| `seatd` | ✅ Built | `local/recipes/system/seatd/recipe.toml` | meson build |
|
||||
| `seatd-redox` | 🟡 TODO | `local/recipes/wayland/seatd-redox/recipe.toml` | "needs redox-drm scheme for DRM lease" |
|
||||
| `virtio-input` | ❌ **MISSING** | N/A | No direct virtio-input driver for QEMU |
|
||||
| `virtio-input` | ✅ **DONE (Phase 5.1, 2026-06-08)** | `local/recipes/drivers/virtio-inputd/source/src/{main,virtio}.rs` (1180 lines) + `recipe.toml` + pcid.d entry in `config/redbear-full.toml` | `cargo check` clean, 65 warnings (all unused keycodes for Phase 5.2 expansion). Polls used ring at 60 Hz, translates virtio_input_event → orbclient::Event → `inputd::ProducerHandle`. |
|
||||
|
||||
**Critical input pipeline gap (v5.0 finding):**
|
||||
|
||||
@@ -313,9 +313,15 @@ ps2d/usbhidd → evdevd scheme → /dev/input/event* → compositor (via wl_seat
|
||||
```
|
||||
|
||||
**Fix required**: Either:
|
||||
- **(a)** Make `ps2d` and `usbhidd` also write to `evdevd` (dual-path)
|
||||
- **(b)** Make `inputd` forward to `evdevd`
|
||||
- **(c)** Create a new `virtio-inputd` that handles QEMU virtio-input directly (preferred)
|
||||
- **(a)** Make `ps2d` and `usbhidd` also write to `evdevd` (dual-path) — pending
|
||||
- **(b)** Make `inputd` forward to `evdevd` — pending
|
||||
- **(c)** Create a new `virtio-inputd` that handles QEMU virtio-input directly (preferred) — ✅ **DONE (Phase 5.1, 2026-06-08)** at `local/recipes/drivers/virtio-inputd/`. The driver writes to `inputd` (Orbital path) via `ProducerHandle`. Phase 5.2 will add a parallel evdevd producer path for Wayland clients.
|
||||
|
||||
**Phase 5.2 follow-up (planned, not yet implemented):**
|
||||
- Add evdev-format translation to `virtio-inputd` (alongside the orbclient path)
|
||||
- Open `ProducerHandle` for `evdevd` scheme at `/scheme/evdev/producer`
|
||||
- Translate virtio_input_event → evdev `input_event` struct (16 bytes: time, type, code, value)
|
||||
- Dual-path: write to both `inputd` and `evdevd`, mirroring what (a) does for `ps2d`/`usbhidd`
|
||||
|
||||
**evdevd init integration:**
|
||||
- `evdevd` recipe installs `/usr/lib/init.d/10_evdevd.service`
|
||||
@@ -447,8 +453,8 @@ Phase 4: Wire input pipeline to compositor → 1-2 weeks
|
||||
└─ 4.3 redbear-compositor reads /dev/input/event*
|
||||
|
||||
Phase 5: Add virtio-input and virtio-snd drivers → 1-2 weeks
|
||||
├─ 5.1 virtio-input driver (or extend usbhidd)
|
||||
└─ 5.2 virtio-snd driver (or extend ihdad)
|
||||
├─ 5.1 virtio-input driver (or extend usbhidd) ✅ DONE 2026-06-08
|
||||
└─ 5.2 virtio-snd driver (or extend ihdad) 🚧 TODO
|
||||
|
||||
Phase 6: Validate end-to-end in QEMU → 1 week
|
||||
└─ 6.1 CachyOS reference comparison: launch KWin
|
||||
@@ -522,7 +528,7 @@ Total: 12-20 weeks with hardware access
|
||||
| 16 | redbear-compositor | wl_data_device, wl_subcompositor missing | 1 week |
|
||||
| 17 | input pipeline | evdevd not in init system | 1 hour |
|
||||
| 18 | input pipeline | usbhidd/ps2d send to inputd, not evdevd | 1 week |
|
||||
| 19 | input pipeline | No virtio-input driver | 1 week |
|
||||
| 19 | input pipeline | No virtio-input driver | ✅ **DONE (Phase 5.1, 2026-06-08)** |
|
||||
| 20 | seatd-redox | DRM lease TODO | 1 week |
|
||||
| 21 | audio | No virtio-snd driver | 1 week |
|
||||
| 22 | KMS | `atomic_check()` ignores connector state | 2 days |
|
||||
@@ -553,9 +559,9 @@ Total: 12-20 weeks with hardware access
|
||||
- plus inherited packages from redbear-mini profile
|
||||
|
||||
**v5.0 changes required to `redbear-full.toml`:**
|
||||
1. Add `evdevd` to the init system (place after `inputd`)
|
||||
2. Add `virtio-snd` driver (after creating it)
|
||||
3. Add `virtio-inputd` driver (after creating it)
|
||||
1. Add `evdevd` to the init system (place after `inputd`) — pending
|
||||
2. Add `virtio-snd` driver (after creating it) — pending
|
||||
3. ✅ Add `virtio-inputd` driver (DONE in v5.1, 2026-06-08) — `/etc/pcid.d/virtio-inputd.toml` matches `class=0x09 vendor=0x1af4 device_id_range=0x1042..=0x107F`
|
||||
4. Add a new `redbear-wayland-weston` or similar smoke test (if not already there)
|
||||
|
||||
---
|
||||
@@ -580,6 +586,7 @@ Total: 12-20 weeks with hardware access
|
||||
| audiod | Source + build |
|
||||
| evdevd | Source + build (no driver feeds it) |
|
||||
| inputd | Source + build + QEMU proof (Orbital path) |
|
||||
| **virtio-inputd** (Phase 5.1, NEW) | **Source + build (cargo check clean) + pcid-spawner config in `redbear-full.toml`** |
|
||||
| KWin | Source (stubbed build) |
|
||||
| **NONE of the above has the ATOMIC connector fix applied** | Needs Phase 1 work |
|
||||
|
||||
@@ -604,6 +611,20 @@ Total: 12-20 weeks with hardware access
|
||||
|
||||
---
|
||||
|
||||
## 9.1 What Changed Since v5.0 (2026-06-08 → 2026-06-08)
|
||||
|
||||
| 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`. |
|
||||
| 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. |
|
||||
|
||||
**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`).
|
||||
|
||||
---
|
||||
|
||||
## 10. Updated Execution Plan (v5.0)
|
||||
|
||||
### Phase 1: Critical DRM atomic modeset fixes (2–3 weeks)
|
||||
@@ -658,10 +679,10 @@ creates a wl_shm buffer, page-flips successfully. Mesa virgl submits a draw call
|
||||
|
||||
### Phase 5: Virtio device drivers (1–2 weeks)
|
||||
|
||||
| # | Task | File | Effort |
|
||||
|---|------|------|--------|
|
||||
| 5.1 | `virtio-inputd` driver (or extend usbhidd) | `local/recipes/drivers/virtio-inputd/` (new) | 1 week |
|
||||
| 5.2 | `virtio-snd` driver (or extend ihdad) | `local/recipes/drivers/virtio-snd/` (new) | 1 week |
|
||||
| # | Task | File | Effort | Status |
|
||||
|---|------|------|--------|--------|
|
||||
| 5.1 | `virtio-inputd` driver (or extend usbhidd) | `local/recipes/drivers/virtio-inputd/` (new) | 1 week | ✅ **DONE (2026-06-08)** — see §9.1 |
|
||||
| 5.2 | `virtio-snd` driver (or extend ihdad) | `local/recipes/drivers/virtio-snd/` (new) | 1 week | 🚧 Pending |
|
||||
|
||||
**Gate**: QEMU with `-device virtio-input` and `-device virtio-snd` works under redbear-full.
|
||||
|
||||
@@ -716,7 +737,7 @@ Week 1-3: Phase 1 — Critical DRM atomic modeset
|
||||
Week 4: Phase 2 — Mesa EGL Wayland fix
|
||||
Week 5-7: Phase 3 — redbear-compositor protocol expansion
|
||||
Week 8-9: Phase 4 — Input pipeline wiring
|
||||
Week 10-11: Phase 5 — Virtio device drivers
|
||||
Week 10-11: Phase 5 — Virtio device drivers [5.1 DONE 2026-06-08, 5.2 pending]
|
||||
Week 12: Phase 6 — QEMU end-to-end validation
|
||||
↓
|
||||
Software-rendered Wayland desktop
|
||||
@@ -798,9 +819,9 @@ These are not "nice to have" — they are how a real Wayland desktop works in 20
|
||||
- [ ] `seatd-redox` has DRM lease
|
||||
|
||||
### Gate E: Virtio device support (end of Phase 5)
|
||||
- [ ] `virtio-inputd` works (or usbhidd extended)
|
||||
- [x] `virtio-inputd` works (or usbhidd extended) — ✅ DONE 2026-06-08 (`cargo check` clean, 1180 lines)
|
||||
- [ ] `virtio-snd` works (or ihdad extended)
|
||||
- [ ] QEMU with `-device virtio-input -device virtio-snd` works
|
||||
- [ ] QEMU with `-device virtio-input -device virtio-snd` works (input done, snd pending)
|
||||
|
||||
### Gate F: QEMU End-to-End (end of Phase 6)
|
||||
- [ ] CachyOS reference comparison shows equivalent protocol/ioctl coverage
|
||||
@@ -856,6 +877,7 @@ These are not "nice to have" — they are how a real Wayland desktop works in 20
|
||||
- `local/recipes/system/evdevd/source/src/{main,device,scheme,translate,types,quirks,key_filter,gesture}.rs` — full evdev
|
||||
- `local/recipes/system/udev-shim/source/src/` — /dev/input/* creation
|
||||
- `local/recipes/drivers/redbear-input-headers/` — Linux input headers
|
||||
- **`local/recipes/drivers/virtio-inputd/` (NEW, Phase 5.1, 2026-06-08)** — QEMU virtio-input-* driver, 1180 lines Rust, `cargo check` clean
|
||||
|
||||
### Config
|
||||
- `config/redbear-full.toml` — **add evdevd to init system**
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[source]
|
||||
path = "source"
|
||||
|
||||
[build]
|
||||
template = "cargo"
|
||||
dependencies = [
|
||||
"redox-driver-sys",
|
||||
"base",
|
||||
]
|
||||
|
||||
[package.files]
|
||||
"/usr/lib/drivers/virtio-inputd" = "virtio-inputd"
|
||||
|
||||
[package.configuration]
|
||||
# virtio-inputd is launched by pcid-spawner on PCI device detection — it has
|
||||
# no init.d service file of its own. pcid-spawner matches vendor=0x1AF4 with
|
||||
# device_id >= 0x1042 and subsystem type=18 (input) to spawn this daemon.
|
||||
@@ -0,0 +1,20 @@
|
||||
[package]
|
||||
name = "virtio-inputd"
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
description = "VirtIO input device driver for Red Bear OS — handles QEMU virtio-input-host-pci, virtio-input-keyboard, virtio-input-mouse, virtio-input-tablet. Events are translated to orbclient format and pushed to inputd."
|
||||
|
||||
[[bin]]
|
||||
name = "virtio-inputd"
|
||||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
log = "0.4"
|
||||
orbclient = "0.3.55"
|
||||
libredox = { version = "=0.1.16", features = ["call", "std"] }
|
||||
redox_syscall = { version = "0.7", features = ["std"] }
|
||||
redox-driver-sys = { path = "../../redox-driver-sys/source" }
|
||||
syscall = { package = "redox_syscall", version = "0.7", features = ["std"] }
|
||||
inputd = { path = "../../../../sources/base/drivers/inputd" }
|
||||
common = { path = "../../../../sources/base/drivers/common" }
|
||||
@@ -0,0 +1,671 @@
|
||||
//! virtio-inputd — VirtIO input device driver for Red Bear OS
|
||||
//!
|
||||
//! Handles the QEMU `virtio-input-*` paravirt input devices:
|
||||
//! - `-device virtio-input-host-pci` (host passthrough)
|
||||
//! - `-device virtio-input-keyboard`
|
||||
//! - `-device virtio-input-mouse`
|
||||
//! - `-device virtio-input-tablet`
|
||||
//!
|
||||
//! ## 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.
|
||||
//!
|
||||
//! ## Event Translation
|
||||
//!
|
||||
//! 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.
|
||||
|
||||
#![forbid(unsafe_op_in_unsafe_fn)]
|
||||
|
||||
use std::env;
|
||||
use std::mem::size_of;
|
||||
use std::process;
|
||||
use std::sync::atomic::{Ordering, fence};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use inputd::ProducerHandle;
|
||||
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};
|
||||
|
||||
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_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 {
|
||||
Pci(String),
|
||||
Mmio(String),
|
||||
Initialization(String),
|
||||
Io(String),
|
||||
Buffer(String),
|
||||
Protocol(String),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for DriverError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Pci(m) | Self::Mmio(m) | Self::Initialization(m) | Self::Io(m)
|
||||
| Self::Buffer(m) | Self::Protocol(m) => write!(f, "{m}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for DriverError {}
|
||||
|
||||
pub type Result<T> = std::result::Result<T, DriverError>;
|
||||
|
||||
impl From<redox_driver_sys::DriverError> for DriverError {
|
||||
fn from(e: redox_driver_sys::DriverError) -> Self {
|
||||
Self::Pci(format!("{e}"))
|
||||
}
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
struct VirtqDesc {
|
||||
addr: u64,
|
||||
len: u32,
|
||||
flags: u16,
|
||||
next: u16,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
struct VirtqUsedElem {
|
||||
id: u32,
|
||||
len: u32,
|
||||
}
|
||||
|
||||
/// A simple virtqueue for the input device.
|
||||
///
|
||||
/// 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
|
||||
/// back to the avail ring after the device has consumed them.
|
||||
struct InputEventQueue {
|
||||
index: u16,
|
||||
size: u16,
|
||||
notify_off: u16,
|
||||
desc: DmaBuffer,
|
||||
avail: DmaBuffer,
|
||||
used: DmaBuffer,
|
||||
event_buffers: DmaBuffer,
|
||||
last_used_idx: u16,
|
||||
}
|
||||
|
||||
impl InputEventQueue {
|
||||
fn new(index: u16, qcfg: &QueueConfig) -> Result<Self> {
|
||||
let size = qcfg.size;
|
||||
let desc_bytes = usize::from(size) * size_of::<VirtqDesc>();
|
||||
let avail_bytes = 6 + usize::from(size) * 2;
|
||||
let used_bytes = 6 + usize::from(size) * size_of::<VirtqUsedElem>();
|
||||
let event_buffers_bytes = usize::from(size) * VIRTIO_INPUT_EVENT_SIZE;
|
||||
|
||||
let desc = DmaBuffer::allocate(desc_bytes, 16)?;
|
||||
let avail = DmaBuffer::allocate(avail_bytes, 2)?;
|
||||
let used = DmaBuffer::allocate(used_bytes, 4)?;
|
||||
let event_buffers = DmaBuffer::allocate(event_buffers_bytes, VIRTIO_INPUT_EVENT_SIZE)?;
|
||||
|
||||
Ok(Self {
|
||||
index,
|
||||
size,
|
||||
notify_off: qcfg.notify_off,
|
||||
desc,
|
||||
avail,
|
||||
used,
|
||||
event_buffers,
|
||||
last_used_idx: 0,
|
||||
})
|
||||
}
|
||||
|
||||
fn write_desc(&mut self, index: u16, desc: VirtqDesc) {
|
||||
let ptr = self.desc.as_mut_ptr() as *mut VirtqDesc;
|
||||
unsafe { ptr.add(index as usize).write(desc) };
|
||||
}
|
||||
|
||||
/// Submit all `size` event buffers to the device. Called once at startup.
|
||||
fn fill_avail(&mut self) {
|
||||
for i in 0..self.size {
|
||||
let buf_phys = self.event_buffers.physical_address()
|
||||
+ (i as usize) * VIRTIO_INPUT_EVENT_SIZE;
|
||||
self.write_desc(
|
||||
i,
|
||||
VirtqDesc {
|
||||
addr: buf_phys as u64,
|
||||
len: VIRTIO_INPUT_EVENT_SIZE as u32,
|
||||
flags: VIRTQ_DESC_F_WRITE,
|
||||
next: 0,
|
||||
},
|
||||
);
|
||||
}
|
||||
for i in 0..self.size {
|
||||
self.push_avail(i);
|
||||
}
|
||||
}
|
||||
|
||||
fn push_avail(&mut self, head: u16) {
|
||||
let avail_idx = self.read_avail_idx();
|
||||
let slot = usize::from(avail_idx % self.size);
|
||||
let ptr = self.avail.as_mut_ptr().wrapping_add(4 + slot * 2) as *mut u16;
|
||||
unsafe { ptr.write_unaligned(head) };
|
||||
}
|
||||
|
||||
fn write_avail_idx(&mut self, value: u16) {
|
||||
let ptr = self.avail.as_mut_ptr().wrapping_add(2) as *mut u16;
|
||||
unsafe { ptr.write_unaligned(value) };
|
||||
}
|
||||
|
||||
fn read_avail_idx(&self) -> u16 {
|
||||
let ptr = self.avail.as_ptr().wrapping_add(2) as *const u16;
|
||||
unsafe { ptr.read_unaligned() }
|
||||
}
|
||||
|
||||
fn read_used_idx(&self) -> u16 {
|
||||
let ptr = self.used.as_ptr().wrapping_add(2) as *const u16;
|
||||
unsafe { ptr.read_unaligned() }
|
||||
}
|
||||
|
||||
fn read_used_elem(&self, slot: usize) -> VirtqUsedElem {
|
||||
let offset = 4 + slot * size_of::<VirtqUsedElem>();
|
||||
let ptr = self.used.as_ptr().wrapping_add(offset) as *const VirtqUsedElem;
|
||||
unsafe { ptr.read_unaligned() }
|
||||
}
|
||||
|
||||
/// Read all completed events since last call. Returns the number drained.
|
||||
fn drain(&mut self, out: &mut Vec<VirtioInputEvent>) {
|
||||
fence(Ordering::SeqCst);
|
||||
let used_idx = self.read_used_idx();
|
||||
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];
|
||||
unsafe {
|
||||
std::ptr::copy_nonoverlapping(buf_ptr, raw.as_mut_ptr(), VIRTIO_INPUT_EVENT_SIZE);
|
||||
}
|
||||
out.push(VirtioInputEvent::read_le(&raw));
|
||||
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);
|
||||
for k in 0..drained_count {
|
||||
let id = recycled_start.wrapping_add(k);
|
||||
self.push_avail(id);
|
||||
}
|
||||
fence(Ordering::Release);
|
||||
self.write_avail_idx(self.last_used_idx);
|
||||
}
|
||||
}
|
||||
|
||||
fn desc_addr(&self) -> u64 {
|
||||
self.desc.physical_address() as u64
|
||||
}
|
||||
fn avail_addr(&self) -> u64 {
|
||||
self.avail.physical_address() as u64
|
||||
}
|
||||
fn used_addr(&self) -> u64 {
|
||||
self.used.physical_address() as u64
|
||||
}
|
||||
}
|
||||
|
||||
/// Map a Linux evdev keycode to the closest character (US QWERTY layout).
|
||||
///
|
||||
/// 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',
|
||||
}
|
||||
}
|
||||
|
||||
/// 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<Event> {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
// 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(),
|
||||
}
|
||||
}
|
||||
|
||||
fn virtio_input_probe(pci: &mut PciDevice) -> Result<bool> {
|
||||
let id = pci.read_config_word(0)?;
|
||||
if id == 0xFFFF {
|
||||
return Ok(false);
|
||||
}
|
||||
// Modern virtio-input: vendor=0x1AF4 (Red Hat virtio) + device=0x1042+ with
|
||||
// subsystem=18 (virtio_input). Legacy virtio-input (no modern transport)
|
||||
// uses device=0x1052 — but virtio-inputd intentionally requires modern.
|
||||
if pci.vendor_id()? != 0x1AF4 {
|
||||
return Ok(false);
|
||||
}
|
||||
let device = pci.device_id()?;
|
||||
if device < 0x1042 {
|
||||
return Ok(false);
|
||||
}
|
||||
// Check revision: 0x01 = modern (only one we support).
|
||||
if pci.revision()? < 1 {
|
||||
return Ok(false);
|
||||
}
|
||||
// Walk capability list to confirm there's a VIRTIO_PCI_CAP_DEVICE_CFG for
|
||||
// type=18 (virtio_input). If absent, it's not an input device.
|
||||
let cap_ptr = pci.read_config_byte(0x34)?;
|
||||
let mut offset = cap_ptr;
|
||||
let mut visited = 0u8;
|
||||
while offset != 0 && visited < 48 {
|
||||
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 {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
offset = cap_next;
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
|
||||
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<String> = env::args().skip(1).collect();
|
||||
let loc_str = args.first().ok_or_else(|| {
|
||||
DriverError::Initialization(
|
||||
"virtio-inputd: missing PCI location arg (segment:bus:device.function)".into(),
|
||||
)
|
||||
})?;
|
||||
let mut parts = loc_str.split([':', '.']);
|
||||
let segment = u16::from_str_radix(parts.next().ok_or_else(|| {
|
||||
DriverError::Initialization("missing segment".into())
|
||||
})?, 16)
|
||||
.map_err(|e| DriverError::Initialization(format!("bad segment: {e}")))?;
|
||||
let bus = u8::from_str_radix(parts.next().ok_or_else(|| {
|
||||
DriverError::Initialization("missing bus".into())
|
||||
})?, 16)
|
||||
.map_err(|e| DriverError::Initialization(format!("bad bus: {e}")))?;
|
||||
let device = u8::from_str_radix(parts.next().ok_or_else(|| {
|
||||
DriverError::Initialization("missing device".into())
|
||||
})?, 16)
|
||||
.map_err(|e| DriverError::Initialization(format!("bad device: {e}")))?;
|
||||
let function = u8::from_str_radix(parts.next().ok_or_else(|| {
|
||||
DriverError::Initialization("missing function".into())
|
||||
})?, 16)
|
||||
.map_err(|e| DriverError::Initialization(format!("bad function: {e}")))?;
|
||||
|
||||
let mut pci_device = PciDevice::open(segment, bus, device, function)?;
|
||||
let pci_info: PciDeviceInfo = pci_device.full_info()?;
|
||||
|
||||
info!(
|
||||
"virtio-inputd: probing {} {:04x}:{:04x} (rev {:02x})",
|
||||
pci_info.location,
|
||||
pci_device.vendor_id()?,
|
||||
pci_device.device_id()?,
|
||||
pci_device.revision()?,
|
||||
);
|
||||
|
||||
if !virtio_input_probe(&mut pci_device)? {
|
||||
return Err(DriverError::Protocol(format!(
|
||||
"{}:{:04x}:{:04x} is not a virtio-input device",
|
||||
pci_info.location,
|
||||
pci_device.vendor_id()?,
|
||||
pci_device.device_id()?,
|
||||
)));
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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}")));
|
||||
}
|
||||
};
|
||||
|
||||
let mut pending_events: Vec<VirtioInputEvent> = Vec::with_capacity(64);
|
||||
let mut translated: Vec<Event> = 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 {
|
||||
pending_events.clear();
|
||||
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) {
|
||||
// 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();
|
||||
}
|
||||
thread::sleep(Duration::from_millis(16));
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
common::setup_logging(
|
||||
"input",
|
||||
"pci",
|
||||
"virtio-inputd",
|
||||
common::output_level(),
|
||||
common::file_level(),
|
||||
);
|
||||
if let Err(e) = run_device() {
|
||||
error!("virtio-inputd: fatal: {e:?}");
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,509 @@
|
||||
//! 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;
|
||||
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 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;
|
||||
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")))?;
|
||||
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)
|
||||
}
|
||||
|
||||
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<()> {
|
||||
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);
|
||||
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 {
|
||||
// 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) {
|
||||
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) {
|
||||
out[i] = self.device_cfg.read8(8 + i);
|
||||
}
|
||||
cap
|
||||
}
|
||||
|
||||
pub fn config_read_absinfo(&mut self, abs_code: u8) -> AbsInfo {
|
||||
self.config_write_select(VIRTIO_INPUT_CFG_ABS_INFO, abs_code);
|
||||
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 }
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../../local/recipes/drivers/virtio-inputd
|
||||
Reference in New Issue
Block a user