feat(adapter-riscv): load init from VirtIO block device with FAT16 filesystem
Adapter probes for VirtIO block device, reads init.elf from FAT16 disk, parses ELF, maps into userspace, and launches. Falls back to embedded minimal init if no disk is present. Also: VirtIO MMIO driver, boot FAT16 reader, disk image build script, Makefile targets for build/run/run-disk.
This commit is contained in:
173
crates/adapter-riscv/src/hw/virtio_blk.rs
Normal file
173
crates/adapter-riscv/src/hw/virtio_blk.rs
Normal file
@@ -0,0 +1,173 @@
|
||||
const VIRTIO_MMIO_BASE: usize = 0x10001000;
|
||||
const VIRTIO_MAGIC: u32 = 0x74726976;
|
||||
const VIRTIO_BLK_DEVICE_ID: u32 = 2;
|
||||
|
||||
const VIRTIO_STATUS_ACK: u32 = 1;
|
||||
const VIRTIO_STATUS_DRIVER: u32 = 2;
|
||||
const VIRTIO_STATUS_FEATURES_OK: u32 = 8;
|
||||
const VIRTIO_STATUS_DRIVER_OK: u32 = 4;
|
||||
|
||||
const QUEUE_SIZE: usize = 8;
|
||||
const SECTOR_SIZE: usize = 512;
|
||||
|
||||
#[repr(C, align(16))]
|
||||
struct VirtqDesc {
|
||||
addr: u64,
|
||||
len: u32,
|
||||
flags: u16,
|
||||
next: u16,
|
||||
}
|
||||
|
||||
#[repr(C, align(2))]
|
||||
struct VirtqAvail {
|
||||
flags: u16,
|
||||
idx: u16,
|
||||
ring: [u16; QUEUE_SIZE],
|
||||
}
|
||||
|
||||
#[repr(C, align(4))]
|
||||
struct VirtqUsed {
|
||||
flags: u16,
|
||||
idx: u16,
|
||||
ring: [VirtqUsedElem; QUEUE_SIZE],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct VirtqUsedElem {
|
||||
id: u32,
|
||||
len: u32,
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
struct VirtioBlkReqHeader {
|
||||
req_type: u32,
|
||||
reserved: u32,
|
||||
sector: u64,
|
||||
}
|
||||
|
||||
static mut DESCS: [VirtqDesc; QUEUE_SIZE] = [const {
|
||||
VirtqDesc { addr: 0, len: 0, flags: 0, next: 0 }
|
||||
}; QUEUE_SIZE];
|
||||
|
||||
static mut AVAIL: VirtqAvail = VirtqAvail {
|
||||
flags: 0, idx: 0, ring: [0; QUEUE_SIZE],
|
||||
};
|
||||
|
||||
static mut USED: VirtqUsed = VirtqUsed {
|
||||
flags: 0, idx: 0, ring: [const { VirtqUsedElem { id: 0, len: 0 } }; QUEUE_SIZE],
|
||||
};
|
||||
|
||||
static mut REQ_HEADER: VirtioBlkReqHeader = VirtioBlkReqHeader {
|
||||
req_type: 0, reserved: 0, sector: 0,
|
||||
};
|
||||
|
||||
static mut REQ_STATUS: u8 = 0xFF;
|
||||
|
||||
fn mmio_read(offset: usize) -> u32 {
|
||||
unsafe { core::ptr::read_volatile((VIRTIO_MMIO_BASE + offset) as *const u32) }
|
||||
}
|
||||
|
||||
fn mmio_write(offset: usize, val: u32) {
|
||||
unsafe { core::ptr::write_volatile((VIRTIO_MMIO_BASE + offset) as *mut u32, val) }
|
||||
}
|
||||
|
||||
pub struct VirtioBlk {
|
||||
initialized: bool,
|
||||
}
|
||||
|
||||
impl VirtioBlk {
|
||||
pub fn probe() -> Option<Self> {
|
||||
let magic = mmio_read(0x000);
|
||||
let device_id = mmio_read(0x008);
|
||||
|
||||
if magic != VIRTIO_MAGIC || device_id != VIRTIO_BLK_DEVICE_ID {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Reset
|
||||
mmio_write(0x070, 0);
|
||||
|
||||
// Acknowledge
|
||||
mmio_write(0x070, VIRTIO_STATUS_ACK);
|
||||
mmio_write(0x070, VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER);
|
||||
|
||||
// No feature negotiation needed for basic read
|
||||
mmio_write(0x024, 0); // DriverFeaturesSel = 0
|
||||
mmio_write(0x020, 0); // DriverFeatures = 0
|
||||
mmio_write(0x070, VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_FEATURES_OK);
|
||||
|
||||
let status = mmio_read(0x070);
|
||||
if status & VIRTIO_STATUS_FEATURES_OK == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Set up queue 0
|
||||
mmio_write(0x030, 0); // QueueSel = 0
|
||||
mmio_write(0x038, QUEUE_SIZE as u32); // QueueNum
|
||||
|
||||
unsafe {
|
||||
let desc_addr = core::ptr::addr_of!(DESCS) as u64;
|
||||
let avail_addr = core::ptr::addr_of!(AVAIL) as u64;
|
||||
let used_addr = core::ptr::addr_of!(USED) as u64;
|
||||
|
||||
mmio_write(0x080, desc_addr as u32);
|
||||
mmio_write(0x084, (desc_addr >> 32) as u32);
|
||||
mmio_write(0x090, avail_addr as u32);
|
||||
mmio_write(0x094, (avail_addr >> 32) as u32);
|
||||
mmio_write(0x0A0, used_addr as u32);
|
||||
mmio_write(0x0A4, (used_addr >> 32) as u32);
|
||||
}
|
||||
|
||||
mmio_write(0x044, 1); // QueueReady
|
||||
mmio_write(0x070, VIRTIO_STATUS_ACK | VIRTIO_STATUS_DRIVER | VIRTIO_STATUS_FEATURES_OK | VIRTIO_STATUS_DRIVER_OK);
|
||||
|
||||
Some(Self { initialized: true })
|
||||
}
|
||||
|
||||
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
|
||||
if !self.initialized { return false; }
|
||||
|
||||
unsafe {
|
||||
REQ_HEADER.req_type = 0; // read
|
||||
REQ_HEADER.reserved = 0;
|
||||
REQ_HEADER.sector = sector as u64;
|
||||
REQ_STATUS = 0xFF;
|
||||
|
||||
// Descriptor 0: header (device reads)
|
||||
DESCS[0].addr = core::ptr::addr_of!(REQ_HEADER) as u64;
|
||||
DESCS[0].len = 16;
|
||||
DESCS[0].flags = 1; // NEXT
|
||||
DESCS[0].next = 1;
|
||||
|
||||
// Descriptor 1: data buffer (device writes)
|
||||
DESCS[1].addr = buf.as_ptr() as u64;
|
||||
DESCS[1].len = SECTOR_SIZE as u32;
|
||||
DESCS[1].flags = 2 | 1; // WRITE | NEXT
|
||||
DESCS[1].next = 2;
|
||||
|
||||
// Descriptor 2: status (device writes)
|
||||
DESCS[2].addr = core::ptr::addr_of!(REQ_STATUS) as u64;
|
||||
DESCS[2].len = 1;
|
||||
DESCS[2].flags = 2; // WRITE
|
||||
DESCS[2].next = 0;
|
||||
|
||||
// Add to available ring
|
||||
let avail_idx = AVAIL.idx;
|
||||
AVAIL.ring[(avail_idx as usize) % QUEUE_SIZE] = 0;
|
||||
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
||||
AVAIL.idx = avail_idx.wrapping_add(1);
|
||||
core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst);
|
||||
|
||||
// Notify device
|
||||
mmio_write(0x050, 0);
|
||||
|
||||
// Wait for completion
|
||||
let expected = avail_idx.wrapping_add(1);
|
||||
while core::ptr::read_volatile(core::ptr::addr_of!(USED.idx)) != expected {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
REQ_STATUS == 0
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user