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:
45
Makefile
Normal file
45
Makefile
Normal file
@@ -0,0 +1,45 @@
|
||||
.PHONY: check fmt clippy test kani build all image run
|
||||
|
||||
RISCV_TARGET = riscv64gc-unknown-none-elf
|
||||
KERNEL_BIN = target/$(RISCV_TARGET)/release/adapter-riscv
|
||||
|
||||
all: check fmt clippy test
|
||||
|
||||
check:
|
||||
cargo check --workspace
|
||||
|
||||
fmt:
|
||||
cargo fmt --all -- --check
|
||||
|
||||
clippy:
|
||||
cargo clippy --workspace -- -D warnings
|
||||
|
||||
test:
|
||||
cargo test --workspace
|
||||
|
||||
kani-types:
|
||||
cargo kani -p types
|
||||
|
||||
kani-domain:
|
||||
cargo kani -p domain
|
||||
|
||||
kani-kernel:
|
||||
cargo kani -p kernel
|
||||
|
||||
kani: kani-types kani-domain kani-kernel
|
||||
|
||||
build:
|
||||
cargo build -p adapter-riscv --target $(RISCV_TARGET) --release
|
||||
|
||||
image: build
|
||||
scripts/make-disk-image.sh
|
||||
|
||||
run: build
|
||||
qemu-system-riscv64 -machine virt -nographic -bios default \
|
||||
-kernel $(KERNEL_BIN)
|
||||
|
||||
run-disk: image
|
||||
qemu-system-riscv64 -machine virt -nographic -bios default \
|
||||
-kernel $(KERNEL_BIN) \
|
||||
-drive file=boot.img,format=raw,id=hd0 \
|
||||
-device virtio-blk-device,drive=hd0
|
||||
@@ -1,6 +1,8 @@
|
||||
pub const RAM_BASE: usize = 0x80000000;
|
||||
pub const RAM_SIZE: usize = 128 * 1024 * 1024;
|
||||
pub const UART_BASE: usize = 0x10000000;
|
||||
pub const VIRTIO_BASE: usize = 0x10001000;
|
||||
pub const VIRTIO_SIZE: usize = 0x8000;
|
||||
pub const PLIC_BASE: usize = 0x0C000000;
|
||||
pub const CLINT_BASE: usize = 0x02000000;
|
||||
pub const USER_POOL_BASE: usize = 0x84000000;
|
||||
|
||||
125
crates/adapter-riscv/src/boot_fs.rs
Normal file
125
crates/adapter-riscv/src/boot_fs.rs
Normal file
@@ -0,0 +1,125 @@
|
||||
use crate::hw::virtio_blk::VirtioBlk;
|
||||
|
||||
const SECTOR_SIZE: usize = 512;
|
||||
const MAX_FILE_SIZE: usize = 64 * 1024; // 64K max for init binary
|
||||
|
||||
struct BootFat16 {
|
||||
reserved_sectors: u16,
|
||||
sectors_per_fat: u16,
|
||||
root_dir_start: u32,
|
||||
root_dir_sectors: u32,
|
||||
data_start: u32,
|
||||
sectors_per_cluster: u8,
|
||||
}
|
||||
|
||||
impl BootFat16 {
|
||||
fn parse(boot_sector: &[u8; SECTOR_SIZE]) -> Option<Self> {
|
||||
if boot_sector[510] != 0x55 || boot_sector[511] != 0xAA {
|
||||
return None;
|
||||
}
|
||||
|
||||
let bytes_per_sector = u16::from_le_bytes([boot_sector[11], boot_sector[12]]);
|
||||
if bytes_per_sector != SECTOR_SIZE as u16 { return None; }
|
||||
|
||||
let sectors_per_cluster = boot_sector[13];
|
||||
let reserved_sectors = u16::from_le_bytes([boot_sector[14], boot_sector[15]]);
|
||||
let num_fats = boot_sector[16];
|
||||
let root_entry_count = u16::from_le_bytes([boot_sector[17], boot_sector[18]]);
|
||||
let sectors_per_fat = u16::from_le_bytes([boot_sector[22], boot_sector[23]]);
|
||||
|
||||
let fat_start = reserved_sectors as u32;
|
||||
let root_dir_start = fat_start + (num_fats as u32) * (sectors_per_fat as u32);
|
||||
let root_dir_sectors = ((root_entry_count as u32) * 32 + 511) / 512;
|
||||
let data_start = root_dir_start + root_dir_sectors;
|
||||
|
||||
Some(Self {
|
||||
reserved_sectors,
|
||||
sectors_per_fat,
|
||||
root_dir_start,
|
||||
root_dir_sectors,
|
||||
data_start,
|
||||
sectors_per_cluster,
|
||||
})
|
||||
}
|
||||
|
||||
fn cluster_to_sector(&self, cluster: u16) -> u32 {
|
||||
self.data_start + ((cluster as u32) - 2) * (self.sectors_per_cluster as u32)
|
||||
}
|
||||
}
|
||||
|
||||
fn to_fat_name(name: &[u8]) -> [u8; 11] {
|
||||
let mut fat = [b' '; 11];
|
||||
let dot_pos = name.iter().position(|&b| b == b'.').unwrap_or(name.len());
|
||||
for i in 0..dot_pos.min(8) { fat[i] = name[i].to_ascii_uppercase(); }
|
||||
if dot_pos < name.len() {
|
||||
for i in 0..(name.len() - dot_pos - 1).min(3) {
|
||||
fat[8 + i] = name[dot_pos + 1 + i].to_ascii_uppercase();
|
||||
}
|
||||
}
|
||||
fat
|
||||
}
|
||||
|
||||
pub fn read_file_from_disk(
|
||||
blk: &mut VirtioBlk,
|
||||
filename: &[u8],
|
||||
out_buf: &mut [u8],
|
||||
) -> Option<usize> {
|
||||
let mut sector_buf = [0u8; SECTOR_SIZE];
|
||||
|
||||
// Read boot sector
|
||||
if !blk.read_sector(0, &mut sector_buf) { return None; }
|
||||
let fs = BootFat16::parse(§or_buf)?;
|
||||
|
||||
let fat_name = to_fat_name(filename);
|
||||
|
||||
// Search root directory
|
||||
let mut first_cluster: u16 = 0;
|
||||
let mut file_size: u32 = 0;
|
||||
let mut found = false;
|
||||
|
||||
for s in 0..fs.root_dir_sectors {
|
||||
if !blk.read_sector((fs.root_dir_start + s) as u32, &mut sector_buf) { continue; }
|
||||
for i in 0..(SECTOR_SIZE / 32) {
|
||||
let off = i * 32;
|
||||
if sector_buf[off] == 0x00 { break; }
|
||||
if sector_buf[off] == 0xE5 { continue; }
|
||||
if sector_buf[off..off + 11] == fat_name {
|
||||
first_cluster = u16::from_le_bytes([sector_buf[off + 26], sector_buf[off + 27]]);
|
||||
file_size = u32::from_le_bytes([
|
||||
sector_buf[off + 28], sector_buf[off + 29],
|
||||
sector_buf[off + 30], sector_buf[off + 31],
|
||||
]);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if found { break; }
|
||||
}
|
||||
|
||||
if !found { return None; }
|
||||
|
||||
let size = (file_size as usize).min(out_buf.len());
|
||||
let mut read = 0;
|
||||
let mut cluster = first_cluster;
|
||||
let sectors_per_cluster = fs.sectors_per_cluster as u32;
|
||||
|
||||
while read < size && cluster >= 2 && cluster < 0xFFF8 {
|
||||
for s in 0..sectors_per_cluster {
|
||||
if read >= size { break; }
|
||||
let sector = fs.cluster_to_sector(cluster) + s;
|
||||
if !blk.read_sector(sector, &mut sector_buf) { return Some(read); }
|
||||
let chunk = (size - read).min(SECTOR_SIZE);
|
||||
out_buf[read..read + chunk].copy_from_slice(§or_buf[..chunk]);
|
||||
read += chunk;
|
||||
}
|
||||
|
||||
// Read next cluster from FAT
|
||||
let fat_offset = (cluster as u32) * 2;
|
||||
let fat_sector = fs.reserved_sectors as u32 + fat_offset / SECTOR_SIZE as u32;
|
||||
let fat_byte = (fat_offset % SECTOR_SIZE as u32) as usize;
|
||||
if !blk.read_sector(fat_sector, &mut sector_buf) { break; }
|
||||
cluster = u16::from_le_bytes([sector_buf[fat_byte], sector_buf[fat_byte + 1]]);
|
||||
}
|
||||
|
||||
Some(read)
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
pub mod plic;
|
||||
pub mod timer;
|
||||
pub mod uart;
|
||||
pub mod virtio_blk;
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -143,6 +143,9 @@ pub fn init_kernel_page_table() -> &'static mut PageTable {
|
||||
let (va, pa) = identity(board::CLINT_BASE);
|
||||
map(root, Mapping { va, pa, pages: 0x10000 / PAGE_SIZE, flags: Sv39Flags::kernel_rw() });
|
||||
|
||||
let (va, pa) = identity(board::VIRTIO_BASE);
|
||||
map(root, Mapping { va, pa, pages: board::VIRTIO_SIZE / PAGE_SIZE, flags: Sv39Flags::kernel_rw() });
|
||||
|
||||
root
|
||||
}
|
||||
|
||||
|
||||
111
scripts/make-disk-image.sh
Executable file
111
scripts/make-disk-image.sh
Executable file
@@ -0,0 +1,111 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
IMG="boot.img"
|
||||
IMG_SIZE_MB=4
|
||||
TARGET="riscv64gc-unknown-none-elf"
|
||||
BUILD_DIR="target/${TARGET}/release"
|
||||
|
||||
echo "=== Creating k-os boot disk image ==="
|
||||
|
||||
# Create empty image
|
||||
dd if=/dev/zero of="${IMG}" bs=1M count=${IMG_SIZE_MB} 2>/dev/null
|
||||
echo "Created ${IMG} (${IMG_SIZE_MB}MB)"
|
||||
|
||||
# Format as FAT16
|
||||
# On macOS: use newfs_msdos, on Linux: use mkfs.fat
|
||||
if command -v newfs_msdos &>/dev/null; then
|
||||
newfs_msdos -F 16 "${IMG}" >/dev/null
|
||||
elif command -v mkfs.fat &>/dev/null; then
|
||||
mkfs.fat -F 16 "${IMG}" >/dev/null
|
||||
else
|
||||
echo "Error: need newfs_msdos (macOS) or mkfs.fat (Linux)"
|
||||
exit 1
|
||||
fi
|
||||
echo "Formatted as FAT16"
|
||||
|
||||
# Mount and copy files
|
||||
MOUNT_DIR=$(mktemp -d)
|
||||
|
||||
if [[ "$(uname)" == "Darwin" ]]; then
|
||||
# macOS: use hdiutil
|
||||
DEVICE=$(hdiutil attach -nomount "${IMG}" 2>/dev/null | head -1 | awk '{print $1}')
|
||||
mount -t msdos "${DEVICE}" "${MOUNT_DIR}" 2>/dev/null || {
|
||||
echo "Warning: could not mount image. Files must be copied manually."
|
||||
echo "On macOS, try: brew install mtools"
|
||||
hdiutil detach "${DEVICE}" 2>/dev/null
|
||||
rmdir "${MOUNT_DIR}"
|
||||
|
||||
# Fallback: use mtools if available
|
||||
if command -v mcopy &>/dev/null; then
|
||||
copy_with_mtools
|
||||
else
|
||||
echo "Install mtools: brew install mtools"
|
||||
exit 0
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
|
||||
copy_files "${MOUNT_DIR}"
|
||||
umount "${MOUNT_DIR}"
|
||||
hdiutil detach "${DEVICE}" 2>/dev/null
|
||||
else
|
||||
# Linux: use mount
|
||||
sudo mount -o loop "${IMG}" "${MOUNT_DIR}"
|
||||
copy_files "${MOUNT_DIR}"
|
||||
sudo umount "${MOUNT_DIR}"
|
||||
fi
|
||||
|
||||
rmdir "${MOUNT_DIR}" 2>/dev/null
|
||||
echo "=== Done: ${IMG} ==="
|
||||
|
||||
copy_files() {
|
||||
local DIR="$1"
|
||||
mkdir -p "${DIR}/servers" "${DIR}/apps"
|
||||
|
||||
# Copy init
|
||||
if [ -f "${BUILD_DIR}/hello" ]; then
|
||||
cp "${BUILD_DIR}/hello" "${DIR}/init.elf"
|
||||
echo " Copied init.elf"
|
||||
fi
|
||||
|
||||
# Copy any server binaries that exist
|
||||
for server in display input log permission storage session; do
|
||||
if [ -f "${BUILD_DIR}/${server}-server" ]; then
|
||||
cp "${BUILD_DIR}/${server}-server" "${DIR}/servers/${server}.elf"
|
||||
echo " Copied servers/${server}.elf"
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy any app binaries that exist
|
||||
for app in snake; do
|
||||
if [ -f "${BUILD_DIR}/${app}" ]; then
|
||||
cp "${BUILD_DIR}/${app}" "${DIR}/apps/${app}.elf"
|
||||
echo " Copied apps/${app}.elf"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
copy_with_mtools() {
|
||||
echo "Using mtools to copy files..."
|
||||
mmd -i "${IMG}" ::servers ::apps 2>/dev/null || true
|
||||
|
||||
if [ -f "${BUILD_DIR}/hello" ]; then
|
||||
mcopy -i "${IMG}" "${BUILD_DIR}/hello" "::init.elf"
|
||||
echo " Copied init.elf"
|
||||
fi
|
||||
|
||||
for server in display input log permission storage session; do
|
||||
if [ -f "${BUILD_DIR}/${server}-server" ]; then
|
||||
mcopy -i "${IMG}" "${BUILD_DIR}/${server}-server" "::servers/${server}.elf"
|
||||
echo " Copied servers/${server}.elf"
|
||||
fi
|
||||
done
|
||||
|
||||
for app in snake; do
|
||||
if [ -f "${BUILD_DIR}/${app}" ]; then
|
||||
mcopy -i "${IMG}" "${BUILD_DIR}/${app}" "::apps/${app}.elf"
|
||||
echo " Copied apps/${app}.elf"
|
||||
fi
|
||||
done
|
||||
}
|
||||
Reference in New Issue
Block a user