feat(adapter): init+storage packed into kernel image; drop adapter FAT16 reader + virtio-blk

This commit is contained in:
2026-10-27 16:58:11 +01:00
parent b80303f553
commit 4030dd4315
10 changed files with 43 additions and 265 deletions

View File

@@ -14,7 +14,6 @@ domain = { workspace = true }
elf = { workspace = true }
platform = { workspace = true }
kernel = { workspace = true }
hw-virtio = { workspace = true }
[profile.release]
panic = "abort"

View File

@@ -1,5 +1,26 @@
use std::path::PathBuf;
fn main() {
let ld = std::path::Path::new("linker.ld").canonicalize().unwrap();
println!("cargo:rustc-link-arg=-T{}", ld.display());
println!("cargo:rerun-if-changed=linker.ld");
// Init and storage are packed into the kernel image: they are what
// reads the disk, so they cannot come from it. `make build` builds them
// first; without them the image boots to "init missing".
let manifest = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap());
let bins = manifest.join("../../target/riscv64gc-unknown-none-elf/release");
let out = PathBuf::from(std::env::var("OUT_DIR").unwrap());
for (var, bin) in [("KOS_INIT_ELF", "init"), ("KOS_STORAGE_ELF", "storage-qemu")] {
let path = bins.join(bin);
println!("cargo:rerun-if-changed={}", path.display());
let path = if path.is_file() {
path
} else {
let empty = out.join(bin);
std::fs::write(&empty, []).unwrap();
empty
};
println!("cargo:rustc-env={}={}", var, path.display());
}
}

View File

@@ -1,5 +1,4 @@
use crate::hw::uart::Uart;
use crate::hw::virtio_blk::VirtioBlk;
use crate::{KERNEL, board, mmu, platform, trap};
unsafe extern "C" {
@@ -72,16 +71,6 @@ pub fn init(uart: &Uart, boot_hart: usize) -> &'static mut mmu::PageTable {
kernel_pt
}
pub fn probe_block_device(uart: &Uart) -> Option<VirtioBlk> {
let blk = VirtioBlk::probe();
if blk.is_some() {
uart.write_str("k-os: VirtIO block device found\n");
} else {
uart.write_str("k-os: no block device\n");
}
blk
}
fn zero_bss() {
unsafe {
let start = &_bss_start as *const u8 as *mut u8;

View File

@@ -1,6 +1,5 @@
use crate::hw::uart::Uart;
use crate::hw::virtio_blk::VirtioBlk;
use crate::{boot_fs, mmu};
use crate::mmu;
use types::address::VirtualAddress;
use types::budget::Limits;
use types::id::{ProcessId, ThreadId};
@@ -10,7 +9,6 @@ const PAGE_SIZE: usize = mmu::PAGE_SIZE;
const USER_VA_BASE: usize = 0x10000;
/// The stack sits just below the image and stops short of the null page.
const USER_STACK_PAGES: usize = USER_VA_BASE / PAGE_SIZE - 1;
const MAX_FILE_SIZE: usize = 64 * 1024;
pub struct LoadedProcess {
pub pid: ProcessId,
@@ -20,23 +18,11 @@ fn kern() -> &'static mut kernel::Kernel<crate::platform::RiscVPlatform> {
unsafe { &mut *crate::kernel_ptr() }
}
/// Scratch space for reading ELF files, allocated once from the boot heap.
fn elf_buffer() -> &'static mut [u8] {
static mut BUF: usize = 0;
// SAFETY: boot is single threaded; the buffer is kernel-only memory
// from the boot allocator and is never handed to a process.
unsafe {
if BUF == 0 {
BUF = mmu::allocator::alloc_zeroed_page();
for _ in 1..MAX_FILE_SIZE / PAGE_SIZE {
mmu::allocator::alloc_zeroed_page();
}
}
core::slice::from_raw_parts_mut(BUF as *mut u8, MAX_FILE_SIZE)
}
}
/// The two programs boot starts, packed into the kernel image.
pub static INIT_ELF: &[u8] = include_bytes!(env!("KOS_INIT_ELF"));
pub static STORAGE_ELF: &[u8] = include_bytes!(env!("KOS_STORAGE_ELF"));
pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
pub fn load_init(uart: &Uart) -> LoadedProcess {
let (pid, tid) = kern()
.boot(
types::bounded::Priority::DEFAULT,
@@ -44,7 +30,7 @@ pub fn load_init(uart: &Uart, blk: &mut VirtioBlk) -> LoadedProcess {
)
.unwrap_or_else(|_| crate::halt(uart, "boot failed"));
let entry = load_elf(uart, blk, pid, b"init.elf");
let entry = load_elf(uart, pid, INIT_ELF);
setup_thread(tid, entry);
uart.write_str("k-os: init loaded\n");
@@ -60,24 +46,18 @@ const STORAGE: Limits = Limits {
regions: 12,
};
pub fn load_server(
uart: &Uart,
blk: &mut VirtioBlk,
init: ProcessId,
filename: &[u8],
label: &str,
) -> LoadedProcess {
/// Storage, which init needs to read anything else, its budget carved
/// from init's.
pub fn load_storage(uart: &Uart, init: ProcessId) -> LoadedProcess {
let pid = kern()
.create_process_internal(init, STORAGE)
.unwrap_or_else(|| crate::halt(uart, "process create failed"));
let entry = load_elf(uart, blk, pid, filename);
let entry = load_elf(uart, pid, STORAGE_ELF);
let tid = kern()
.spawn_thread_internal(pid)
.unwrap_or_else(|| crate::halt(uart, "thread spawn failed"));
setup_thread(tid, entry);
uart.write_str("k-os: ");
uart.write_str(label);
uart.write_str(" loaded\n");
uart.write_str("k-os: storage loaded\n");
LoadedProcess { pid }
}
@@ -127,13 +107,10 @@ fn segment_perms(flags: elf::SegmentFlags) -> Option<PagePerms> {
/// Loads `filename` into `pid` through the kernel's own mapping API, so
/// every page is tracked, charged and checked like any other. Halts on any
/// malformed or overlapping image: boot servers are trusted to be sane.
fn load_elf(uart: &Uart, blk: &mut VirtioBlk, pid: ProcessId, filename: &[u8]) -> usize {
let buf = elf_buffer();
let file_size = boot_fs::read_file_from_disk(blk, filename, buf).unwrap_or(0);
if file_size == 0 {
crate::halt(uart, "ELF not found");
fn load_elf(uart: &Uart, pid: ProcessId, file: &[u8]) -> usize {
if file.is_empty() {
crate::halt(uart, "program missing from the kernel image (make build)");
}
let file = &buf[..file_size];
let elf_info = elf::parse(file).unwrap_or_else(|_| crate::halt(uart, "bad ELF"));
for segment in elf_info.segments() {

View File

@@ -1,143 +0,0 @@
use crate::hw::virtio_blk::VirtioBlk;
const SECTOR_SIZE: usize = 512;
struct BootFat16 {
reserved_sectors: 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).div_ceil(512);
let data_start = root_dir_start + root_dir_sectors;
Some(Self {
reserved_sectors,
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];
if !blk.read_sector(0, &mut sector_buf) {
return None;
}
let fs = BootFat16::parse(&sector_buf)?;
let fat_name = to_fat_name(filename);
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, &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 && (2..0xFFF8).contains(&cluster) {
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(&sector_buf[..chunk]);
read += chunk;
}
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)
}

View File

@@ -1,4 +1,3 @@
pub mod plic;
pub mod timer;
pub mod uart;
pub mod virtio_blk;

View File

@@ -1,63 +0,0 @@
use crate::mmu::{phys_to_virt, virt_to_phys};
use hw_virtio::block::{self, DmaAddrs};
use hw_virtio::mmio::{self, DirectMmio};
use hw_virtio::queue::{QUEUE_SIZE, QueueAddrs, VirtioQueue};
use types::address::{PhysicalAddress, VirtualAddress};
const SECTOR_SIZE: usize = 512;
#[repr(C, align(4096))]
struct VirtqMem {
_data: [u8; 8192],
}
static mut VQUEUE: VirtqMem = VirtqMem { _data: [0; 8192] };
static mut REQ_HEADER: block::BlockReqHeader = block::BlockReqHeader {
req_type: 0,
reserved: 0,
sector: 0,
};
static mut REQ_STATUS: u8 = 0xFF;
static mut SECTOR_BUF: [u8; SECTOR_SIZE] = [0; SECTOR_SIZE];
pub struct VirtioBlk {
mmio: DirectMmio,
queue: VirtioQueue,
dma: DmaAddrs,
}
impl VirtioBlk {
pub fn probe() -> Option<Self> {
let mmio = mmio::scan_for_device(phys_to_virt(mmio::MMIO_FIRST), mmio::DEVICE_ID_BLOCK)?;
// The device sees physical addresses; the CPU uses kernel ones.
let vq_va = core::ptr::addr_of!(VQUEUE) as usize;
mmio::init_legacy_device(&mmio, virt_to_phys(vq_va), QUEUE_SIZE as u32);
let queue = VirtioQueue::new(QueueAddrs::from_base(vq_va));
let pair = |va: usize| {
(
PhysicalAddress::from_raw(virt_to_phys(va)),
VirtualAddress::from_raw(va),
)
};
let (header_pa, header_va) = pair(core::ptr::addr_of_mut!(REQ_HEADER) as usize);
let (status_pa, status_va) = pair(core::ptr::addr_of_mut!(REQ_STATUS) as usize);
let (data_pa, data_va) = pair(core::ptr::addr_of_mut!(SECTOR_BUF) as usize);
let dma = DmaAddrs {
header_pa,
header_va,
status_pa,
status_va,
data_pa,
data_va,
};
Some(Self { mmio, queue, dma })
}
pub fn read_sector(&mut self, sector: u32, buf: &mut [u8; SECTOR_SIZE]) -> bool {
block::read_sector(&self.mmio, &self.queue, &self.dma, sector, buf)
}
}

View File

@@ -3,7 +3,6 @@
mod board;
mod boot;
mod boot_fs;
mod devices;
mod entry;
mod hw;
@@ -36,12 +35,9 @@ extern "C" fn kmain(hart: usize) -> ! {
boot::hardware::init(&uart, hart);
// Not before init: zeroing .bss resets the lock.
smp::lock();
let mut blk =
boot::hardware::probe_block_device(&uart).unwrap_or_else(|| halt(&uart, "no block device"));
let init = boot::loader::load_init(&uart, &mut blk);
// Init needs storage to load anything else; the rest is init's to start.
let storage = boot::loader::load_server(&uart, &mut blk, init.pid, b"storage.elf", "storage");
// Both come from the kernel image; init starts everything else from disk.
let init = boot::loader::load_init(&uart);
let storage = boot::loader::load_storage(&uart, init.pid);
let board = board::devices().unwrap_or_else(|| halt(&uart, "bad board table"));
kern_mut()

View File

@@ -3,7 +3,7 @@
mod keymap;
use hw_virtio::mmio::MmioAccess;
use port_input_device::{InputDevice, InputEvent, Key};
use port_input_device::{InputDevice, InputEvent};
const INPUT_MMIO_VA: usize = 0x25000;
const VQUEUE_VA: usize = 0x26000;