Merge pull request 'feature/nikocs/keyboard' (#2) from feature/nikocs/keyboard into develop

Reviewed-on: #2
This commit is contained in:
husky 2025-09-11 18:22:56 -07:00
commit 0ee0cd2ddd
4 changed files with 415 additions and 4 deletions

View file

@ -2,4 +2,4 @@
storage_device="${1}"
qemu-system-riscv32 -machine virt -bios none -drive if=none,format=raw,file="${storage_device}",id=disk1 -device virtio-blk-device,drive=disk1 -display sdl -device virtio-gpu-device -serial stdio -m 10M -device loader,cpu-num=0,file=target/riscv32imac-unknown-none-elf/release/lbos -monitor telnet:127.0.0.1:1235,server,nowait -d guest_errors,unimp
qemu-system-riscv32 -machine virt -bios none -drive if=none,format=raw,file="${storage_device}",id=disk1 -device virtio-blk-device,drive=disk1 -display sdl -device virtio-gpu-device -device virtio-keyboard-device -serial stdio -m 10M -device loader,cpu-num=0,file=target/riscv32imac-unknown-none-elf/release/lbos -monitor telnet:127.0.0.1:1235,server,nowait -d guest_errors,unimp

View file

@ -60,9 +60,13 @@ SECTIONS {
PROVIDE(_heap_size = _MEM_END - _heap_start);
PROVIDE(_virtio_queue_1_start = ORIGIN(virtqueues));
PROVIDE(_virtio_queue_1_end = _virtio_queue_1_start + 0x10000);
PROVIDE(_virtio_queue_1_end = _virtio_queue_1_start + 0x2000);
PROVIDE(_virtio_queue_2_start = _virtio_queue_1_end);
PROVIDE(_virtio_queue_2_end = _virtio_queue_2_start + 0x10000);
PROVIDE(_virtio_queue_2_end = _virtio_queue_2_start + 0x2000);
PROVIDE(_virtio_queue_3_start = _virtio_queue_2_end);
PROVIDE(_virtio_queue_3_end = _virtio_queue_3_start + 0x2000);
PROVIDE(_virtio_queue_4_start = _virtio_queue_3_end);
PROVIDE(_virtio_queue_4_end = _virtio_queue_4_start + 0x2000);
PROVIDE(_framebuffer_start = ORIGIN(framebuffer));
}

379
src/dev/virtio/input.rs Normal file
View file

@ -0,0 +1,379 @@
use core::sync::atomic::{AtomicBool, Ordering};
use crate::dev::virtio::{
Descriptor, VIRTIO_DESC_F_WRITE, VIRTIO_MMIO_GUEST_FEATURES, VIRTIO_MMIO_GUEST_PAGE_SIZE,
VIRTIO_MMIO_QUEUE_NUM, VIRTIO_MMIO_QUEUE_NUM_MAX, VIRTIO_MMIO_QUEUE_PFN, VIRTIO_MMIO_QUEUE_SEL,
VIRTIO_MMIO_STATUS, VIRTIO_MMIO_STATUS_ACKNOWLEDGE, VIRTIO_MMIO_STATUS_DRIVER,
VIRTIO_MMIO_STATUS_DRIVER_OK, VIRTIO_MMIO_STATUS_FAILED, VIRTIO_MMIO_STATUS_FEATURES_OK,
VIRTIO_QUEUE_SIZE, VirtQueue,
};
use crate::trafficcontrol::TrafficControl;
unsafe extern "C" {
fn _virtio_queue_3_start();
fn _virtio_queue_4_start();
}
pub const VIRTIO_INPUT_EVENT_TYPE_KEYBOARD: u16 = 1;
#[repr(C)]
pub struct Event {
pub event_type: u16,
pub code: u16,
pub value: u32,
}
pub const VIRTIO_INPUT_EVENTBUFFER_SIZE: usize = 8;
pub struct VirtIoInputDevice {
pub addr: usize,
pub queue: usize,
pub idx: u16,
pub ack_used_idx: u16,
pub status_queue: usize,
pub status_queue_idx: u16,
pub status_queue_ack_used_idx: u16,
pub event_buffer: usize,
}
pub enum VirtIoInputDeviceError {
FeatureSetMismatch,
QueueSetupFailed,
}
impl VirtIoInputDevice {
pub fn new_and_init(
tc: &mut TrafficControl,
addr: usize,
) -> Result<Self, VirtIoInputDeviceError> {
// reset device (write 0 to status)
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(0);
}
// set ack bit
let mut status = VIRTIO_MMIO_STATUS_ACKNOWLEDGE;
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(status);
}
// set driver bit
status |= VIRTIO_MMIO_STATUS_DRIVER;
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(status);
}
// send feature set
let guest_features = 0; // todo: configure properly
unsafe {
((addr + VIRTIO_MMIO_GUEST_FEATURES) as *mut u32).write_volatile(guest_features);
}
status |= VIRTIO_MMIO_STATUS_FEATURES_OK;
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(status);
}
// make sure features ok is still set, otherwise failed
if unsafe { ((addr + VIRTIO_MMIO_STATUS) as *const u32).read_volatile() }
& VIRTIO_MMIO_STATUS_FEATURES_OK
== 0
{
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(VIRTIO_MMIO_STATUS_FAILED);
}
return Err(VirtIoInputDeviceError::FeatureSetMismatch);
}
// setup event queue
let queue_max_by_device =
unsafe { ((addr + VIRTIO_MMIO_QUEUE_NUM_MAX) as *const u32).read_volatile() };
if queue_max_by_device < VIRTIO_QUEUE_SIZE as _ {
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(VIRTIO_MMIO_STATUS_FAILED);
}
return Err(VirtIoInputDeviceError::QueueSetupFailed);
}
unsafe {
((addr + VIRTIO_MMIO_QUEUE_NUM) as *mut u32).write_volatile(VIRTIO_QUEUE_SIZE as _);
}
unsafe {
((addr + VIRTIO_MMIO_QUEUE_SEL) as *mut u32).write_volatile(0);
}
let event_queue_ptr = _virtio_queue_3_start as usize;
// zero out queue
for i in 0..size_of::<VirtQueue>() {
unsafe {
((event_queue_ptr + i) as *mut u8).write_volatile(0);
}
}
unsafe {
((addr + VIRTIO_MMIO_GUEST_PAGE_SIZE) as *mut u32).write_volatile(4096);
}
unsafe {
((addr + VIRTIO_MMIO_QUEUE_PFN) as *mut u32)
.write_volatile(event_queue_ptr as u32 / 4096);
}
// setup status queue
unsafe {
((addr + VIRTIO_MMIO_QUEUE_SEL) as *mut u32).write_volatile(1);
}
let status_queue_ptr = _virtio_queue_4_start as usize;
// zero out queue
for i in 0..size_of::<VirtQueue>() {
unsafe {
((status_queue_ptr + i) as *mut u8).write_volatile(0);
}
}
unsafe {
((addr + VIRTIO_MMIO_GUEST_PAGE_SIZE) as *mut u32).write_volatile(4096);
}
unsafe {
((addr + VIRTIO_MMIO_QUEUE_PFN) as *mut u32)
.write_volatile(status_queue_ptr as u32 / 4096);
}
// all done!
status |= VIRTIO_MMIO_STATUS_DRIVER_OK;
unsafe {
((addr + VIRTIO_MMIO_STATUS) as *mut u32).write_volatile(status);
}
let event_buffer_ptr = unsafe {
tc.memory_manager
.as_mut()
.unwrap_unchecked()
.alloc_n_blocks((size_of::<Event>() * VIRTIO_INPUT_EVENTBUFFER_SIZE).div_ceil(512))
};
let mut input = VirtIoInputDevice {
addr,
queue: event_queue_ptr,
idx: 0,
ack_used_idx: 0,
status_queue: status_queue_ptr,
status_queue_idx: 0,
status_queue_ack_used_idx: 0,
event_buffer: event_buffer_ptr as usize,
};
for i in 0..VIRTIO_INPUT_EVENTBUFFER_SIZE {
input.fill_event(i);
}
Ok(input)
}
fn fill_event(&mut self, eventi: usize) {
let desc = Descriptor {
addr: self.event_buffer as u64 + (eventi * size_of::<Event>()) as u64,
len: size_of::<Event>() as u32,
flags: VIRTIO_DESC_F_WRITE,
next: 0,
};
let head = self.idx;
let queue = unsafe { &mut (*(self.queue as *mut VirtQueue)) };
queue.desc[self.idx as usize] = desc;
self.idx = (self.idx + 1) % VIRTIO_QUEUE_SIZE as u16;
queue.avail.ring[queue.avail.idx as usize % VIRTIO_QUEUE_SIZE] = head;
queue.avail.idx = queue.avail.idx.wrapping_add(1);
}
pub fn pending(&mut self, tc: &mut TrafficControl) {
// event queue
let queue = unsafe { &(*(self.queue as *mut VirtQueue)) };
while self.ack_used_idx != queue.used.idx {
let elem = &queue.used.ring[self.ack_used_idx as usize % VIRTIO_QUEUE_SIZE];
self.ack_used_idx = self.ack_used_idx.wrapping_add(1);
let desc = &queue.desc[elem.id as usize];
let event = unsafe { &*(desc.addr as *const Event) };
self.fill_event(elem.id as usize);
match event.event_type {
x if x == VIRTIO_INPUT_EVENT_TYPE_KEYBOARD => {
let keycode = event.code;
let down = event.value == 1;
// first, handle shift todo: handle more control characters
if keycode == LinuxKeycode::LeftShift as u16 || keycode == LinuxKeycode::RightShift as u16 {
UPPERCASE.store(down, Ordering::Relaxed);
}
// next, handle ascii characters
if !down { // write on UP
let ascii = KEYMAP_ASCII.iter().find_map(|(a, b)| {
if keycode == *a as u16 {
Some(*b)
} else {
None
}
});
if let Some(mut ascii) = ascii {
if UPPERCASE.load(Ordering::Relaxed) {
if ascii.is_ascii_alphabetic() {
ascii = ascii.to_ascii_uppercase();
} else {
// todo: handle other characters
}
}
tc.write_inbuf(ascii);
}
}
}
_ => {}
}
}
// status queue
let queue = unsafe { &(*(self.status_queue as *mut VirtQueue)) };
while self.status_queue_ack_used_idx != queue.used.idx {
let elem =
&queue.used.ring[self.status_queue_ack_used_idx as usize % VIRTIO_QUEUE_SIZE];
let desc = &queue.desc[elem.id as usize];
let event = unsafe { &*(desc.addr as *const Event) };
self.status_queue_ack_used_idx = self.status_queue_ack_used_idx.wrapping_add(1);
}
}
}
static UPPERCASE: AtomicBool = AtomicBool::new(false);
// linux keycodes
#[repr(u16)]
#[derive(Copy, Clone)]
pub enum LinuxKeycode {
ESC = 1,
Key1 = 2,
Key2 = 3,
Key3 = 4,
Key4 = 5,
Key5 = 6,
Key6 = 7,
Key7 = 8,
Key8 = 9,
Key9 = 10,
Key0 = 11,
Minus = 12,
Equal = 13,
Backspace = 14,
Tab = 15,
Q = 16,
W = 17,
E = 18,
R = 19,
T = 20,
Y = 21,
U = 22,
I = 23,
O = 24,
P = 25,
LeftBracket = 26,
RightBracket = 27,
Enter = 28,
LeftControl = 29,
A = 30,
S = 31,
D = 32,
F = 33,
G = 34,
H = 35,
J = 36,
K = 37,
L = 38,
Semicolon = 39,
Apostrophe = 40,
Grave = 41,
LeftShift = 42,
Backslash = 43,
Z = 44,
X = 45,
C = 46,
V = 47,
B = 48,
N = 49,
M = 50,
Comma = 51,
Dot = 52,
Slash = 53,
RightShift = 54,
KPAsterisk = 55,
LeftAlt = 56,
Space = 57,
CapsLock = 58,
F1 = 59,
F2 = 60,
F3 = 61,
F4 = 62,
F5 = 63,
F6 = 64,
F7 = 65,
F8 = 66,
F9 = 67,
F10 = 68,
NumLock = 69,
ScrollLock = 70,
KP7 = 71,
KP8 = 72,
KP9 = 73,
KPMinus = 74,
KP4 = 75,
KP5 = 76,
KP6 = 77,
KPPlus = 78,
KP1 = 79,
KP2 = 80,
KP3 = 81,
KP0 = 82,
KPDot = 83,
}
// keys that do not map directly are handled outside of this
const KEYMAP_ASCII: &[(LinuxKeycode, u8)] = &[
(LinuxKeycode::ESC, 0x1B),
(LinuxKeycode::Key1, b'1'),
(LinuxKeycode::Key2, b'2'),
(LinuxKeycode::Key3, b'3'),
(LinuxKeycode::Key4, b'4'),
(LinuxKeycode::Key5, b'5'),
(LinuxKeycode::Key6, b'6'),
(LinuxKeycode::Key7, b'7'),
(LinuxKeycode::Key8, b'8'),
(LinuxKeycode::Key9, b'9'),
(LinuxKeycode::Key0, b'0'),
(LinuxKeycode::Minus, b'-'),
(LinuxKeycode::Equal, b'='),
(LinuxKeycode::Backspace, 0x7F),
(LinuxKeycode::Tab, 0x9),
(LinuxKeycode::Q, b'q'),
(LinuxKeycode::W, b'w'),
(LinuxKeycode::E, b'e'),
(LinuxKeycode::R, b'r'),
(LinuxKeycode::T, b't'),
(LinuxKeycode::Y, b'y'),
(LinuxKeycode::U, b'u'),
(LinuxKeycode::I, b'i'),
(LinuxKeycode::O, b'o'),
(LinuxKeycode::P, b'p'),
(LinuxKeycode::LeftBracket, b'['),
(LinuxKeycode::RightBracket, b']'),
(LinuxKeycode::Enter, b'\r'),
(LinuxKeycode::A, b'a'),
(LinuxKeycode::S, b's'),
(LinuxKeycode::D, b'd'),
(LinuxKeycode::F, b'f'),
(LinuxKeycode::G, b'g'),
(LinuxKeycode::H, b'h'),
(LinuxKeycode::J, b'j'),
(LinuxKeycode::K, b'k'),
(LinuxKeycode::L, b'l'),
(LinuxKeycode::Semicolon, b';'),
(LinuxKeycode::Apostrophe, b'\''),
(LinuxKeycode::Grave, b'`'),
(LinuxKeycode::Backslash, b'\\'),
(LinuxKeycode::Z, b'z'),
(LinuxKeycode::X, b'x'),
(LinuxKeycode::C, b'c'),
(LinuxKeycode::V, b'v'),
(LinuxKeycode::B, b'b'),
(LinuxKeycode::N, b'n'),
(LinuxKeycode::M, b'm'),
(LinuxKeycode::Comma, b','),
(LinuxKeycode::Dot, b'.'),
(LinuxKeycode::Slash, b'/'),
(LinuxKeycode::KPAsterisk, b'*'),
(LinuxKeycode::Space, b' '),
];

View file

@ -5,6 +5,7 @@ use core::sync::atomic::Ordering;
use crate::dev::{FRAMEBUFFER_ADDR, FRAMEBUFFER_BPP};
use crate::dev::virtio::block::{VirtIoBlockDevice, VirtIoBlockDeviceError};
use crate::dev::virtio::gpu::{VirtIoGpuDevice, VirtIoGpuDeviceError};
use crate::dev::virtio::input::{VirtIoInputDevice, VirtIoInputDeviceError};
use crate::rough_panic;
use crate::spinlock::Spinlock;
use crate::strprint::twodigit;
@ -12,6 +13,7 @@ use crate::trafficcontrol::{TrafficControl};
mod block;
mod gpu;
mod input;
pub const VIRTIO_MMIO_START: usize = 0x1000_1000;
pub const VIRTIO_MMIO_END: usize = 0x1000_8000;
@ -46,6 +48,7 @@ pub static VIRTIO_GPU_DEVICE: Spinlock<Option<u8>> = Spinlock::new(None);
pub enum VirtIoDevice {
BlockDevice(VirtIoBlockDevice),
GPUDevice(VirtIoGpuDevice),
InputDevice(VirtIoInputDevice),
}
#[repr(C)]
@ -159,6 +162,29 @@ pub fn probe_virtio_devices(tc: &mut TrafficControl) {
}
}
}
18 => {
// input device
let input_device = VirtIoInputDevice::new_and_init(tc, addr);
if let Ok(input_device) = input_device {
devices[i] = Some(VirtIoDevice::InputDevice(input_device));
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio input device found\n");
}
} else if let Err(e) = input_device {
match e {
VirtIoInputDeviceError::FeatureSetMismatch => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio input device feature mismatch\n");
}
}
VirtIoInputDeviceError::QueueSetupFailed => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("virtio input device queue setup failed\n");
}
}
}
}
}
x => {
if let Some(serial_port) = &serial_port {
serial_port.putstr("unsupported device type ");
@ -187,7 +213,6 @@ pub fn handle_interrupt(interrupt: u32, tc: &mut TrafficControl) {
}
}
VirtIoDevice::GPUDevice(gpudev) => {
return;
let gpu = {
let lock = VIRTIO_GPU_DEVICE.lock();
*lock
@ -198,6 +223,9 @@ pub fn handle_interrupt(interrupt: u32, tc: &mut TrafficControl) {
}
}
}
VirtIoDevice::InputDevice(inputdev) => {
inputdev.pending(tc);
}
}
}
}