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:
10
Cargo.lock
generated
10
Cargo.lock
generated
@@ -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",
|
||||||
|
|||||||
@@ -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" }
|
||||||
|
|||||||
5
Makefile
5
Makefile
@@ -52,5 +52,6 @@ run-gui: image
|
|||||||
-kernel $(KERNEL_BIN) \
|
-kernel $(KERNEL_BIN) \
|
||||||
-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 \
|
||||||
-serial stdio
|
-device virtio-keyboard-device \
|
||||||
|
-serial stdio
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
9
crates/drivers/input/virtio/Cargo.toml
Normal file
9
crates/drivers/input/virtio/Cargo.toml
Normal 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 }
|
||||||
159
crates/drivers/input/virtio/src/lib.rs
Normal file
159
crates/drivers/input/virtio/src/lib.rs
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user