keyboard through PCI&VirtIO

This commit is contained in:
2026-03-07 09:59:39 +01:00
parent 9b6aec28f5
commit 3947663b13
9 changed files with 325 additions and 153 deletions

View File

@@ -5,24 +5,33 @@ use core::{
use crate::{
println,
uart::write_char_uart,
virtio::{
QUEUE_SIZE, STATUS_ACKNOWLEDGE, STATUS_DRIVER, STATUS_DRIVER_OK, VIRTIO_MMIO_INTERRUPT_ACK,
VIRTIO_MMIO_QUEUE_NUM, VIRTIO_MMIO_QUEUE_NUM_MAX, VIRTIO_MMIO_QUEUE_PFN,
VIRTIO_MMIO_QUEUE_SEL, VIRTIO_MMIO_STATUS, VirtioInputEvent, Virtqueue,
},
virtio::{QUEUE_SIZE, VirtioInputEvent, Virtqueue},
};
pub struct VirtioInputDriver {
base_addr: usize,
pub struct VirtioPciDriver {
// Les 3 zones clés du PCI Modern
common_cfg: usize,
notify_cfg: usize,
isr_cfg: usize,
queue: &'static mut Virtqueue,
event_pool: [VirtioInputEvent; QUEUE_SIZE],
last_used_idx: u16,
notify_off: u32, // Multiplier from PCI notify capability (used to compute notify address)
}
impl VirtioInputDriver {
pub const unsafe fn new(base_addr: usize, queue_mem: &'static mut Virtqueue) -> Self {
impl VirtioPciDriver {
pub const unsafe fn new(
common_cfg: usize,
notify_cfg: usize,
isr_cfg: usize,
notify_mult: u32,
queue_mem: &'static mut Virtqueue,
) -> Self {
Self {
base_addr,
common_cfg,
notify_cfg,
isr_cfg,
queue: queue_mem,
event_pool: [VirtioInputEvent {
event_type: 0,
@@ -30,146 +39,127 @@ impl VirtioInputDriver {
value: 0,
}; QUEUE_SIZE],
last_used_idx: 0,
notify_off: notify_mult,
}
}
pub unsafe fn init(&mut self) {
unsafe {
// 1. Reset & Status (Ack + Driver)
self.write_reg(0x070, 0);
self.write_reg(0x070, 1 | 2);
println!("{:x}", self.common_cfg);
// --- 1. Reset & Status ---
self.write_common_u8(0x14, 0); // device_status
self.write_common_u8(0x14, 1 | 2); // ACK | DRIVER
// 2. Négociation Features (Obligatoire en Modern pour débloquer les queues)
self.write_reg(0x024, 1); // Select Page 1
let f1 = self.read_reg(0x010);
self.write_reg(0x020, f1 | 1); // On accepte VERSION_1 (Bit 32 global)
// --- 2. Négociation Features (Bit 32 = Version 1) ---
self.write_common_u32(0x08, 1); // driver_feature_select = page 1
let f1 = self.read_common_u32(0x0C); // driver_feature
self.write_common_u32(0x0C, f1 | 1); // Accepter VIRTIO_F_VERSION_1
self.write_reg(0x070, 1 | 2 | 8); // STATUS_FEATURES_OK
if (self.read_reg(0x070) & 8) == 0 {
panic!("Features rejected");
}
// 3. Configuration de la Queue
self.write_reg(0x030, 0); // Select Queue 0
let max = self.read_reg(0x034); // QUEUE_NUM_MAX
self.write_reg(0x038, QUEUE_SIZE as u32);
// 4. Envoi des adresses 64 bits (Plus besoin de PFN !)
let desc_addr = &self.queue.descriptors as *const _ as u64;
self.write_reg(0x080, (desc_addr & 0xffffffff) as u32);
self.write_reg(0x084, (desc_addr >> 32) as u32);
let avail_addr = &self.queue.available as *const _ as u64;
self.write_reg(0x090, (avail_addr & 0xffffffff) as u32);
self.write_reg(0x094, (avail_addr >> 32) as u32);
let used_addr = &self.queue.used as *const _ as u64;
self.write_reg(0x0a0, (used_addr & 0xffffffff) as u32);
self.write_reg(0x0a4, (used_addr >> 32) as u32);
// 5. REMPLISSAGE INITIAL
for i in 0..QUEUE_SIZE {
self.queue.descriptors[i].addr = &self.event_pool[i] as *const _ as u64;
self.queue.descriptors[i].len = core::mem::size_of::<VirtioInputEvent>() as u32;
self.queue.descriptors[i].flags = 2; // Writeable
self.queue.available.ring[i] = i as u16;
}
self.queue
.available
.idx
.store(QUEUE_SIZE as u16, Ordering::Release);
// 6. ACTIVATION (L'étape que tout le monde oublie en Modern)
self.write_reg(0x044, 1); // QUEUE_READY
// 7. DRIVER_OK
self.write_reg(0x070, 1 | 2 | 4 | 8);
self.activate_queue();
self.write_common_u8(0x14, 1 | 2 | 8); // FEATURES_OK
if (self.read_common_u8(0x14) & 8) == 0 {
panic!("PCI Virtio: Features rejected");
}
}
unsafe fn activate_queue(&mut self) {
// --- 3. Configuration de la Queue 0 ---
self.write_common_u16(0x16, 0); // queue_select = 0
// Fixer la taille
self.write_common_u16(0x18, QUEUE_SIZE as u16); // queue_size
// --- 4. Adresses 64 bits ---
let desc_addr = &self.queue.descriptors as *const _ as u64;
println!("desc_addr: {:x}", desc_addr);
self.write_common_u32(0x20, (desc_addr & 0xffffffff) as u32); // queue_desc_lo
self.write_common_u32(0x24, (desc_addr >> 32) as u32); // queue_desc_hi
let avail_addr = &self.queue.available as *const _ as u64;
self.write_common_u32(0x28, (avail_addr & 0xffffffff) as u32); // queue_avail_lo
self.write_common_u32(0x2C, (avail_addr >> 32) as u32); // queue_avail_hi
let used_addr = &self.queue.used as *const _ as u64;
self.write_common_u32(0x30, (used_addr & 0xffffffff) as u32); // queue_used_lo
self.write_common_u32(0x34, (used_addr >> 32) as u32); // queue_used_hi
// --- 5. Remplissage initial ---
for i in 0..QUEUE_SIZE {
self.queue.descriptors[i].addr = &self.event_pool[i] as *const _ as u64;
self.queue.descriptors[i].len = core::mem::size_of::<VirtioInputEvent>() as u32;
self.queue.descriptors[i].flags = 2; // VRING_DESC_F_WRITE
self.queue.available.ring[i] = i as u16;
}
self.queue
.available
.idx
.store(QUEUE_SIZE as u16, Ordering::Release);
// --- 6. Activation ---
self.write_common_u16(0x1C, 1); // queue_enable = 1
if self.read_common_u16(0x1C) == 0 {
panic!("Le périphérique refuse d'activer la queue !");
}
// --- 7. Driver OK (must be set after queue is enabled) ---
self.write_common_u8(0x14, 1 | 2 | 4 | 8);
// --- 8. PREMIER KICK ---
// Compute the notify address for queue 0 using the multiplier provided by PCI capability
let notify_addr = self.notify_cfg + (self.notify_off as usize * 0);
unsafe {
// 1. Sélectionner la queue
self.write_reg(0x030, 0);
// 2. Écrire la taille (doit correspondre à tes structures Rust)
self.write_reg(0x038, QUEUE_SIZE as u32);
// 3. Écrire les adresses (ORDRE CRITIQUE : LOW puis HIGH)
let desc_addr = &self.queue.descriptors as *const _ as u64;
self.write_reg(0x080, desc_addr as u32); // DescLow
self.write_reg(0x084, (desc_addr >> 32) as u32); // DescHigh
let avail_addr = &self.queue.available as *const _ as u64;
self.write_reg(0x090, avail_addr as u32); // AvailLow
self.write_reg(0x094, (avail_addr >> 32) as u32); // AvailHigh
let used_addr = &self.queue.used as *const _ as u64;
self.write_reg(0x0a0, used_addr as u32); // UsedLow
self.write_reg(0x0a4, (used_addr >> 32) as u32); // UsedHigh
// 4. LE KICK : Activer la queue
self.write_reg(0x044, 1); // QUEUE_READY = 1
// 5. SYNC : On s'assure que le périphérique a bien pris le READY
if self.read_reg(0x044) == 0 {
panic!("La queue refuse de passer en READY. Vérifiez les adresses !");
}
core::ptr::write_volatile(notify_addr as *mut u16, 0); // 0 = index de la queue
}
}
/// Appelé lors d'une interruption clavier
pub fn handle_interrupt(&mut self) {
let used_idx = self.queue.used.idx.load(Ordering::Acquire);
// 1. Lire et acquitter l'ISR PCI (Indispensable pour baisser la ligne IRQ)
let isr = unsafe { read_volatile(self.isr_cfg as *const u8) };
if isr & 1 == 0 {
return;
} // Pas une interruption de queue
let used_idx = self.queue.used.idx.load(Ordering::Acquire);
while self.last_used_idx != used_idx {
let ring_slot = self.last_used_idx as usize % QUEUE_SIZE;
let used_elem = &self.queue.used.ring[ring_slot];
let event = &self.event_pool[used_elem.id as usize];
if event.event_type == 1 {
// EV_KEY
self.on_key(event.code, event.value);
println!("key pressed");
// self.on_key(event.code, event.value);
}
// Recyclage du descripteur : on le rend disponible à nouveau
let avail_idx = self.queue.available.idx.load(Ordering::Relaxed) as usize % QUEUE_SIZE;
self.queue.available.ring[avail_idx] = used_elem.id as u16;
// Recyclage
let avail_head = self.queue.available.idx.load(Ordering::Relaxed) as usize % QUEUE_SIZE;
self.queue.available.ring[avail_head] = used_elem.id as u16;
self.queue.available.idx.fetch_add(1, Ordering::Release);
self.last_used_idx = self.last_used_idx.wrapping_add(1);
}
// Acquitter l'interruption
// Notifier qu'on a traité les buffers (si besoin)
unsafe {
self.write_reg(VIRTIO_MMIO_INTERRUPT_ACK, 1);
write_volatile(self.notify_cfg as *mut u16, 0);
}
}
fn on_key(&self, code: u16, value: u32) {
let state = match value {
1 => "Pressed",
0 => "Released",
2 => "Repeat",
_ => "Unknown",
};
// write_char_uart((b'0' + (code / 10) as u8 % 10) as char);
// write_char_uart((b'0' + (code % 10) as u8) as char);
write_char_uart(code as u8 as char);
write_char_uart('\n');
write_char_uart('\r');
// Ici, implémentez votre conversion Scancode -> ASCII
// println!("Key Code: {} - State: {}", code, state);
// Helpers de lecture/écriture (Note : PCI utilise des tailles mixtes u8/u16/u32)
unsafe fn write_common_u8(&self, off: usize, val: u8) {
write_volatile((self.common_cfg + off) as *mut u8, val);
}
unsafe fn write_reg(&self, offset: usize, val: u32) {
unsafe { write_volatile((self.base_addr + offset) as *mut u32, val) };
unsafe fn write_common_u16(&self, off: usize, val: u16) {
write_volatile((self.common_cfg + off) as *mut u16, val);
}
unsafe fn read_reg(&self, offset: usize) -> u32 {
unsafe { read_volatile((self.base_addr + offset) as *const u32) }
unsafe fn write_common_u32(&self, off: usize, val: u32) {
write_volatile((self.common_cfg + off) as *mut u32, val);
}
unsafe fn read_common_u8(&self, off: usize) -> u8 {
read_volatile((self.common_cfg + off) as *const u8)
}
unsafe fn read_common_u16(&self, off: usize) -> u16 {
read_volatile((self.common_cfg + off) as *const u16)
}
unsafe fn read_common_u32(&self, off: usize) -> u32 {
read_volatile((self.common_cfg + off) as *const u32)
}
}
@@ -177,16 +167,20 @@ pub const PLIC_BASE: usize = 0x0c00_0000;
pub const IRQ_VIRTIO: u32 = 1;
pub const S_MODE_CLAIM_COMPLETE: *mut u32 = 0x0c20_1004 as *mut u32;
pub unsafe fn init_plic_m_mode() {
// 1. Priority : identique pour tous les modes
let priority_ptr = (PLIC_BASE + 4 * IRQ_VIRTIO as usize) as *mut u32;
pub fn init_plic_pci(irq: u32) {
let priority_ptr = (PLIC_BASE + (irq as usize * 4)) as *mut u32;
unsafe { priority_ptr.write_volatile(1) };
// 2. Enable : Pour Hart 0 M-Mode, l'offset est 0x2000
let enable_ptr = (PLIC_BASE + 0x2080) as *mut u32;
unsafe { enable_ptr.write_volatile(1 << IRQ_VIRTIO) };
// 3. Threshold : Pour Hart 0 M-Mode, l'offset est 0x200000
// Mettre le Threshold à 0 pour Hart 0 S-Mode (offset 0x201000)
let threshold_ptr = (PLIC_BASE + 0x201000) as *mut u32;
unsafe { threshold_ptr.write_volatile(0) };
// Activer l'IRQ pour le S-Mode (Hart 0)
// Attention : l'enable bit pour l'IRQ 33 est le bit 1 du deuxième mot u32
let enable_reg_offset = (irq / 32) as usize * 4;
let enable_ptr = (PLIC_BASE + 0x2080 + enable_reg_offset) as *mut u32;
unsafe {
let current = enable_ptr.read_volatile();
enable_ptr.write_volatile(current | (1 << (irq % 32)));
}
}