feat(adapter): init+storage packed into kernel image; drop adapter FAT16 reader + virtio-blk
This commit is contained in:
@@ -14,7 +14,6 @@ domain = { workspace = true }
|
||||
elf = { workspace = true }
|
||||
platform = { workspace = true }
|
||||
kernel = { workspace = true }
|
||||
hw-virtio = { workspace = true }
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(§or_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(§or_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)
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
pub mod plic;
|
||||
pub mod timer;
|
||||
pub mod uart;
|
||||
pub mod virtio_blk;
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user