feat: VirtIO keyboard input for QEMU GUI

- Add VirtIO input device driver (device ID 18) reading from eventq
- Translate Linux keycodes (WASD, arrows) to InputEvent
- Kernel probes for VirtIO input device, falls back to UART
- Add -device virtio-keyboard-device to run-gui Makefile target
- input-qemu tries VirtIO first, falls back to UART
This commit is contained in:
2026-10-19 00:30:20 +02:00
parent 68849f698b
commit 0a47b5489a
9 changed files with 259 additions and 12 deletions

10
Cargo.lock generated
View File

@@ -114,6 +114,15 @@ dependencies = [
"port-input-device", "port-input-device",
] ]
[[package]]
name = "driver-input-virtio"
version = "0.1.0"
dependencies = [
"hw-virtio",
"port-input-device",
"types",
]
[[package]] [[package]]
name = "echo" name = "echo"
version = "0.1.0" version = "0.1.0"
@@ -164,6 +173,7 @@ name = "input-qemu"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"driver-input-uart", "driver-input-uart",
"driver-input-virtio",
"input-server", "input-server",
"server-framework", "server-framework",
"types", "types",

View File

@@ -51,6 +51,7 @@ members = [
"crates/bins/input-qemu", "crates/bins/input-qemu",
"crates/bins/storage-qemu", "crates/bins/storage-qemu",
"crates/drivers/input/uart", "crates/drivers/input/uart",
"crates/drivers/input/virtio",
"crates/hw-virtio", "crates/hw-virtio",
] ]
resolver = "2" resolver = "2"
@@ -108,6 +109,7 @@ driver-framebuffer-virtio = { path = "crates/drivers/framebuffer/virtio" }
driver-framebuffer-mock = { path = "crates/drivers/framebuffer/mock" } driver-framebuffer-mock = { path = "crates/drivers/framebuffer/mock" }
driver-block-virtio = { path = "crates/drivers/block/virtio" } driver-block-virtio = { path = "crates/drivers/block/virtio" }
driver-input-uart = { path = "crates/drivers/input/uart" } driver-input-uart = { path = "crates/drivers/input/uart" }
driver-input-virtio = { path = "crates/drivers/input/virtio" }
# apps # apps
snake = { path = "crates/apps/snake" } snake = { path = "crates/apps/snake" }

View File

@@ -53,4 +53,5 @@ run-gui: image
-drive file=boot.img,if=none,format=raw,id=hd0 \ -drive file=boot.img,if=none,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 \ -device virtio-blk-device,drive=hd0 \
-device virtio-gpu-device \ -device virtio-gpu-device \
-device virtio-keyboard-device \
-serial stdio -serial stdio

View File

@@ -3,18 +3,66 @@ use crate::{board, mmu};
use types::id::ProcessId; use types::id::ProcessId;
const UART_VA: usize = 0x20000; const UART_VA: usize = 0x20000;
const INPUT_MMIO_VA: usize = 0x25000;
const INPUT_VQUEUE_VA: usize = 0x26000;
const INPUT_EVBUF_VA: usize = 0x28000;
const INPUT_PHYSINFO_VA: usize = 0x29000;
fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() }
}
pub fn configure(uart: &Uart, input_pid: ProcessId) { pub fn configure(uart: &Uart, input_pid: ProcessId) {
let kern = unsafe { &mut *crate::kernel_ptr() }; let pt_addr = kern().platform_mut().page_table_for(input_pid);
let pt_addr = kern.platform_mut().page_table_for(input_pid);
let pt = unsafe { &mut *(pt_addr as *mut mmu::PageTable) }; let pt = unsafe { &mut *(pt_addr as *mut mmu::PageTable) };
// Always map UART for fallback
map_page(pt, UART_VA, board::UART_BASE);
// Try to find VirtIO input device
if let Some(mmio) = hw_virtio::mmio::scan_for_device(hw_virtio::mmio::DEVICE_ID_INPUT) {
let base = mmio.base();
map_page(pt, INPUT_MMIO_VA, base);
let vq_phys = alloc_contiguous(2);
map_range(pt, INPUT_VQUEUE_VA, vq_phys, 2);
let evbuf_phys = mmu::allocator::alloc_zeroed_page();
map_page(pt, INPUT_EVBUF_VA, evbuf_phys);
let info_phys = mmu::allocator::alloc_zeroed_page();
map_page(pt, INPUT_PHYSINFO_VA, info_phys);
unsafe {
core::ptr::write_volatile(info_phys as *mut u64, evbuf_phys as u64);
core::ptr::write_volatile((info_phys as *mut u64).add(1), vq_phys as u64);
}
hw_virtio::mmio::init_legacy_device(&mmio, vq_phys, 32);
uart.write_str("k-os: VirtIO keyboard configured\n");
} else {
uart.write_str("k-os: UART mapped for input\n");
}
}
fn map_page(pt: &mut mmu::PageTable, va: usize, pa: usize) {
mmu::map(pt, mmu::Mapping { mmu::map(pt, mmu::Mapping {
va: types::address::VirtualAddress::from_raw(UART_VA), va: types::address::VirtualAddress::from_raw(va),
pa: types::address::PhysicalAddress::from_raw(board::UART_BASE), pa: types::address::PhysicalAddress::from_raw(pa),
pages: 1, pages: 1,
flags: mmu::Sv39Flags::user_rw(), flags: mmu::Sv39Flags::user_rw(),
}); });
}
uart.write_str("k-os: UART mapped for input\n");
fn map_range(pt: &mut mmu::PageTable, va_base: usize, pa_base: usize, count: usize) {
for i in 0..count {
map_page(pt, va_base + i * 4096, pa_base + i * 4096);
}
}
fn alloc_contiguous(pages: usize) -> usize {
let first = mmu::allocator::alloc_zeroed_page();
for _ in 1..pages {
mmu::allocator::alloc_zeroed_page();
}
first
} }

View File

@@ -15,3 +15,4 @@ userspace = { workspace = true }
server-framework = { workspace = true } server-framework = { workspace = true }
input-server = { workspace = true } input-server = { workspace = true }
driver-input-uart = { workspace = true } driver-input-uart = { workspace = true }
driver-input-virtio = { workspace = true }

View File

@@ -3,14 +3,30 @@
extern crate userspace_rt; extern crate userspace_rt;
use driver_input_uart::UartInputDevice;
use types::bounded::CapSlot; use types::bounded::CapSlot;
#[unsafe(no_mangle)] #[unsafe(no_mangle)]
extern "C" fn main() { extern "C" fn main() {
let device = UartInputDevice::new();
let mut srv = input_server::server::InputServer::new(device);
let ep = CapSlot::new(0).unwrap(); let ep = CapSlot::new(0).unwrap();
server_framework::serve(&mut srv, ep);
if let Some(dev) = driver_input_virtio::VirtioInputDevice::try_new() {
run_input(dev, ep);
} else {
run_uart(ep);
}
loop { userspace::syscall::yield_now(); } loop { userspace::syscall::yield_now(); }
} }
#[inline(never)]
fn run_input(device: driver_input_virtio::VirtioInputDevice, ep: CapSlot) {
let mut srv = input_server::server::InputServer::new(device);
server_framework::serve(&mut srv, ep);
}
#[inline(never)]
fn run_uart(ep: CapSlot) {
let device = driver_input_uart::UartInputDevice::new();
let mut srv = input_server::server::InputServer::new(device);
server_framework::serve(&mut srv, ep);
}

View File

@@ -0,0 +1,9 @@
[package]
name = "driver-input-virtio"
version = "0.1.0"
edition = "2024"
[dependencies]
port-input-device = { workspace = true }
hw-virtio = { workspace = true }
types = { workspace = true }

View File

@@ -0,0 +1,159 @@
#![no_std]
use hw_virtio::mmio::MmioAccess;
use port_input_device::{InputDevice, InputEvent, Key};
const INPUT_MMIO_VA: usize = 0x25000;
const VQUEUE_VA: usize = 0x26000;
const EVBUF_VA: usize = 0x28000;
const PHYSINFO_VA: usize = 0x29000;
const QUEUE_SIZE: usize = 32;
const EVENT_SIZE: usize = 8;
const EV_KEY: u16 = 1;
const KEY_PRESS: u32 = 1;
struct MmioInput;
impl hw_virtio::mmio::MmioAccess for MmioInput {
fn read(&self, offset: usize) -> u32 {
unsafe { core::ptr::read_volatile((INPUT_MMIO_VA + offset) as *const u32) }
}
fn write(&self, offset: usize, val: u32) {
unsafe { core::ptr::write_volatile((INPUT_MMIO_VA + offset) as *mut u32, val) }
}
}
pub struct VirtioInputDevice {
last_used: u16,
next_avail: u16,
}
impl VirtioInputDevice {
pub fn try_new() -> Option<Self> {
let phys = unsafe { core::ptr::read_volatile(PHYSINFO_VA as *const u64) };
if phys == 0 { return None; }
let mut dev = Self { last_used: 0, next_avail: 0 };
dev.fill_queue();
Some(dev)
}
fn fill_queue(&mut self) {
let evbuf_phys = unsafe { core::ptr::read_volatile(PHYSINFO_VA as *const u64) } as usize;
let desc_base = VQUEUE_VA;
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
for i in 0..QUEUE_SIZE {
let desc = desc_base + i * 16;
let buf_pa = evbuf_phys + i * EVENT_SIZE;
unsafe {
core::ptr::write_volatile(desc as *mut u64, buf_pa as u64);
core::ptr::write_volatile((desc + 8) as *mut u32, EVENT_SIZE as u32);
core::ptr::write_volatile((desc + 12) as *mut u16, 2); // WRITE flag
core::ptr::write_volatile((desc + 14) as *mut u16, 0); // no next
}
unsafe {
let ring_entry = avail_base + 4 + (i % QUEUE_SIZE) * 2;
core::ptr::write_volatile(ring_entry as *mut u16, i as u16);
}
}
unsafe {
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
core::ptr::write_volatile((avail_base + 2) as *mut u16, QUEUE_SIZE as u16);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
}
let mmio = MmioInput;
mmio.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
self.next_avail = QUEUE_SIZE as u16;
}
fn resubmit(&mut self, desc_idx: u16) {
let avail_base = VQUEUE_VA + QUEUE_SIZE * 16;
let slot = self.next_avail as usize % QUEUE_SIZE;
unsafe {
core::ptr::write_volatile((avail_base + 4 + slot * 2) as *mut u16, desc_idx);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
self.next_avail = self.next_avail.wrapping_add(1);
core::ptr::write_volatile((avail_base + 2) as *mut u16, self.next_avail);
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
}
let mmio = MmioInput;
mmio.write(hw_virtio::mmio::REG_QUEUE_NOTIFY, 0);
}
fn read_event(&self, desc_idx: u16) -> (u16, u16, u32) {
let buf_va = EVBUF_VA + (desc_idx as usize) * EVENT_SIZE;
unsafe {
let ev_type = core::ptr::read_volatile(buf_va as *const u16);
let ev_code = core::ptr::read_volatile((buf_va + 2) as *const u16);
let ev_value = core::ptr::read_volatile((buf_va + 4) as *const u32);
(ev_type, ev_code, ev_value)
}
}
fn used_idx(&self) -> u16 {
let used_base = VQUEUE_VA + 4096;
unsafe { core::ptr::read_volatile((used_base + 2) as *const u16) }
}
fn used_entry_id(&self, idx: u16) -> u16 {
let used_base = VQUEUE_VA + 4096;
let entry = used_base + 4 + (idx as usize % QUEUE_SIZE) * 8;
unsafe { core::ptr::read_volatile(entry as *const u32) as u16 }
}
}
fn linux_keycode_to_key(code: u16) -> Option<Key> {
match code {
17 => Some(Key::Up), // KEY_W
31 => Some(Key::Down), // KEY_S
30 => Some(Key::Left), // KEY_A
32 => Some(Key::Right), // KEY_D
103 => Some(Key::Up), // KEY_UP
108 => Some(Key::Down), // KEY_DOWN
105 => Some(Key::Left), // KEY_LEFT
106 => Some(Key::Right),// KEY_RIGHT
28 => Some(Key::Enter), // KEY_ENTER
1 => Some(Key::Escape), // KEY_ESC
57 => Some(Key::Space), // KEY_SPACE
14 => Some(Key::Backspace), // KEY_BACKSPACE
15 => Some(Key::Tab), // KEY_TAB
_ => None,
}
}
impl InputDevice for VirtioInputDevice {
fn poll(&mut self) -> Option<InputEvent> {
let current_used = self.used_idx();
if current_used == self.last_used {
return None;
}
let desc_idx = self.used_entry_id(self.last_used);
self.last_used = self.last_used.wrapping_add(1);
let (ev_type, ev_code, ev_value) = self.read_event(desc_idx);
self.resubmit(desc_idx);
if ev_type == EV_KEY {
if let Some(key) = linux_keycode_to_key(ev_code) {
if ev_value == KEY_PRESS {
return Some(InputEvent::KeyDown(key));
} else if ev_value == 0 {
return Some(InputEvent::KeyUp(key));
}
}
}
None
}
fn has_event(&self) -> bool {
self.used_idx() != self.last_used
}
}

View File

@@ -26,6 +26,7 @@ pub const STATUS_DRIVER_OK: u32 = 4;
pub const DEVICE_ID_BLOCK: u32 = 2; pub const DEVICE_ID_BLOCK: u32 = 2;
pub const DEVICE_ID_GPU: u32 = 16; pub const DEVICE_ID_GPU: u32 = 16;
pub const DEVICE_ID_INPUT: u32 = 18;
pub const PAGE_SIZE: usize = 4096; pub const PAGE_SIZE: usize = 4096;