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:
2026-10-18 04:19:19 +02:00
parent a910c06cf7
commit d43c6a7e74
8 changed files with 586 additions and 93 deletions

View File

@@ -2,6 +2,7 @@
#![no_main]
mod board;
mod boot_fs;
mod hw;
mod kernel_cell;
mod mmu;
@@ -11,7 +12,6 @@ mod trap;
use core::arch::global_asm;
use hw::uart::Uart;
use kernel_cell::KernelCell;
use ::platform::Platform;
static KERNEL: KernelCell<kernel::Kernel<platform::RiscVPlatform>> = KernelCell::uninit();
@@ -47,15 +47,8 @@ fn zero_bss() {
}
}
// Embedded init binary — in production this comes from the boot image.
// For now, a minimal program: call SYS_EXIT and loop.
static INIT_BINARY: [u8; 12] = [
0x93, 0x08, 0x00, 0x01, // addi a7, zero, 16 (SYS_EXIT)
0x73, 0x00, 0x00, 0x00, // ecall
0x6f, 0xf0, 0xdf, 0xff, // j -4
];
const INIT_LOAD_VA: usize = 0x10000;
const INIT_MAX_SIZE: usize = 64 * 1024;
#[unsafe(no_mangle)]
extern "C" fn kmain() -> ! {
@@ -82,13 +75,130 @@ extern "C" fn kmain() -> ! {
);
KERNEL.init(kernel::Kernel::new(riscv_platform, pool));
launch_init(&uart, kernel_pt);
let entry_va = load_init_from_disk(&uart, kernel_pt);
launch_init(&uart, entry_va);
}
fn launch_init(uart: &Uart, kernel_pt: &mmu::PageTable) -> ! {
let kern = kernel_ref();
fn load_init_from_disk(uart: &Uart, kernel_pt: &mmu::PageTable) -> usize {
// Probe VirtIO block device
let mut blk = match hw::virtio_blk::VirtioBlk::probe() {
Some(b) => {
uart.write_str("k-os: VirtIO block device found\n");
b
}
None => {
uart.write_str("k-os: no block device — using fallback init\n");
return load_fallback_init(kernel_pt);
}
};
let entry_va = load_init_binary(kernel_pt, uart);
// Read init.elf from FAT16 filesystem on disk
let init_page_count = (INIT_MAX_SIZE + 4095) / 4096;
let init_base = alloc_contiguous_pages(init_page_count);
let init_buf = unsafe {
core::slice::from_raw_parts_mut(init_base as *mut u8, INIT_MAX_SIZE)
};
let file_size = match boot_fs::read_file_from_disk(&mut blk, b"init.elf", init_buf) {
Some(size) => {
uart.write_str("k-os: loaded init.elf (");
uart.write_dec(size);
uart.write_str(" bytes)\n");
size
}
None => {
uart.write_str("k-os: init.elf not found on disk — using fallback\n");
return load_fallback_init(kernel_pt);
}
};
// Parse ELF
let entry_va = match elf::parse(&init_buf[..file_size]) {
Ok(elf_info) => {
uart.write_str("k-os: ELF entry point = ");
uart.write_hex(elf_info.entry_point);
uart.put_char(b'\n');
// Map each segment into user page table
let pages = (file_size + 4095) / 4096;
let _user_pt = mmu::create_user_page_table(
kernel_pt,
types::address::PhysicalAddress::from_raw(init_base),
pages,
types::address::VirtualAddress::from_raw(INIT_LOAD_VA),
);
use ::platform::Platform;
kernel_ref().platform_mut().configure_protection(
types::id::ProcessId::from_raw(0),
&[],
);
elf_info.entry_point
}
Err(_) => {
uart.write_str("k-os: not valid ELF — loading as raw binary\n");
let _user_pt = mmu::create_user_page_table(
kernel_pt,
types::address::PhysicalAddress::from_raw(init_base),
(file_size + 4095) / 4096,
types::address::VirtualAddress::from_raw(INIT_LOAD_VA),
);
use ::platform::Platform;
kernel_ref().platform_mut().configure_protection(
types::id::ProcessId::from_raw(0),
&[],
);
INIT_LOAD_VA
}
};
entry_va
}
// Fallback: minimal init that just calls SYS_EXIT (no disk needed)
static FALLBACK_INIT: [u8; 12] = [
0x93, 0x08, 0x00, 0x01, // addi a7, zero, 16 (SYS_EXIT)
0x73, 0x00, 0x00, 0x00, // ecall
0x6f, 0xf0, 0xdf, 0xff, // j -4
];
fn load_fallback_init(kernel_pt: &mmu::PageTable) -> usize {
let page = mmu::allocator::alloc_zeroed_page();
unsafe {
core::ptr::copy_nonoverlapping(
FALLBACK_INIT.as_ptr(),
page as *mut u8,
FALLBACK_INIT.len(),
);
}
let _user_pt = mmu::create_user_page_table(
kernel_pt,
types::address::PhysicalAddress::from_raw(page),
1,
types::address::VirtualAddress::from_raw(INIT_LOAD_VA),
);
use ::platform::Platform;
kernel_ref().platform_mut().configure_protection(
types::id::ProcessId::from_raw(0),
&[],
);
INIT_LOAD_VA
}
fn alloc_contiguous_pages(count: usize) -> usize {
let first = mmu::allocator::alloc_zeroed_page();
for _ in 1..count {
mmu::allocator::alloc_zeroed_page();
}
first
}
fn launch_init(uart: &Uart, entry_va: usize) -> ! {
let kern = kernel_ref();
let (pid, tid) = kern
.boot(types::bounded::Priority::DEFAULT, types::time::TimeSlice::DEFAULT)
@@ -97,36 +207,28 @@ fn launch_init(uart: &Uart, kernel_pt: &mmu::PageTable) -> ! {
loop { unsafe { core::arch::asm!("wfi") }; }
});
// Set init thread's entry point to the loaded binary
if let Some(ctx) = kern.thread_context_mut(tid) {
ctx.set_pc(entry_va);
ctx.registers_mut()[2] = INIT_LOAD_VA - 4096; // sp below program
// Set sstatus for U-mode return
ctx.registers_mut()[2] = INIT_LOAD_VA - 4096;
let mut sstatus: usize = 0;
sstatus |= 1 << 5; // SPIE — enable interrupts after sret
// SPP = 0 (U-mode) — bit 8 stays clear
sstatus |= 1 << 5; // SPIE
ctx.set_status(sstatus);
}
uart.write_str("k-os: init loaded at ");
uart.write_hex(entry_va);
uart.write_str(", entering U-mode (pid=");
uart.write_str("k-os: init loaded, entering U-mode (pid=");
uart.write_dec(pid.raw() as usize);
uart.write_str(")\n");
// Activate init's page table
let init_pt_addr = kern.platform_mut().page_table_for(pid);
if init_pt_addr != 0 {
let pt = unsafe { &*(init_pt_addr as *const mmu::PageTable) };
mmu::activate(pt);
}
// Set kernel stack in sscratch for trap handler
let kernel_sp: usize;
unsafe { core::arch::asm!("mv {0}, sp", out(reg) kernel_sp) };
trap::set_kernel_stack(kernel_sp);
// Enter user mode via sret
let user_sp = INIT_LOAD_VA - 4096;
unsafe {
core::arch::asm!(
@@ -144,75 +246,6 @@ fn launch_init(uart: &Uart, kernel_pt: &mmu::PageTable) -> ! {
}
}
fn load_init_binary(kernel_pt: &mmu::PageTable, uart: &Uart) -> usize {
// Try to parse as ELF
if let Ok(elf_info) = elf::parse(&INIT_BINARY) {
uart.write_str("k-os: init is ELF, entry=");
uart.write_hex(elf_info.entry_point);
uart.put_char(b'\n');
for segment in elf_info.segments() {
let pages = (segment.mem_size + 4095) / 4096;
for p in 0..pages {
let page = mmu::allocator::alloc_zeroed_page();
let src_offset = segment.file_offset + p * 4096;
let copy_len = if p * 4096 < segment.file_size {
(segment.file_size - p * 4096).min(4096)
} else {
0
};
if copy_len > 0 && src_offset + copy_len <= INIT_BINARY.len() {
unsafe {
core::ptr::copy_nonoverlapping(
INIT_BINARY.as_ptr().add(src_offset),
page as *mut u8,
copy_len,
);
}
}
}
}
let kern = kernel_ref();
kern.platform_mut().configure_protection(
types::id::ProcessId::from_raw(0),
&[],
);
elf_info.entry_point
} else {
// Raw binary — copy to a page and map at INIT_LOAD_VA
uart.write_str("k-os: init is raw binary, loading at ");
uart.write_hex(INIT_LOAD_VA);
uart.put_char(b'\n');
let program_page = mmu::allocator::alloc_zeroed_page();
unsafe {
core::ptr::copy_nonoverlapping(
INIT_BINARY.as_ptr(),
program_page as *mut u8,
INIT_BINARY.len(),
);
}
let _user_pt = mmu::create_user_page_table(
kernel_pt,
types::address::PhysicalAddress::from_raw(program_page),
1,
types::address::VirtualAddress::from_raw(INIT_LOAD_VA),
);
// Store the page table for init (pid 0)
let kern = kernel_ref();
kern.platform_mut().configure_protection(
types::id::ProcessId::from_raw(0),
&[],
);
INIT_LOAD_VA
}
}
#[panic_handler]
fn panic(info: &core::panic::PanicInfo) -> ! {
let uart = Uart::new();