From 38f20cc3c4f8a7c0fcca45c282663902eee82f05 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Sun, 18 Oct 2026 13:02:30 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20proper=20IPC-based=20boot=20=E2=80=94?= =?UTF-8?q?=20init=20loads=20servers=20from=20storage=20via=20IPC,=20VirtI?= =?UTF-8?q?O=20GPU=20driver?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete microkernel boot pipeline: adapter loads only init + storage + display from disk. Init bootstraps all other servers by reading binaries from the storage server via IPC, parsing ELF, creating processes, mapping pages, and spawning threads. New syscalls: - CreateEndpoint: userspace endpoint creation with full capability grant - DebugPutchar: debug output to UART from userspace - MapPage: cross-process page mapping (init loads binaries into children) - SpawnThreadIn: spawn thread in another process via capability Key fixes: - Per-process L1 page tables for VPN[2]=0 (prevents cross-process corruption) - Separate kernel_pt from page_tables[0] (init pid=0 overwrote kernel PT ref) - Thread Running state set in switch_to_next (syscalls rejected without it) - Pending IPC results applied during context switch - SIE disabled during boot (timer can't preempt into unready processes) - User heap VA starts at 0x40000 (avoids shared buffer at 0x30000) - RequestPages maps at user VAs via per-process heap allocator VirtIO GPU: - Adapter probes for GPU device, initializes resource/scanout/backing - Display server writes to mapped framebuffer, flushes via GPU virtqueue - 640x480 BGRA framebuffer with transfer-to-host and resource-flush Storage server: - Userspace VirtIO block driver using pre-mapped MMIO and DMA pages - FAT16 filesystem reader serving file-read requests via IPC - Shared buffer protocol: init writes filename, storage writes file data Echo app for IPC testing (adapter-loaded echo proved the pipeline end-to-end). --- Cargo.lock | 109 ++++ Cargo.toml | 1 + Makefile | 10 +- crates/adapter-riscv/src/board.rs | 2 +- crates/adapter-riscv/src/hw/mod.rs | 1 + crates/adapter-riscv/src/hw/virtio_blk.rs | 8 +- crates/adapter-riscv/src/hw/virtio_gpu.rs | 165 +++++ crates/adapter-riscv/src/main.rs | 615 ++++++++++++------ crates/adapter-riscv/src/mmu/mod.rs | 2 +- crates/adapter-riscv/src/mmu/page_table.rs | 57 +- crates/adapter-riscv/src/platform.rs | 33 +- crates/adapter-riscv/src/trap/mod.rs | 53 +- crates/apps/echo/Cargo.toml | 13 + crates/apps/echo/src/main.rs | 18 + crates/domain/src/memory/process_memory.rs | 12 + crates/domain/src/syscall.rs | 13 + crates/init/Cargo.toml | 1 + crates/init/src/boot.rs | 120 ---- crates/init/src/lib.rs | 180 ++++- crates/init/src/main.rs | 4 +- crates/init/src/manifest.rs | 112 ---- crates/init/src/protocol_helpers.rs | 44 -- crates/init/src/shutdown.rs | 25 - crates/init/src/supervisor.rs | 143 ---- crates/kernel/src/lib.rs | 32 + crates/kernel/src/syscall/ipc.rs | 25 + crates/kernel/src/syscall/lifecycle.rs | 47 ++ crates/kernel/src/syscall/memory.rs | 92 +-- crates/kernel/src/syscall/mod.rs | 8 + crates/kernel/src/syscall/simple.rs | 5 + crates/kernel/src/tests/mock_platform.rs | 5 + crates/platform/src/lib.rs | 5 +- .../src/adapters/qemu_virtio.rs | 151 ++++- .../storage-server/src/adapters/mod.rs | 1 + .../storage-server/src/adapters/virtio_blk.rs | 183 ++++++ crates/servers/storage-server/src/lib.rs | 83 ++- crates/types/src/syscall_abi.rs | 4 + crates/userspace/src/arch/mock.rs | 8 + crates/userspace/src/arch/riscv.rs | 31 + crates/userspace/src/syscall.rs | 18 +- scripts/make-disk-image.sh | 45 +- 41 files changed, 1676 insertions(+), 808 deletions(-) create mode 100644 crates/adapter-riscv/src/hw/virtio_gpu.rs create mode 100644 crates/apps/echo/Cargo.toml create mode 100644 crates/apps/echo/src/main.rs delete mode 100644 crates/init/src/boot.rs delete mode 100644 crates/init/src/manifest.rs delete mode 100644 crates/init/src/protocol_helpers.rs delete mode 100644 crates/init/src/shutdown.rs delete mode 100644 crates/init/src/supervisor.rs create mode 100644 crates/servers/storage-server/src/adapters/virtio_blk.rs diff --git a/Cargo.lock b/Cargo.lock index 509db75..da460ff 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6,6 +6,17 @@ version = 4 name = "accessibility-server" version = "0.1.0" +[[package]] +name = "adapter-riscv" +version = "0.1.0" +dependencies = [ + "domain", + "elf", + "kernel", + "platform", + "types", +] + [[package]] name = "alarm-server" version = "0.1.0" @@ -41,6 +52,12 @@ version = "0.1.0" [[package]] name = "display-server" version = "0.1.0" +dependencies = [ + "server-framework", + "types", + "userspace", + "userspace-rt", +] [[package]] name = "domain" @@ -49,6 +66,18 @@ dependencies = [ "types", ] +[[package]] +name = "echo" +version = "0.1.0" +dependencies = [ + "userspace", + "userspace-rt", +] + +[[package]] +name = "elf" +version = "0.1.0" + [[package]] name = "gatekeeper" version = "0.1.0" @@ -57,18 +86,56 @@ version = "0.1.0" name = "gps-server" version = "0.1.0" +[[package]] +name = "hello" +version = "0.1.0" +dependencies = [ + "userspace-rt", +] + +[[package]] +name = "init" +version = "0.1.0" +dependencies = [ + "elf", + "types", + "userspace", + "userspace-rt", +] + [[package]] name = "input-server" version = "0.1.0" +dependencies = [ + "server-framework", + "types", + "userspace", + "userspace-rt", +] [[package]] name = "kernel" version = "0.1.0" +dependencies = [ + "domain", + "platform", + "types", +] [[package]] name = "keychain-server" version = "0.1.0" +[[package]] +name = "log-server" +version = "0.1.0" +dependencies = [ + "server-framework", + "types", + "userspace", + "userspace-rt", +] + [[package]] name = "media-index-server" version = "0.1.0" @@ -88,6 +155,12 @@ version = "0.1.0" [[package]] name = "permission-manager" version = "0.1.0" +dependencies = [ + "server-framework", + "types", + "userspace", + "userspace-rt", +] [[package]] name = "platform" @@ -108,14 +181,39 @@ version = "0.1.0" [[package]] name = "server-framework" version = "0.1.0" +dependencies = [ + "types", + "userspace", +] + +[[package]] +name = "session-server" +version = "0.1.0" +dependencies = [ + "server-framework", + "types", + "userspace", + "userspace-rt", +] [[package]] name = "snake" version = "0.1.0" +dependencies = [ + "types", + "userspace", + "userspace-rt", +] [[package]] name = "storage-server" version = "0.1.0" +dependencies = [ + "server-framework", + "types", + "userspace", + "userspace-rt", +] [[package]] name = "sync-server" @@ -140,3 +238,14 @@ version = "0.1.0" [[package]] name = "userspace" version = "0.1.0" +dependencies = [ + "types", +] + +[[package]] +name = "userspace-rt" +version = "0.1.0" +dependencies = [ + "types", + "userspace", +] diff --git a/Cargo.toml b/Cargo.toml index 83dd806..6e71fbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,6 +2,7 @@ members = [ "crates/adapter-riscv", "crates/apps/hello", + "crates/apps/echo", "crates/apps/snake", "crates/domain", "crates/elf", diff --git a/Makefile b/Makefile index 4622d60..1828f86 100644 --- a/Makefile +++ b/Makefile @@ -41,5 +41,13 @@ run: build run-disk: image qemu-system-riscv64 -machine virt -nographic -bios default \ -kernel $(KERNEL_BIN) \ - -drive file=boot.img,format=raw,id=hd0 \ + -drive file=boot.img,if=none,format=raw,id=hd0 \ -device virtio-blk-device,drive=hd0 + +run-gui: image + qemu-system-riscv64 -machine virt -bios default \ + -kernel $(KERNEL_BIN) \ + -drive file=boot.img,if=none,format=raw,id=hd0 \ + -device virtio-blk-device,drive=hd0 \ + -device virtio-gpu-device \ + -serial stdio diff --git a/crates/adapter-riscv/src/board.rs b/crates/adapter-riscv/src/board.rs index f48cc1a..343ff71 100644 --- a/crates/adapter-riscv/src/board.rs +++ b/crates/adapter-riscv/src/board.rs @@ -6,5 +6,5 @@ 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; -pub const USER_POOL_PAGES: usize = 256; +pub const USER_POOL_PAGES: usize = 4096; pub const TICK_INTERVAL: u64 = 10_000_000; diff --git a/crates/adapter-riscv/src/hw/mod.rs b/crates/adapter-riscv/src/hw/mod.rs index 679c465..6b1686a 100644 --- a/crates/adapter-riscv/src/hw/mod.rs +++ b/crates/adapter-riscv/src/hw/mod.rs @@ -2,3 +2,4 @@ pub mod plic; pub mod timer; pub mod uart; pub mod virtio_blk; +pub mod virtio_gpu; diff --git a/crates/adapter-riscv/src/hw/virtio_blk.rs b/crates/adapter-riscv/src/hw/virtio_blk.rs index 6f0558f..c118d61 100644 --- a/crates/adapter-riscv/src/hw/virtio_blk.rs +++ b/crates/adapter-riscv/src/hw/virtio_blk.rs @@ -77,11 +77,11 @@ static mut REQ_HEADER: VirtioBlkReqHeader = VirtioBlkReqHeader { static mut REQ_STATUS: u8 = 0xFF; -fn mmio_read(base: usize, offset: usize) -> u32 { +pub fn mmio_read(base: usize, offset: usize) -> u32 { unsafe { core::ptr::read_volatile((base + offset) as *const u32) } } -fn mmio_write(base: usize, offset: usize, val: u32) { +pub fn mmio_write(base: usize, offset: usize, val: u32) { unsafe { core::ptr::write_volatile((base + offset) as *mut u32, val) } } @@ -91,6 +91,10 @@ pub struct VirtioBlk { } impl VirtioBlk { + pub fn base(&self) -> usize { + self.base + } + pub fn probe() -> Option { for slot in 0..VIRTIO_MMIO_SLOTS { let base = VIRTIO_MMIO_FIRST + slot * VIRTIO_MMIO_STRIDE; diff --git a/crates/adapter-riscv/src/hw/virtio_gpu.rs b/crates/adapter-riscv/src/hw/virtio_gpu.rs new file mode 100644 index 0000000..9e3ec82 --- /dev/null +++ b/crates/adapter-riscv/src/hw/virtio_gpu.rs @@ -0,0 +1,165 @@ +const VIRTIO_MAGIC: u32 = 0x74726976; +const VIRTIO_GPU_DEVICE_ID: u32 = 16; +const VIRTIO_MMIO_FIRST: usize = 0x10001000; +const VIRTIO_MMIO_STRIDE: usize = 0x1000; +const VIRTIO_MMIO_SLOTS: usize = 8; +const QUEUE_SIZE: usize = 8; + +use super::virtio_blk::{mmio_read, mmio_write}; + +pub struct VirtioGpu { + base: usize, +} + +#[repr(C)] +struct GpuCtrlHdr { + hdr_type: u32, + flags: u32, + fence_id: u64, + ctx_id: u32, + ring_idx: u8, + _pad: [u8; 3], +} + +impl VirtioGpu { + pub fn probe() -> Option { + for slot in 0..VIRTIO_MMIO_SLOTS { + let base = VIRTIO_MMIO_FIRST + slot * VIRTIO_MMIO_STRIDE; + let magic = mmio_read(base, 0x000); + let device_id = mmio_read(base, 0x008); + if magic == VIRTIO_MAGIC && device_id == VIRTIO_GPU_DEVICE_ID { + return Some(Self { base }); + } + } + None + } + + pub fn base(&self) -> usize { + self.base + } + + pub fn init_device(&self, vqueue_phys: usize) { + let base = self.base; + + mmio_write(base, 0x070, 0); + while mmio_read(base, 0x070) != 0 {} + + mmio_write(base, 0x070, 1); // ACK + mmio_write(base, 0x070, mmio_read(base, 0x070) | 2); // DRIVER + mmio_write(base, 0x028, 4096); // legacy guest page size + mmio_write(base, 0x014, 0); // features sel + mmio_write(base, 0x024, 0); // driver features sel + mmio_write(base, 0x020, 0); // driver features + + // controlq (index 0) + mmio_write(base, 0x030, 0); + let qmax = mmio_read(base, 0x034); + let qnum = (QUEUE_SIZE as u32).min(qmax); + mmio_write(base, 0x038, qnum); + mmio_write(base, 0x040, (vqueue_phys / 4096) as u32); + + mmio_write(base, 0x070, mmio_read(base, 0x070) | 4); // DRIVER_OK + } + + pub fn setup_framebuffer( + &self, + cmd_buf_phys: usize, + vqueue_phys: usize, + fb_phys: usize, + width: u32, + height: u32, + ) { + self.send_create_2d(cmd_buf_phys, vqueue_phys, 1, width, height); + self.send_attach_backing(cmd_buf_phys, vqueue_phys, 1, fb_phys, width * height * 4); + self.send_set_scanout(cmd_buf_phys, vqueue_phys, 0, 1, width, height); + } + + fn send_cmd(&self, cmd_phys: usize, cmd_len: u32, resp_phys: usize, resp_len: u32, vqueue_phys: usize) { + unsafe { + let descs = vqueue_phys as *mut [u64; 2]; // each desc = addr(u64) + len(u32) + flags(u16) + next(u16) + let desc0 = vqueue_phys as *mut u8; + // desc 0: request (readable) + core::ptr::write_volatile(desc0 as *mut u64, cmd_phys as u64); + core::ptr::write_volatile(desc0.add(8) as *mut u32, cmd_len); + core::ptr::write_volatile(desc0.add(12) as *mut u16, 1); // NEXT + core::ptr::write_volatile(desc0.add(14) as *mut u16, 1); // next=1 + + // desc 1: response (writable) + let desc1 = desc0.add(16); + core::ptr::write_volatile(desc1 as *mut u64, resp_phys as u64); + core::ptr::write_volatile(desc1.add(8) as *mut u32, resp_len); + core::ptr::write_volatile(desc1.add(12) as *mut u16, 2); // WRITE + core::ptr::write_volatile(desc1.add(14) as *mut u16, 0); + + // avail ring: after descs (8 descs * 16 bytes = 128 bytes) + let avail = vqueue_phys + QUEUE_SIZE * 16; + let avail_idx = core::ptr::read_volatile((avail + 2) as *const u16); + core::ptr::write_volatile( + (avail + 4 + (avail_idx as usize % QUEUE_SIZE) * 2) as *mut u16, + 0, + ); + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + core::ptr::write_volatile((avail + 2) as *mut u16, avail_idx.wrapping_add(1)); + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + + mmio_write(self.base, 0x050, 0); // notify queue 0 + + // poll used ring (at page boundary = vqueue_phys + 4096) + let used = vqueue_phys + 4096; + let expected = avail_idx.wrapping_add(1); + let mut timeout = 10_000_000u32; + while core::ptr::read_volatile((used + 2) as *const u16) != expected { + core::hint::spin_loop(); + timeout -= 1; + if timeout == 0 { return; } + } + } + } + + fn send_create_2d(&self, cmd_phys: usize, vq_phys: usize, resource_id: u32, width: u32, height: u32) { + // cmd header (24 bytes) + create_2d fields (16 bytes) = 40 bytes + unsafe { + let p = cmd_phys as *mut u8; + core::ptr::write_bytes(p, 0, 4096); + core::ptr::write_volatile(p as *mut u32, 0x0101); // RESOURCE_CREATE_2D + core::ptr::write_volatile(p.add(24) as *mut u32, resource_id); + core::ptr::write_volatile(p.add(28) as *mut u32, 1); // B8G8R8A8_UNORM + core::ptr::write_volatile(p.add(32) as *mut u32, width); + core::ptr::write_volatile(p.add(36) as *mut u32, height); + } + let resp_phys = cmd_phys + 512; + self.send_cmd(cmd_phys, 40, resp_phys, 24, vq_phys); + } + + fn send_attach_backing(&self, cmd_phys: usize, vq_phys: usize, resource_id: u32, fb_phys: usize, fb_size: u32) { + // header (24) + attach_backing (8) + mem_entry (16) = 48 bytes + unsafe { + let p = cmd_phys as *mut u8; + core::ptr::write_bytes(p, 0, 512); + core::ptr::write_volatile(p as *mut u32, 0x0104); // RESOURCE_ATTACH_BACKING + core::ptr::write_volatile(p.add(24) as *mut u32, resource_id); + core::ptr::write_volatile(p.add(28) as *mut u32, 1); // nr_entries + // mem_entry + core::ptr::write_volatile(p.add(32) as *mut u64, fb_phys as u64); + core::ptr::write_volatile(p.add(40) as *mut u32, fb_size); + } + let resp_phys = cmd_phys + 512; + self.send_cmd(cmd_phys, 48, resp_phys, 24, vq_phys); + } + + fn send_set_scanout(&self, cmd_phys: usize, vq_phys: usize, scanout_id: u32, resource_id: u32, width: u32, height: u32) { + // header (24) + rect (16) + scanout_id (4) + resource_id (4) = 48 bytes + unsafe { + let p = cmd_phys as *mut u8; + core::ptr::write_bytes(p, 0, 512); + core::ptr::write_volatile(p as *mut u32, 0x0103); // SET_SCANOUT + // rect: x=0, y=0, width, height at offset 24 + core::ptr::write_volatile(p.add(32) as *mut u32, width); + core::ptr::write_volatile(p.add(36) as *mut u32, height); + core::ptr::write_volatile(p.add(40) as *mut u32, scanout_id); + core::ptr::write_volatile(p.add(44) as *mut u32, resource_id); + } + let resp_phys = cmd_phys + 512; + self.send_cmd(cmd_phys, 48, resp_phys, 24, vq_phys); + } +} diff --git a/crates/adapter-riscv/src/main.rs b/crates/adapter-riscv/src/main.rs index f48c644..d357676 100644 --- a/crates/adapter-riscv/src/main.rs +++ b/crates/adapter-riscv/src/main.rs @@ -12,6 +12,7 @@ mod trap; use core::arch::global_asm; use hw::uart::Uart; use kernel_cell::KernelCell; +use types::id::{ProcessId, ThreadId}; static KERNEL: KernelCell> = KernelCell::uninit(); @@ -47,8 +48,24 @@ fn zero_bss() { } } -const INIT_LOAD_VA: usize = 0x10000; -const INIT_MAX_SIZE: usize = 64 * 1024; +const USER_VA_BASE: usize = 0x10000; +const MAX_FILE_SIZE: usize = 64 * 1024; +const SHARED_BUF_VA: usize = 0x30000; +const SHARED_BUF_PAGES: usize = 16; +const STORAGE_VIRTIO_VA: usize = 0x20000; +const STORAGE_VQUEUE_VA: usize = 0x21000; +const STORAGE_REQHDR_VA: usize = 0x23000; +const STORAGE_PHYSINFO_VA: usize = 0x24000; + +const DISPLAY_GPU_VA: usize = 0x20000; +const DISPLAY_VQUEUE_VA: usize = 0x21000; +const DISPLAY_CMDBUF_VA: usize = 0x23000; +const DISPLAY_PHYSINFO_VA: usize = 0x24000; +const DISPLAY_FB_VA: usize = 0x100000; +const DISPLAY_FB_WIDTH: u32 = 640; +const DISPLAY_FB_HEIGHT: u32 = 480; +const DISPLAY_FB_BPP: usize = 4; +const DISPLAY_FB_PAGES: usize = (DISPLAY_FB_WIDTH as usize * DISPLAY_FB_HEIGHT as usize * DISPLAY_FB_BPP + 4095) / 4096; #[unsafe(no_mangle)] extern "C" fn kmain() -> ! { @@ -75,231 +92,427 @@ extern "C" fn kmain() -> ! { ); KERNEL.init(kernel::Kernel::new(riscv_platform, pool)); - let entry_va = load_init_from_disk(&uart, kernel_pt); - - launch_init(&uart, entry_va); -} - -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); - } - }; - - // 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 and map segments - 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'); - - // Create a user page table with kernel mappings - let user_pt = mmu::create_user_page_table( - kernel_pt, - types::address::PhysicalAddress::from_raw(0), - 0, - types::address::VirtualAddress::from_raw(0x10000), - ); - - // Map each PT_LOAD segment at its virtual address - for segment in elf_info.segments() { - let page_start = segment.vaddr & !0xFFF; - let offset_in_page = segment.vaddr & 0xFFF; - let total_size = offset_in_page + segment.mem_size; - let seg_pages = (total_size + 4095) / 4096; - - for p in 0..seg_pages { - let page = mmu::allocator::alloc_zeroed_page(); - - let page_va_start = page_start + p * 4096; - let seg_start_in_file = if p == 0 { 0 } else { p * 4096 - offset_in_page }; - let dest_offset = if p == 0 { offset_in_page } else { 0 }; - let copy_len = if seg_start_in_file < segment.file_size { - (segment.file_size - seg_start_in_file).min(4096 - dest_offset) - } else { - 0 - }; - - if copy_len > 0 { - let src = segment.file_offset + seg_start_in_file; - if src + copy_len <= file_size { - unsafe { - core::ptr::copy_nonoverlapping( - init_buf.as_ptr().add(src), - (page as *mut u8).add(dest_offset), - copy_len, - ); - } - } - } - - let flags = if segment.flags.execute { - mmu::Sv39Flags::user_rx() - } else if segment.flags.write { - mmu::Sv39Flags::user_rw() - } else { - mmu::Sv39Flags::user_rx() - }; - mmu::map(user_pt, mmu::Mapping { - va: types::address::VirtualAddress::from_raw(page_va_start), - pa: types::address::PhysicalAddress::from_raw(page), - pages: 1, - flags, - }); - } - uart.write_str("k-os: mapped segment at "); - uart.write_hex(segment.vaddr); - uart.write_str(" ("); - uart.write_dec(seg_pages); - uart.write_str(" pages)\n"); - } - - // Map stack pages below the lowest segment (4 pages = 16K) - let stack_pages = 4; - for i in 0..stack_pages { - let stack_page = mmu::allocator::alloc_zeroed_page(); - let stack_va = 0x10000 - (stack_pages - i) * 4096; - mmu::map(user_pt, mmu::Mapping { - va: types::address::VirtualAddress::from_raw(stack_va), - pa: types::address::PhysicalAddress::from_raw(stack_page), - pages: 1, - flags: mmu::Sv39Flags::user_rw(), - }); - } - - // Store page table for init process - let kern = kernel_ref(); - let pt_addr = user_pt as *const mmu::PageTable as usize; - kern.platform_mut().set_page_table( - types::id::ProcessId::from_raw(0), - pt_addr, - ); - - elf_info.entry_point - } - Err(_) => { - uart.write_str("k-os: not valid ELF — loading as raw binary\n"); - load_fallback_init(kernel_pt) - } - }; - - 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(), - ); + // --- Read init + storage from disk --- + let mut blk = hw::virtio_blk::VirtioBlk::probe(); + if blk.is_none() { + halt(&uart, "no block device"); } - 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 -} + uart.write_str("k-os: VirtIO block device found\n"); -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 init (pid 0) — kern.boot() grants ProcessCreate cap at slot 0 + let (init_pid, init_tid) = kernel_ref() .boot(types::bounded::Priority::DEFAULT, types::time::TimeSlice::DEFAULT) - .unwrap_or_else(|_| { - uart.write_str("k-os: FATAL — boot failed\n"); - loop { unsafe { core::arch::asm!("wfi") }; } + .unwrap_or_else(|_| halt(&uart, "boot failed")); + + let init_entry = load_elf_into_process(&uart, blk.as_mut(), kernel_pt, init_pid, b"init.elf"); + if init_entry == 0 { halt(&uart, "init.elf not found"); } + setup_thread(init_tid, init_entry); + uart.write_str("k-os: init loaded\n"); + + // Load storage server + let storage_pid = kernel_ref().create_process_internal() + .unwrap_or_else(|| halt(&uart, "storage process failed")); + let storage_entry = load_elf_into_process(&uart, blk.as_mut(), kernel_pt, storage_pid, b"storage.elf"); + if storage_entry == 0 { halt(&uart, "storage.elf not found"); } + let storage_tid = kernel_ref().spawn_thread_internal(storage_pid) + .unwrap_or_else(|| halt(&uart, "storage thread failed")); + setup_thread(storage_tid, storage_entry); + uart.write_str("k-os: storage loaded\n"); + + // Load display server + let display_pid = kernel_ref().create_process_internal() + .unwrap_or_else(|| halt(&uart, "display process failed")); + let display_entry = load_elf_into_process(&uart, blk.as_mut(), kernel_pt, display_pid, b"display.elf"); + if display_entry == 0 { halt(&uart, "display.elf not found"); } + let display_tid = kernel_ref().spawn_thread_internal(display_pid) + .unwrap_or_else(|| halt(&uart, "display thread failed")); + setup_thread(display_tid, display_entry); + uart.write_str("k-os: display loaded\n"); + + // Done reading from disk — drop the adapter's VirtIO driver + let virtio_base = blk.as_ref().map(|b| b.base()).unwrap_or(0); + drop(blk); + + // --- Set up VirtIO for storage server --- + let storage_pt = kernel_ref().platform_mut().page_table_for(storage_pid); + let storage_pt_ref = unsafe { &mut *(storage_pt as *mut mmu::PageTable) }; + + // Map VirtIO MMIO into storage's address space + mmu::map(storage_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(STORAGE_VIRTIO_VA), + pa: types::address::PhysicalAddress::from_raw(virtio_base), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + + // Allocate virtqueue pages (2 pages) and map into storage + let vqueue_phys = mmu::allocator::alloc_zeroed_page(); + mmu::allocator::alloc_zeroed_page(); // second page (contiguous) + for i in 0..2usize { + mmu::map(storage_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(STORAGE_VQUEUE_VA + i * 4096), + pa: types::address::PhysicalAddress::from_raw(vqueue_phys + i * 4096), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + } + + // Allocate request header page and map into storage + let reqhdr_phys = mmu::allocator::alloc_zeroed_page(); + mmu::map(storage_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(STORAGE_REQHDR_VA), + pa: types::address::PhysicalAddress::from_raw(reqhdr_phys), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + + // Allocate phys info page — storage reads DMA addresses from here + let physinfo_phys = mmu::allocator::alloc_zeroed_page(); + mmu::map(storage_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(STORAGE_PHYSINFO_VA), + pa: types::address::PhysicalAddress::from_raw(physinfo_phys), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + // Write physical addresses: [req_hdr_pa, status_pa, sector_buf_pa] + unsafe { + let info = physinfo_phys as *mut u64; + core::ptr::write_volatile(info, reqhdr_phys as u64); // req_hdr_pa + core::ptr::write_volatile(info.add(1), (reqhdr_phys + 16) as u64); // status_pa + core::ptr::write_volatile(info.add(2), (reqhdr_phys + 32) as u64); // sector_buf_pa + } + + // Re-initialize VirtIO device with storage's virtqueue + reinit_virtio_for_storage(virtio_base, vqueue_phys); + uart.write_str("k-os: VirtIO configured for storage\n"); + + // --- Set up VirtIO GPU for display server --- + let display_pt = kernel_ref().platform_mut().page_table_for(display_pid); + let display_pt_ref = unsafe { &mut *(display_pt as *mut mmu::PageTable) }; + + if let Some(gpu) = hw::virtio_gpu::VirtioGpu::probe() { + let gpu_base = gpu.base(); + + // Map GPU MMIO into display's address space + mmu::map(display_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(DISPLAY_GPU_VA), + pa: types::address::PhysicalAddress::from_raw(gpu_base), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), }); - if let Some(ctx) = kern.thread_context_mut(tid) { - ctx.set_pc(entry_va); - ctx.registers_mut()[2] = INIT_LOAD_VA; - let mut sstatus: usize = 0; - sstatus |= 1 << 5; // SPIE - ctx.set_status(sstatus); + // Allocate GPU virtqueue (2 pages) + let gpu_vq_phys = mmu::allocator::alloc_zeroed_page(); + mmu::allocator::alloc_zeroed_page(); + for i in 0..2usize { + mmu::map(display_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(DISPLAY_VQUEUE_VA + i * 4096), + pa: types::address::PhysicalAddress::from_raw(gpu_vq_phys + i * 4096), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + } + + // Allocate command buffer (1 page) + let gpu_cmd_phys = mmu::allocator::alloc_zeroed_page(); + mmu::map(display_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(DISPLAY_CMDBUF_VA), + pa: types::address::PhysicalAddress::from_raw(gpu_cmd_phys), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + + // Allocate framebuffer (300 pages, contiguous) + let fb_phys = mmu::allocator::alloc_zeroed_page(); + for _ in 1..DISPLAY_FB_PAGES { + mmu::allocator::alloc_zeroed_page(); + } + for i in 0..DISPLAY_FB_PAGES { + mmu::map(display_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(DISPLAY_FB_VA + i * 4096), + pa: types::address::PhysicalAddress::from_raw(fb_phys + i * 4096), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + } + + // Write phys-info for display server + let gpu_physinfo = mmu::allocator::alloc_zeroed_page(); + mmu::map(display_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(DISPLAY_PHYSINFO_VA), + pa: types::address::PhysicalAddress::from_raw(gpu_physinfo), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + unsafe { + let info = gpu_physinfo as *mut u64; + core::ptr::write_volatile(info, gpu_cmd_phys as u64); + core::ptr::write_volatile(info.add(1), (gpu_cmd_phys + 512) as u64); // response area + core::ptr::write_volatile(info.add(2), fb_phys as u64); + core::ptr::write_volatile(info.add(3), DISPLAY_FB_WIDTH as u64); + core::ptr::write_volatile(info.add(4), DISPLAY_FB_HEIGHT as u64); + } + + // Initialize GPU device and set up framebuffer + gpu.init_device(gpu_vq_phys); + gpu.setup_framebuffer(gpu_cmd_phys, gpu_vq_phys, fb_phys, DISPLAY_FB_WIDTH, DISPLAY_FB_HEIGHT); + uart.write_str("k-os: VirtIO GPU configured\n"); + } else { + uart.write_str("k-os: no GPU found (display will be headless)\n"); } - uart.write_str("k-os: init loaded, entering U-mode (pid="); - uart.write_dec(pid.raw() as usize); - uart.write_str(")\n"); + // --- Allocate shared buffer and map into both processes --- + let init_pt = kernel_ref().platform_mut().page_table_for(init_pid); + let init_pt_ref = unsafe { &mut *(init_pt as *mut mmu::PageTable) }; - 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); + for i in 0..SHARED_BUF_PAGES { + let page = mmu::allocator::alloc_zeroed_page(); + let va = SHARED_BUF_VA + i * 4096; + mmu::map(init_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(va), + pa: types::address::PhysicalAddress::from_raw(page), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + mmu::map(storage_pt_ref, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(va), + pa: types::address::PhysicalAddress::from_raw(page), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + } + uart.write_str("k-os: shared buffer mapped\n"); + + // --- Grant capabilities --- + let all_perms = types::permission::PermissionSet::empty() + .with(types::permission::Permission::Read) + .with(types::permission::Permission::Write) + .with(types::permission::Permission::Execute) + .with(types::permission::Permission::Delegate); + + // Create storage endpoint + let storage_ep = kernel_ref().create_endpoint() + .unwrap_or_else(|_| halt(&uart, "endpoint create failed")); + + // Init caps: slot 0 = ProcessCreate (from boot), slot 1 = Process(storage), slot 2 = Endpoint + kernel_ref().grant_capability( + init_pid, + domain::resource::Resource::Process(storage_pid), + all_perms, + ).unwrap_or_else(|_| halt(&uart, "grant Process cap failed")); + kernel_ref().grant_capability( + init_pid, + domain::resource::Resource::Endpoint(storage_ep), + all_perms, + ).unwrap_or_else(|_| halt(&uart, "grant Endpoint to init failed")); + + // Storage caps: slot 0 = Endpoint (Read+Execute for receive/send) + let storage_ep_perms = types::permission::PermissionSet::empty() + .with(types::permission::Permission::Read) + .with(types::permission::Permission::Execute); + kernel_ref().grant_capability( + storage_pid, + domain::resource::Resource::Endpoint(storage_ep), + storage_ep_perms, + ).unwrap_or_else(|_| halt(&uart, "grant Endpoint to storage failed")); + + // Create display endpoint + let display_ep = kernel_ref().create_endpoint() + .unwrap_or_else(|_| halt(&uart, "display endpoint failed")); + + // Display caps: slot 0 = Endpoint (Read+Execute) + kernel_ref().grant_capability( + display_pid, + domain::resource::Resource::Endpoint(display_ep), + storage_ep_perms, // same Read+Execute + ).unwrap_or_else(|_| halt(&uart, "grant Endpoint to display failed")); + + // Init also gets display endpoint (slot 3) and display process cap (slot 4) for IPC + kernel_ref().grant_capability( + init_pid, + domain::resource::Resource::Endpoint(display_ep), + all_perms, + ).unwrap_or_else(|_| halt(&uart, "grant display ep to init failed")); + kernel_ref().grant_capability( + init_pid, + domain::resource::Resource::Process(display_pid), + all_perms, + ).unwrap_or_else(|_| halt(&uart, "grant display proc to init failed")); + + uart.write_str("k-os: capabilities granted\n"); + + // --- Enter userspace with init, passing shared_buf VA in a0 --- + uart.write_str("k-os: entering userspace\n"); + enter_userspace(init_pid, init_entry, SHARED_BUF_VA); +} + +fn reinit_virtio_for_storage(base: usize, vqueue_phys: usize) { + use hw::virtio_blk::{mmio_read, mmio_write}; + + // Reset + mmio_write(base, 0x070, 0); + while mmio_read(base, 0x070) != 0 {} + + // Acknowledge + Driver + mmio_write(base, 0x070, 1); // ACK + mmio_write(base, 0x070, mmio_read(base, 0x070) | 2); // DRIVER + + // Legacy: set guest page size + mmio_write(base, 0x028, 4096); + + // Features: accept none + mmio_write(base, 0x014, 0); + mmio_write(base, 0x024, 0); + mmio_write(base, 0x020, 0); + + // Queue 0 setup + mmio_write(base, 0x030, 0); // select queue 0 + let queue_max = mmio_read(base, 0x034); + let queue_num = 8u32.min(queue_max); + mmio_write(base, 0x038, queue_num); + + // Point device to storage's virtqueue (physical page number) + mmio_write(base, 0x040, (vqueue_phys / 4096) as u32); + + // DRIVER_OK + mmio_write(base, 0x070, mmio_read(base, 0x070) | 4); +} + +fn load_elf_into_process( + _uart: &Uart, + blk: Option<&mut hw::virtio_blk::VirtioBlk>, + kernel_pt: &mmu::PageTable, + pid: ProcessId, + filename: &[u8], +) -> usize { + let buf_base = mmu::allocator::alloc_zeroed_page(); + let file_pages = MAX_FILE_SIZE / 4096; + for _ in 1..file_pages { + mmu::allocator::alloc_zeroed_page(); + } + let buf = unsafe { core::slice::from_raw_parts_mut(buf_base as *mut u8, MAX_FILE_SIZE) }; + + let file_size = match blk { + Some(b) => boot_fs::read_file_from_disk(b, filename, buf).unwrap_or(0), + None => 0, + }; + if file_size == 0 { return 0; } + + let elf_info = match elf::parse(&buf[..file_size]) { + Ok(info) => info, + Err(_) => return 0, + }; + + let user_pt = mmu::create_user_page_table( + kernel_pt, + types::address::PhysicalAddress::from_raw(0), + 0, + types::address::VirtualAddress::from_raw(USER_VA_BASE), + ); + + for segment in elf_info.segments() { + let page_start = segment.vaddr & !0xFFF; + let offset_in_page = segment.vaddr & 0xFFF; + let total_size = offset_in_page + segment.mem_size; + let seg_pages = (total_size + 4095) / 4096; + + for p in 0..seg_pages { + let page = mmu::allocator::alloc_zeroed_page(); + let page_va = page_start + p * 4096; + let seg_file_start = if p == 0 { 0 } else { p * 4096 - offset_in_page }; + let dest_off = if p == 0 { offset_in_page } else { 0 }; + let copy_len = if seg_file_start < segment.file_size { + (segment.file_size - seg_file_start).min(4096 - dest_off) + } else { 0 }; + + if copy_len > 0 { + let src = segment.file_offset + seg_file_start; + if src + copy_len <= file_size { + unsafe { + core::ptr::copy_nonoverlapping( + buf.as_ptr().add(src), + (page as *mut u8).add(dest_off), + copy_len, + ); + } + } + } + + let flags = if segment.flags.execute { + mmu::Sv39Flags::user_rx() + } else if segment.flags.write { + mmu::Sv39Flags::user_rw() + } else { + mmu::Sv39Flags::user_rx() + }; + mmu::map(user_pt, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(page_va), + pa: types::address::PhysicalAddress::from_raw(page), + pages: 1, + flags, + }); + } + } + + // Stack: 4 pages below USER_VA_BASE + for i in 0..4usize { + let sp = mmu::allocator::alloc_zeroed_page(); + mmu::map(user_pt, mmu::Mapping { + va: types::address::VirtualAddress::from_raw(USER_VA_BASE - (4 - i) * 4096), + pa: types::address::PhysicalAddress::from_raw(sp), + pages: 1, + flags: mmu::Sv39Flags::user_rw(), + }); + } + + kernel_ref().platform_mut().set_page_table(pid, user_pt as *const mmu::PageTable as usize); + elf_info.entry_point +} + +fn setup_thread(tid: ThreadId, entry: usize) { + if let Some(ctx) = kernel_ref().thread_context_mut(tid) { + ctx.set_pc(entry); + ctx.registers_mut()[2] = USER_VA_BASE; + ctx.set_status(1 << 5); + } +} + +fn enter_userspace(pid: ProcessId, entry: usize, a0: usize) -> ! { + let kern = kernel_ref(); + let pt_addr = kern.platform_mut().page_table_for(pid); + if pt_addr != 0 { + mmu::activate(unsafe { &*(pt_addr as *const mmu::PageTable) }); } let kernel_sp: usize; unsafe { core::arch::asm!("mv {0}, sp", out(reg) kernel_sp) }; trap::set_kernel_stack(kernel_sp); - let user_sp = INIT_LOAD_VA; unsafe { core::arch::asm!( "csrc sstatus, {spp}", "csrs sstatus, {spie}", "csrw sepc, {entry}", "mv sp, {stack}", + "mv a0, {arg}", "sret", spp = in(reg) 1usize << 8, spie = in(reg) 1usize << 5, - entry = in(reg) entry_va, - stack = in(reg) user_sp, + entry = in(reg) entry, + stack = in(reg) USER_VA_BASE, + arg = in(reg) a0, options(noreturn), ); } } +fn halt(uart: &Uart, msg: &str) -> ! { + uart.write_str("k-os: FATAL — "); + uart.write_str(msg); + uart.put_char(b'\n'); + loop { unsafe { core::arch::asm!("wfi") }; } +} + #[panic_handler] fn panic(info: &core::panic::PanicInfo) -> ! { let uart = Uart::new(); @@ -310,7 +523,5 @@ fn panic(info: &core::panic::PanicInfo) -> ! { uart.write_dec(location.line() as usize); } uart.put_char(b'\n'); - loop { - unsafe { core::arch::asm!("wfi") }; - } + loop { unsafe { core::arch::asm!("wfi") }; } } diff --git a/crates/adapter-riscv/src/mmu/mod.rs b/crates/adapter-riscv/src/mmu/mod.rs index 24278dc..4d36cac 100644 --- a/crates/adapter-riscv/src/mmu/mod.rs +++ b/crates/adapter-riscv/src/mmu/mod.rs @@ -2,4 +2,4 @@ pub mod allocator; pub mod page_table; pub use allocator::init_allocator; -pub use page_table::{activate, init_kernel_page_table, create_user_page_table, map, Sv39Flags, Mapping, PageTable, PAGE_SIZE}; +pub use page_table::{activate, init_kernel_page_table, create_user_page_table, map, translate, Sv39Flags, Mapping, PageTable, PAGE_SIZE}; diff --git a/crates/adapter-riscv/src/mmu/page_table.rs b/crates/adapter-riscv/src/mmu/page_table.rs index 57f226f..085f896 100644 --- a/crates/adapter-riscv/src/mmu/page_table.rs +++ b/crates/adapter-riscv/src/mmu/page_table.rs @@ -92,6 +92,21 @@ fn vpn(va: usize, level: usize) -> usize { (va >> (12 + 9 * level)) & 0x1FF } +pub fn translate(root: &PageTable, va: usize) -> Option { + let vpn2 = vpn(va, 2); + if !root.entries[vpn2].is_valid() { return None; } + let l1 = unsafe { &*((root.entries[vpn2].ppn() << 12) as *const PageTable) }; + + let vpn1 = vpn(va, 1); + if !l1.entries[vpn1].is_valid() { return None; } + let l0 = unsafe { &*((l1.entries[vpn1].ppn() << 12) as *const PageTable) }; + + let vpn0 = vpn(va, 0); + if !l0.entries[vpn0].is_valid() { return None; } + let pa = (l0.entries[vpn0].ppn() << 12) as usize | (va & 0xFFF); + Some(pa) +} + fn map_page(root: &mut PageTable, va: usize, pa: usize, flags: Sv39Flags) { let vpn2 = vpn(va, 2); let vpn1 = vpn(va, 1); @@ -163,36 +178,32 @@ pub fn activate(root: &PageTable) { pub fn create_user_page_table( kernel_root: &PageTable, - program_pa: PhysicalAddress, - program_pages: usize, - user_va: VirtualAddress, + _program_pa: PhysicalAddress, + _program_pages: usize, + _user_va: VirtualAddress, ) -> &'static mut PageTable { let root = alloc_page_table(); for i in 0..512 { - if kernel_root.entries[i].is_valid() { + if !kernel_root.entries[i].is_valid() { + continue; + } + if i == 0 { + // User mappings land in VPN[2]=0 — each process needs its own L1 + let kernel_l1_addr = (kernel_root.entries[i].ppn() << 12) as *const PageTable; + let kernel_l1 = unsafe { &*kernel_l1_addr }; + let new_l1 = alloc_page_table(); + for j in 0..512 { + if kernel_l1.entries[j].is_valid() { + new_l1.entries[j] = kernel_l1.entries[j]; + } + } + let ppn = (new_l1 as *const PageTable as u64) >> 12; + root.entries[i] = PageTableEntry::new_branch(ppn); + } else { root.entries[i] = kernel_root.entries[i]; } } - if program_pages > 0 { - map(root, Mapping { - va: user_va, - pa: program_pa, - pages: program_pages, - flags: Sv39Flags::user_rx(), - }); - - let stack_table = alloc_page_table(); - let stack_pa = PhysicalAddress::from_raw(stack_table as *const PageTable as usize); - let stack_va = VirtualAddress::from_raw(user_va.raw().wrapping_sub(PAGE_SIZE)); - map(root, Mapping { - va: stack_va, - pa: stack_pa, - pages: 1, - flags: Sv39Flags::user_rw(), - }); - } - root } diff --git a/crates/adapter-riscv/src/platform.rs b/crates/adapter-riscv/src/platform.rs index 4ce0e4a..058f790 100644 --- a/crates/adapter-riscv/src/platform.rs +++ b/crates/adapter-riscv/src/platform.rs @@ -16,14 +16,17 @@ use crate::trap; pub struct RiscVPlatform { num_cores: u8, + kernel_pt: usize, page_tables: [usize; MAX_PROCESSES], } impl RiscVPlatform { pub fn new(kernel_pt_addr: usize) -> Self { - let mut pt = [0usize; MAX_PROCESSES]; - pt[0] = kernel_pt_addr; - Self { num_cores: 1, page_tables: pt } + Self { + num_cores: 1, + kernel_pt: kernel_pt_addr, + page_tables: [0usize; MAX_PROCESSES], + } } pub fn page_table_for(&self, pid: ProcessId) -> usize { @@ -37,7 +40,7 @@ impl RiscVPlatform { fn ensure_page_table(&mut self, pid: ProcessId) -> &mut mmu::PageTable { let idx = pid.raw() as usize; if self.page_tables[idx] == 0 { - let kernel_pt = unsafe { &*(self.page_tables[0] as *const mmu::PageTable) }; + let kernel_pt = unsafe { &*(self.kernel_pt as *const mmu::PageTable) }; let user_pt = mmu::create_user_page_table( kernel_pt, PhysicalAddress::from_raw(0), @@ -131,6 +134,28 @@ impl Platform for RiscVPlatform { }); } + fn debug_putchar(&self, byte: u8) { + crate::hw::uart::Uart::new().put_char(byte); + } + + fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option { + let pt_addr = self.page_tables[process.raw() as usize]; + if pt_addr == 0 { return None; } + let pt = unsafe { &*(pt_addr as *const mmu::PageTable) }; + mmu::translate(pt, va.raw()).map(PhysicalAddress::from_raw) + } + + fn map_user_page(&mut self, process: ProcessId, va: VirtualAddress, pa: PhysicalAddress, writable: bool, executable: bool) { + let pt = self.ensure_page_table(process); + let flags = mmu::Sv39Flags::user(true, writable, executable); + mmu::map(pt, mmu::Mapping { + va, + pa, + pages: 1, + flags, + }); + } + fn halt(&self) -> ! { unsafe { core::arch::asm!( diff --git a/crates/adapter-riscv/src/trap/mod.rs b/crates/adapter-riscv/src/trap/mod.rs index e2fc98c..2351176 100644 --- a/crates/adapter-riscv/src/trap/mod.rs +++ b/crates/adapter-riscv/src/trap/mod.rs @@ -135,7 +135,7 @@ pub fn init() { let addr = trap_vector as usize; core::arch::asm!("csrw stvec, {0}", in(reg) addr); core::arch::asm!("csrw sscratch, zero"); - core::arch::asm!("csrs sstatus, {0}", in(reg) 1usize << 1); + // SIE stays clear — interrupts are enabled only when entering userspace via sret let sie_bits = (1usize << 5) | (1usize << 9) | (1usize << 1); core::arch::asm!("csrs sie, {0}", in(reg) sie_bits); } @@ -230,6 +230,25 @@ fn decode_syscall(frame: &TrapFrame) -> Option { count, }) } + SYS_SPAWN_THREAD_IN => { + let slot = types::bounded::CapSlot::new(a0 as u8)?; + Some(Syscall::SpawnThreadIn { + process: slot, + entry: types::address::EntryPoint::from_raw(a1), + stack: types::address::StackPointer::from_raw(a2), + }) + } + SYS_CREATE_ENDPOINT => Some(Syscall::CreateEndpoint), + SYS_DEBUG_PUTCHAR => Some(Syscall::DebugPutchar { byte: a0 as u8 }), + SYS_MAP_PAGE => { + let slot = types::bounded::CapSlot::new(a0 as u8)?; + Some(Syscall::MapPage { + process: slot, + src_va: types::address::VirtualAddress::from_raw(a1), + dst_va: types::address::VirtualAddress::from_raw(a2), + executable: a3 != 0, + }) + } _ => None, } } @@ -272,6 +291,30 @@ fn load_context_to_frame(ctx: &domain::thread::SavedContext, frame: &mut TrapFra frame.sstatus |= 1 << 5; // set SPIE } +fn apply_pending_result(frame: &mut TrapFrame, tid: types::id::ThreadId) { + let kern = crate::kernel_ref(); + if let Some(result) = kern.take_pending_result(tid) { + write_result_direct(frame, &result); + } +} + +fn write_result_direct(frame: &mut TrapFrame, result: &kernel::SyscallResult) { + use kernel::SyscallResult; + match result { + SyscallResult::Ok => frame.regs[10] = 0, + SyscallResult::Value(v) => frame.regs[10] = *v, + SyscallResult::Slot(s) => frame.regs[10] = s.value() as usize, + SyscallResult::Message(m) => { + frame.regs[10] = m.word(0); + frame.regs[11] = m.word(1); + frame.regs[12] = m.word(2); + frame.regs[13] = m.word(3); + } + SyscallResult::Error(_) => frame.regs[10] = usize::MAX, + SyscallResult::WakeReason(_) => frame.regs[10] = 0, + } +} + fn switch_to_thread(frame: &mut TrapFrame, current_tid: types::id::ThreadId, next_tid: types::id::ThreadId) { let kern = crate::kernel_ref(); @@ -286,6 +329,8 @@ fn switch_to_thread(frame: &mut TrapFrame, current_tid: types::id::ThreadId, nex load_context_to_frame(ctx, frame); } + apply_pending_result(frame, next_tid); + if next_pid != current_pid { if let Some(pid) = next_pid { let pt_addr = kern.platform_mut().page_table_for(pid); @@ -308,10 +353,15 @@ fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) { let next_pid = kern.thread_process(next_tid); let current_pid = kern.thread_process(current_tid); + let core = types::id::CoreId::from_raw(0); + let _ = kern.set_thread_running(next_tid, core); + if let Some(ctx) = kern.thread_context(next_tid) { load_context_to_frame(ctx, frame); } + apply_pending_result(frame, next_tid); + if next_pid != current_pid { if let Some(pid) = next_pid { let pt_addr = kern.platform_mut().page_table_for(pid); @@ -322,7 +372,6 @@ fn switch_to_next(frame: &mut TrapFrame, current_tid: types::id::ThreadId) { } } } else { - // no runnable thread — idle loop { unsafe { core::arch::asm!("wfi") }; } } } diff --git a/crates/apps/echo/Cargo.toml b/crates/apps/echo/Cargo.toml new file mode 100644 index 0000000..34002fb --- /dev/null +++ b/crates/apps/echo/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "echo" +version = "0.1.0" +edition = "2024" + +[[bin]] +name = "echo" +test = false +bench = false + +[dependencies] +userspace = { workspace = true } +userspace-rt = { workspace = true } diff --git a/crates/apps/echo/src/main.rs b/crates/apps/echo/src/main.rs new file mode 100644 index 0000000..02b1302 --- /dev/null +++ b/crates/apps/echo/src/main.rs @@ -0,0 +1,18 @@ +#![no_std] +#![no_main] + +extern crate userspace_rt; + +use userspace::syscall; + +#[unsafe(no_mangle)] +extern "C" fn main() { + // Cap slot 0 = endpoint cap (granted by adapter at boot) + let ep: usize = 0; + + // Receive a message, echo it back with word0 incremented + let (w0, w1, w2, w3) = syscall::receive(ep); + syscall::send(ep, w0.wrapping_add(1), w1, w2, w3); + + syscall::exit(); +} diff --git a/crates/domain/src/memory/process_memory.rs b/crates/domain/src/memory/process_memory.rs index d8eff63..7ecab8f 100644 --- a/crates/domain/src/memory/process_memory.rs +++ b/crates/domain/src/memory/process_memory.rs @@ -1,11 +1,16 @@ +use types::address::VirtualAddress; use types::id::ProcessId; use crate::error::KernelError; +const USER_HEAP_START: usize = 0x40000; +const PAGE_SIZE: usize = 4096; + pub struct ProcessMemory { process: ProcessId, page_count: usize, limit: usize, + next_heap_va: usize, } impl ProcessMemory { @@ -14,9 +19,16 @@ impl ProcessMemory { process, page_count: 0, limit, + next_heap_va: USER_HEAP_START, } } + pub fn alloc_user_va(&mut self, pages: usize) -> VirtualAddress { + let va = self.next_heap_va; + self.next_heap_va += pages * PAGE_SIZE; + VirtualAddress::from_raw(va) + } + pub fn process(&self) -> ProcessId { self.process } diff --git a/crates/domain/src/syscall.rs b/crates/domain/src/syscall.rs index 757e655..251e108 100644 --- a/crates/domain/src/syscall.rs +++ b/crates/domain/src/syscall.rs @@ -87,4 +87,17 @@ pub enum Syscall { Yield, Exit, Halt, + CreateEndpoint, + DebugPutchar { byte: u8 }, + MapPage { + process: CapSlot, + src_va: VirtualAddress, + dst_va: VirtualAddress, + executable: bool, + }, + SpawnThreadIn { + process: CapSlot, + entry: EntryPoint, + stack: StackPointer, + }, } diff --git a/crates/init/Cargo.toml b/crates/init/Cargo.toml index ad956cd..8a61eb2 100644 --- a/crates/init/Cargo.toml +++ b/crates/init/Cargo.toml @@ -9,6 +9,7 @@ test = false bench = false [dependencies] +elf = { workspace = true } types = { workspace = true } userspace = { workspace = true } userspace-rt = { workspace = true } diff --git a/crates/init/src/boot.rs b/crates/init/src/boot.rs deleted file mode 100644 index 08e42c8..0000000 --- a/crates/init/src/boot.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::manifest::{BinarySource, BootManifest, EndpointBinding, HardwareCap}; -use crate::protocol_helpers; -use crate::supervisor::Supervisor; -use types::bounded::CapSlot; -use types::id::ProcessId; -use userspace::syscall; - -const MAX_ENDPOINTS: usize = 16; -const MAX_PIDS: usize = 16; - -struct EndpointEntry { - name: &'static str, - slot: CapSlot, -} - -struct EndpointRegistry { - entries: [Option; MAX_ENDPOINTS], - count: usize, -} - -impl EndpointRegistry { - fn new() -> Self { - Self { - entries: [const { None }; MAX_ENDPOINTS], - count: 0, - } - } - - fn register(&mut self, name: &'static str, slot: CapSlot) { - if self.count < MAX_ENDPOINTS { - self.entries[self.count] = Some(EndpointEntry { name, slot }); - self.count += 1; - } - } - - fn find(&self, name: &str) -> Option { - for entry in &self.entries { - if let Some(e) = entry { - if e.name == name { - return Some(e.slot); - } - } - } - None - } -} - -pub struct BootResult { - pub storage_endpoint: CapSlot, - pub create_cap: CapSlot, - pub server_pids: [Option; MAX_PIDS], - pub server_count: usize, -} - -impl BootResult { - pub fn empty() -> Self { - Self { - storage_endpoint: CapSlot::new(0).unwrap_or_else(|| unreachable!()), - create_cap: CapSlot::new(0).unwrap_or_else(|| unreachable!()), - server_pids: [const { None }; MAX_PIDS], - server_count: 0, - } - } -} - -pub fn boot(manifest: &BootManifest, create_cap: CapSlot, result: &mut BootResult) { - let mut endpoints = EndpointRegistry::new(); - result.create_cap = create_cap; - result.storage_endpoint = create_cap; - result.server_count = 0; - - for (i, server) in manifest.servers.iter().enumerate() { - let pid_raw = syscall::create_process(create_cap.value() as usize); - let pid = ProcessId::from_raw(pid_raw as u16); - if i < MAX_PIDS { - result.server_pids[i] = Some(pid); - } - - for binding in server.endpoints { - if let EndpointBinding::Own { name } = binding { - if let Some(slot) = CapSlot::new(0) { - endpoints.register(name, slot); - if *name == "storage" { - result.storage_endpoint = slot; - } - } - } - } - - for hw_cap in server.hardware_caps { - match hw_cap { - HardwareCap::Framebuffer { .. } => {} - HardwareCap::Irq { .. } => {} - HardwareCap::Memory { .. } => {} - } - } - } - - for server in manifest.servers { - if let BinarySource::Storage { path } = server.binary { - protocol_helpers::storage_write_binary( - result.storage_endpoint, - path.as_bytes(), - &[], - ); - } - } - - result.server_count = manifest.servers.len().min(MAX_PIDS); -} - -pub fn create_supervisor(manifest: &BootManifest, result: &BootResult) -> Supervisor { - let mut supervisor = Supervisor::new(manifest, result.storage_endpoint, result.create_cap); - for i in 0..result.server_count { - if let Some(pid) = result.server_pids[i] { - supervisor.register_pid(i, pid); - } - } - supervisor -} diff --git a/crates/init/src/lib.rs b/crates/init/src/lib.rs index 0f11502..7282680 100644 --- a/crates/init/src/lib.rs +++ b/crates/init/src/lib.rs @@ -1,18 +1,174 @@ #![no_std] -pub mod boot; -pub mod manifest; -pub mod protocol_helpers; -pub mod shutdown; -pub mod supervisor; +use userspace::syscall; -pub fn run() -> ! { - let create_cap = types::bounded::CapSlot::new(0) - .unwrap_or_else(|| unreachable!()); +const PAGE_SIZE: usize = 4096; +const USER_CODE_BASE: usize = 0x10000; +const USER_STACK_TOP: usize = USER_CODE_BASE; +const USER_STACK_PAGES: usize = 4; +const CMD_READ_FILE: usize = 1; - let mut result = boot::BootResult::empty(); - boot::boot(&manifest::SNAKE_MANIFEST, create_cap, &mut result); +const CREATE_CAP: usize = 0; +const STORAGE_EP_CAP: usize = 2; - let mut sup = boot::create_supervisor(&manifest::SNAKE_MANIFEST, &result); - sup.run(create_cap); +fn debug_print(s: &[u8]) { + for &b in s { + syscall::debug_putchar(b); + } +} + +fn debug_hex(mut val: usize) { + debug_print(b"0x"); + if val == 0 { + syscall::debug_putchar(b'0'); + return; + } + let mut buf = [0u8; 16]; + let mut i = 0; + while val > 0 { + let d = (val & 0xF) as u8; + buf[i] = if d < 10 { b'0' + d } else { b'a' + d - 10 }; + val >>= 4; + i += 1; + } + while i > 0 { + i -= 1; + syscall::debug_putchar(buf[i]); + } +} + +fn request_file(shared_buf: *mut u8, filename: &[u8]) -> usize { + unsafe { + for (i, &b) in filename.iter().enumerate() { + core::ptr::write_volatile(shared_buf.add(i), b); + } + } + syscall::send(STORAGE_EP_CAP, CMD_READ_FILE, filename.len(), 0, 0); + let (file_size, _, _, _) = syscall::receive(STORAGE_EP_CAP); + file_size +} + +struct LoadedProcess { + process_cap: usize, + entry_point: usize, +} + +fn load_binary(shared_buf: *mut u8, filename: &[u8]) -> Option { + let file_size = request_file(shared_buf, filename); + if file_size == 0 { + return None; + } + + let elf_data = unsafe { core::slice::from_raw_parts(shared_buf as *const u8, file_size) }; + let elf_info = match elf::parse(elf_data) { + Ok(info) => info, + Err(_) => return None, + }; + + let process_cap = syscall::create_process(CREATE_CAP); + if process_cap == usize::MAX { + debug_print(b"create_process failed\n"); + return None; + } + + for segment in elf_info.segments() { + let page_start = segment.vaddr & !0xFFF; + let offset_in_page = segment.vaddr & 0xFFF; + let total_size = offset_in_page + segment.mem_size; + let seg_pages = (total_size + PAGE_SIZE - 1) / PAGE_SIZE; + + let local_va = syscall::request_pages(seg_pages); + if local_va == usize::MAX { + debug_print(b"request_pages failed\n"); + return None; + } + + let local_ptr = local_va as *mut u8; + unsafe { + core::ptr::write_bytes(local_ptr, 0, seg_pages * PAGE_SIZE); + } + + let copy_len = segment.file_size.min(segment.mem_size); + if copy_len > 0 { + let src = segment.file_offset; + if src + copy_len <= file_size { + unsafe { + core::ptr::copy_nonoverlapping( + shared_buf.add(src), + local_ptr.add(offset_in_page), + copy_len, + ); + } + } + } + + for p in 0..seg_pages { + let src_page = local_va + p * PAGE_SIZE; + let dst_page = page_start + p * PAGE_SIZE; + syscall::map_page(process_cap, src_page, dst_page, segment.flags.execute); + } + } + + for i in 0..USER_STACK_PAGES { + let stack_va = syscall::request_pages(1); + if stack_va == usize::MAX { return None; } + let dst = USER_STACK_TOP - (USER_STACK_PAGES - i) * PAGE_SIZE; + syscall::map_page(process_cap, stack_va, dst, false); + } + + Some(LoadedProcess { + process_cap, + entry_point: elf_info.entry_point, + }) +} + +struct ServerInfo { + filename: &'static [u8], + name: &'static [u8], +} + +// display is loaded by the adapter (needs GPU MMIO access) +const SERVERS: &[ServerInfo] = &[ + ServerInfo { filename: b"input.elf", name: b"input" }, + ServerInfo { filename: b"log.elf", name: b"log" }, + ServerInfo { filename: b"permis.elf", name: b"permis" }, + ServerInfo { filename: b"session.elf", name: b"session" }, + ServerInfo { filename: b"snake.elf", name: b"snake" }, +]; + +pub fn run(shared_buf_va: usize) -> ! { + let shared_buf = shared_buf_va as *mut u8; + + debug_print(b"init: started (pid="); + debug_hex(syscall::get_process_id()); + debug_print(b")\n"); + + for server in SERVERS { + debug_print(b"init: loading "); + debug_print(server.name); + debug_print(b"... "); + + let loaded = match load_binary(shared_buf, server.filename) { + Some(l) => l, + None => { + debug_print(b"SKIP\n"); + continue; + } + }; + + let ep_cap = syscall::create_endpoint(); + let read_execute: usize = (1 << 0) | (1 << 2); + syscall::delegate(ep_cap, loaded.process_cap, read_execute); + syscall::spawn_thread_in(loaded.process_cap, loaded.entry_point, USER_STACK_TOP); + + debug_print(b"ok (entry="); + debug_hex(loaded.entry_point); + debug_print(b")\n"); + } + + debug_print(b"init: all loaded, supervising\n"); + + loop { + syscall::yield_now(); + } } diff --git a/crates/init/src/main.rs b/crates/init/src/main.rs index d7ceb9a..0ed7516 100644 --- a/crates/init/src/main.rs +++ b/crates/init/src/main.rs @@ -4,6 +4,6 @@ extern crate userspace_rt; #[unsafe(no_mangle)] -extern "C" fn main() { - init::run(); +extern "C" fn main(shared_buf: usize) { + init::run(shared_buf); } diff --git a/crates/init/src/manifest.rs b/crates/init/src/manifest.rs deleted file mode 100644 index 57a7dbd..0000000 --- a/crates/init/src/manifest.rs +++ /dev/null @@ -1,112 +0,0 @@ -use types::address::PhysicalAddress; -use types::bounded::Priority; -use types::id::IrqId; - -#[derive(Debug, Clone, Copy)] -pub enum HardwareCap { - Framebuffer { base: PhysicalAddress, pages: usize }, - Irq { irq: IrqId }, - Memory { base: PhysicalAddress, pages: usize }, -} - -#[derive(Debug, Clone, Copy)] -pub enum EndpointBinding { - Own { name: &'static str }, - ConnectTo { server: &'static str, endpoint: &'static str }, -} - -#[derive(Debug, Clone, Copy)] -pub enum BinarySource { - Ram { base: PhysicalAddress, size: usize }, - Storage { path: &'static str }, -} - -#[derive(Debug)] -pub struct ServerEntry { - pub name: &'static str, - pub priority: Priority, - pub memory_pages: usize, - pub hardware_caps: &'static [HardwareCap], - pub endpoints: &'static [EndpointBinding], - pub restartable: bool, - pub binary: BinarySource, -} - -#[derive(Debug)] -pub struct BootManifest { - pub servers: &'static [ServerEntry], -} - -pub static SNAKE_MANIFEST: BootManifest = BootManifest { - servers: &[ - ServerEntry { - name: "log", - priority: Priority::DEFAULT, - memory_pages: 64, - hardware_caps: &[], - endpoints: &[EndpointBinding::Own { name: "log" }], - restartable: true, - binary: BinarySource::Storage { path: "/servers/log.elf" }, - }, - ServerEntry { - name: "permission", - priority: Priority::DEFAULT, - memory_pages: 32, - hardware_caps: &[], - endpoints: &[EndpointBinding::Own { name: "permission" }], - restartable: true, - binary: BinarySource::Storage { path: "/servers/permission.elf" }, - }, - ServerEntry { - name: "storage", - priority: Priority::DEFAULT, - memory_pages: 128, - hardware_caps: &[], - endpoints: &[EndpointBinding::Own { name: "storage" }], - restartable: true, - binary: BinarySource::Ram { - base: PhysicalAddress::from_raw(0), - size: 0, - }, - }, - ServerEntry { - name: "display", - priority: Priority::DEFAULT, - memory_pages: 256, - hardware_caps: &[ - HardwareCap::Framebuffer { - base: PhysicalAddress::from_raw(0x10008000), - pages: 75, - }, - ], - endpoints: &[EndpointBinding::Own { name: "display" }], - restartable: true, - binary: BinarySource::Storage { path: "/servers/display.elf" }, - }, - ServerEntry { - name: "input", - priority: Priority::DEFAULT, - memory_pages: 32, - hardware_caps: &[ - HardwareCap::Irq { irq: IrqId::from_raw(10) }, - ], - endpoints: &[EndpointBinding::Own { name: "input" }], - restartable: true, - binary: BinarySource::Storage { path: "/servers/input.elf" }, - }, - ServerEntry { - name: "session", - priority: Priority::DEFAULT, - memory_pages: 64, - hardware_caps: &[], - endpoints: &[ - EndpointBinding::Own { name: "session" }, - EndpointBinding::ConnectTo { server: "display", endpoint: "display" }, - EndpointBinding::ConnectTo { server: "input", endpoint: "input" }, - EndpointBinding::ConnectTo { server: "storage", endpoint: "storage" }, - ], - restartable: true, - binary: BinarySource::Storage { path: "/servers/session.elf" }, - }, - ], -}; diff --git a/crates/init/src/protocol_helpers.rs b/crates/init/src/protocol_helpers.rs deleted file mode 100644 index a53ca72..0000000 --- a/crates/init/src/protocol_helpers.rs +++ /dev/null @@ -1,44 +0,0 @@ -use types::bounded::CapSlot; - -#[derive(Debug, Clone, Copy)] -pub struct StorageHandle(u16); - -impl StorageHandle { - pub fn raw(&self) -> u16 { self.0 } -} - -fn pack_path(path: &[u8]) -> (usize, usize) { - let mut w0 = [0u8; 8]; - let mut w1 = [0u8; 8]; - for (i, &b) in path.iter().enumerate() { - if i < 8 { w0[i] = b; } - else if i < 16 { w1[i - 8] = b; } - } - (usize::from_le_bytes(w0), usize::from_le_bytes(w1)) -} - -pub fn storage_open(endpoint: CapSlot, path: &[u8]) -> Option { - let (w0, w1) = pack_path(path); - let _ = userspace::syscall::send(endpoint.value() as usize, 0, w0, w1, 0); - let (tag, handle, _, _) = userspace::syscall::receive(endpoint.value() as usize); - if tag == 0 { - Some(StorageHandle(handle as u16)) - } else { - None - } -} - -pub fn storage_close(endpoint: CapSlot, handle: StorageHandle) { - let _ = userspace::syscall::send( - endpoint.value() as usize, - 3, - handle.raw() as usize, - 0, - 0, - ); -} - -pub fn storage_write_binary(endpoint: CapSlot, path: &[u8], _binary: &[u8]) { - let (w0, w1) = pack_path(path); - let _ = userspace::syscall::send(endpoint.value() as usize, 5, w0, w1, 0); -} diff --git a/crates/init/src/shutdown.rs b/crates/init/src/shutdown.rs deleted file mode 100644 index ae42fde..0000000 --- a/crates/init/src/shutdown.rs +++ /dev/null @@ -1,25 +0,0 @@ -use types::bounded::CapSlot; - -const SHUTDOWN_TIMEOUT_TICKS: usize = 50_000_000; -const SHUTDOWN_SIGNAL: usize = 0xFFFF_FFFF; - -/// Best-effort shutdown: notify all servers, wait briefly for them to flush, then halt. -/// -/// Servers that receive the shutdown signal should flush state to storage and exit. -/// We can't block on individual acks because a crashed server would deadlock us. -/// Instead, we yield repeatedly until the timeout expires, giving servers time to finish. -pub fn shutdown(server_endpoints: &[CapSlot], endpoint_count: usize) -> ! { - for i in 0..endpoint_count { - let ep = server_endpoints[i].value() as usize; - userspace::syscall::send(ep, SHUTDOWN_SIGNAL, 0, 0, 0); - } - - let start = userspace::syscall::get_time(); - let deadline = start + SHUTDOWN_TIMEOUT_TICKS; - - while userspace::syscall::get_time() < deadline { - userspace::syscall::yield_now(); - } - - userspace::syscall::halt(); -} diff --git a/crates/init/src/supervisor.rs b/crates/init/src/supervisor.rs deleted file mode 100644 index ee73bce..0000000 --- a/crates/init/src/supervisor.rs +++ /dev/null @@ -1,143 +0,0 @@ -use crate::manifest::{BinarySource, BootManifest}; -use types::bounded::CapSlot; -use types::id::ProcessId; - -const MAX_SERVERS: usize = 16; -const MAX_RESTARTS: u8 = 5; -const BACKOFF_TICKS: [usize; 5] = [0, 10_000_000, 50_000_000, 300_000_000, 300_000_000]; - -struct ServerState { - name: &'static str, - pid: Option, - restartable: bool, - restart_count: u8, - binary: BinarySource, -} - -pub struct Supervisor { - servers: [Option; MAX_SERVERS], - count: usize, - storage_endpoint: CapSlot, - create_cap: CapSlot, -} - -impl Supervisor { - pub fn new(manifest: &BootManifest, storage_endpoint: CapSlot, create_cap: CapSlot) -> Self { - let mut servers = [const { None }; MAX_SERVERS]; - for (i, entry) in manifest.servers.iter().enumerate() { - if i < MAX_SERVERS { - servers[i] = Some(ServerState { - name: entry.name, - pid: None, - restartable: entry.restartable, - restart_count: 0, - binary: entry.binary, - }); - } - } - Self { - servers, - count: manifest.servers.len().min(MAX_SERVERS), - storage_endpoint, - create_cap, - } - } - - pub fn register_pid(&mut self, index: usize, pid: ProcessId) { - if let Some(state) = &mut self.servers[index] { - state.pid = Some(pid); - } - } - - pub fn should_restart(&self, index: usize) -> bool { - self.servers[index] - .as_ref() - .map(|s| s.restartable && s.restart_count < MAX_RESTARTS) - .unwrap_or(false) - } - - fn find_by_pid(&self, pid: ProcessId) -> Option { - for i in 0..self.count { - if let Some(state) = &self.servers[i] { - if state.pid == Some(pid) { - return Some(i); - } - } - } - None - } - - fn backoff_delay(&self, restart_count: u8) -> usize { - let idx = (restart_count as usize).min(BACKOFF_TICKS.len() - 1); - BACKOFF_TICKS[idx] - } - - fn wait_ticks(&self, ticks: usize) { - if ticks == 0 { return; } - let start = userspace::syscall::get_time(); - let deadline = start + ticks; - while userspace::syscall::get_time() < deadline { - userspace::syscall::yield_now(); - } - } - - fn restart_server(&mut self, index: usize) { - let (binary, restart_count) = match &self.servers[index] { - Some(state) => (state.binary, state.restart_count), - None => return, - }; - - let delay = self.backoff_delay(restart_count); - self.wait_ticks(delay); - - match binary { - BinarySource::Ram { .. } => { - let new_pid = userspace::syscall::create_process( - self.create_cap.value() as usize, - ); - if let Some(state) = &mut self.servers[index] { - state.pid = Some(ProcessId::from_raw(new_pid as u16)); - state.restart_count += 1; - } - } - BinarySource::Storage { path } => { - self.load_from_storage_and_restart(index, path); - } - } - } - - fn load_from_storage_and_restart(&mut self, index: usize, path: &str) { - use crate::protocol_helpers; - - let handle = protocol_helpers::storage_open(self.storage_endpoint, path.as_bytes()); - if handle.is_none() { return; } - - let new_pid = userspace::syscall::create_process( - self.create_cap.value() as usize, - ); - - if let Some(h) = handle { - protocol_helpers::storage_close(self.storage_endpoint, h); - } - - if let Some(state) = &mut self.servers[index] { - state.pid = Some(ProcessId::from_raw(new_pid as u16)); - state.restart_count += 1; - } - } - - pub fn run(&mut self, fault_endpoint: CapSlot) -> ! { - loop { - let (crashed_pid_raw, _fault_code, _fault_addr, _fault_pc) = - userspace::syscall::receive(fault_endpoint.value() as usize); - - let crashed_pid = ProcessId::from_raw(crashed_pid_raw as u16); - - if let Some(index) = self.find_by_pid(crashed_pid) { - if self.should_restart(index) { - self.restart_server(index); - } - } - } - } -} diff --git a/crates/kernel/src/lib.rs b/crates/kernel/src/lib.rs index 6465e11..118043e 100644 --- a/crates/kernel/src/lib.rs +++ b/crates/kernel/src/lib.rs @@ -105,6 +105,14 @@ impl Kernel

{ self.pending_results[thread.raw() as usize].as_ref() } + pub fn take_pending_result(&mut self, thread: ThreadId) -> Option { + self.pending_results[thread.raw() as usize].take() + } + + pub fn set_thread_running(&mut self, tid: ThreadId, core: types::id::CoreId) -> Result<(), domain::error::KernelError> { + self.threads.get_mut(tid)?.set_running(core) + } + pub fn free_page_count(&self) -> usize { self.page_pool.free_count() } @@ -136,6 +144,30 @@ impl Kernel

{ self.scheduler.next(core) } + pub fn create_process_internal(&mut self) -> Option { + let pid = self.processes.create(CapabilityTable::new(), None).ok()?; + self.process_memory[pid.raw() as usize] = + Some(domain::memory::process_memory::ProcessMemory::new(pid, 1024)); + Some(pid) + } + + pub fn spawn_thread_internal( + &mut self, + pid: ProcessId, + ) -> Option { + use domain::scheduler::Scheduler; + let tid = self.threads.spawn( + pid, + Priority::DEFAULT, + TimeSlice::DEFAULT, + None, + ).ok()?; + let _ = self.processes.get_mut(pid).map(|p| p.add_thread(tid)); + let core = self.platform.core_id(); + self.scheduler.add(tid, Priority::DEFAULT, core); + Some(tid) + } + pub fn create_endpoint(&mut self) -> Result { self.endpoints.create().ok_or(KernelError::NoFreeSlot) } diff --git a/crates/kernel/src/syscall/ipc.rs b/crates/kernel/src/syscall/ipc.rs index 14f3922..7113fb6 100644 --- a/crates/kernel/src/syscall/ipc.rs +++ b/crates/kernel/src/syscall/ipc.rs @@ -4,6 +4,31 @@ use super::{SyscallOutcome, SyscallResult}; use crate::Kernel; impl Kernel

{ + pub(crate) fn handle_create_endpoint( + &mut self, + caller_pid: types::id::ProcessId, + ) -> SyscallOutcome { + let ep_id = match self.create_endpoint() { + Ok(id) => id, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + let all_perms = types::permission::PermissionSet::empty() + .with(types::permission::Permission::Read) + .with(types::permission::Permission::Write) + .with(types::permission::Permission::Execute) + .with(types::permission::Permission::Delegate); + + match self.grant_cap( + caller_pid, + domain::resource::Resource::Endpoint(ep_id), + all_perms, + ) { + Ok(slot) => SyscallOutcome::Complete(SyscallResult::Slot(slot)), + Err(e) => SyscallOutcome::Complete(SyscallResult::Error(e)), + } + } + pub(crate) fn handle_send( &mut self, caller_tid: types::id::ThreadId, diff --git a/crates/kernel/src/syscall/lifecycle.rs b/crates/kernel/src/syscall/lifecycle.rs index fa54241..05d7484 100644 --- a/crates/kernel/src/syscall/lifecycle.rs +++ b/crates/kernel/src/syscall/lifecycle.rs @@ -70,6 +70,53 @@ impl Kernel

{ SyscallOutcome::Complete(SyscallResult::Slot(slot)) } + pub(crate) fn handle_spawn_thread_in( + &mut self, + caller_pid: types::id::ProcessId, + process_cap: types::bounded::CapSlot, + entry: types::address::EntryPoint, + stack: types::address::StackPointer, + ) -> SyscallOutcome { + use domain::scheduler::Scheduler; + use domain::thread::SavedContext; + + let target_pid = match self.check_cap( + caller_pid, + process_cap, + types::permission::Permission::Execute, + ) { + Ok(cap) => match cap.resource() { + domain::resource::Resource::Process(pid) => *pid, + _ => return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::PermissionDenied { + slot: process_cap, + needed: types::permission::Permission::Execute, + }, + )), + }, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + let priority = types::bounded::Priority::new(16) + .unwrap_or_else(|| unreachable!()); + let time_slice = types::time::TimeSlice::new(100); + let context = SavedContext::with_entry_and_stack(entry, stack); + + let tid = match self.threads.spawn_with_context(target_pid, priority, time_slice, None, context) { + Ok(t) => t, + Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), + }; + + if let Err(e) = self.processes.get_mut(target_pid).and_then(|p| p.add_thread(tid)) { + let _ = self.threads.destroy(tid); + return SyscallOutcome::Complete(SyscallResult::Error(e)); + } + + let core = self.platform.core_id(); + self.scheduler.add(tid, priority, core); + SyscallOutcome::Complete(SyscallResult::Value(tid.raw() as usize)) + } + pub(crate) fn handle_kill( &mut self, caller_pid: types::id::ProcessId, diff --git a/crates/kernel/src/syscall/memory.rs b/crates/kernel/src/syscall/memory.rs index 90e963d..1886f74 100644 --- a/crates/kernel/src/syscall/memory.rs +++ b/crates/kernel/src/syscall/memory.rs @@ -1,4 +1,5 @@ use platform::Platform; +use types::address::{PhysicalAddress, VirtualAddress}; use super::{SyscallOutcome, SyscallResult}; use crate::Kernel; @@ -11,19 +12,22 @@ impl Kernel

{ ) -> SyscallOutcome { let pages = count.value() as usize; - if let Some(pm) = &mut self.process_memory[pid.raw() as usize] { - if pm.grant(pages).is_err() { - return SyscallOutcome::Complete(SyscallResult::Error( - domain::error::KernelError::OutOfMemory, - )); - } - } else { - return SyscallOutcome::Complete(SyscallResult::Error( + let pm = match &mut self.process_memory[pid.raw() as usize] { + Some(pm) => pm, + None => return SyscallOutcome::Complete(SyscallResult::Error( domain::error::KernelError::InvalidProcess(pid), + )), + }; + + if pm.grant(pages).is_err() { + return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::OutOfMemory, )); } - let addr = match self.page_pool.allocate(pages) { + let user_va = pm.alloc_user_va(pages); + + let phys_addr = match self.page_pool.allocate(pages) { Some(a) => a, None => { if let Some(pm) = &mut self.process_memory[pid.raw() as usize] { @@ -35,48 +39,60 @@ impl Kernel

{ } }; - let region = domain::memory::MemoryRegion::new( - types::id::RegionId::from_raw(0), - pid, - addr, - count, - types::generation::Generation::initial(), - ); - let region_id = match self.regions.insert(region) { - Some(id) => id, - None => { - self.page_pool.free(addr, pages); - if let Some(pm) = &mut self.process_memory[pid.raw() as usize] { - let _ = pm.release(pages); - } - return SyscallOutcome::Complete(SyscallResult::Error( - domain::error::KernelError::OutOfMemory, - )); - } - }; + for i in 0..pages { + let page_va = VirtualAddress::from_raw(user_va.raw() + i * 4096); + let page_pa = PhysicalAddress::from_raw(phys_addr.raw() + i * 4096); + self.platform.map_user_page(pid, page_va, page_pa, true, false); + } - let slot = match self.grant_cap( - pid, - domain::resource::Resource::Memory(region_id), - types::permission::PermissionSet::empty() - .with(types::permission::Permission::Read) - .with(types::permission::Permission::Write), + SyscallOutcome::Complete(SyscallResult::Value(user_va.raw())) + } + + pub(crate) fn handle_map_page( + &mut self, + caller_pid: types::id::ProcessId, + process_cap: types::bounded::CapSlot, + src_va: VirtualAddress, + dst_va: VirtualAddress, + executable: bool, + ) -> SyscallOutcome { + let target_pid = match self.check_cap( + caller_pid, + process_cap, + types::permission::Permission::Execute, ) { - Ok(s) => s, + Ok(cap) => match cap.resource() { + domain::resource::Resource::Process(pid) => *pid, + _ => return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::PermissionDenied { + slot: process_cap, + needed: types::permission::Permission::Execute, + }, + )), + }, Err(e) => return SyscallOutcome::Complete(SyscallResult::Error(e)), }; - SyscallOutcome::Complete(SyscallResult::Slot(slot)) + let pa = match self.platform.translate_va(caller_pid, src_va) { + Some(pa) => pa, + None => return SyscallOutcome::Complete(SyscallResult::Error( + domain::error::KernelError::OutOfMemory, + )), + }; + + self.platform.map_user_page(target_pid, dst_va, pa, !executable, executable); + + SyscallOutcome::Complete(SyscallResult::Ok) } pub(crate) fn handle_release_pages( &mut self, pid: types::id::ProcessId, - addr: types::address::VirtualAddress, + addr: VirtualAddress, count: types::bounded::PageCount, ) -> SyscallOutcome { let pages = count.value() as usize; - let phys = types::address::PhysicalAddress::from_raw(addr.raw()); + let phys = PhysicalAddress::from_raw(addr.raw()); let region_id = match self.regions.find_by_base(pid, phys) { Some((rid, _)) => rid, diff --git a/crates/kernel/src/syscall/mod.rs b/crates/kernel/src/syscall/mod.rs index 20bd1df..88bf343 100644 --- a/crates/kernel/src/syscall/mod.rs +++ b/crates/kernel/src/syscall/mod.rs @@ -55,6 +55,14 @@ impl Kernel

{ Syscall::AcknowledgeInterrupt { irq } => self.handle_ack_interrupt(pid, irq), Syscall::WaitAny { sources } => self.handle_wait_any(tid, pid, sources), Syscall::Halt => self.handle_halt(pid), + Syscall::CreateEndpoint => self.handle_create_endpoint(pid), + Syscall::DebugPutchar { byte } => self.handle_debug_putchar(byte), + Syscall::MapPage { process, src_va, dst_va, executable } => { + self.handle_map_page(pid, process, src_va, dst_va, executable) + } + Syscall::SpawnThreadIn { process, entry, stack } => { + self.handle_spawn_thread_in(pid, process, entry, stack) + } } } } diff --git a/crates/kernel/src/syscall/simple.rs b/crates/kernel/src/syscall/simple.rs index 8b5cc9d..c991be8 100644 --- a/crates/kernel/src/syscall/simple.rs +++ b/crates/kernel/src/syscall/simple.rs @@ -93,6 +93,11 @@ impl Kernel

{ SyscallOutcome::TerminateCaller } + pub(crate) fn handle_debug_putchar(&self, byte: u8) -> SyscallOutcome { + self.platform.debug_putchar(byte); + SyscallOutcome::Complete(SyscallResult::Ok) + } + pub(crate) fn handle_halt( &mut self, pid: types::id::ProcessId, diff --git a/crates/kernel/src/tests/mock_platform.rs b/crates/kernel/src/tests/mock_platform.rs index 8a4d6a9..a20a204 100644 --- a/crates/kernel/src/tests/mock_platform.rs +++ b/crates/kernel/src/tests/mock_platform.rs @@ -57,4 +57,9 @@ impl Platform for MockPlatform { fn halt(&self) -> ! { loop {} } + fn debug_putchar(&self, _byte: u8) {} + fn translate_va(&self, _process: types::id::ProcessId, _va: types::address::VirtualAddress) -> Option { + None + } + fn map_user_page(&mut self, _process: types::id::ProcessId, _va: types::address::VirtualAddress, _pa: types::address::PhysicalAddress, _writable: bool, _executable: bool) {} } diff --git a/crates/platform/src/lib.rs b/crates/platform/src/lib.rs index 8b72f16..050f98f 100644 --- a/crates/platform/src/lib.rs +++ b/crates/platform/src/lib.rs @@ -2,7 +2,7 @@ use domain::memory::MemoryRegion; use types::{ - address::{EntryPoint, PhysicalAddress, StackPointer}, + address::{EntryPoint, PhysicalAddress, StackPointer, VirtualAddress}, bounded::PageCount, id::{CoreId, IrqId, ProcessId}, permission::PermissionSet, @@ -27,4 +27,7 @@ pub trait Platform { permissions: PermissionSet, ); fn halt(&self) -> !; + fn debug_putchar(&self, byte: u8); + fn translate_va(&self, process: ProcessId, va: VirtualAddress) -> Option; + fn map_user_page(&mut self, process: ProcessId, va: VirtualAddress, pa: PhysicalAddress, writable: bool, executable: bool); } diff --git a/crates/servers/display-server/src/adapters/qemu_virtio.rs b/crates/servers/display-server/src/adapters/qemu_virtio.rs index 6e4d586..9efa4ab 100644 --- a/crates/servers/display-server/src/adapters/qemu_virtio.rs +++ b/crates/servers/display-server/src/adapters/qemu_virtio.rs @@ -1,34 +1,123 @@ use crate::framebuffer::Framebuffer; -const VIRTIO_GPU_BASE: usize = 0x10008000; -const FB_WIDTH: u16 = 640; -const FB_HEIGHT: u16 = 480; +const GPU_MMIO_VA: usize = 0x20000; +const VQUEUE_VA: usize = 0x21000; +const CMDBUF_VA: usize = 0x23000; +const PHYSINFO_VA: usize = 0x24000; +const FB_VA: usize = 0x100000; const FB_BPP: usize = 4; +const QUEUE_SIZE: usize = 8; + +struct PhysInfo { + cmd_pa: u64, + resp_pa: u64, + fb_pa: u64, + width: u64, + height: u64, +} + +fn read_phys_info() -> PhysInfo { + unsafe { + let p = PHYSINFO_VA as *const u64; + PhysInfo { + cmd_pa: core::ptr::read_volatile(p), + resp_pa: core::ptr::read_volatile(p.add(1)), + fb_pa: core::ptr::read_volatile(p.add(2)), + width: core::ptr::read_volatile(p.add(3)), + height: core::ptr::read_volatile(p.add(4)), + } + } +} + +fn mmio_write(offset: usize, val: u32) { + unsafe { core::ptr::write_volatile((GPU_MMIO_VA + offset) as *mut u32, val) } +} pub struct VirtioFramebuffer { - buffer: &'static mut [u8], width: u16, height: u16, stride: usize, + phys: PhysInfo, } impl VirtioFramebuffer { - pub fn new(buffer_addr: usize) -> Self { - let stride = FB_WIDTH as usize * FB_BPP; - let size = stride * FB_HEIGHT as usize; - let buffer = unsafe { core::slice::from_raw_parts_mut(buffer_addr as *mut u8, size) }; + pub fn new(_buffer_addr: usize) -> Self { + let phys = read_phys_info(); + let width = phys.width as u16; + let height = phys.height as u16; + let stride = width as usize * FB_BPP; - for byte in buffer.iter_mut() { - *byte = 0; - } + Self { width, height, stride, phys } + } - Self { - buffer, - width: FB_WIDTH, - height: FB_HEIGHT, - stride, + fn send_gpu_cmd(&self, cmd_len: u32, resp_len: u32) { + unsafe { + let desc0 = VQUEUE_VA as *mut u8; + core::ptr::write_volatile(desc0 as *mut u64, self.phys.cmd_pa); + core::ptr::write_volatile(desc0.add(8) as *mut u32, cmd_len); + core::ptr::write_volatile(desc0.add(12) as *mut u16, 1); // NEXT + core::ptr::write_volatile(desc0.add(14) as *mut u16, 1); + + let desc1 = desc0.add(16); + core::ptr::write_volatile(desc1 as *mut u64, self.phys.resp_pa); + core::ptr::write_volatile(desc1.add(8) as *mut u32, resp_len); + core::ptr::write_volatile(desc1.add(12) as *mut u16, 2); // WRITE + core::ptr::write_volatile(desc1.add(14) as *mut u16, 0); + + let avail = VQUEUE_VA + QUEUE_SIZE * 16; + let avail_idx = core::ptr::read_volatile((avail + 2) as *const u16); + core::ptr::write_volatile( + (avail + 4 + (avail_idx as usize % QUEUE_SIZE) * 2) as *mut u16, + 0, + ); + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + core::ptr::write_volatile((avail + 2) as *mut u16, avail_idx.wrapping_add(1)); + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + + mmio_write(0x050, 0); + + let used = VQUEUE_VA + 4096; + let expected = avail_idx.wrapping_add(1); + let mut timeout = 10_000_000u32; + while core::ptr::read_volatile((used + 2) as *const u16) != expected { + core::hint::spin_loop(); + timeout -= 1; + if timeout == 0 { return; } + } } } + + fn transfer_and_flush(&self, x: u32, y: u32, w: u32, h: u32) { + // TRANSFER_TO_HOST_2D + unsafe { + let p = CMDBUF_VA as *mut u8; + core::ptr::write_bytes(p, 0, 512); + core::ptr::write_volatile(p as *mut u32, 0x0105); // TRANSFER_TO_HOST_2D + // rect at offset 24 + core::ptr::write_volatile(p.add(24) as *mut u32, x); + core::ptr::write_volatile(p.add(28) as *mut u32, y); + core::ptr::write_volatile(p.add(32) as *mut u32, w); + core::ptr::write_volatile(p.add(36) as *mut u32, h); + // offset at 40 + core::ptr::write_volatile(p.add(40) as *mut u64, 0); + // resource_id at 48 + core::ptr::write_volatile(p.add(48) as *mut u32, 1); + } + self.send_gpu_cmd(56, 24); + + // RESOURCE_FLUSH + unsafe { + let p = CMDBUF_VA as *mut u8; + core::ptr::write_bytes(p, 0, 512); + core::ptr::write_volatile(p as *mut u32, 0x0106); // RESOURCE_FLUSH + core::ptr::write_volatile(p.add(24) as *mut u32, x); + core::ptr::write_volatile(p.add(28) as *mut u32, y); + core::ptr::write_volatile(p.add(32) as *mut u32, w); + core::ptr::write_volatile(p.add(36) as *mut u32, h); + core::ptr::write_volatile(p.add(40) as *mut u32, 1); // resource_id + } + self.send_gpu_cmd(48, 24); + } } impl Framebuffer for VirtioFramebuffer { @@ -41,15 +130,12 @@ impl Framebuffer for VirtioFramebuffer { } fn write_pixel(&mut self, x: u16, y: u16, color: u32) { - if x >= self.width || y >= self.height { - return; - } + if x >= self.width || y >= self.height { return; } let offset = (y as usize) * self.stride + (x as usize) * FB_BPP; - let bytes = color.to_le_bytes(); - self.buffer[offset] = bytes[0]; - self.buffer[offset + 1] = bytes[1]; - self.buffer[offset + 2] = bytes[2]; - self.buffer[offset + 3] = bytes[3]; + let buf = FB_VA + offset; + unsafe { + core::ptr::write_volatile(buf as *mut u32, color); + } } fn fill_rect(&mut self, x: u16, y: u16, w: u16, h: u16, color: u32) { @@ -61,21 +147,14 @@ impl Framebuffer for VirtioFramebuffer { } fn clear(&mut self, color: u32) { - let bytes = color.to_le_bytes(); - for y in 0..self.height as usize { - for x in 0..self.width as usize { - let offset = y * self.stride + x * FB_BPP; - self.buffer[offset] = bytes[0]; - self.buffer[offset + 1] = bytes[1]; - self.buffer[offset + 2] = bytes[2]; - self.buffer[offset + 3] = bytes[3]; - } + let total = self.width as usize * self.height as usize; + let buf = FB_VA as *mut u32; + for i in 0..total { + unsafe { core::ptr::write_volatile(buf.add(i), color); } } } fn flush(&mut self) { - // VirtIO GPU flush would send TRANSFER_TO_HOST_2D + RESOURCE_FLUSH - // For now, the buffer is memory-mapped — no explicit flush needed - // when using ramfb or a simple scanout + self.transfer_and_flush(0, 0, self.width as u32, self.height as u32); } } diff --git a/crates/servers/storage-server/src/adapters/mod.rs b/crates/servers/storage-server/src/adapters/mod.rs index 4eb8f1b..c1ff392 100644 --- a/crates/servers/storage-server/src/adapters/mod.rs +++ b/crates/servers/storage-server/src/adapters/mod.rs @@ -1,2 +1,3 @@ pub mod mock_block; pub mod mock_fs; +pub mod virtio_blk; diff --git a/crates/servers/storage-server/src/adapters/virtio_blk.rs b/crates/servers/storage-server/src/adapters/virtio_blk.rs new file mode 100644 index 0000000..b767c0c --- /dev/null +++ b/crates/servers/storage-server/src/adapters/virtio_blk.rs @@ -0,0 +1,183 @@ +use crate::block_device::{BlockDevice, SectorId}; + +const QUEUE_SIZE: usize = 8; +const SECTOR_SIZE: usize = 512; + +const MMIO_BASE: usize = 0x20000; +const VQUEUE_VA: usize = 0x21000; +const REQ_HDR_VA: usize = 0x23000; + +// Physical addresses are written to a known location by the adapter at boot +// Layout at 0x24000: [req_hdr_pa: u64, status_pa: u64, sector_buf_pa: u64] +const PHYS_INFO_VA: usize = 0x24000; + +#[repr(C)] +#[derive(Clone, Copy)] +struct VirtqDesc { + addr: u64, + len: u32, + flags: u16, + next: u16, +} + +#[repr(C)] +struct VirtqAvail { + flags: u16, + idx: u16, + ring: [u16; QUEUE_SIZE], + used_event: u16, +} + +#[repr(C)] +#[derive(Clone, Copy)] +struct VirtqUsedElem { + id: u32, + len: u32, +} + +#[repr(C)] +struct VirtqUsed { + flags: u16, + idx: u16, + ring: [VirtqUsedElem; QUEUE_SIZE], +} + +#[repr(C)] +struct VirtioBlkReqHeader { + req_type: u32, + reserved: u32, + sector: u64, +} + +fn mmio_read(offset: usize) -> u32 { + unsafe { core::ptr::read_volatile((MMIO_BASE + offset) as *const u32) } +} + +fn mmio_write(offset: usize, val: u32) { + unsafe { core::ptr::write_volatile((MMIO_BASE + offset) as *mut u32, val) } +} + +fn descs() -> *mut VirtqDesc { + VQUEUE_VA as *mut VirtqDesc +} + +fn avail() -> *mut VirtqAvail { + (VQUEUE_VA + QUEUE_SIZE * 16) as *mut VirtqAvail +} + +fn used() -> *mut VirtqUsed { + (VQUEUE_VA + 0x1000) as *mut VirtqUsed +} + +fn req_header() -> *mut VirtioBlkReqHeader { + REQ_HDR_VA as *mut VirtioBlkReqHeader +} + +fn req_status() -> *mut u8 { + (REQ_HDR_VA + 16) as *mut u8 +} + +struct PhysAddrs { + req_hdr: u64, + status: u64, + sector_buf: u64, +} + +fn read_phys_addrs() -> PhysAddrs { + unsafe { + let base = PHYS_INFO_VA as *const u64; + PhysAddrs { + req_hdr: core::ptr::read_volatile(base), + status: core::ptr::read_volatile(base.add(1)), + sector_buf: core::ptr::read_volatile(base.add(2)), + } + } +} + +pub struct VirtioBlockDevice { + phys: PhysAddrs, +} + +impl VirtioBlockDevice { + pub fn new() -> Self { + Self { phys: read_phys_addrs() } + } +} + +impl BlockDevice for VirtioBlockDevice { + fn sector_size(&self) -> usize { + SECTOR_SIZE + } + + fn sector_count(&self) -> usize { + 0xFFFF + } + + fn read_sector(&mut self, sector: SectorId, buf: &mut [u8]) -> bool { + if buf.len() < SECTOR_SIZE { return false; } + + // Write to the local VA — device reads via physical address + unsafe { + let hdr = req_header(); + (*hdr).req_type = 0; + (*hdr).reserved = 0; + (*hdr).sector = sector.raw() as u64; + + core::ptr::write_volatile(req_status(), 0xFF); + + // Sector buffer: use a fixed area at REQ_HDR_VA + 32 + let sector_buf_va = (REQ_HDR_VA + 32) as *mut u8; + + let d = descs(); + (*d.add(0)) = VirtqDesc { + addr: self.phys.req_hdr, + len: 16, + flags: 1, + next: 1, + }; + (*d.add(1)) = VirtqDesc { + addr: self.phys.sector_buf, + len: SECTOR_SIZE as u32, + flags: 2 | 1, + next: 2, + }; + (*d.add(2)) = VirtqDesc { + addr: self.phys.status, + len: 1, + flags: 2, + next: 0, + }; + + let a = avail(); + let avail_idx = core::ptr::read_volatile(&(*a).idx); + (*a).ring[(avail_idx as usize) % QUEUE_SIZE] = 0; + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + core::ptr::write_volatile(&mut (*a).idx, avail_idx.wrapping_add(1)); + core::sync::atomic::fence(core::sync::atomic::Ordering::SeqCst); + + mmio_write(0x050, 0); + + let expected = avail_idx.wrapping_add(1); + let u = used(); + let mut timeout = 10_000_000u32; + while core::ptr::read_volatile(&(*u).idx) != expected { + core::hint::spin_loop(); + timeout -= 1; + if timeout == 0 { return false; } + } + + if core::ptr::read_volatile(req_status()) != 0 { + return false; + } + + // Copy from sector buffer VA to caller's buffer + core::ptr::copy_nonoverlapping(sector_buf_va, buf.as_mut_ptr(), SECTOR_SIZE); + } + + true + } + + fn write_sector(&mut self, _sector: SectorId, _buf: &[u8]) -> bool { + false + } +} diff --git a/crates/servers/storage-server/src/lib.rs b/crates/servers/storage-server/src/lib.rs index 9aa7b57..f847505 100644 --- a/crates/servers/storage-server/src/lib.rs +++ b/crates/servers/storage-server/src/lib.rs @@ -11,16 +11,81 @@ pub mod server; #[path = "tests/mod.rs"] mod tests; +use userspace::syscall; + +const CMD_READ_FILE: usize = 1; +const SHARED_BUF_VA: usize = 0x30000; +const SHARED_BUF_SIZE: usize = 64 * 1024; + +fn shared_buf() -> &'static mut [u8] { + unsafe { core::slice::from_raw_parts_mut(SHARED_BUF_VA as *mut u8, SHARED_BUF_SIZE) } +} + +fn debug_print(s: &[u8]) { + for &b in s { + syscall::debug_putchar(b); + } +} + pub fn run() -> ! { - #[cfg(feature = "qemu-virt")] - { - let device = adapters::mock_block::MockBlockDevice::new(64); - let filesystem = fs::fat16::Fat16::mount(device); - if let Some(mut fs) = filesystem { - let mut srv = server::StorageServer::new(fs); - server_framework::serve(&mut srv, 0); + debug_print(b"storage: started\n"); + + let device = adapters::virtio_blk::VirtioBlockDevice::new(); + let fat16 = fs::fat16::Fat16::mount(device); + if fat16.is_none() { + debug_print(b"storage: FAT16 mount failed\n"); + loop { syscall::yield_now(); } + } + let mut fat16 = fat16.unwrap(); + debug_print(b"storage: FAT16 mounted\n"); + + let ep: usize = 0; + + loop { + let (w0, w1, _w2, _w3) = syscall::receive(ep); + + match w0 { + CMD_READ_FILE => { + let filename_len = w1; + let buf = shared_buf(); + let filename_len = filename_len.min(buf.len()); + + let mut name_buf = [0u8; 32]; + let name_len = filename_len.min(32); + name_buf[..name_len].copy_from_slice(&buf[..name_len]); + + debug_print(b"storage: read "); + debug_print(&name_buf[..name_len]); + debug_print(b"..."); + + let file_size = read_entire_file(&mut fat16, &name_buf[..name_len], buf); + + debug_print(if file_size > 0 { b"ok\n" } else { b"NOT FOUND\n" }); + + syscall::send(ep, file_size, 0, 0, 0); + } + _ => { + syscall::send(ep, 0, 0, 0, 0); + } } } - - loop { userspace::syscall::yield_now(); } +} + +fn read_entire_file( + fat: &mut fs::fat16::Fat16, + filename: &[u8], + out: &mut [u8], +) -> usize { + use filesystem::FileSystem; + + let handle = match fat.open(filename) { + Some(h) => h, + None => return 0, + }; + + let size = fat.file_size(handle).unwrap_or(0); + let to_read = size.min(out.len()); + let read = fat.read(handle, &mut out[..to_read]).unwrap_or(0); + fat.close(handle); + read } diff --git a/crates/types/src/syscall_abi.rs b/crates/types/src/syscall_abi.rs index 469eabc..f866aa0 100644 --- a/crates/types/src/syscall_abi.rs +++ b/crates/types/src/syscall_abi.rs @@ -16,3 +16,7 @@ pub const SYS_GET_PROCESS_ID: usize = 14; pub const SYS_YIELD: usize = 15; pub const SYS_EXIT: usize = 16; pub const SYS_HALT: usize = 17; +pub const SYS_CREATE_ENDPOINT: usize = 18; +pub const SYS_DEBUG_PUTCHAR: usize = 19; +pub const SYS_MAP_PAGE: usize = 20; +pub const SYS_SPAWN_THREAD_IN: usize = 21; diff --git a/crates/userspace/src/arch/mock.rs b/crates/userspace/src/arch/mock.rs index 0878d7e..4aef986 100644 --- a/crates/userspace/src/arch/mock.rs +++ b/crates/userspace/src/arch/mock.rs @@ -10,6 +10,14 @@ pub fn syscall2(_nr: usize, _a0: usize, _a1: usize) -> usize { 0 } +pub fn syscall3(_nr: usize, _a0: usize, _a1: usize, _a2: usize) -> usize { + 0 +} + +pub fn syscall4(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize) -> usize { + 0 +} + pub fn syscall5(_nr: usize, _a0: usize, _a1: usize, _a2: usize, _a3: usize, _a4: usize) -> usize { 0 } diff --git a/crates/userspace/src/arch/riscv.rs b/crates/userspace/src/arch/riscv.rs index 2622fcc..60ba1b2 100644 --- a/crates/userspace/src/arch/riscv.rs +++ b/crates/userspace/src/arch/riscv.rs @@ -38,6 +38,37 @@ pub fn syscall2(nr: usize, a0: usize, a1: usize) -> usize { ret } +#[inline(always)] +pub fn syscall3(nr: usize, a0: usize, a1: usize, a2: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => ret, + in("a1") a1, + in("a2") a2, + ); + } + ret +} + +#[inline(always)] +pub fn syscall4(nr: usize, a0: usize, a1: usize, a2: usize, a3: usize) -> usize { + let ret: usize; + unsafe { + core::arch::asm!( + "ecall", + in("a7") nr, + inlateout("a0") a0 => ret, + in("a1") a1, + in("a2") a2, + in("a3") a3, + ); + } + ret +} + #[inline(always)] pub fn syscall5(nr: usize, a0: usize, a1: usize, a2: usize, a3: usize, a4: usize) -> usize { let ret: usize; diff --git a/crates/userspace/src/syscall.rs b/crates/userspace/src/syscall.rs index d340b13..715fb17 100644 --- a/crates/userspace/src/syscall.rs +++ b/crates/userspace/src/syscall.rs @@ -40,7 +40,7 @@ pub fn kill(process_cap: usize) -> usize { } pub fn delegate(slot: usize, to: usize, permissions: usize) -> usize { - arch::syscall2(SYS_DELEGATE, slot, to) + arch::syscall3(SYS_DELEGATE, slot, to, permissions) } pub fn revoke(slot: usize) -> usize { @@ -59,3 +59,19 @@ pub fn send(endpoint: usize, w0: usize, w1: usize, w2: usize, w3: usize) -> usiz pub fn receive(endpoint: usize) -> (usize, usize, usize, usize) { arch::syscall_receive(SYS_RECEIVE, endpoint) } + +pub fn create_endpoint() -> usize { + arch::syscall0(SYS_CREATE_ENDPOINT) +} + +pub fn debug_putchar(byte: u8) { + arch::syscall1(SYS_DEBUG_PUTCHAR, byte as usize); +} + +pub fn map_page(process_cap: usize, src_va: usize, dst_va: usize, executable: bool) -> usize { + arch::syscall4(SYS_MAP_PAGE, process_cap, src_va, dst_va, executable as usize) +} + +pub fn spawn_thread_in(process_cap: usize, entry: usize, stack: usize) -> usize { + arch::syscall3(SYS_SPAWN_THREAD_IN, process_cap, entry, stack) +} diff --git a/scripts/make-disk-image.sh b/scripts/make-disk-image.sh index 3605670..fa8ce80 100755 --- a/scripts/make-disk-image.sh +++ b/scripts/make-disk-image.sh @@ -8,43 +8,30 @@ BUILD_DIR="target/${TARGET}/release" echo "=== Creating k-os boot disk image ===" -# Create and format FAT16 image (mtools required: brew install mtools) dd if=/dev/zero of="${IMG}" bs=1K count=${IMG_SIZE_KB} 2>/dev/null mformat -i "${IMG}" -t 8 -h 2 -s 32 -r 16 -c 1 -M 512 :: echo "Created and formatted ${IMG} (${IMG_SIZE_KB}KB, FAT16)" -# Create directories -mmd -i "${IMG}" ::servers 2>/dev/null || true -mmd -i "${IMG}" ::apps 2>/dev/null || true - -# Copy init -if [ -f "${BUILD_DIR}/init" ]; then - mcopy -i "${IMG}" "${BUILD_DIR}/init" "::init.elf" - echo " Copied init.elf ($(stat -f%z "${BUILD_DIR}/init" 2>/dev/null || stat -c%s "${BUILD_DIR}/init") bytes)" -fi - -# Copy server binaries -for server in display-server input-server log-server permission-manager storage-server session-server; do - if [ -f "${BUILD_DIR}/${server}" ]; then - mcopy -i "${IMG}" "${BUILD_DIR}/${server}" "::servers/${server}.elf" - echo " Copied servers/${server}.elf" +# Copy with 8.3 names (no long filenames, no tilde mangling) +copy() { + local src="$1" dst="$2" + if [ -f "${BUILD_DIR}/${src}" ]; then + mcopy -i "${IMG}" "${BUILD_DIR}/${src}" "::${dst}" + echo " ${dst}" fi -done +} -# Copy app binaries -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 +copy init "init.elf" +copy echo "echo.elf" +copy snake "snake.elf" +copy display-server "display.elf" +copy input-server "input.elf" +copy log-server "log.elf" +copy permission-manager "permis.elf" +copy storage-server "storage.elf" +copy session-server "session.elf" -# List contents echo "" echo "Disk contents:" mdir -i "${IMG}" :: -mdir -i "${IMG}" ::servers 2>/dev/null || true -mdir -i "${IMG}" ::apps 2>/dev/null || true - -echo "" echo "=== Done: ${IMG} ==="