aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile5
-rw-r--r--mirai/boot/multiboot2.zig10
-rw-r--r--mirai/boot/sequence.zig1
-rw-r--r--mirai/drivers/keyboard.zig67
-rw-r--r--mirai/hikari/loader.zig353
-rw-r--r--mirai/invocations/getkeychar.zig17
-rw-r--r--mirai/invocations/handler.zig2
-rw-r--r--mirai/invocations/syscall.zig22
-rw-r--r--mirai/invocations/wait.zig29
-rw-r--r--mirai/invocations/yield.zig7
-rw-r--r--mirai/kata/kata.zig7
-rw-r--r--mirai/kata/sensei.zig117
-rw-r--r--mirai/memory/paging.zig40
-rw-r--r--mirai/mirai.zig8
-rw-r--r--system/ash/ash.zig45
-rw-r--r--system/ash/ash.zon8
-rw-r--r--system/libraries/akiba/io.zig16
-rw-r--r--system/libraries/akiba/kata.zig12
-rw-r--r--system/libraries/akiba/sys.zig13
-rw-r--r--system/pulse/pulse.zig19
20 files changed, 546 insertions, 252 deletions
diff --git a/Makefile b/Makefile
index ecd1d07..85bb2eb 100644
--- a/Makefile
+++ b/Makefile
@@ -179,8 +179,9 @@ prepare-filesystem: build-grub
echo " ✓ pulse.akibainit"; \
else \
echo " Wrapping $$sysname.akiba..."; \
- $(BUILD_DIR)/bin/akibabuilder "$(BUILD_DIR)/system/$$sysname" "$(FS_ROOT)/system/$$sysname.akiba" cli && \
- echo " ✓ $$sysname.akiba"; \
+ mkdir -p "$(FS_ROOT)/system/$$sysname" && \
+ $(BUILD_DIR)/bin/akibabuilder "$(BUILD_DIR)/system/$$sysname" "$(FS_ROOT)/system/$$sysname/$$sysname.akiba" cli && \
+ echo " ✓ system/$$sysname/$$sysname.akiba"; \
fi; \
fi; \
fi; \
diff --git a/mirai/boot/multiboot2.zig b/mirai/boot/multiboot2.zig
index 3a8ae2a..8356a03 100644
--- a/mirai/boot/multiboot2.zig
+++ b/mirai/boot/multiboot2.zig
@@ -20,6 +20,16 @@ pub const FramebufferInfo = struct {
const MAX_MEMORY_ENTRIES = 32;
var memory_entries: [MAX_MEMORY_ENTRIES]MemoryEntry = undefined;
+var saved_framebuffer: ?FramebufferInfo = null;
+
+pub fn init_framebuffer(fb: FramebufferInfo) void {
+ saved_framebuffer = fb;
+}
+
+pub fn get_framebuffer() ?FramebufferInfo {
+ return saved_framebuffer;
+}
+
pub fn parse_memory_map(addr: u64) []MemoryEntry {
serial.print("\n=== Multiboot2 Memory Map ===\n");
diff --git a/mirai/boot/sequence.zig b/mirai/boot/sequence.zig
index ed56adc..0dd66a9 100644
--- a/mirai/boot/sequence.zig
+++ b/mirai/boot/sequence.zig
@@ -172,6 +172,7 @@ pub fn run(multiboot_info_addr: u64) void {
boot_print("Initializing framebuffer... ");
terminal.init(fb);
crimson.init(fb);
+ multiboot.init_framebuffer(fb);
terminal_ready = true;
replay_messages();
boot_ok();
diff --git a/mirai/drivers/keyboard.zig b/mirai/drivers/keyboard.zig
index bb04f6e..1a50af6 100644
--- a/mirai/drivers/keyboard.zig
+++ b/mirai/drivers/keyboard.zig
@@ -1,6 +1,7 @@
//! PS/2 Keyboard Driver
const serial = @import("serial.zig");
+const sensei = @import("../kata/sensei.zig");
const KEYBOARD_DATA_PORT: u16 = 0x60;
const KEYBOARD_STATUS_PORT: u16 = 0x64;
@@ -12,13 +13,11 @@ var ctrl_pressed = false;
var alt_pressed = false;
var caps_lock = false;
-// Input buffer for shell
+// Circular buffer for keyboard input
const BUFFER_SIZE = 256;
-var input_buffer: [BUFFER_SIZE]u8 = undefined;
-var buffer_pos: usize = 0;
-
-// External function to send keys to terminal/shell
-extern fn on_key_typed(char: u8) void;
+var key_buffer: [BUFFER_SIZE]u8 = undefined;
+var read_pos: usize = 0;
+var write_pos: usize = 0;
const SCANCODE_TO_ASCII: [128]u8 = .{
0, 27, '1', '2', '3', '4', '5', '6',
@@ -145,38 +144,38 @@ fn handle_scancode(scancode: u8) void {
}
fn on_key_press(ascii: u8) void {
- // Echo to serial
+ // Echo to serial only
serial.write(ascii);
-
- // Send to terminal/shell
- on_key_typed(ascii);
-
- // Handle backspace
- if (ascii == '\x08') {
- if (buffer_pos > 0) {
- buffer_pos -= 1;
- }
- return;
- }
-
- // Handle enter
- if (ascii == '\n') {
- serial.print("\n");
- buffer_pos = 0;
- return;
- }
-
- // Add to buffer
- if (buffer_pos < BUFFER_SIZE - 1) {
- input_buffer[buffer_pos] = ascii;
- buffer_pos += 1;
+ serial.print(" [keypress] write_pos=");
+ serial.print_hex(write_pos);
+ serial.print(", read_pos=");
+ serial.print_hex(read_pos);
+ serial.print("\n");
+
+ // Add to circular buffer
+ const next_write = (write_pos + 1) % BUFFER_SIZE;
+ if (next_write != read_pos) {
+ key_buffer[write_pos] = ascii;
+ write_pos = next_write;
+
+ // Wake one blocked kata waiting for keyboard input
+ sensei.wake_one_blocked_kata();
+ } else {
+ serial.print("Keyboard buffer full!\n");
}
}
-pub fn get_input_buffer() []const u8 {
- return input_buffer[0..buffer_pos];
+pub fn has_input() bool {
+ return read_pos != write_pos;
}
-pub fn clear_buffer() void {
- buffer_pos = 0;
+// Non-blocking read
+pub fn read_char() ?u8 {
+ if (read_pos == write_pos) {
+ return null;
+ }
+
+ const char = key_buffer[read_pos];
+ read_pos = (read_pos + 1) % BUFFER_SIZE;
+ return char;
}
diff --git a/mirai/hikari/loader.zig b/mirai/hikari/loader.zig
index ae78ac9..a6fa00a 100644
--- a/mirai/hikari/loader.zig
+++ b/mirai/hikari/loader.zig
@@ -1,212 +1,275 @@
//! Hikari - The ELF loader
//! Hikari (光) = Light - illuminates programs into execution
-const ahci = @import("../drivers/ahci.zig");
+const serial = @import("../drivers/serial.zig");
+const heap = @import("../memory/heap.zig");
+const pmm = @import("../memory/pmm.zig");
+const paging = @import("../memory/paging.zig");
const afs = @import("../fs/afs.zig");
+const ahci = @import("../drivers/ahci.zig");
const elf = @import("elf.zig");
const format = @import("format.zig");
-const gdt = @import("../boot/gdt.zig");
const kata_mod = @import("../kata/kata.zig");
-const paging = @import("../memory/paging.zig");
-const pmm = @import("../memory/pmm.zig");
const sensei = @import("../kata/sensei.zig");
-const serial = @import("../drivers/serial.zig");
+const gdt = @import("../boot/gdt.zig");
+const boot = @import("../boot/multiboot2.zig");
-const PAGE_SIZE: u64 = 4096;
const HIGHER_HALF: u64 = 0xFFFF800000000000;
+const USER_STACK_TOP: u64 = 0x00007FFFFFF00000;
+const USER_STACK_PAGES: u64 = 4;
pub fn init() void {
- serial.print("\n=== Hikari Loader ===\n");
- serial.print("Akiba executable loader initialized\n");
+ serial.print("Hikari loader initialized\n");
}
pub fn load_init_system(fs: *afs.AFS(ahci.BlockDevice)) !u32 {
- serial.print("\n=== Loading Init System ===\n");
-
- const pulse_path = "/system/akiba/pulse.akibainit";
- return load_program(fs, pulse_path);
+ return load_program(fs, "/system/akiba/pulse.akibainit");
}
pub fn load_program(fs: *afs.AFS(ahci.BlockDevice), path: []const u8) !u32 {
- serial.print("\n=== Loading Program ===\n");
- serial.print("Path: ");
+ serial.print("Loading program: ");
serial.print(path);
serial.print("\n");
- var file_buffer: [1024 * 1024]u8 = undefined;
- const bytes_read = try fs.read_file_by_path(path, &file_buffer);
-
- const file_data = file_buffer[0..bytes_read];
- const akiba_exe = try format.parse_akiba(file_data);
- const elf_info = try elf.parse_elf(akiba_exe.elf_data);
-
- const new_kata = try kata_mod.create_kata();
-
- try setup_kata_memory(new_kata, &elf_info, akiba_exe.elf_data);
- setup_kata_context(new_kata, elf_info.entry_point);
+ // Allocate file buffer directly from PMM (2MB is too large for heap slab allocator)
+ const buffer_size: usize = 2 * 1024 * 1024;
+ const num_pages = (buffer_size + 0xFFF) / 0x1000; // 512 pages
+
+ // Allocate pages and track them in a fixed array
+ var pages: [512]u64 = undefined;
+ var pages_allocated: usize = 0;
+
+ var i: usize = 0;
+ while (i < num_pages) : (i += 1) {
+ const page = pmm.alloc_page() orelse {
+ // Free already allocated pages on error
+ var j: usize = 0;
+ while (j < pages_allocated) : (j += 1) {
+ pmm.free_page(pages[j]);
+ }
+ return error.OutOfMemory;
+ };
+ pages[i] = page;
+ pages_allocated += 1;
+ }
+ defer {
+ var j: usize = 0;
+ while (j < pages_allocated) : (j += 1) {
+ pmm.free_page(pages[j]);
+ }
+ }
- sensei.enqueue_kata(new_kata);
+ // Map all pages into a contiguous virtual buffer
+ // For simplicity, just use the physical pages directly via higher half mapping
+ // This assumes PMM returns reasonably contiguous pages
+ const file_buffer_phys = pages[0];
+ const file_buffer_ptr = @as([*]u8, @ptrFromInt(file_buffer_phys + HIGHER_HALF));
+ const file_buffer = file_buffer_ptr[0..buffer_size];
+
+ const bytes_read = fs.read_file_by_path(path, file_buffer) catch |err| {
+ serial.print("Error reading file: ");
+ serial.print(@errorName(err));
+ serial.print("\n");
+ return err;
+ };
+
+ const akiba_data = format.parse_akiba(file_buffer[0..bytes_read]) catch |err| {
+ serial.print("Error parsing Akiba format: ");
+ serial.print(@errorName(err));
+ serial.print("\n");
+ return err;
+ };
+
+ const elf_info = elf.parse_elf(akiba_data.elf_data) catch |err| {
+ serial.print("Error parsing ELF: ");
+ serial.print(@errorName(err));
+ serial.print("\n");
+ return err;
+ };
+
+ serial.print(" Entry point: ");
+ serial.print_hex(elf_info.entry_point);
+ serial.print("\n");
- serial.print("Kata ");
- serial.print_hex(new_kata.id);
+ const kata = kata_mod.create_kata() catch |err| {
+ serial.print("Error creating Kata: ");
+ serial.print(@errorName(err));
+ serial.print("\n");
+ return err;
+ };
+
+ setup_kata_memory(kata, akiba_data.elf_data, elf_info) catch |err| {
+ serial.print("Error setting up memory: ");
+ serial.print(@errorName(err));
+ serial.print("\n");
+ kata_mod.dissolve_kata(kata.id);
+ return err;
+ };
+
+ setup_kata_context(kata, elf_info.entry_point);
+ sensei.enqueue_kata(kata);
+
+ serial.print(" Kata ");
+ serial.print_hex(kata.id);
serial.print(" ready\n");
- return new_kata.id;
+ return kata.id;
}
-fn setup_kata_memory(kata: *kata_mod.Kata, elf_info: *const elf.ELFInfo, elf_data: []const u8) !void {
- // Create isolated page table for this process
- kata.page_table = try paging.create_page_table();
+fn setup_kata_memory(kata: *kata_mod.Kata, elf_data: []const u8, elf_info: elf.ELFInfo) !void {
+ serial.print(" Setting up memory...\n");
+
+ const new_cr3 = try paging.create_page_table();
+ kata.page_table = new_cr3;
- serial.print("Kata page table: ");
- serial.print_hex(kata.page_table);
+ serial.print(" New CR3: ");
+ serial.print_hex(new_cr3);
serial.print("\n");
- // Load program segments at their original ELF addresses
- for (elf_info.program_headers) |ph| {
- if (ph.type == elf.PT_LOAD) {
- try load_segment(kata, ph, elf_data);
+ for (elf_info.program_headers) |phdr| {
+ if (phdr.type == elf.PT_LOAD) {
+ try load_segment(kata.page_table, elf_data, phdr);
}
}
- // Allocate user stack in high canonical address
- const user_stack_base: u64 = 0x00007FFFFFF00000;
- const user_stack_size: u64 = PAGE_SIZE * 4;
+ const user_stack_base = USER_STACK_TOP - (USER_STACK_PAGES * 0x1000);
+ var i: u64 = 0;
+ while (i < USER_STACK_PAGES) : (i += 1) {
+ const page_phys = pmm.alloc_page() orelse return error.OutOfMemory;
+ const page_virt = user_stack_base + (i * 0x1000);
- try allocate_user_stack(kata, user_stack_base, user_stack_size);
- kata.user_stack_top = user_stack_base + user_stack_size;
+ _ = try paging.map_page_in_table(kata.page_table, page_virt, page_phys, 0b111);
- serial.print("User stack: ");
- serial.print_hex(kata.user_stack_top);
- serial.print("\n");
+ const zero_ptr = @as([*]volatile u8, @ptrFromInt(page_phys + HIGHER_HALF));
+ @memset(zero_ptr[0..0x1000], 0);
+ }
+
+ kata.user_stack_top = USER_STACK_TOP;
- // Allocate kernel stack in higher-half
- const kernel_stack_phys = pmm.alloc_page() orelse return error.OutOfMemory;
- kata.stack_top = kernel_stack_phys + HIGHER_HALF + PAGE_SIZE;
+ const kernel_stack_page = pmm.alloc_page() orelse return error.OutOfMemory;
+ kata.stack_top = kernel_stack_page + HIGHER_HALF + 0x1000;
- serial.print("Kernel stack: ");
+ serial.print(" User stack: ");
+ serial.print_hex(kata.user_stack_top);
+ serial.print("\n");
+ serial.print(" Kernel stack: ");
serial.print_hex(kata.stack_top);
serial.print("\n");
-}
-fn load_segment(kata: *kata_mod.Kata, ph: elf.ELF64ProgramHeader, elf_data: []const u8) !void {
- serial.print("Loading segment at ");
- serial.print_hex(ph.vaddr);
- serial.print(", size: ");
- serial.print_hex(ph.memsz);
- serial.print(", file offset: ");
- serial.print_hex(ph.offset);
- serial.print("\n");
+ if (boot.get_framebuffer()) |fb_info| {
+ serial.print(" Mapping framebuffer for userspace access...\n");
- var vaddr = ph.vaddr;
- var remaining = ph.memsz;
- var file_offset: u64 = 0;
-
- while (remaining > 0) {
- const page_base = vaddr & ~@as(u64, PAGE_SIZE - 1);
- const offset_in_page = vaddr - page_base;
- const bytes_in_page = @min(PAGE_SIZE - offset_in_page, remaining);
-
- // Allocate and map page
- const phys_page = pmm.alloc_page() orelse return error.OutOfMemory;
- const result = try paging.map_page_in_table(kata.page_table, page_base, phys_page, 0b111);
- const was_mapped = result[0];
- const actual_phys = result[1];
-
- // DEBUG: Verify mapping
- if (page_base == 0x400000) {
- serial.print("DEBUG: Mapped 0x400000 -> phys ");
- serial.print_hex(actual_phys);
- serial.print(", was_mapped=");
- serial.print(if (was_mapped) "true\n" else "false\n");
-
- // Verify we can read it back
- const verify_phys = paging.get_physical_address(kata.page_table, 0x400000) catch 0;
- serial.print("DEBUG: Verify readback: ");
- serial.print_hex(verify_phys);
- serial.print("\n");
- }
+ const fb_start = fb_info.addr;
+ const fb_size = fb_info.height * fb_info.pitch;
+ const fb_pages = (fb_size + 0xFFF) / 0x1000;
- if (was_mapped) {
- pmm.free_page(phys_page);
- }
+ serial.print(" FB addr: ");
+ serial.print_hex(fb_start);
+ serial.print(", size: ");
+ serial.print_hex(fb_size);
+ serial.print(", pages: ");
+ serial.print_hex(fb_pages);
+ serial.print("\n");
- // Zero page and write segment data
- const dest_base = @as([*]u8, @ptrFromInt(actual_phys + HIGHER_HALF));
+ var page: u64 = 0;
+ while (page < fb_pages) : (page += 1) {
+ const phys = fb_start + (page * 0x1000);
+ const virt = phys;
- if (!was_mapped) {
- var i: usize = 0;
- while (i < PAGE_SIZE) : (i += 1) {
- dest_base[i] = 0;
- }
+ _ = try paging.map_page_in_table(kata.page_table, virt, phys, 0b111);
}
- if (file_offset < ph.filesz) {
- const file_bytes = @min(bytes_in_page, ph.filesz - file_offset);
- const src_offset = ph.offset + file_offset;
- const src = elf_data[src_offset .. src_offset + file_bytes];
+ serial.print(" Framebuffer mapped successfully\n");
+ } else {
+ serial.print(" WARNING: No framebuffer info available\n");
+ }
+}
- const dest = dest_base + offset_in_page;
- for (src, 0..) |byte, j| {
- dest[j] = byte;
- }
- }
+fn load_segment(cr3: u64, elf_data: []const u8, phdr: elf.ELF64ProgramHeader) !void {
+ serial.print(" Loading segment at ");
+ serial.print_hex(phdr.vaddr);
+ serial.print(" (");
+ serial.print_hex(phdr.memsz);
+ serial.print(" bytes)\n");
- vaddr += bytes_in_page;
- file_offset += bytes_in_page;
- remaining -= bytes_in_page;
- }
+ const start_addr = phdr.vaddr;
+ const mem_size = phdr.memsz;
+ const file_size = phdr.filesz;
- // Verify entry point was loaded correctly
- if (ph.vaddr == 0x400000) {
- serial.print("DEBUG: Verifying entry point at 0x");
- serial.print_hex(ph.vaddr);
- serial.print(":\n Bytes: ");
- const page_phys = paging.get_physical_address(kata.page_table, 0x400000) catch 0;
- if (page_phys != 0) {
- const bytes = @as([*]u8, @ptrFromInt(page_phys + HIGHER_HALF));
- var i: usize = 0;
- while (i < 32) : (i += 1) {
- serial.print_hex(bytes[i]);
- serial.print(" ");
- }
- serial.print("\n");
- }
+ const start_page = start_addr & ~@as(u64, 0xFFF);
+ const end_addr = start_addr + mem_size;
+ const end_page = (end_addr + 0xFFF) & ~@as(u64, 0xFFF);
+ const num_pages = (end_page - start_page) / 0x1000;
+
+ var page_idx: u64 = 0;
+ while (page_idx < num_pages) : (page_idx += 1) {
+ const page_virt = start_page + (page_idx * 0x1000);
+ const page_phys = pmm.alloc_page() orelse return error.OutOfMemory;
+
+ _ = try paging.map_page_in_table(cr3, page_virt, page_phys, 0b111);
+
+ const zero_ptr = @as([*]volatile u8, @ptrFromInt(page_phys + HIGHER_HALF));
+ @memset(zero_ptr[0..0x1000], 0);
}
-}
-fn allocate_user_stack(kata: *kata_mod.Kata, base: u64, size: u64) !void {
- var addr = base;
- var remaining = size;
+ if (file_size > 0) {
+ const source = elf_data[phdr.offset .. phdr.offset + file_size];
- while (remaining > 0) {
- const phys_page = pmm.alloc_page() orelse return error.OutOfMemory;
+ var copied: u64 = 0;
+ while (copied < file_size) {
+ const virt_addr = start_addr + copied;
+ const page_virt = virt_addr & ~@as(u64, 0xFFF);
- const flags: u64 = 0b111; // Present + Writable + User
- const result = try paging.map_page_in_table(kata.page_table, addr, phys_page, flags);
- const was_mapped = result[0];
- const actual_phys = result[1];
+ const phys = paging.virt_to_phys(cr3, page_virt) orelse return error.PageNotMapped;
- if (was_mapped) {
- pmm.free_page(phys_page);
- }
+ const page_offset = virt_addr & 0xFFF;
+ const bytes_in_page = @min(0x1000 - page_offset, file_size - copied);
+
+ const dest_ptr = @as([*]volatile u8, @ptrFromInt(phys + HIGHER_HALF + page_offset));
+ const src_ptr = source[copied .. copied + bytes_in_page];
- // Zero the stack page
- const dest = @as([*]u8, @ptrFromInt(actual_phys + HIGHER_HALF));
- var i: usize = 0;
- while (i < PAGE_SIZE) : (i += 1) {
- dest[i] = 0;
+ @memcpy(dest_ptr[0..bytes_in_page], src_ptr);
+ copied += bytes_in_page;
}
+ }
- addr += PAGE_SIZE;
- remaining -= PAGE_SIZE;
+ if (phdr.vaddr == 0x400000) {
+ const phys = paging.virt_to_phys(cr3, 0x400000) orelse return error.PageNotMapped;
+ const check_ptr = @as([*]volatile u8, @ptrFromInt(phys + HIGHER_HALF));
+ serial.print(" Entry point bytes: ");
+ serial.print_hex(check_ptr[0]);
+ serial.print(" ");
+ serial.print_hex(check_ptr[1]);
+ serial.print(" ");
+ serial.print_hex(check_ptr[2]);
+ serial.print("\n");
}
}
fn setup_kata_context(kata: *kata_mod.Kata, entry_point: u64) void {
+ serial.print(" Setting up context...\n");
+
kata.context.rip = entry_point;
kata.context.rsp = kata.user_stack_top;
kata.context.rflags = 0x3202;
kata.context.cs = gdt.USER_CODE | 3;
kata.context.ss = gdt.USER_DATA | 3;
+
+ kata.context.rax = 0;
+ kata.context.rbx = 0;
+ kata.context.rcx = 0;
+ kata.context.rdx = 0;
+ kata.context.rsi = 0;
+ kata.context.rdi = 0;
+ kata.context.rbp = 0;
+ kata.context.r8 = 0;
+ kata.context.r9 = 0;
+ kata.context.r10 = 0;
+ kata.context.r11 = 0;
+ kata.context.r12 = 0;
+ kata.context.r13 = 0;
+ kata.context.r14 = 0;
+ kata.context.r15 = 0;
+
+ serial.print(" Context ready\n");
}
diff --git a/mirai/invocations/getkeychar.zig b/mirai/invocations/getkeychar.zig
new file mode 100644
index 0000000..582574c
--- /dev/null
+++ b/mirai/invocations/getkeychar.zig
@@ -0,0 +1,17 @@
+//! Getkeychar invocation - read one character from keyboard
+
+const keyboard = @import("../drivers/keyboard.zig");
+const handler = @import("handler.zig");
+const serial = @import("../drivers/serial.zig");
+
+pub fn invoke(context: *handler.InvocationContext) void {
+ if (keyboard.read_char()) |char| {
+ serial.print("getkeychar: got char ");
+ serial.print_hex(char);
+ serial.print("\n");
+ context.rax = char;
+ } else {
+ // No input available - return -2 (EAGAIN)
+ context.rax = @as(u64, @bitCast(@as(i64, -2)));
+ }
+}
diff --git a/mirai/invocations/handler.zig b/mirai/invocations/handler.zig
index 348ee2a..a3476a2 100644
--- a/mirai/invocations/handler.zig
+++ b/mirai/invocations/handler.zig
@@ -14,6 +14,7 @@ const spawn = @import("spawn.zig");
const view = @import("view.zig");
const wait = @import("wait.zig");
const yield = @import("yield.zig");
+const getkeychar = @import("getkeychar.zig");
pub fn init(fs: *afs.AFS(ahci.BlockDevice)) void {
serial.print("\n=== Invocation Handler ===\n");
@@ -41,6 +42,7 @@ pub fn handle_invocation(context: *InvocationContext) void {
0x06 => spawn.invoke(context),
0x07 => wait.invoke(context),
0x08 => yield.invoke(context),
+ 0x09 => getkeychar.invoke(context),
else => {
serial.print("Unknown invocation: ");
serial.print_hex(invocation_num);
diff --git a/mirai/invocations/syscall.zig b/mirai/invocations/syscall.zig
index 0aa683e..4d0e96e 100644
--- a/mirai/invocations/syscall.zig
+++ b/mirai/invocations/syscall.zig
@@ -225,17 +225,17 @@ export fn handle_syscall(ctx_ptr: u64) void {
// Write back return value (RAX)
regs.rax = inv_ctx.rax;
- // DEBUG: Log what we're returning
- serial.print("Syscall complete, returning to RIP: ");
- serial.print_hex(regs.user_rip);
- serial.print(", RAX: ");
- serial.print_hex(regs.rax);
- serial.print(", CR3: ");
- const cr3 = asm volatile ("mov %%cr3, %[result]"
- : [result] "=r" (-> u64),
- );
- serial.print_hex(cr3);
- serial.print("\n");
+ // // DEBUG: Log what we're returning
+ // serial.print("Syscall complete, returning to RIP: ");
+ // serial.print_hex(regs.user_rip);
+ // serial.print(", RAX: ");
+ // serial.print_hex(regs.rax);
+ // serial.print(", CR3: ");
+ // const cr3 = asm volatile ("mov %%cr3, %[result]"
+ // : [result] "=r" (-> u64),
+ // );
+ // serial.print_hex(cr3);
+ // serial.print("\n");
}
// Context saved on kernel stack
diff --git a/mirai/invocations/wait.zig b/mirai/invocations/wait.zig
index a627b00..2854860 100644
--- a/mirai/invocations/wait.zig
+++ b/mirai/invocations/wait.zig
@@ -29,20 +29,23 @@ pub fn invoke(ctx: *handler.InvocationContext) void {
return;
}
- // Mark current Kata as waiting
- if (sensei.get_current_kata()) |current| {
- current.state = .Waiting;
- current.waiting_for = target_id;
- }
+ // Target is still running - block this kata
+ const current = sensei.get_current_kata() orelse {
+ ctx.rax = @as(u64, @bitCast(@as(i64, -1)));
+ return;
+ };
- // Schedule another Kata
- sensei.schedule();
+ serial.print(" Kata still running, blocking parent\n");
+ current.state = .Waiting;
+ current.waiting_for = target_id;
- // When we resume, the target has exited
- const exit_code = target.exit_code;
- serial.print(" Kata exited with code: ");
- serial.print_hex(exit_code);
- serial.print("\n");
+ // Schedule another kata
+ sensei.schedule();
- ctx.rax = exit_code;
+ // When we return here, check if target has exited
+ if (target.state == .Dissolved) {
+ ctx.rax = target.exit_code;
+ } else {
+ ctx.rax = @as(u64, @bitCast(@as(i64, -1)));
+ }
}
diff --git a/mirai/invocations/yield.zig b/mirai/invocations/yield.zig
index dd1a23a..da709b3 100644
--- a/mirai/invocations/yield.zig
+++ b/mirai/invocations/yield.zig
@@ -10,9 +10,14 @@ pub fn invoke(context: *handler.InvocationContext) void {
return;
};
- // Mark current kata as READY so scheduler can pick it again
+ // Mark current kata as READY
current.state = kata_mod.KataState.Ready;
+ // Only enqueue if not already in queue
+ if (!sensei.is_in_queue(current)) {
+ sensei.enqueue_kata(current);
+ }
+
// Trigger scheduler to pick next ready kata (might pick us again if we're only one)
sensei.schedule();
diff --git a/mirai/kata/kata.zig b/mirai/kata/kata.zig
index 11db4b4..0337f2b 100644
--- a/mirai/kata/kata.zig
+++ b/mirai/kata/kata.zig
@@ -9,6 +9,7 @@ pub const KataState = enum {
Ready,
Running,
Waiting,
+ Blocked,
Dissolved,
};
@@ -207,10 +208,16 @@ pub fn get_kata(id: u32) ?*Kata {
}
pub fn dissolve_kata(kata_id: u32) void {
+ const sensei = @import("sensei.zig");
+
for (&kata_pool, 0..) |*kata, i| {
if (kata_used[i] and kata.id == kata_id) {
kata.state = .Dissolved;
kata_used[i] = false;
+
+ // Wake any katas waiting for this one
+ sensei.wake_waiting_katas(kata_id);
+
return;
}
}
diff --git a/mirai/kata/sensei.zig b/mirai/kata/sensei.zig
index baa94df..a84d42e 100644
--- a/mirai/kata/sensei.zig
+++ b/mirai/kata/sensei.zig
@@ -5,6 +5,7 @@ const kata_mod = @import("kata.zig");
const idt = @import("../interrupts/idt.zig");
const serial = @import("../drivers/serial.zig");
const shift = @import("shift.zig");
+const keyboard = @import("../drivers/keyboard.zig");
const Kata = kata_mod.Kata;
@@ -86,12 +87,18 @@ pub fn dequeue_kata(target_kata: *Kata) void {
}
}
+// Check if a kata is already in the run queue
+pub fn is_in_queue(target_kata: *Kata) bool {
+ var current = run_queue_head;
+ while (current) |kata| {
+ if (kata == target_kata) return true;
+ current = kata.next;
+ }
+ return false;
+}
+
// Pick next Kata to run (lowest vruntime)
fn pick_next_kata() ?*Kata {
- serial.print("pick_next_kata called, checking queue...\n");
- serial.print(" run_queue_head: ");
- serial.print_hex(@intFromPtr(run_queue_head));
- serial.print("\n");
// Debug: show all katas
var i: usize = 0;
@@ -114,12 +121,6 @@ fn pick_next_kata() ?*Kata {
serial.print(" Traversing queue:\n");
while (current) |kata| {
- serial.print(" Checking Kata ");
- serial.print_hex(kata.id);
- serial.print(", state ");
- serial.print_hex(@intFromEnum(kata.state));
- serial.print("\n");
-
if (kata.state == .Ready and kata.vruntime < local_min_vruntime) {
local_min_vruntime = kata.vruntime;
chosen = kata;
@@ -137,7 +138,7 @@ fn pick_next_kata() ?*Kata {
return k;
}
- // NEW: If no Ready kata, check if current_kata should keep running
+ // If no Ready kata, check if current_kata should keep running
if (current_kata) |curr| {
if (curr.state == .Running) {
serial.print(" No Ready kata, continuing current: ");
@@ -163,6 +164,11 @@ fn pick_next_kata() ?*Kata {
pub fn on_tick() void {
tick_count += 1;
+ // Wake blocked katas if keyboard has input
+ if (tick_count % 5 == 0) { // Check every 5ms
+ wake_blocked_katas();
+ }
+
if (current_kata) |kata| {
const delta = (TICK_NANOSECONDS * 1024) / kata.weight;
kata.vruntime += delta;
@@ -182,14 +188,7 @@ pub fn on_tick() void {
// Main scheduling decision
pub fn schedule() void {
- const rbp = asm volatile ("mov %%rbp, %[ret]"
- : [ret] "=r" (-> u64),
- );
- serial.print("\n=== Sensei Schedule (called from RBP: ");
- serial.print_hex(rbp);
- serial.print(") ===\n");
- serial.print("\n=== Sensei Schedule ===\n");
- wake_waiting_katas();
+ wake_all_waiting_katas();
const next = pick_next_kata();
@@ -200,10 +199,12 @@ pub fn schedule() void {
}
}
- // DON'T return early - always handle the shift properly
+ // If same kata is picked, we still need to dequeue it and set to Running
if (next == current_kata and next != null) {
- // Same kata, but we might be coming from interrupt
- // Just continue - no shift needed
+ // Same kata picked - dequeue it since it was enqueued by yield
+ dequeue_kata(next.?);
+ next.?.state = .Running;
+ // No shift needed - just return
return;
}
@@ -229,7 +230,7 @@ pub fn schedule() void {
}
}
-fn wake_waiting_katas() void {
+fn wake_all_waiting_katas() void {
var i: usize = 0;
while (i < kata_mod.MAX_KATA) : (i += 1) {
if (!kata_mod.kata_used[i]) continue;
@@ -254,6 +255,76 @@ fn wake_waiting_katas() void {
}
}
+// Wake katas waiting for a specific kata (called when a kata dissolves)
+pub fn wake_waiting_katas(target_id: u32) void {
+ var i: usize = 0;
+ while (i < kata_mod.MAX_KATA) : (i += 1) {
+ if (!kata_mod.kata_used[i]) continue;
+
+ const kata = &kata_mod.kata_pool[i];
+ if (kata.state == .Waiting and kata.waiting_for == target_id) {
+ serial.print("Sensei: Waking Kata ");
+ serial.print_hex(kata.id);
+ serial.print(" (target ");
+ serial.print_hex(target_id);
+ serial.print(" dissolved)\n");
+
+ kata.state = .Ready;
+ enqueue_kata(kata);
+ kata.waiting_for = 0;
+ }
+ }
+}
+
+// Wake katas blocked on keyboard input
+fn wake_blocked_katas() void {
+ // Only wake if keyboard has input
+ if (!keyboard.has_input()) return;
+
+ // Wake only ONE blocked kata per character (FIFO order)
+ var i: usize = 0;
+ while (i < kata_mod.MAX_KATA) : (i += 1) {
+ if (!kata_mod.kata_used[i]) continue;
+
+ const kata = &kata_mod.kata_pool[i];
+ if (kata.state == .Blocked) {
+ kata.state = .Ready;
+ enqueue_kata(kata);
+ // Only wake one kata at a time for keyboard input
+ return;
+ }
+ }
+}
+
+// Wake one blocked kata (called from keyboard interrupt)
+pub fn wake_one_blocked_kata() void {
+ var i: usize = 0;
+ while (i < kata_mod.MAX_KATA) : (i += 1) {
+ if (!kata_mod.kata_used[i]) continue;
+
+ const kata = &kata_mod.kata_pool[i];
+ if (kata.state == .Blocked) {
+ kata.state = .Ready;
+ enqueue_kata(kata);
+ return;
+ }
+ }
+}
+
+// Wake a specific kata by ID (called from interrupt handlers)
+pub fn wake_kata(kata_id: u32) void {
+ const kata = kata_mod.get_kata(kata_id) orelse return;
+
+ if (kata.state == .Blocked) {
+ serial.print("Sensei: Waking blocked Kata ");
+ serial.print_hex(kata_id);
+ serial.print("\n");
+
+ kata.state = .Ready;
+ enqueue_kata(kata);
+ }
+}
+
pub fn get_current_kata() ?*Kata {
return current_kata;
}
diff --git a/mirai/memory/paging.zig b/mirai/memory/paging.zig
index c449afa..df01079 100644
--- a/mirai/memory/paging.zig
+++ b/mirai/memory/paging.zig
@@ -192,6 +192,46 @@ pub fn get_physical_address(page_table: u64, vaddr: u64) !u64 {
return (pt[pt_index] & ~@as(u64, 0xFFF)) + offset;
}
+pub fn virt_to_phys(cr3: u64, virt: u64) ?u64 {
+ // Walk the 4-level page table
+ const pml4_addr = cr3 + HIGHER_HALF_START;
+ const pml4 = @as([*]u64, @ptrFromInt(pml4_addr));
+
+ const pml4_index = (virt >> 39) & 0x1FF;
+ const pml4_entry = pml4[pml4_index];
+
+ if ((pml4_entry & 1) == 0) return null; // Not present
+
+ const pdp_addr = (pml4_entry & 0x000FFFFFFFFFF000) + HIGHER_HALF_START;
+ const pdp = @as([*]u64, @ptrFromInt(pdp_addr));
+
+ const pdp_index = (virt >> 30) & 0x1FF;
+ const pdp_entry = pdp[pdp_index];
+
+ if ((pdp_entry & 1) == 0) return null; // Not present
+
+ const pd_addr = (pdp_entry & 0x000FFFFFFFFFF000) + HIGHER_HALF_START;
+ const pd = @as([*]u64, @ptrFromInt(pd_addr));
+
+ const pd_index = (virt >> 21) & 0x1FF;
+ const pd_entry = pd[pd_index];
+
+ if ((pd_entry & 1) == 0) return null; // Not present
+
+ const pt_addr = (pd_entry & 0x000FFFFFFFFFF000) + HIGHER_HALF_START;
+ const pt = @as([*]u64, @ptrFromInt(pt_addr));
+
+ const pt_index = (virt >> 12) & 0x1FF;
+ const pt_entry = pt[pt_index];
+
+ if ((pt_entry & 1) == 0) return null; // Not present
+
+ const phys_base = pt_entry & 0x000FFFFFFFFFF000;
+ const offset = virt & 0xFFF;
+
+ return phys_base + offset;
+}
+
pub fn init() void {
serial.print("\n=== Page Table Manager ===\n");
serial.print("CR3: ");
diff --git a/mirai/mirai.zig b/mirai/mirai.zig
index 9280485..fbd4e26 100644
--- a/mirai/mirai.zig
+++ b/mirai/mirai.zig
@@ -1,4 +1,3 @@
-const ash = @import("ash/ash.zig");
const crimson = @import("crimson/panic.zig");
const serial = @import("drivers/serial.zig");
const sequence = @import("boot/sequence.zig");
@@ -13,9 +12,6 @@ export fn mirai(multiboot_info_addr: u64) noreturn {
// Run boot sequence
sequence.run(multiboot_info_addr);
- // Initialize shell
- ash.init(sequence.get_filesystem());
-
serial.print("\n** Boot Complete **\n");
while (true) {
@@ -26,7 +22,3 @@ export fn mirai(multiboot_info_addr: u64) noreturn {
pub fn panic(message: []const u8, _: ?*@import("std").builtin.StackTrace, _: ?usize) noreturn {
crimson.collapse(message, null);
}
-
-export fn on_key_typed(char: u8) void {
- ash.on_key_press(char);
-}
diff --git a/system/ash/ash.zig b/system/ash/ash.zig
new file mode 100644
index 0000000..577dea3
--- /dev/null
+++ b/system/ash/ash.zig
@@ -0,0 +1,45 @@
+//! Ash - Akiba Shell
+
+const akiba = @import("akiba");
+
+const MAX_INPUT = 256;
+var input_buffer: [MAX_INPUT]u8 = undefined;
+var input_len: usize = 0;
+
+export fn _start() noreturn {
+ while (true) {
+ // Print prompt
+ akiba.io.print("/ >>> ") catch {};
+
+ // Read line
+ input_len = 0;
+ while (true) {
+ const char = akiba.io.getchar() catch continue;
+
+ if (char == '\n') {
+ akiba.io.print("\n") catch {};
+ break;
+ } else if (char == '\x08') {
+ // Backspace
+ if (input_len > 0) {
+ input_len -= 1;
+ // Echo backspace to screen
+ akiba.io.print("\x08") catch {};
+ }
+ } else if (char >= 32 and char <= 126 and input_len < MAX_INPUT - 1) {
+ input_buffer[input_len] = char;
+ input_len += 1;
+ // Echo character to screen
+ const char_str = [_]u8{char};
+ akiba.io.print(&char_str) catch {};
+ }
+ }
+
+ // TODO: Parse and execute command
+ if (input_len > 0) {
+ akiba.io.print("Command \"") catch {};
+ akiba.io.print(input_buffer[0..input_len]) catch {};
+ akiba.io.print("\" not implemented yet\n") catch {};
+ }
+ }
+}
diff --git a/system/ash/ash.zon b/system/ash/ash.zon
new file mode 100644
index 0000000..0c72775
--- /dev/null
+++ b/system/ash/ash.zon
@@ -0,0 +1,8 @@
+.{
+ .name = "ash",
+ .version = "0.1.0",
+ .paths = .{""},
+ .dependencies = .{
+ .akiba = .{ .path = "../libraries/akiba" },
+ },
+}
diff --git a/system/libraries/akiba/io.zig b/system/libraries/akiba/io.zig
index b8f8eef..35ff739 100644
--- a/system/libraries/akiba/io.zig
+++ b/system/libraries/akiba/io.zig
@@ -1,6 +1,7 @@
//! File I/O operations
const sys = @import("sys.zig");
+const kata = @import("kata.zig");
pub const Error = error{
NotFound,
@@ -51,6 +52,21 @@ pub fn mark(fd: FileDescriptor, data: []const u8) Error!usize {
return result;
}
+pub fn getchar() !u8 {
+ // Loop until character is available, yielding CPU
+ while (true) {
+ const result = sys.syscall0(.getkeychar);
+ if (result != @as(u64, @bitCast(@as(i64, -2)))) {
+ if (result != @as(u64, @bitCast(@as(i64, -1)))) {
+ return @truncate(result);
+ }
+ return error.ReadFailed;
+ }
+ // No input yet (-2 = EAGAIN), yield and try again
+ kata.yield();
+ }
+}
+
pub fn print(text: []const u8) Error!void {
_ = try mark(stream, text);
}
diff --git a/system/libraries/akiba/kata.zig b/system/libraries/akiba/kata.zig
index 2283ce8..14f2946 100644
--- a/system/libraries/akiba/kata.zig
+++ b/system/libraries/akiba/kata.zig
@@ -20,9 +20,13 @@ pub fn spawn(path: []const u8) !u32 {
}
pub fn wait(pid: u32) !u64 {
- const result = sys.syscall1(.wait, pid);
- if (result == @as(u64, @bitCast(@as(i64, -1)))) {
- return error.WaitFailed;
+ // Retry loop: keep checking until the target exits
+ while (true) {
+ const result = sys.syscall1(.wait, pid);
+ if (result != @as(u64, @bitCast(@as(i64, -1)))) {
+ return result;
+ }
+ // Target still running, yield and try again
+ yield();
}
- return result;
}
diff --git a/system/libraries/akiba/sys.zig b/system/libraries/akiba/sys.zig
index ee74777..10329ff 100644
--- a/system/libraries/akiba/sys.zig
+++ b/system/libraries/akiba/sys.zig
@@ -9,6 +9,7 @@ pub const Invocation = enum(u64) {
spawn = 0x06,
wait = 0x07,
yield = 0x08,
+ getkeychar = 0x09,
};
pub fn syscall0(inv: Invocation) u64 {
@@ -16,8 +17,7 @@ pub fn syscall0(inv: Invocation) u64 {
asm volatile ("syscall"
: [ret] "={rax}" (result),
: [inv] "{rax}" (@intFromEnum(inv)),
- : .{ .rcx = true, .r11 = true, .memory = true }
- );
+ : .{ .rcx = true, .r11 = true, .memory = true });
return result;
}
@@ -27,8 +27,7 @@ pub fn syscall1(inv: Invocation, arg1: u64) u64 {
: [ret] "={rax}" (result),
: [inv] "{rax}" (@intFromEnum(inv)),
[arg1] "{rdi}" (arg1),
- : .{ .rcx = true, .r11 = true, .memory = true }
- );
+ : .{ .rcx = true, .r11 = true, .memory = true });
return result;
}
@@ -39,8 +38,7 @@ pub fn syscall2(inv: Invocation, arg1: u64, arg2: u64) u64 {
: [inv] "{rax}" (@intFromEnum(inv)),
[arg1] "{rdi}" (arg1),
[arg2] "{rsi}" (arg2),
- : .{ .rcx = true, .r11 = true, .memory = true }
- );
+ : .{ .rcx = true, .r11 = true, .memory = true });
return result;
}
@@ -52,7 +50,6 @@ pub fn syscall3(inv: Invocation, arg1: u64, arg2: u64, arg3: u64) u64 {
[arg1] "{rdi}" (arg1),
[arg2] "{rsi}" (arg2),
[arg3] "{rdx}" (arg3),
- : .{ .rcx = true, .r11 = true, .memory = true }
- );
+ : .{ .rcx = true, .r11 = true, .memory = true });
return result;
}
diff --git a/system/pulse/pulse.zig b/system/pulse/pulse.zig
index c558eb3..2da96cf 100644
--- a/system/pulse/pulse.zig
+++ b/system/pulse/pulse.zig
@@ -4,9 +4,22 @@
const akiba = @import("akiba");
export fn _start() noreturn {
- akiba.io.println("Pulse: System ready!") catch {};
-
+ // Init loop - respawn shell when it exits
while (true) {
- akiba.kata.yield();
+ const shell_pid = akiba.kata.spawn("/system/ash/ash.akiba") catch {
+ akiba.io.println("Pulse: Failed to spawn shell") catch {};
+ akiba.kata.yield();
+ continue;
+ };
+
+ // Wait for shell to exit
+ _ = akiba.kata.wait(shell_pid) catch {
+ akiba.io.println("Pulse: Shell wait failed") catch {};
+ akiba.kata.yield();
+ continue;
+ };
+
+ // Shell exited, respawn
+ akiba.io.println("\nPulse: Shell exited, respawning...\n") catch {};
}
}