From ab93a33bbd1b8f030debdc294b7e0cdd6a786e0f Mon Sep 17 00:00:00 2001 From: Dawid Sobczak Date: Wed, 15 Jun 2022 13:19:20 +0100 Subject: [PATCH 1/8] Split the project into packages. --- build.zig | 19 +++++++++++++++++++ src/kernel/arch.zig | 13 ------------- src/kernel/arch/x86/arch.zig | 21 +++++++++++---------- src/kernel/arch/x86/cmos.zig | 2 +- src/kernel/arch/x86/gdt.zig | 2 +- src/kernel/arch/x86/idt.zig | 2 +- src/kernel/arch/x86/irq.zig | 6 +++--- src/kernel/arch/x86/isr.zig | 4 ++-- src/kernel/arch/x86/keyboard.zig | 2 +- src/kernel/arch/x86/paging.zig | 2 +- src/kernel/arch/x86/pci.zig | 2 +- src/kernel/arch/x86/pic.zig | 2 +- src/kernel/arch/x86/pit.zig | 2 +- src/kernel/arch/x86/rtc.zig | 4 ++-- src/kernel/arch/x86/syscalls.zig | 7 ++++--- src/kernel/arch/x86/tty.zig | 2 +- src/kernel/arch/x86/vga.zig | 2 +- src/kernel/filesystem/fat32.zig | 4 +++- src/kernel/keyboard.zig | 4 +++- src/kernel/kmain.zig | 32 ++++++++++++++++---------------- src/kernel/panic.zig | 4 +++- src/kernel/pluto.zig | 14 ++++++++++++++ src/kernel/pmm.zig | 2 +- src/kernel/scheduler.zig | 2 +- src/kernel/serial.zig | 4 +++- src/kernel/syscalls.zig | 4 +++- src/kernel/task.zig | 2 +- src/kernel/tty.zig | 4 +++- src/kernel/vmm.zig | 6 +++--- test/mock/kernel/arch_mock.zig | 25 ++++++++++++++++--------- test/mock/kernel/gdt_mock.zig | 4 +--- test/mock/kernel/idt_mock.zig | 2 +- 32 files changed, 123 insertions(+), 84 deletions(-) delete mode 100644 src/kernel/arch.zig create mode 100644 src/kernel/pluto.zig diff --git a/build.zig b/build.zig index d63a392..b743dd2 100644 --- a/build.zig +++ b/build.zig @@ -13,6 +13,7 @@ const File = fs.File; const Mode = std.builtin.Mode; const TestMode = rt.TestMode; const ArrayList = std.ArrayList; +const Pkg = std.build.Pkg; const Fat32 = @import("mkfat32.zig").Fat32; const x86_i686 = CrossTarget{ @@ -37,7 +38,10 @@ pub fn build(b: *Builder) !void { b.default_step.dependOn(&fmt_step.step); const main_src = "src/kernel/kmain.zig"; + const pluto_src = "src/kernel/pluto.zig"; const arch_root = "src/kernel/arch"; + const arch_mock_src = "test/mock/kernel/arch_mock.zig"; + const arch_src = try fs.path.join(b.allocator, &[_][]const u8{ arch_root, arch, "arch.zig" }); const linker_script_path = try fs.path.join(b.allocator, &[_][]const u8{ arch_root, arch, "link.ld" }); const output_iso = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "pluto.iso" }); const iso_dir_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "iso" }); @@ -68,6 +72,16 @@ pub fn build(b: *Builder) !void { exec.setLinkerScriptPath(std.build.FileSource{ .path = linker_script_path }); exec.setTarget(target); + var pluto_pkg = Pkg{ .name = "pluto", .path = .{ .path = pluto_src } }; + var arch_pkg = Pkg{ .name = "arch", .path = .{ .path = arch_src } }; + var arch_mock_pkg = Pkg{ .name = "arch_mock", .path = .{ .path = arch_mock_src } }; + arch_mock_pkg.dependencies = &[_]Pkg{ arch_pkg, pluto_pkg, exec_options.getPackage("build_options") }; + pluto_pkg.dependencies = &[_]Pkg{ arch_pkg, arch_mock_pkg, exec_options.getPackage("build_options") }; + arch_pkg.dependencies = &[_]Pkg{ pluto_pkg, arch_mock_pkg, exec_options.getPackage("build_options") }; + exec.addPackage(pluto_pkg); + exec.addPackage(arch_pkg); + exec.addPackage(arch_mock_pkg); + const make_iso = switch (target.getCpuArch()) { .i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec_output_path, ramdisk_path, output_iso }), else => unreachable, @@ -113,6 +127,9 @@ pub fn build(b: *Builder) !void { unit_tests.addOptions("build_options", unit_test_options); unit_test_options.addOption(TestMode, "test_mode", test_mode); unit_tests.setTarget(.{ .cpu_arch = target.cpu_arch }); + unit_tests.addPackage(pluto_pkg); + unit_tests.addPackage(arch_pkg); + unit_tests.addPackage(arch_mock_pkg); if (builtin.os.tag != .windows) { b.enable_qemu = true; @@ -123,6 +140,8 @@ pub fn build(b: *Builder) !void { mock_gen.setMainPkgPath("."); const mock_gen_run = mock_gen.run(); unit_tests.step.dependOn(&mock_gen_run.step); + exec.step.dependOn(&mock_gen_run.step); + b.default_step.dependOn(&mock_gen_run.step); // Create test FAT32 image const test_fat32_img_step = Fat32BuilderStep.create(b, .{}, test_fat32_image_path); diff --git a/src/kernel/arch.zig b/src/kernel/arch.zig deleted file mode 100644 index e3e812e..0000000 --- a/src/kernel/arch.zig +++ /dev/null @@ -1,13 +0,0 @@ -const std = @import("std"); -const builtin = @import("builtin"); -const is_test = builtin.is_test; -const build_options = @import("build_options"); - -pub const internals = if (is_test) @import("../../test/mock/kernel/arch_mock.zig") else switch (builtin.cpu.arch) { - .i386 => @import("arch/x86/arch.zig"), - else => unreachable, -}; - -test "" { - _ = @import("arch/x86/arch.zig"); -} diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index 2b4db4f..c7f4c29 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -3,11 +3,11 @@ const Allocator = std.mem.Allocator; const log = std.log.scoped(.x86_arch); const builtin = @import("builtin"); const cmos = @import("cmos.zig"); -const gdt = @import("gdt.zig"); -const idt = @import("idt.zig"); +pub const gdt = @import("gdt.zig"); +pub const idt = @import("idt.zig"); const irq = @import("irq.zig"); const isr = @import("isr.zig"); -const paging = @import("paging.zig"); +pub const paging = @import("paging.zig"); const pic = @import("pic.zig"); const pci = @import("pci.zig"); const pit = @import("pit.zig"); @@ -16,15 +16,16 @@ const serial = @import("serial.zig"); const syscalls = @import("syscalls.zig"); const tty = @import("tty.zig"); const vga = @import("vga.zig"); -const mem = @import("../../mem.zig"); const multiboot = @import("multiboot.zig"); -const vmm = @import("../../vmm.zig"); const keyboard = @import("keyboard.zig"); -const Serial = @import("../../serial.zig").Serial; -const panic = @import("../../panic.zig").panic; -const TTY = @import("../../tty.zig").TTY; -const Keyboard = @import("../../keyboard.zig").Keyboard; -const Task = @import("../../task.zig").Task; +const pluto = @import("pluto"); +const mem = pluto.mem; +const vmm = pluto.vmm; +const Keyboard = pluto.keyboard.Keyboard; +const Serial = pluto.serial.Serial; +const panic = pluto.panic_root.panic; +const TTY = pluto.tty.TTY; +const Task = pluto.task.Task; const MemProfile = mem.MemProfile; /// The type of a device. diff --git a/src/kernel/arch/x86/cmos.zig b/src/kernel/arch/x86/cmos.zig index f95b1db..a8d0370 100644 --- a/src/kernel/arch/x86/cmos.zig +++ b/src/kernel/arch/x86/cmos.zig @@ -3,7 +3,7 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const expectEqual = std.testing.expectEqual; const build_options = @import("build_options"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); /// The current year to be used for calculating the 4 digit year, as the CMOS return the last two /// digits of the year. diff --git a/src/kernel/arch/x86/gdt.zig b/src/kernel/arch/x86/gdt.zig index 8483cb6..fd947ae 100644 --- a/src/kernel/arch/x86/gdt.zig +++ b/src/kernel/arch/x86/gdt.zig @@ -6,7 +6,7 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const panic = @import("../../panic.zig").panic; const build_options = @import("build_options"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); /// The access bits for a GDT entry. const AccessBits = packed struct { diff --git a/src/kernel/arch/x86/idt.zig b/src/kernel/arch/x86/idt.zig index b4567b7..048bfa5 100644 --- a/src/kernel/arch/x86/idt.zig +++ b/src/kernel/arch/x86/idt.zig @@ -8,7 +8,7 @@ const is_test = builtin.is_test; const panic = @import("../../panic.zig").panic; const build_options = @import("build_options"); const gdt = if (is_test) @import("../../../../test/mock/kernel/gdt_mock.zig") else @import("gdt.zig"); -const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (builtin.is_test) @import("arch_mock") else @import("arch.zig"); /// The structure that contains all the information that each IDT entry needs. pub const IdtEntry = packed struct { diff --git a/src/kernel/arch/x86/irq.zig b/src/kernel/arch/x86/irq.zig index 94e2152..4e6a030 100644 --- a/src/kernel/arch/x86/irq.zig +++ b/src/kernel/arch/x86/irq.zig @@ -7,9 +7,9 @@ const expectError = std.testing.expectError; const log = std.log.scoped(.x86_irq); const build_options = @import("build_options"); const panic = @import("../../panic.zig").panic; -const idt = if (is_test) @import("../../../../test/mock/kernel/idt_mock.zig") else @import("idt.zig"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); -const pic = if (is_test) @import("../../../../test/mock/kernel/pic_mock.zig") else @import("pic.zig"); +const idt = if (is_test) @import("arch_mock").idt_mock else @import("idt.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const pic = if (is_test) @import("arch_mock").pic_mock else @import("pic.zig"); const interrupts = @import("interrupts.zig"); /// The error set for the IRQ. This will be from installing a IRQ handler. diff --git a/src/kernel/arch/x86/isr.zig b/src/kernel/arch/x86/isr.zig index eabb1a1..3a44a63 100644 --- a/src/kernel/arch/x86/isr.zig +++ b/src/kernel/arch/x86/isr.zig @@ -8,8 +8,8 @@ const log = std.log.scoped(.x86_isr); const build_options = @import("build_options"); const syscalls = @import("syscalls.zig"); const panic = @import("../../panic.zig").panic; -const idt = if (is_test) @import("../../../../test/mock/kernel/idt_mock.zig") else @import("idt.zig"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const idt = if (is_test) @import("arch_mock").idt_mock else @import("idt.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); const interrupts = @import("interrupts.zig"); /// The error set for the ISR. This will be from installing a ISR handler. diff --git a/src/kernel/arch/x86/keyboard.zig b/src/kernel/arch/x86/keyboard.zig index 567573e..dc6f263 100644 --- a/src/kernel/arch/x86/keyboard.zig +++ b/src/kernel/arch/x86/keyboard.zig @@ -6,7 +6,7 @@ const testing = std.testing; const log = std.log.scoped(.x86_keyboard); const irq = @import("irq.zig"); const pic = @import("pic.zig"); -const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (builtin.is_test) @import("arch_mock") else @import("arch.zig"); const panic = @import("../../panic.zig").panic; const kb = @import("../../keyboard.zig"); const Keyboard = kb.Keyboard; diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index f87abf0..1de58d2 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -7,7 +7,7 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const panic = @import("../../panic.zig").panic; const build_options = @import("build_options"); -const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (builtin.is_test) @import("arch_mock") else @import("arch.zig"); const isr = @import("isr.zig"); const MemProfile = @import("../../mem.zig").MemProfile; const tty = @import("../../tty.zig"); diff --git a/src/kernel/arch/x86/pci.zig b/src/kernel/arch/x86/pci.zig index 769b996..d0e6654 100644 --- a/src/kernel/arch/x86/pci.zig +++ b/src/kernel/arch/x86/pci.zig @@ -6,7 +6,7 @@ const build_options = @import("build_options"); const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const log = std.log.scoped(.pci); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); /// The port address for selecting a 32bit register in the PCI configuration space. const CONFIG_ADDRESS: u16 = 0x0CF8; diff --git a/src/kernel/arch/x86/pic.zig b/src/kernel/arch/x86/pic.zig index f37ad46..85cb244 100644 --- a/src/kernel/arch/x86/pic.zig +++ b/src/kernel/arch/x86/pic.zig @@ -5,7 +5,7 @@ const log = std.log.scoped(.x86_pic); const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); const panic = @import("../../panic.zig").panic; // ---------- diff --git a/src/kernel/arch/x86/pit.zig b/src/kernel/arch/x86/pit.zig index 6f3e961..946e870 100644 --- a/src/kernel/arch/x86/pit.zig +++ b/src/kernel/arch/x86/pit.zig @@ -7,7 +7,7 @@ const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; const log = std.log.scoped(.x86_pit); const build_options = @import("build_options"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); const panic = @import("../../panic.zig").panic; const irq = @import("irq.zig"); const pic = @import("pic.zig"); diff --git a/src/kernel/arch/x86/rtc.zig b/src/kernel/arch/x86/rtc.zig index 7221ca6..1091ead 100644 --- a/src/kernel/arch/x86/rtc.zig +++ b/src/kernel/arch/x86/rtc.zig @@ -6,11 +6,11 @@ const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; const log = std.log.scoped(.x86_rtc); const build_options = @import("build_options"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); const pic = @import("pic.zig"); const pit = @import("pit.zig"); const irq = @import("irq.zig"); -const cmos = if (is_test) @import("../../../../test/mock/kernel/cmos_mock.zig") else @import("cmos.zig"); +const cmos = if (is_test) @import("arch_mock").cmos_mock else @import("cmos.zig"); const panic = @import("../../panic.zig").panic; const scheduler = @import("../../scheduler.zig"); diff --git a/src/kernel/arch/x86/syscalls.zig b/src/kernel/arch/x86/syscalls.zig index 01ee414..9cdbaca 100644 --- a/src/kernel/arch/x86/syscalls.zig +++ b/src/kernel/arch/x86/syscalls.zig @@ -3,12 +3,13 @@ const log = std.log.scoped(.x86_syscalls); const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); const testing = std.testing; const expect = std.testing.expect; const isr = @import("isr.zig"); -const panic = @import("../../panic.zig").panic; -const syscalls = @import("../../syscalls.zig"); +const pluto = @import("pluto"); +const panic = pluto.panic_root.panic; +const syscalls = pluto.syscalls; /// The isr number associated with syscalls pub const INTERRUPT: u16 = 0x80; diff --git a/src/kernel/arch/x86/tty.zig b/src/kernel/arch/x86/tty.zig index 0c4db91..a3144fc 100644 --- a/src/kernel/arch/x86/tty.zig +++ b/src/kernel/arch/x86/tty.zig @@ -7,7 +7,7 @@ const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; const log = std.log.scoped(.x86_tty); const build_options = @import("build_options"); -const vga = if (is_test) @import("../../../../test/mock/kernel/vga_mock.zig") else @import("vga.zig"); +const vga = if (is_test) @import("arch_mock").vga_mock else @import("vga.zig"); const panic = @import("../../panic.zig").panic; /// The error set for if there is an error whiles printing. diff --git a/src/kernel/arch/x86/vga.zig b/src/kernel/arch/x86/vga.zig index d016276..d448815 100644 --- a/src/kernel/arch/x86/vga.zig +++ b/src/kernel/arch/x86/vga.zig @@ -4,7 +4,7 @@ const is_test = builtin.is_test; const expectEqual = std.testing.expectEqual; const log = std.log.scoped(.x86_vga); const build_options = @import("build_options"); -const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); const panic = @import("../../panic.zig").panic; /// The port address for the VGA register selection. diff --git a/src/kernel/filesystem/fat32.zig b/src/kernel/filesystem/fat32.zig index aba3e5f..cf554be 100644 --- a/src/kernel/filesystem/fat32.zig +++ b/src/kernel/filesystem/fat32.zig @@ -8,7 +8,9 @@ const log = std.log.scoped(.fat32); const AutoHashMap = std.AutoHashMap; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; -const arch = @import("../arch.zig").internals; +const builtins = @import("builtin"); +const is_test = builtins.is_test; +const arch = if (is_test) @import("arch_mock") else @import("arch"); const vfs = @import("vfs.zig"); const mem = @import("../mem.zig"); const CodePage = @import("../code_page/code_page.zig").CodePage; diff --git a/src/kernel/keyboard.zig b/src/kernel/keyboard.zig index 8e5a5fe..463467f 100644 --- a/src/kernel/keyboard.zig +++ b/src/kernel/keyboard.zig @@ -2,7 +2,9 @@ const std = @import("std"); const testing = std.testing; const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; -const arch = @import("arch.zig").internals; +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const arch = if (is_test) @import("arch_mock") else @import("arch"); /// An arbitrary number of keys to remember before dropping any more that arrive. Is a power of two so we can use nice overflowing addition pub const QUEUE_SIZE = 32; diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 89b52ff..27eee06 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -1,23 +1,23 @@ const std = @import("std"); -const kmain_log = std.log.scoped(.kmain); const builtin = @import("builtin"); -const is_test = builtin.is_test; +const arch = if (is_test) @import("arch_mock") else @import("arch"); +const pluto = @import("pluto"); const build_options = @import("build_options"); -const arch = @import("arch.zig").internals; -const tty = @import("tty.zig"); -const log_root = @import("log.zig"); -const pmm = @import("pmm.zig"); -const serial = @import("serial.zig"); -const vmm = @import("vmm.zig"); -const mem = @import("mem.zig"); -const panic_root = @import("panic.zig"); -const task = @import("task.zig"); -const heap = @import("heap.zig"); -const scheduler = @import("scheduler.zig"); -const vfs = @import("filesystem/vfs.zig"); -const initrd = @import("filesystem/initrd.zig"); -const keyboard = @import("keyboard.zig"); +const is_test = builtin.is_test; +const kmain_log = std.log.scoped(.kmain); const Allocator = std.mem.Allocator; +const tty = pluto.tty; +const panic_root = pluto.panic_root; +const log_root = pluto.log_root; +const heap = pluto.heap; +const serial = pluto.serial; +const pmm = pluto.pmm; +const vmm = pluto.vmm; +const keyboard = pluto.keyboard; +const initrd = pluto.initrd; +const vfs = pluto.vfs; +const scheduler = pluto.scheduler; +const task = pluto.task; comptime { if (!is_test) { diff --git a/src/kernel/panic.zig b/src/kernel/panic.zig index bfc2a28..31814b0 100644 --- a/src/kernel/panic.zig +++ b/src/kernel/panic.zig @@ -1,6 +1,8 @@ const std = @import("std"); const builtin = std.builtin; -const arch = @import("arch.zig").internals; +const builtins = @import("builtin"); +const is_test = builtins.is_test; +const arch = if (is_test) @import("arch_mock") else @import("arch"); const mem = @import("mem.zig"); const build_options = @import("build_options"); const ArrayList = std.ArrayList; diff --git a/src/kernel/pluto.zig b/src/kernel/pluto.zig new file mode 100644 index 0000000..91e38f0 --- /dev/null +++ b/src/kernel/pluto.zig @@ -0,0 +1,14 @@ +pub const tty = @import("tty.zig"); +pub const log_root = @import("log.zig"); +pub const pmm = @import("pmm.zig"); +pub const serial = @import("serial.zig"); +pub const vmm = @import("vmm.zig"); +pub const mem = @import("mem.zig"); +pub const panic_root = @import("panic.zig"); +pub const task = @import("task.zig"); +pub const heap = @import("heap.zig"); +pub const scheduler = @import("scheduler.zig"); +pub const vfs = @import("filesystem/vfs.zig"); +pub const initrd = @import("filesystem/initrd.zig"); +pub const keyboard = @import("keyboard.zig"); +pub const syscalls = @import("syscalls.zig"); diff --git a/src/kernel/pmm.zig b/src/kernel/pmm.zig index 395269d..0299a9f 100644 --- a/src/kernel/pmm.zig +++ b/src/kernel/pmm.zig @@ -2,7 +2,7 @@ const is_test = @import("builtin").is_test; const std = @import("std"); const log = std.log.scoped(.pmm); const build_options = @import("build_options"); -const arch = @import("arch.zig").internals; +const arch = if (is_test) @import("arch_mock") else @import("arch"); const MemProfile = @import("mem.zig").MemProfile; const testing = std.testing; const panic = @import("panic.zig").panic; diff --git a/src/kernel/scheduler.zig b/src/kernel/scheduler.zig index d05336e..62099fd 100644 --- a/src/kernel/scheduler.zig +++ b/src/kernel/scheduler.zig @@ -6,7 +6,7 @@ const log = std.log.scoped(.scheduler); const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = @import("arch.zig").internals; +const arch = if (is_test) @import("arch_mock") else @import("arch"); const panic = @import("panic.zig").panic; const task = @import("task.zig"); const vmm = @import("vmm.zig"); diff --git a/src/kernel/serial.zig b/src/kernel/serial.zig index 2ee6ea7..1b56f87 100644 --- a/src/kernel/serial.zig +++ b/src/kernel/serial.zig @@ -1,4 +1,6 @@ -const arch = @import("arch.zig").internals; +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const arch = if (is_test) @import("arch_mock") else @import("arch"); const build_options = @import("build_options"); pub const Serial = struct { diff --git a/src/kernel/syscalls.zig b/src/kernel/syscalls.zig index bb25793..02b5791 100644 --- a/src/kernel/syscalls.zig +++ b/src/kernel/syscalls.zig @@ -2,7 +2,9 @@ const std = @import("std"); const scheduler = @import("scheduler.zig"); const panic = @import("panic.zig").panic; const log = std.log.scoped(.syscalls); -const arch = @import("arch.zig").internals; +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const arch = if (is_test) @import("arch_mock") else @import("arch"); /// A compilation of all errors that syscall handlers could return. pub const Error = error{OutOfMemory}; diff --git a/src/kernel/task.zig b/src/kernel/task.zig index fa45f88..426e0d0 100644 --- a/src/kernel/task.zig +++ b/src/kernel/task.zig @@ -4,7 +4,7 @@ const expectError = std.testing.expectError; const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = @import("arch.zig").internals; +const arch = if (is_test) @import("arch_mock") else @import("arch"); const panic = @import("panic.zig").panic; const vmm = @import("vmm.zig"); const pmm = @import("pmm.zig"); diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig index 9a4cf54..0f2dc34 100644 --- a/src/kernel/tty.zig +++ b/src/kernel/tty.zig @@ -3,7 +3,9 @@ const fmt = std.fmt; const Allocator = std.mem.Allocator; const log = std.log.scoped(.tty); const build_options = @import("build_options"); -const arch = @import("arch.zig").internals; +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const arch = if (is_test) @import("arch_mock") else @import("arch"); const panic = @import("panic.zig").panic; /// The OutStream for the format function diff --git a/src/kernel/vmm.zig b/src/kernel/vmm.zig index 453fb0f..8bfda75 100644 --- a/src/kernel/vmm.zig +++ b/src/kernel/vmm.zig @@ -1,7 +1,5 @@ const build_options = @import("build_options"); const mock_path = build_options.mock_path; -const builtin = std.builtin; -const is_test = builtin.is_test; const std = @import("std"); const log = std.log.scoped(.vmm); const bitmap = @import("bitmap.zig"); @@ -9,7 +7,9 @@ const pmm = @import("pmm.zig"); const mem = @import("mem.zig"); const tty = @import("tty.zig"); const panic = @import("panic.zig").panic; -const arch = @import("arch.zig").internals; +const builtins = @import("builtin"); +const is_test = builtins.is_test; +const arch = if (is_test) @import("arch_mock") else @import("arch"); const Allocator = std.mem.Allocator; const assert = std.debug.assert; diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index a2b38b4..a08c9ef 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -1,18 +1,25 @@ const std = @import("std"); const builtin = @import("builtin"); -const Allocator = std.mem.Allocator; -const mem = @import("../../../src/kernel/mem.zig"); -const MemProfile = mem.MemProfile; +const pluto = @import("pluto"); +const arch = @import("arch"); const pci = @import("pci_mock.zig"); const gdt = @import("gdt_mock.zig"); const idt = @import("idt_mock.zig"); -const vmm = @import("../../../src/kernel/vmm.zig"); const paging = @import("paging_mock.zig"); -const Serial = @import("../../../src/kernel/serial.zig").Serial; -const TTY = @import("../../../src/kernel/tty.zig").TTY; -const Keyboard = @import("../../../src/kernel/keyboard.zig").Keyboard; -const task = @import("../../../src/kernel/task.zig"); -const x86_paging = @import("../../../src/kernel/arch/x86/paging.zig"); +pub const cmos_mock = @import("cmos_mock.zig"); +pub const vga_mock = @import("vga_mock.zig"); +pub const pic_mock = @import("pic_mock.zig"); +pub const idt_mock = @import("idt_mock.zig"); +pub const pci_mock = @import("pci_mock.zig"); +const x86_paging = arch.paging; +const vmm = pluto.vmm; +const mem = pluto.mem; +const Serial = pluto.serial.Serial; +const TTY = pluto.tty.TTY; +const Keyboard = pluto.keyboard.Keyboard; +const task = pluto.task; +const Allocator = std.mem.Allocator; +const MemProfile = mem.MemProfile; pub const Device = pci.PciDeviceInfo; pub const DateTime = struct { diff --git a/test/mock/kernel/gdt_mock.zig b/test/mock/kernel/gdt_mock.zig index 2c65f8b..414aa3b 100644 --- a/test/mock/kernel/gdt_mock.zig +++ b/test/mock/kernel/gdt_mock.zig @@ -1,6 +1,4 @@ -// Can't do: TODO: https://github.com/SamTebbs33/pluto/issues/77 -//const src_gdt = @import("arch").gdt; -const src_gdt = @import("../../../src/kernel/arch/x86/gdt.zig"); +const src_gdt = @import("arch").gdt; const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; diff --git a/test/mock/kernel/idt_mock.zig b/test/mock/kernel/idt_mock.zig index ed50fd2..b8abe14 100644 --- a/test/mock/kernel/idt_mock.zig +++ b/test/mock/kernel/idt_mock.zig @@ -1,4 +1,4 @@ -const src_idt = @import("../../../src/kernel/arch/x86/idt.zig"); +const src_idt = @import("arch").idt; const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; From 6156f1b30aa558acaa580a91d888af9e1ecacb1f Mon Sep 17 00:00:00 2001 From: Sam Tebbs Date: Sun, 5 Jun 2022 23:26:29 +0100 Subject: [PATCH 2/8] Create task 0 like other tasks --- src/kernel/arch/x86/arch.zig | 84 ++++++++++++++++++---------------- src/kernel/kmain.zig | 2 +- src/kernel/scheduler.zig | 24 +++++----- src/kernel/task.zig | 53 +++++++++++---------- test/mock/kernel/arch_mock.zig | 3 +- 5 files changed, 86 insertions(+), 80 deletions(-) diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index c7f4c29..187bc59 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -531,8 +531,8 @@ pub fn initKeyboard(allocator: Allocator) Allocator.Error!*Keyboard { } /// -/// Initialise a stack used for creating a task. -/// Currently only support fn () noreturn functions for the entry point. +/// Initialise a stack and vmm payload used for creating a task. +/// Currently only supports fn () noreturn functions for the entry point. /// /// Arguments: /// IN task: *Task - The task to be initialised. The function will only modify whatever @@ -541,62 +541,66 @@ pub fn initKeyboard(allocator: Allocator) Allocator.Error!*Keyboard { /// IN entry_point: usize - The pointer to the entry point of the function. Functions only /// supported is fn () noreturn /// IN allocator: Allocator - The allocator use for allocating a stack. +/// IN set_up_stack: bool - Set up the kernel and user stacks (register values, PC etc.) for task entry /// /// Error: Allocator.Error /// OutOfMemory - Unable to allocate space for the stack. /// -pub fn initTask(task: *Task, entry_point: usize, allocator: Allocator) Allocator.Error!void { - const data_offset = if (task.kernel) gdt.KERNEL_DATA_OFFSET else gdt.USER_DATA_OFFSET | 0b11; - // Setting the bottom two bits of the code offset designates that this is a ring 3 task - const code_offset = if (task.kernel) gdt.KERNEL_CODE_OFFSET else gdt.USER_CODE_OFFSET | 0b11; - // Ring switches push and pop two extra values on interrupt: user_esp and user_ss - const kernel_stack_bottom = if (task.kernel) task.kernel_stack.len - 18 else task.kernel_stack.len - 20; - - var stack = &task.kernel_stack; - +pub fn initTask(task: *Task, entry_point: usize, allocator: Allocator, set_up_stack: bool) Allocator.Error!void { // TODO Will need to add the exit point // Set up everything as a kernel task task.vmm.payload = &paging.kernel_directory; - stack.*[kernel_stack_bottom] = mem.virtToPhys(@ptrToInt(&paging.kernel_directory)); - stack.*[kernel_stack_bottom + 1] = data_offset; // gs - stack.*[kernel_stack_bottom + 2] = data_offset; // fs - stack.*[kernel_stack_bottom + 3] = data_offset; // es - stack.*[kernel_stack_bottom + 4] = data_offset; // ds - stack.*[kernel_stack_bottom + 5] = 0; // edi - stack.*[kernel_stack_bottom + 6] = 0; // esi - // End of the stack - stack.*[kernel_stack_bottom + 7] = @ptrToInt(&stack.*[stack.len - 1]); // ebp - stack.*[kernel_stack_bottom + 8] = 0; // esp (temp) this won't be popped by popa bc intel is dump XD + var stack = &task.kernel_stack; + const kernel_stack_bottom = if (!set_up_stack) 0 else if (task.kernel) task.kernel_stack.len - 18 else task.kernel_stack.len - 20; + if (set_up_stack) { + const data_offset = if (task.kernel) gdt.KERNEL_DATA_OFFSET else gdt.USER_DATA_OFFSET | 0b11; + // Setting the bottom two bits of the code offset designates that this is a ring 3 task + const code_offset = if (task.kernel) gdt.KERNEL_CODE_OFFSET else gdt.USER_CODE_OFFSET | 0b11; + // Ring switches push and pop two extra values on interrupt: user_esp and user_ss - stack.*[kernel_stack_bottom + 9] = 0; // ebx - stack.*[kernel_stack_bottom + 10] = 0; // edx - stack.*[kernel_stack_bottom + 11] = 0; // ecx - stack.*[kernel_stack_bottom + 12] = 0; // eax + stack.*[kernel_stack_bottom] = mem.virtToPhys(@ptrToInt(&paging.kernel_directory)); + stack.*[kernel_stack_bottom + 1] = data_offset; // gs + stack.*[kernel_stack_bottom + 2] = data_offset; // fs + stack.*[kernel_stack_bottom + 3] = data_offset; // es + stack.*[kernel_stack_bottom + 4] = data_offset; // ds - stack.*[kernel_stack_bottom + 13] = 0; // int_num - stack.*[kernel_stack_bottom + 14] = 0; // error_code + stack.*[kernel_stack_bottom + 5] = 0; // edi + stack.*[kernel_stack_bottom + 6] = 0; // esi + // End of the stack + stack.*[kernel_stack_bottom + 7] = @ptrToInt(&stack.*[stack.len - 1]); // ebp + stack.*[kernel_stack_bottom + 8] = 0; // esp (temp) this won't be popped by popa bc intel is dump XD - stack.*[kernel_stack_bottom + 15] = entry_point; // eip - stack.*[kernel_stack_bottom + 16] = code_offset; // cs - stack.*[kernel_stack_bottom + 17] = 0x202; // eflags + stack.*[kernel_stack_bottom + 9] = 0; // ebx + stack.*[kernel_stack_bottom + 10] = 0; // edx + stack.*[kernel_stack_bottom + 11] = 0; // ecx + stack.*[kernel_stack_bottom + 12] = 0; // eax - if (!task.kernel) { - // Put the extra values on the kernel stack needed when chaning privilege levels - stack.*[kernel_stack_bottom + 18] = @ptrToInt(&task.user_stack[task.user_stack.len - 1]); // user_esp - stack.*[kernel_stack_bottom + 19] = data_offset; // user_ss + stack.*[kernel_stack_bottom + 13] = 0; // int_num + stack.*[kernel_stack_bottom + 14] = 0; // error_code - if (!builtin.is_test) { - // Create a new page directory for the user task by mirroring the kernel directory - // We need kernel mem mapped so we don't get a page fault when entering kernel code from an interrupt - task.vmm.payload = &(try allocator.allocAdvanced(paging.Directory, paging.PAGE_SIZE_4KB, 1, .exact))[0]; - task.vmm.payload.* = paging.kernel_directory.copy(); + stack.*[kernel_stack_bottom + 15] = entry_point; // eip + stack.*[kernel_stack_bottom + 16] = code_offset; // cs + stack.*[kernel_stack_bottom + 17] = 0x202; // eflags + if (!task.kernel) { + // Put the extra values on the kernel stack needed when chaning privilege levels + stack.*[kernel_stack_bottom + 18] = @ptrToInt(&task.user_stack[task.user_stack.len - 1]); // user_esp + stack.*[kernel_stack_bottom + 19] = data_offset; // user_ss + } + task.stack_pointer = @ptrToInt(&stack.*[kernel_stack_bottom]); + } + + if (!task.kernel and !builtin.is_test) { + // Create a new page directory for the user task by mirroring the kernel directory + // We need kernel mem mapped so we don't get a page fault when entering kernel code from an interrupt + task.vmm.payload = &(try allocator.allocAdvanced(paging.Directory, paging.PAGE_SIZE_4KB, 1, .exact))[0]; + task.vmm.payload.* = paging.kernel_directory.copy(); + if (set_up_stack) { stack.*[kernel_stack_bottom] = vmm.kernel_vmm.virtToPhys(@ptrToInt(task.vmm.payload)) catch |e| { panic(@errorReturnTrace(), "Failed to get the physical address of the user task's page directory: {}\n", .{e}); }; } } - task.stack_pointer = @ptrToInt(&stack.*[kernel_stack_bottom]); } /// diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 27eee06..6ad1079 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -144,7 +144,7 @@ export fn kmain(boot_payload: arch.BootPayload) void { kmain_log.info("Creating init2\n", .{}); // Create a init2 task - var stage2_task = task.Task.create(@ptrToInt(initStage2), true, kernel_vmm, kernel_heap.allocator()) catch |e| { + var stage2_task = task.Task.create(@ptrToInt(initStage2), true, kernel_vmm, kernel_heap.allocator(), true) catch |e| { panic_root.panic(@errorReturnTrace(), "Failed to create init stage 2 task: {}\n", .{e}); }; scheduler.scheduleTask(stage2_task, kernel_heap.allocator()) catch |e| { diff --git a/src/kernel/scheduler.zig b/src/kernel/scheduler.zig index 62099fd..6179927 100644 --- a/src/kernel/scheduler.zig +++ b/src/kernel/scheduler.zig @@ -137,15 +137,13 @@ pub fn init(allocator: Allocator, mem_profile: *const mem.MemProfile) Allocator. // Init the task list for round robin tasks = TailQueue(*Task){}; - // Set up the init task to continue execution - current_task = try allocator.create(Task); + // Set up the init task to continue execution. + // The kernel stack will point to the stack section rather than the heap + current_task = try Task.create(0, true, &vmm.kernel_vmm, allocator, false); errdefer allocator.destroy(current_task); - // PID 0 - current_task.pid = 0; + const kernel_stack_size = @ptrToInt(&KERNEL_STACK_END) - @ptrToInt(&KERNEL_STACK_START); current_task.kernel_stack = @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..kernel_stack_size]; - current_task.user_stack = &[_]usize{}; - current_task.kernel = true; // ESP will be saved on next schedule // Run the runtime tests here @@ -155,7 +153,7 @@ pub fn init(allocator: Allocator, mem_profile: *const mem.MemProfile) Allocator. } // Create the idle task when there are no more tasks left - var idle_task = try Task.create(@ptrToInt(idle), true, &vmm.kernel_vmm, allocator); + var idle_task = try Task.create(@ptrToInt(idle), true, &vmm.kernel_vmm, allocator, true); errdefer idle_task.destroy(allocator); try scheduleTask(idle_task, allocator); @@ -195,21 +193,21 @@ test "pickNextTask" { tasks = TailQueue(*Task){}; // Set up a current task - var first = try allocator.create(Task); + var first = try Task.create(0, true, &vmm.kernel_vmm, allocator, false); // We use an intermediary variable to avoid a double-free. // Deferring freeing current_task will free whatever current_task points to at the end - defer allocator.destroy(first); + defer first.destroy(allocator); current_task = first; current_task.pid = 0; current_task.kernel_stack = @intToPtr([*]u32, @ptrToInt(&KERNEL_STACK_START))[0..4096]; current_task.stack_pointer = @ptrToInt(&KERNEL_STACK_START); // Create two tasks and schedule them - var test_fn1_task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator); + var test_fn1_task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator, true); defer test_fn1_task.destroy(allocator); try scheduleTask(test_fn1_task, allocator); - var test_fn2_task = try Task.create(@ptrToInt(test_fn2), true, undefined, allocator); + var test_fn2_task = try Task.create(@ptrToInt(test_fn2), true, undefined, allocator, true); defer test_fn2_task.destroy(allocator); try scheduleTask(test_fn2_task, allocator); @@ -254,7 +252,7 @@ test "createNewTask add new task" { // Init the task list tasks = TailQueue(*Task){}; - var test_fn1_task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator); + var test_fn1_task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator, true); defer test_fn1_task.destroy(allocator); try scheduleTask(test_fn1_task, allocator); @@ -309,7 +307,7 @@ fn rt_variable_preserved(allocator: Allocator) void { defer allocator.destroy(is_set); is_set.* = true; - var test_task = Task.create(@ptrToInt(task_function), true, &vmm.kernel_vmm, allocator) catch |e| panic(@errorReturnTrace(), "Failed to create task in rt_variable_preserved: {}\n", .{e}); + var test_task = Task.create(@ptrToInt(task_function), true, &vmm.kernel_vmm, allocator, true) catch |e| panic(@errorReturnTrace(), "Failed to create task in rt_variable_preserved: {}\n", .{e}); scheduleTask(test_task, allocator) catch |e| panic(@errorReturnTrace(), "Failed to schedule a task in rt_variable_preserved: {}\n", .{e}); // TODO: Need to add the ability to remove tasks diff --git a/src/kernel/task.zig b/src/kernel/task.zig index 426e0d0..9cbfc2c 100644 --- a/src/kernel/task.zig +++ b/src/kernel/task.zig @@ -25,12 +25,7 @@ pub const EntryPoint = usize; const PidBitmap = bitmap.Bitmap(1024, usize); /// The list of PIDs that have been allocated. -var all_pids: PidBitmap = init: { - var pids = PidBitmap.init(1024, null) catch unreachable; - // Reserve PID 0 for the init task - _ = pids.setFirstFree() orelse unreachable; - break :init pids; -}; +var all_pids = PidBitmap.init(1024, null) catch unreachable; /// The default stack size of a task. Currently this is set to a page size. pub const STACK_SIZE: u32 = arch.MEMORY_BLOCK_SIZE / @sizeOf(u32); @@ -75,15 +70,15 @@ pub const Task = struct { /// OutOfMemory - If there is no more memory to allocate. Any memory or PID allocated will /// be freed on return. /// - pub fn create(entry_point: EntryPoint, kernel: bool, task_vmm: *vmm.VirtualMemoryManager(arch.VmmPayload), allocator: Allocator) Allocator.Error!*Task { + pub fn create(entry_point: EntryPoint, kernel: bool, task_vmm: *vmm.VirtualMemoryManager(arch.VmmPayload), allocator: Allocator, alloc_kernel_stack: bool) Allocator.Error!*Task { var task = try allocator.create(Task); errdefer allocator.destroy(task); const pid = allocatePid(); errdefer freePid(pid) catch |e| panic(@errorReturnTrace(), "Failed to free task PID in errdefer ({}): {}\n", .{ pid, e }); - var k_stack = try allocator.alloc(usize, STACK_SIZE); - errdefer allocator.free(k_stack); + var k_stack = if (alloc_kernel_stack) try allocator.alloc(usize, STACK_SIZE) else &[_]usize{}; + errdefer if (alloc_kernel_stack) allocator.free(k_stack); var u_stack = if (kernel) &[_]usize{} else try allocator.alloc(usize, STACK_SIZE); errdefer if (!kernel) allocator.free(u_stack); @@ -92,18 +87,18 @@ pub const Task = struct { .pid = pid, .kernel_stack = k_stack, .user_stack = u_stack, - .stack_pointer = @ptrToInt(&k_stack[STACK_SIZE - 1]), + .stack_pointer = if (!alloc_kernel_stack) 0 else @ptrToInt(&k_stack[STACK_SIZE - 1]), .kernel = kernel, .vmm = task_vmm, }; - try arch.initTask(task, entry_point, allocator); + try arch.initTask(task, entry_point, allocator, alloc_kernel_stack); return task; } pub fn createFromElf(program_elf: elf.Elf, kernel: bool, task_vmm: *vmm.VirtualMemoryManager(arch.VmmPayload), allocator: Allocator) (bitmap.BitmapError || vmm.VmmError || Allocator.Error)!*Task { - const task = try create(program_elf.header.entry_address, kernel, task_vmm, allocator); + const task = try create(program_elf.header.entry_address, kernel, task_vmm, allocator, true); errdefer task.destroy(allocator); // Iterate over sections @@ -145,7 +140,7 @@ pub const Task = struct { freePid(self.pid) catch |e| panic(@errorReturnTrace(), "Failed to free task's PID ({}): {}\n", .{ self.pid, e }); // We need to check that the the stack has been allocated as task 0 (init) won't have a // stack allocated as this in the linker script - if (@ptrToInt(self.kernel_stack.ptr) != @ptrToInt(&KERNEL_STACK_START)) { + if (@ptrToInt(self.kernel_stack.ptr) != @ptrToInt(&KERNEL_STACK_START) and self.kernel_stack.len > 0) { allocator.free(self.kernel_stack); } if (!self.kernel) { @@ -192,8 +187,8 @@ test "create out of memory for task" { // Set the global allocator var fa = FailingAllocator.init(testing_allocator, 0); - try expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), true, undefined, fa.allocator())); - try expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), false, undefined, fa.allocator())); + try expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), true, undefined, fa.allocator(), true)); + try expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), false, undefined, fa.allocator(), true)); // Make sure any memory allocated is freed try expectEqual(fa.allocated_bytes, fa.freed_bytes); @@ -208,8 +203,8 @@ test "create out of memory for stack" { // Set the global allocator var fa = FailingAllocator.init(testing_allocator, 1); - try expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), true, undefined, fa.allocator())); - try expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), false, undefined, fa.allocator())); + try expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), true, undefined, fa.allocator(), true)); + try expectError(error.OutOfMemory, Task.create(@ptrToInt(test_fn1), false, undefined, fa.allocator(), true)); // Make sure any memory allocated is freed try expectEqual(fa.allocated_bytes, fa.freed_bytes); @@ -221,7 +216,7 @@ test "create out of memory for stack" { } test "create expected setup" { - var task = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator); + var task = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator, true); defer task.destroy(std.testing.allocator); // Will allocate the first PID 0 @@ -229,7 +224,7 @@ test "create expected setup" { try expectEqual(task.kernel_stack.len, STACK_SIZE); try expectEqual(task.user_stack.len, 0); - var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, std.testing.allocator); + var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, std.testing.allocator, true); defer user_task.destroy(std.testing.allocator); try expectEqual(user_task.pid, 1); try expectEqual(user_task.user_stack.len, STACK_SIZE); @@ -241,8 +236,8 @@ test "destroy cleans up" { // So if any alloc were not freed, this will fail the test var allocator = std.testing.allocator; - var task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator); - var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, allocator); + var task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator, true); + var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, allocator, true); task.destroy(allocator); user_task.destroy(allocator); @@ -254,8 +249,8 @@ test "destroy cleans up" { } test "Multiple create" { - var task1 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator); - var task2 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator); + var task1 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator, true); + var task2 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator, true); try expectEqual(task1.pid, 0); try expectEqual(task2.pid, 1); @@ -271,7 +266,7 @@ test "Multiple create" { if (i > 0) try expectEqual(bmp, 0); } - var task3 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator); + var task3 = try Task.create(@ptrToInt(test_fn1), true, undefined, std.testing.allocator, true); try expectEqual(task3.pid, 0); try expectEqual(all_pids.bitmaps[0], 3); @@ -282,7 +277,7 @@ test "Multiple create" { task2.destroy(std.testing.allocator); task3.destroy(std.testing.allocator); - var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, std.testing.allocator); + var user_task = try Task.create(@ptrToInt(test_fn1), false, undefined, std.testing.allocator, true); try expectEqual(user_task.pid, 0); try expectEqual(all_pids.bitmaps[0], 1); @@ -378,3 +373,11 @@ test "createFromElf clean-up" { the_elf.section_headers[1].flags |= elf.SECTION_ALLOCATABLE; try std.testing.expectError(error.AlreadyAllocated, Task.createFromElf(the_elf, true, &the_vmm, std.testing.allocator)); } + +test "create doesn't allocate kernel stack" { + var allocator = std.testing.allocator; + const task = try Task.create(@ptrToInt(test_fn1), true, undefined, allocator, false); + defer task.destroy(allocator); + try std.testing.expectEqualSlices(usize, task.kernel_stack, &[_]usize{}); + try std.testing.expectEqual(task.stack_pointer, 0); +} diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index a08c9ef..ef44557 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -213,11 +213,12 @@ pub fn initMem(payload: BootPayload) Allocator.Error!mem.MemProfile { }; } -pub fn initTask(t: *Task, entry_point: usize, allocator: Allocator) Allocator.Error!void { +pub fn initTask(t: *Task, entry_point: usize, allocator: Allocator, set_up_stack: bool) Allocator.Error!void { // Suppress unused variable warnings _ = t; _ = entry_point; _ = allocator; + _ = set_up_stack; } pub fn initKeyboard(allocator: Allocator) Allocator.Error!?*Keyboard { From a023fadc24f2ecc82695cd8840056680477eef10 Mon Sep 17 00:00:00 2001 From: Sam Tebbs Date: Tue, 25 Oct 2022 16:27:50 +0100 Subject: [PATCH 3/8] Revert "Split the project into packages." This reverts commit ab93a33bbd1b8f030debdc294b7e0cdd6a786e0f. --- build.zig | 19 ------------------ src/kernel/arch.zig | 13 ++++++++++++ src/kernel/arch/x86/arch.zig | 21 ++++++++++---------- src/kernel/arch/x86/cmos.zig | 2 +- src/kernel/arch/x86/gdt.zig | 2 +- src/kernel/arch/x86/idt.zig | 2 +- src/kernel/arch/x86/irq.zig | 6 +++--- src/kernel/arch/x86/isr.zig | 4 ++-- src/kernel/arch/x86/keyboard.zig | 2 +- src/kernel/arch/x86/paging.zig | 2 +- src/kernel/arch/x86/pci.zig | 2 +- src/kernel/arch/x86/pic.zig | 2 +- src/kernel/arch/x86/pit.zig | 2 +- src/kernel/arch/x86/rtc.zig | 4 ++-- src/kernel/arch/x86/syscalls.zig | 7 +++---- src/kernel/arch/x86/tty.zig | 2 +- src/kernel/arch/x86/vga.zig | 2 +- src/kernel/filesystem/fat32.zig | 4 +--- src/kernel/keyboard.zig | 4 +--- src/kernel/kmain.zig | 34 ++++++++++++++++---------------- src/kernel/panic.zig | 4 +--- src/kernel/pluto.zig | 14 ------------- src/kernel/pmm.zig | 2 +- src/kernel/scheduler.zig | 2 +- src/kernel/serial.zig | 4 +--- src/kernel/syscalls.zig | 4 +--- src/kernel/task.zig | 2 +- src/kernel/tty.zig | 4 +--- src/kernel/vmm.zig | 6 +++--- test/mock/kernel/arch_mock.zig | 25 +++++++++-------------- test/mock/kernel/gdt_mock.zig | 4 +++- test/mock/kernel/idt_mock.zig | 2 +- 32 files changed, 85 insertions(+), 124 deletions(-) create mode 100644 src/kernel/arch.zig delete mode 100644 src/kernel/pluto.zig diff --git a/build.zig b/build.zig index b743dd2..d63a392 100644 --- a/build.zig +++ b/build.zig @@ -13,7 +13,6 @@ const File = fs.File; const Mode = std.builtin.Mode; const TestMode = rt.TestMode; const ArrayList = std.ArrayList; -const Pkg = std.build.Pkg; const Fat32 = @import("mkfat32.zig").Fat32; const x86_i686 = CrossTarget{ @@ -38,10 +37,7 @@ pub fn build(b: *Builder) !void { b.default_step.dependOn(&fmt_step.step); const main_src = "src/kernel/kmain.zig"; - const pluto_src = "src/kernel/pluto.zig"; const arch_root = "src/kernel/arch"; - const arch_mock_src = "test/mock/kernel/arch_mock.zig"; - const arch_src = try fs.path.join(b.allocator, &[_][]const u8{ arch_root, arch, "arch.zig" }); const linker_script_path = try fs.path.join(b.allocator, &[_][]const u8{ arch_root, arch, "link.ld" }); const output_iso = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "pluto.iso" }); const iso_dir_path = try fs.path.join(b.allocator, &[_][]const u8{ b.install_path, "iso" }); @@ -72,16 +68,6 @@ pub fn build(b: *Builder) !void { exec.setLinkerScriptPath(std.build.FileSource{ .path = linker_script_path }); exec.setTarget(target); - var pluto_pkg = Pkg{ .name = "pluto", .path = .{ .path = pluto_src } }; - var arch_pkg = Pkg{ .name = "arch", .path = .{ .path = arch_src } }; - var arch_mock_pkg = Pkg{ .name = "arch_mock", .path = .{ .path = arch_mock_src } }; - arch_mock_pkg.dependencies = &[_]Pkg{ arch_pkg, pluto_pkg, exec_options.getPackage("build_options") }; - pluto_pkg.dependencies = &[_]Pkg{ arch_pkg, arch_mock_pkg, exec_options.getPackage("build_options") }; - arch_pkg.dependencies = &[_]Pkg{ pluto_pkg, arch_mock_pkg, exec_options.getPackage("build_options") }; - exec.addPackage(pluto_pkg); - exec.addPackage(arch_pkg); - exec.addPackage(arch_mock_pkg); - const make_iso = switch (target.getCpuArch()) { .i386 => b.addSystemCommand(&[_][]const u8{ "./makeiso.sh", boot_path, modules_path, iso_dir_path, exec_output_path, ramdisk_path, output_iso }), else => unreachable, @@ -127,9 +113,6 @@ pub fn build(b: *Builder) !void { unit_tests.addOptions("build_options", unit_test_options); unit_test_options.addOption(TestMode, "test_mode", test_mode); unit_tests.setTarget(.{ .cpu_arch = target.cpu_arch }); - unit_tests.addPackage(pluto_pkg); - unit_tests.addPackage(arch_pkg); - unit_tests.addPackage(arch_mock_pkg); if (builtin.os.tag != .windows) { b.enable_qemu = true; @@ -140,8 +123,6 @@ pub fn build(b: *Builder) !void { mock_gen.setMainPkgPath("."); const mock_gen_run = mock_gen.run(); unit_tests.step.dependOn(&mock_gen_run.step); - exec.step.dependOn(&mock_gen_run.step); - b.default_step.dependOn(&mock_gen_run.step); // Create test FAT32 image const test_fat32_img_step = Fat32BuilderStep.create(b, .{}, test_fat32_image_path); diff --git a/src/kernel/arch.zig b/src/kernel/arch.zig new file mode 100644 index 0000000..e3e812e --- /dev/null +++ b/src/kernel/arch.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const build_options = @import("build_options"); + +pub const internals = if (is_test) @import("../../test/mock/kernel/arch_mock.zig") else switch (builtin.cpu.arch) { + .i386 => @import("arch/x86/arch.zig"), + else => unreachable, +}; + +test "" { + _ = @import("arch/x86/arch.zig"); +} diff --git a/src/kernel/arch/x86/arch.zig b/src/kernel/arch/x86/arch.zig index 187bc59..fa7c75b 100644 --- a/src/kernel/arch/x86/arch.zig +++ b/src/kernel/arch/x86/arch.zig @@ -3,11 +3,11 @@ const Allocator = std.mem.Allocator; const log = std.log.scoped(.x86_arch); const builtin = @import("builtin"); const cmos = @import("cmos.zig"); -pub const gdt = @import("gdt.zig"); -pub const idt = @import("idt.zig"); +const gdt = @import("gdt.zig"); +const idt = @import("idt.zig"); const irq = @import("irq.zig"); const isr = @import("isr.zig"); -pub const paging = @import("paging.zig"); +const paging = @import("paging.zig"); const pic = @import("pic.zig"); const pci = @import("pci.zig"); const pit = @import("pit.zig"); @@ -16,16 +16,15 @@ const serial = @import("serial.zig"); const syscalls = @import("syscalls.zig"); const tty = @import("tty.zig"); const vga = @import("vga.zig"); +const mem = @import("../../mem.zig"); const multiboot = @import("multiboot.zig"); +const vmm = @import("../../vmm.zig"); const keyboard = @import("keyboard.zig"); -const pluto = @import("pluto"); -const mem = pluto.mem; -const vmm = pluto.vmm; -const Keyboard = pluto.keyboard.Keyboard; -const Serial = pluto.serial.Serial; -const panic = pluto.panic_root.panic; -const TTY = pluto.tty.TTY; -const Task = pluto.task.Task; +const Serial = @import("../../serial.zig").Serial; +const panic = @import("../../panic.zig").panic; +const TTY = @import("../../tty.zig").TTY; +const Keyboard = @import("../../keyboard.zig").Keyboard; +const Task = @import("../../task.zig").Task; const MemProfile = mem.MemProfile; /// The type of a device. diff --git a/src/kernel/arch/x86/cmos.zig b/src/kernel/arch/x86/cmos.zig index a8d0370..f95b1db 100644 --- a/src/kernel/arch/x86/cmos.zig +++ b/src/kernel/arch/x86/cmos.zig @@ -3,7 +3,7 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const expectEqual = std.testing.expectEqual; const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); /// The current year to be used for calculating the 4 digit year, as the CMOS return the last two /// digits of the year. diff --git a/src/kernel/arch/x86/gdt.zig b/src/kernel/arch/x86/gdt.zig index fd947ae..8483cb6 100644 --- a/src/kernel/arch/x86/gdt.zig +++ b/src/kernel/arch/x86/gdt.zig @@ -6,7 +6,7 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const panic = @import("../../panic.zig").panic; const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); /// The access bits for a GDT entry. const AccessBits = packed struct { diff --git a/src/kernel/arch/x86/idt.zig b/src/kernel/arch/x86/idt.zig index 048bfa5..b4567b7 100644 --- a/src/kernel/arch/x86/idt.zig +++ b/src/kernel/arch/x86/idt.zig @@ -8,7 +8,7 @@ const is_test = builtin.is_test; const panic = @import("../../panic.zig").panic; const build_options = @import("build_options"); const gdt = if (is_test) @import("../../../../test/mock/kernel/gdt_mock.zig") else @import("gdt.zig"); -const arch = if (builtin.is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); /// The structure that contains all the information that each IDT entry needs. pub const IdtEntry = packed struct { diff --git a/src/kernel/arch/x86/irq.zig b/src/kernel/arch/x86/irq.zig index 4e6a030..94e2152 100644 --- a/src/kernel/arch/x86/irq.zig +++ b/src/kernel/arch/x86/irq.zig @@ -7,9 +7,9 @@ const expectError = std.testing.expectError; const log = std.log.scoped(.x86_irq); const build_options = @import("build_options"); const panic = @import("../../panic.zig").panic; -const idt = if (is_test) @import("arch_mock").idt_mock else @import("idt.zig"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); -const pic = if (is_test) @import("arch_mock").pic_mock else @import("pic.zig"); +const idt = if (is_test) @import("../../../../test/mock/kernel/idt_mock.zig") else @import("idt.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); +const pic = if (is_test) @import("../../../../test/mock/kernel/pic_mock.zig") else @import("pic.zig"); const interrupts = @import("interrupts.zig"); /// The error set for the IRQ. This will be from installing a IRQ handler. diff --git a/src/kernel/arch/x86/isr.zig b/src/kernel/arch/x86/isr.zig index 3a44a63..eabb1a1 100644 --- a/src/kernel/arch/x86/isr.zig +++ b/src/kernel/arch/x86/isr.zig @@ -8,8 +8,8 @@ const log = std.log.scoped(.x86_isr); const build_options = @import("build_options"); const syscalls = @import("syscalls.zig"); const panic = @import("../../panic.zig").panic; -const idt = if (is_test) @import("arch_mock").idt_mock else @import("idt.zig"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const idt = if (is_test) @import("../../../../test/mock/kernel/idt_mock.zig") else @import("idt.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); const interrupts = @import("interrupts.zig"); /// The error set for the ISR. This will be from installing a ISR handler. diff --git a/src/kernel/arch/x86/keyboard.zig b/src/kernel/arch/x86/keyboard.zig index dc6f263..567573e 100644 --- a/src/kernel/arch/x86/keyboard.zig +++ b/src/kernel/arch/x86/keyboard.zig @@ -6,7 +6,7 @@ const testing = std.testing; const log = std.log.scoped(.x86_keyboard); const irq = @import("irq.zig"); const pic = @import("pic.zig"); -const arch = if (builtin.is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); const panic = @import("../../panic.zig").panic; const kb = @import("../../keyboard.zig"); const Keyboard = kb.Keyboard; diff --git a/src/kernel/arch/x86/paging.zig b/src/kernel/arch/x86/paging.zig index 1de58d2..f87abf0 100644 --- a/src/kernel/arch/x86/paging.zig +++ b/src/kernel/arch/x86/paging.zig @@ -7,7 +7,7 @@ const builtin = @import("builtin"); const is_test = builtin.is_test; const panic = @import("../../panic.zig").panic; const build_options = @import("build_options"); -const arch = if (builtin.is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (builtin.is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); const isr = @import("isr.zig"); const MemProfile = @import("../../mem.zig").MemProfile; const tty = @import("../../tty.zig"); diff --git a/src/kernel/arch/x86/pci.zig b/src/kernel/arch/x86/pci.zig index d0e6654..769b996 100644 --- a/src/kernel/arch/x86/pci.zig +++ b/src/kernel/arch/x86/pci.zig @@ -6,7 +6,7 @@ const build_options = @import("build_options"); const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; const log = std.log.scoped(.pci); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); /// The port address for selecting a 32bit register in the PCI configuration space. const CONFIG_ADDRESS: u16 = 0x0CF8; diff --git a/src/kernel/arch/x86/pic.zig b/src/kernel/arch/x86/pic.zig index 85cb244..f37ad46 100644 --- a/src/kernel/arch/x86/pic.zig +++ b/src/kernel/arch/x86/pic.zig @@ -5,7 +5,7 @@ const log = std.log.scoped(.x86_pic); const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); const panic = @import("../../panic.zig").panic; // ---------- diff --git a/src/kernel/arch/x86/pit.zig b/src/kernel/arch/x86/pit.zig index 946e870..6f3e961 100644 --- a/src/kernel/arch/x86/pit.zig +++ b/src/kernel/arch/x86/pit.zig @@ -7,7 +7,7 @@ const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; const log = std.log.scoped(.x86_pit); const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); const panic = @import("../../panic.zig").panic; const irq = @import("irq.zig"); const pic = @import("pic.zig"); diff --git a/src/kernel/arch/x86/rtc.zig b/src/kernel/arch/x86/rtc.zig index 1091ead..7221ca6 100644 --- a/src/kernel/arch/x86/rtc.zig +++ b/src/kernel/arch/x86/rtc.zig @@ -6,11 +6,11 @@ const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; const log = std.log.scoped(.x86_rtc); const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); const pic = @import("pic.zig"); const pit = @import("pit.zig"); const irq = @import("irq.zig"); -const cmos = if (is_test) @import("arch_mock").cmos_mock else @import("cmos.zig"); +const cmos = if (is_test) @import("../../../../test/mock/kernel/cmos_mock.zig") else @import("cmos.zig"); const panic = @import("../../panic.zig").panic; const scheduler = @import("../../scheduler.zig"); diff --git a/src/kernel/arch/x86/syscalls.zig b/src/kernel/arch/x86/syscalls.zig index 9cdbaca..01ee414 100644 --- a/src/kernel/arch/x86/syscalls.zig +++ b/src/kernel/arch/x86/syscalls.zig @@ -3,13 +3,12 @@ const log = std.log.scoped(.x86_syscalls); const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); const testing = std.testing; const expect = std.testing.expect; const isr = @import("isr.zig"); -const pluto = @import("pluto"); -const panic = pluto.panic_root.panic; -const syscalls = pluto.syscalls; +const panic = @import("../../panic.zig").panic; +const syscalls = @import("../../syscalls.zig"); /// The isr number associated with syscalls pub const INTERRUPT: u16 = 0x80; diff --git a/src/kernel/arch/x86/tty.zig b/src/kernel/arch/x86/tty.zig index a3144fc..0c4db91 100644 --- a/src/kernel/arch/x86/tty.zig +++ b/src/kernel/arch/x86/tty.zig @@ -7,7 +7,7 @@ const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; const log = std.log.scoped(.x86_tty); const build_options = @import("build_options"); -const vga = if (is_test) @import("arch_mock").vga_mock else @import("vga.zig"); +const vga = if (is_test) @import("../../../../test/mock/kernel/vga_mock.zig") else @import("vga.zig"); const panic = @import("../../panic.zig").panic; /// The error set for if there is an error whiles printing. diff --git a/src/kernel/arch/x86/vga.zig b/src/kernel/arch/x86/vga.zig index d448815..d016276 100644 --- a/src/kernel/arch/x86/vga.zig +++ b/src/kernel/arch/x86/vga.zig @@ -4,7 +4,7 @@ const is_test = builtin.is_test; const expectEqual = std.testing.expectEqual; const log = std.log.scoped(.x86_vga); const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch.zig"); +const arch = if (is_test) @import("../../../../test/mock/kernel/arch_mock.zig") else @import("arch.zig"); const panic = @import("../../panic.zig").panic; /// The port address for the VGA register selection. diff --git a/src/kernel/filesystem/fat32.zig b/src/kernel/filesystem/fat32.zig index cf554be..aba3e5f 100644 --- a/src/kernel/filesystem/fat32.zig +++ b/src/kernel/filesystem/fat32.zig @@ -8,9 +8,7 @@ const log = std.log.scoped(.fat32); const AutoHashMap = std.AutoHashMap; const Allocator = std.mem.Allocator; const ArrayList = std.ArrayList; -const builtins = @import("builtin"); -const is_test = builtins.is_test; -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("../arch.zig").internals; const vfs = @import("vfs.zig"); const mem = @import("../mem.zig"); const CodePage = @import("../code_page/code_page.zig").CodePage; diff --git a/src/kernel/keyboard.zig b/src/kernel/keyboard.zig index 463467f..8e5a5fe 100644 --- a/src/kernel/keyboard.zig +++ b/src/kernel/keyboard.zig @@ -2,9 +2,7 @@ const std = @import("std"); const testing = std.testing; const ArrayList = std.ArrayList; const Allocator = std.mem.Allocator; -const builtin = @import("builtin"); -const is_test = builtin.is_test; -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; /// An arbitrary number of keys to remember before dropping any more that arrive. Is a power of two so we can use nice overflowing addition pub const QUEUE_SIZE = 32; diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 6ad1079..8d2ab82 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -1,23 +1,23 @@ const std = @import("std"); -const builtin = @import("builtin"); -const arch = if (is_test) @import("arch_mock") else @import("arch"); -const pluto = @import("pluto"); -const build_options = @import("build_options"); -const is_test = builtin.is_test; const kmain_log = std.log.scoped(.kmain); +const builtin = @import("builtin"); +const is_test = builtin.is_test; +const build_options = @import("build_options"); +const arch = @import("arch.zig").internals; +const tty = @import("tty.zig"); +const log_root = @import("log.zig"); +const pmm = @import("pmm.zig"); +const serial = @import("serial.zig"); +const vmm = @import("vmm.zig"); +const mem = @import("mem.zig"); +const panic_root = @import("panic.zig"); +const task = @import("task.zig"); +const heap = @import("heap.zig"); +const scheduler = @import("scheduler.zig"); +const vfs = @import("filesystem/vfs.zig"); +const initrd = @import("filesystem/initrd.zig"); +const keyboard = @import("keyboard.zig"); const Allocator = std.mem.Allocator; -const tty = pluto.tty; -const panic_root = pluto.panic_root; -const log_root = pluto.log_root; -const heap = pluto.heap; -const serial = pluto.serial; -const pmm = pluto.pmm; -const vmm = pluto.vmm; -const keyboard = pluto.keyboard; -const initrd = pluto.initrd; -const vfs = pluto.vfs; -const scheduler = pluto.scheduler; -const task = pluto.task; comptime { if (!is_test) { diff --git a/src/kernel/panic.zig b/src/kernel/panic.zig index 31814b0..bfc2a28 100644 --- a/src/kernel/panic.zig +++ b/src/kernel/panic.zig @@ -1,8 +1,6 @@ const std = @import("std"); const builtin = std.builtin; -const builtins = @import("builtin"); -const is_test = builtins.is_test; -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; const mem = @import("mem.zig"); const build_options = @import("build_options"); const ArrayList = std.ArrayList; diff --git a/src/kernel/pluto.zig b/src/kernel/pluto.zig deleted file mode 100644 index 91e38f0..0000000 --- a/src/kernel/pluto.zig +++ /dev/null @@ -1,14 +0,0 @@ -pub const tty = @import("tty.zig"); -pub const log_root = @import("log.zig"); -pub const pmm = @import("pmm.zig"); -pub const serial = @import("serial.zig"); -pub const vmm = @import("vmm.zig"); -pub const mem = @import("mem.zig"); -pub const panic_root = @import("panic.zig"); -pub const task = @import("task.zig"); -pub const heap = @import("heap.zig"); -pub const scheduler = @import("scheduler.zig"); -pub const vfs = @import("filesystem/vfs.zig"); -pub const initrd = @import("filesystem/initrd.zig"); -pub const keyboard = @import("keyboard.zig"); -pub const syscalls = @import("syscalls.zig"); diff --git a/src/kernel/pmm.zig b/src/kernel/pmm.zig index 0299a9f..395269d 100644 --- a/src/kernel/pmm.zig +++ b/src/kernel/pmm.zig @@ -2,7 +2,7 @@ const is_test = @import("builtin").is_test; const std = @import("std"); const log = std.log.scoped(.pmm); const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; const MemProfile = @import("mem.zig").MemProfile; const testing = std.testing; const panic = @import("panic.zig").panic; diff --git a/src/kernel/scheduler.zig b/src/kernel/scheduler.zig index 6179927..5dc68a1 100644 --- a/src/kernel/scheduler.zig +++ b/src/kernel/scheduler.zig @@ -6,7 +6,7 @@ const log = std.log.scoped(.scheduler); const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; const panic = @import("panic.zig").panic; const task = @import("task.zig"); const vmm = @import("vmm.zig"); diff --git a/src/kernel/serial.zig b/src/kernel/serial.zig index 1b56f87..2ee6ea7 100644 --- a/src/kernel/serial.zig +++ b/src/kernel/serial.zig @@ -1,6 +1,4 @@ -const builtin = @import("builtin"); -const is_test = builtin.is_test; -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; const build_options = @import("build_options"); pub const Serial = struct { diff --git a/src/kernel/syscalls.zig b/src/kernel/syscalls.zig index 02b5791..bb25793 100644 --- a/src/kernel/syscalls.zig +++ b/src/kernel/syscalls.zig @@ -2,9 +2,7 @@ const std = @import("std"); const scheduler = @import("scheduler.zig"); const panic = @import("panic.zig").panic; const log = std.log.scoped(.syscalls); -const builtin = @import("builtin"); -const is_test = builtin.is_test; -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; /// A compilation of all errors that syscall handlers could return. pub const Error = error{OutOfMemory}; diff --git a/src/kernel/task.zig b/src/kernel/task.zig index 9cbfc2c..e60fba9 100644 --- a/src/kernel/task.zig +++ b/src/kernel/task.zig @@ -4,7 +4,7 @@ const expectError = std.testing.expectError; const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; const panic = @import("panic.zig").panic; const vmm = @import("vmm.zig"); const pmm = @import("pmm.zig"); diff --git a/src/kernel/tty.zig b/src/kernel/tty.zig index 0f2dc34..9a4cf54 100644 --- a/src/kernel/tty.zig +++ b/src/kernel/tty.zig @@ -3,9 +3,7 @@ const fmt = std.fmt; const Allocator = std.mem.Allocator; const log = std.log.scoped(.tty); const build_options = @import("build_options"); -const builtin = @import("builtin"); -const is_test = builtin.is_test; -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; const panic = @import("panic.zig").panic; /// The OutStream for the format function diff --git a/src/kernel/vmm.zig b/src/kernel/vmm.zig index 8bfda75..453fb0f 100644 --- a/src/kernel/vmm.zig +++ b/src/kernel/vmm.zig @@ -1,5 +1,7 @@ const build_options = @import("build_options"); const mock_path = build_options.mock_path; +const builtin = std.builtin; +const is_test = builtin.is_test; const std = @import("std"); const log = std.log.scoped(.vmm); const bitmap = @import("bitmap.zig"); @@ -7,9 +9,7 @@ const pmm = @import("pmm.zig"); const mem = @import("mem.zig"); const tty = @import("tty.zig"); const panic = @import("panic.zig").panic; -const builtins = @import("builtin"); -const is_test = builtins.is_test; -const arch = if (is_test) @import("arch_mock") else @import("arch"); +const arch = @import("arch.zig").internals; const Allocator = std.mem.Allocator; const assert = std.debug.assert; diff --git a/test/mock/kernel/arch_mock.zig b/test/mock/kernel/arch_mock.zig index ef44557..e959ec2 100644 --- a/test/mock/kernel/arch_mock.zig +++ b/test/mock/kernel/arch_mock.zig @@ -1,25 +1,18 @@ const std = @import("std"); const builtin = @import("builtin"); -const pluto = @import("pluto"); -const arch = @import("arch"); +const Allocator = std.mem.Allocator; +const mem = @import("../../../src/kernel/mem.zig"); +const MemProfile = mem.MemProfile; const pci = @import("pci_mock.zig"); const gdt = @import("gdt_mock.zig"); const idt = @import("idt_mock.zig"); +const vmm = @import("../../../src/kernel/vmm.zig"); const paging = @import("paging_mock.zig"); -pub const cmos_mock = @import("cmos_mock.zig"); -pub const vga_mock = @import("vga_mock.zig"); -pub const pic_mock = @import("pic_mock.zig"); -pub const idt_mock = @import("idt_mock.zig"); -pub const pci_mock = @import("pci_mock.zig"); -const x86_paging = arch.paging; -const vmm = pluto.vmm; -const mem = pluto.mem; -const Serial = pluto.serial.Serial; -const TTY = pluto.tty.TTY; -const Keyboard = pluto.keyboard.Keyboard; -const task = pluto.task; -const Allocator = std.mem.Allocator; -const MemProfile = mem.MemProfile; +const Serial = @import("../../../src/kernel/serial.zig").Serial; +const TTY = @import("../../../src/kernel/tty.zig").TTY; +const Keyboard = @import("../../../src/kernel/keyboard.zig").Keyboard; +const task = @import("../../../src/kernel/task.zig"); +const x86_paging = @import("../../../src/kernel/arch/x86/paging.zig"); pub const Device = pci.PciDeviceInfo; pub const DateTime = struct { diff --git a/test/mock/kernel/gdt_mock.zig b/test/mock/kernel/gdt_mock.zig index 414aa3b..2c65f8b 100644 --- a/test/mock/kernel/gdt_mock.zig +++ b/test/mock/kernel/gdt_mock.zig @@ -1,4 +1,6 @@ -const src_gdt = @import("arch").gdt; +// Can't do: TODO: https://github.com/SamTebbs33/pluto/issues/77 +//const src_gdt = @import("arch").gdt; +const src_gdt = @import("../../../src/kernel/arch/x86/gdt.zig"); const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; diff --git a/test/mock/kernel/idt_mock.zig b/test/mock/kernel/idt_mock.zig index b8abe14..ed50fd2 100644 --- a/test/mock/kernel/idt_mock.zig +++ b/test/mock/kernel/idt_mock.zig @@ -1,4 +1,4 @@ -const src_idt = @import("arch").idt; +const src_idt = @import("../../../src/kernel/arch/x86/idt.zig"); const mock_framework = @import("mock_framework.zig"); pub const initTest = mock_framework.initTest; From 426eb13d46cc629d641981fa14a036869bc124ee Mon Sep 17 00:00:00 2001 From: Sam Tebbs Date: Sun, 5 Jun 2022 23:30:32 +0100 Subject: [PATCH 4/8] Add VFS syscalls --- src/kernel/filesystem/vfs.zig | 27 +- src/kernel/kmain.zig | 2 + src/kernel/scheduler.zig | 2 +- src/kernel/syscalls.zig | 664 +++++++++++++++++++++++++++++++++- src/kernel/task.zig | 184 ++++++++++ 5 files changed, 855 insertions(+), 24 deletions(-) diff --git a/src/kernel/filesystem/vfs.zig b/src/kernel/filesystem/vfs.zig index 8a906b0..fce16a6 100644 --- a/src/kernel/filesystem/vfs.zig +++ b/src/kernel/filesystem/vfs.zig @@ -211,13 +211,8 @@ pub const DirNode = struct { /// See the documentation for FileSystem.Open pub fn open(self: *const DirNode, name: []const u8, flags: OpenFlags, args: OpenArgs) (Allocator.Error || Error)!*Node { - var fs = self.fs; - var node = self; - if (self.mount) |mnt| { - fs = mnt.fs; - node = mnt; - } - return fs.open(fs, node, name, flags, args); + var node = self.mount orelse self; + return node.fs.open(node.fs, node, name, flags, args); } /// See the documentation for FileSystem.Close @@ -426,6 +421,20 @@ pub fn open(path: []const u8, follow_symlinks: bool, flags: OpenFlags, args: Ope return try traversePath(path, follow_symlinks, flags, args); } +/// +/// Close a node. +/// +/// Arguments: +/// IN node: Node - The node to close +/// +pub fn close(node: Node) void { + switch (node) { + .Dir => |d| d.close(), + .File => |f| f.close(), + .Symlink => |s| s.close(), + } +} + /// /// Open a file at a path. /// @@ -592,7 +601,7 @@ const TestFS = struct { const Self = @This(); - fn deinit(self: *@This()) void { + pub fn deinit(self: *@This()) void { self.tree.deinit(self.allocator); self.allocator.destroy(self.fs); } @@ -718,7 +727,7 @@ const TestFS = struct { } }; -fn testInitFs(allocator: Allocator) !*TestFS { +pub fn testInitFs(allocator: Allocator) !*TestFS { const fs = try allocator.create(FileSystem); var testfs = try allocator.create(TestFS); var root_node = try allocator.create(Node); diff --git a/src/kernel/kmain.zig b/src/kernel/kmain.zig index 8d2ab82..d6cd618 100644 --- a/src/kernel/kmain.zig +++ b/src/kernel/kmain.zig @@ -17,6 +17,7 @@ const scheduler = @import("scheduler.zig"); const vfs = @import("filesystem/vfs.zig"); const initrd = @import("filesystem/initrd.zig"); const keyboard = @import("keyboard.zig"); +const syscalls = @import("syscalls.zig"); const Allocator = std.mem.Allocator; comptime { @@ -96,6 +97,7 @@ export fn kmain(boot_payload: arch.BootPayload) void { panic_root.panic(@errorReturnTrace(), "Failed to initialise kernel heap: {}\n", .{e}); }; + syscalls.init(kernel_heap.allocator()); tty.init(kernel_heap.allocator(), boot_payload); var arch_kb = keyboard.init(fixed_allocator.allocator()) catch |e| { panic_root.panic(@errorReturnTrace(), "Failed to inititalise keyboard: {}\n", .{e}); diff --git a/src/kernel/scheduler.zig b/src/kernel/scheduler.zig index 5dc68a1..852823d 100644 --- a/src/kernel/scheduler.zig +++ b/src/kernel/scheduler.zig @@ -27,7 +27,7 @@ extern var KERNEL_STACK_START: []u32; extern var KERNEL_STACK_END: []u32; /// The current task running -var current_task: *Task = undefined; +pub var current_task: *Task = undefined; /// Array list of all runnable tasks var tasks: TailQueue(*Task) = undefined; diff --git a/src/kernel/syscalls.zig b/src/kernel/syscalls.zig index bb25793..ddec609 100644 --- a/src/kernel/syscalls.zig +++ b/src/kernel/syscalls.zig @@ -1,14 +1,99 @@ const std = @import("std"); +const testing = std.testing; +const is_test = @import("builtin").is_test; const scheduler = @import("scheduler.zig"); const panic = @import("panic.zig").panic; const log = std.log.scoped(.syscalls); const arch = @import("arch.zig").internals; +const vfs = @import("filesystem/vfs.zig"); +const task = @import("task.zig"); +const vmm = @import("vmm.zig"); +const mem = @import("mem.zig"); +const pmm = @import("pmm.zig"); +const bitmap = @import("bitmap.zig"); -/// A compilation of all errors that syscall handlers could return. -pub const Error = error{OutOfMemory}; +var allocator: std.mem.Allocator = undefined; + +/// The maximum amount of data to allocate when copying user memory into kernel memory +pub const USER_MAX_DATA_LEN = 16 * 1024; + +pub const Error = error{ NoMoreFSHandles, TooBig, NotAFile }; /// All implemented syscalls pub const Syscall = enum { + /// Open a new vfs node + /// + /// Arguments: + /// path_ptr: usize - The user/kernel pointer to the file path to open + /// path_len: usize - The length of the file path + /// flags: usize - The flag specifying what to do with the opened node. Use the integer value of vfs.OpenFlags + /// args: usize - The user/kernel pointer to the structure holding the vfs.OpenArgs + /// ignored: usize - Ignored + /// + /// Return: usize + /// The handle for the opened vfs node + /// + /// Error: + /// NoMoreFSHandles - The task has reached the maximum number of allowed vfs handles + /// OutOfMemory - There wasn't enough kernel (heap or VMM) memory left to fulfill the request. + /// TooBig - The path length is greater than allowed + /// InvalidAddress - A pointer that the user task passed is invalid (not mapped, out of bounds etc.) + /// InvalidFlags - The flags provided don't correspond to a vfs.OpenFlags value + /// Refer to vfs.Error for details on what causes vfs errors + /// + Open, + + /// Read data from an open vfs file + /// + /// Arguments: + /// node_handle: usize - The file handle returned from the open syscall + /// buff_ptr: usize ` - The user/kernel address of the buffer to put the read data in + /// buff_len: usize - The size of the buffer + /// ignored1: usize - Ignored + /// ignored2: usize - Ignored + /// + /// Return: usize + /// The number of bytes read and put into the buffer + /// + /// Error: + /// OutOfBounds - The node handle is outside of the maximum per process + /// TooBig - The buffer is bigger than what a user process is allowed to give the kernel + /// NotAFile - The handle does not correspond to a file + /// Refer to vfs.FileNode.read and vmm.VirtualMemoryManager.copyData for details on what causes other errors + /// + Read, + /// Write data from to open vfs file + /// + /// Arguments: + /// node_handle: usize - The file handle returned from the open syscall + /// buff_ptr: usize ` - The user/kernel address of the buffer containing the data to write + /// buff_len: usize - The size of the buffer + /// ignored1: usize - Ignored + /// ignored2: usize - Ignored + /// + /// Return: usize + /// The number of bytes written + /// + /// Error: + /// OutOfBounds - The node handle is outside of the maximum per process + /// TooBig - The buffer is bigger than what a user process is allowed to give the kernel + /// NotAFile - The handle does not correspond to a file + /// Refer to vfs.FileNode.read and vmm.VirtualMemoryManager.copyData for details on what causes other errors + /// + Write, + /// + /// Close an open vfs node. What it means to "close" depends on the underlying file system, but often it will cause the file to be committed to disk or for a network socket to be closed + /// + /// Arguments: + /// node_handle: usize - The handle to close + /// ignored1..4: usize - Ignored + /// + /// Return: void + /// + /// Error: + /// OutOfBounds - The node handle is outside of the maximum per process + /// NotOpened - The node handle hasn't been opened + Close, Test1, Test2, Test3, @@ -24,6 +109,10 @@ pub const Syscall = enum { /// fn getHandler(self: @This()) Handler { return switch (self) { + .Open => handleOpen, + .Read => handleRead, + .Write => handleWrite, + .Close => handleClose, .Test1 => handleTest1, .Test2 => handleTest2, .Test3 => handleTest3, @@ -42,21 +131,26 @@ pub const Syscall = enum { pub fn isTest(self: @This()) bool { return switch (self) { .Test1, .Test2, .Test3 => true, + else => false, }; } }; /// A function that can handle a syscall and return a result or an error -pub const Handler = fn (ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize; +pub const Handler = fn (ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) anyerror!usize; + +pub fn init(alloc: std.mem.Allocator) void { + allocator = alloc; +} /// -/// Convert an error code to an instance of Error. The conversion must be synchronised with toErrorCode +/// Convert an error code to an instance of anyerror. The conversion must be synchronised with toErrorCode /// Passing an error code that does not correspond to an error results in safety-protected undefined behaviour /// /// Arguments: /// IN code: u16 - The erorr code to convert /// -/// Return: Error +/// Return: anyerror /// The error corresponding to the error code /// pub fn fromErrorCode(code: u16) anyerror { @@ -64,10 +158,10 @@ pub fn fromErrorCode(code: u16) anyerror { } /// -/// Convert an instance of Error to an error code. The conversion must be synchronised with fromErrorCode +/// Convert an instance of anyerror to an error code. The conversion must be synchronised with fromErrorCode /// /// Arguments: -/// IN err: Error - The erorr to convert +/// IN err: anyerror - The erorr to convert /// /// Return: u16 /// The error code corresponding to the error @@ -86,14 +180,223 @@ pub fn toErrorCode(err: anyerror) u16 { /// Return: usize /// The syscall result /// -/// Error: Error +/// Error: anyerror /// The error raised by the handler /// -pub fn handle(syscall: Syscall, ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize { +pub fn handle(syscall: Syscall, ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) anyerror!usize { return try syscall.getHandler()(ctx, arg1, arg2, arg3, arg4, arg5); } -pub fn handleTest1(ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize { +/// +/// Get a slice containing the data at an address and length. If the current task is a kernel task then a simple pointer to slice conversion is performed, +/// otherwise the slice is allocated on the heap and the data is copied in from user space. +/// +/// Arguments: +/// IN ptr: usize - The slice's address +/// IN len: usize - The number of bytes +/// +/// Error: Error || Allocator.Error || VmmError || BitmapError +/// OutOfMemory - There wasn't enough kernel (heap or VMM) memory left to fulfill the request. +/// TooBig - The user task requested to have too much data copied +/// NotAllocated - The pointer hasn't been mapped by the task +/// OutOfBounds - The pointer and length is out of bounds of the task's VMM +/// +/// Return: []u8 +/// The slice of data. Will be stack-allocated if the current task is kernel-level, otherwise will be heap-allocated +/// +fn getData(ptr: usize, len: usize) (Error || std.mem.Allocator.Error || vmm.VmmError || bitmap.BitmapError)![]u8 { + if (scheduler.current_task.kernel) { + if (try vmm.kernel_vmm.isSet(ptr)) { + return @intToPtr([*]u8, ptr)[0..len]; + } else { + return error.NotAllocated; + } + } else { + if (len > USER_MAX_DATA_LEN) { + return Error.TooBig; + } + var buff = try allocator.alloc(u8, len); + errdefer allocator.free(buff); + try vmm.kernel_vmm.copyData(scheduler.current_task.vmm, false, buff, ptr); + return buff; + } +} + +/// Open a new vfs node +/// +/// Arguments: +/// path_ptr: usize - The user/kernel pointer to the file path to open +/// path_len: usize - The length of the file path +/// flags: usize - The flag specifying what to do with the opened node. Use the integer value of vfs.OpenFlags +/// args: usize - The user/kernel pointer to the structure holding the vfs.OpenArgs +/// ignored: usize - Ignored +/// +/// Return: usize +/// The handle for the opened vfs node +/// +/// Error: +/// NoMoreFSHandles - The task has reached the maximum number of allowed vfs handles +/// OutOfMemory - There wasn't enough kernel (heap or VMM) memory left to fulfill the request. +/// TooBig - The path length is greater than allowed +/// InvalidAddress - A pointer that the user task passed is invalid (not mapped, out of bounds etc.) +/// InvalidFlags - The flags provided don't correspond to a vfs.OpenFlags value +/// Refer to vfs.Error for details on what causes vfs errors +/// +fn handleOpen(ctx: *const arch.CpuState, path_ptr: usize, path_len: usize, flags: usize, args: usize, ignored: usize) anyerror!usize { + _ = ctx; + _ = ignored; + const current_task = scheduler.current_task; + if (!current_task.hasFreeVFSHandle()) { + return Error.NoMoreFSHandles; + } + + // Fetch the open arguments from user/kernel memory + var open_args: vfs.OpenArgs = if (args == 0) .{} else blk: { + const data = try getData(args, @sizeOf(vfs.OpenArgs)); + defer if (!current_task.kernel) allocator.free(data); + break :blk std.mem.bytesAsValue(vfs.OpenArgs, data[0..@sizeOf(vfs.OpenArgs)]).*; + }; + // The symlink target could refer to a location in user memory so convert that too + if (open_args.symlink_target) |target| { + open_args.symlink_target = try getData(@ptrToInt(target.ptr), target.len); + } + defer if (!current_task.kernel) if (open_args.symlink_target) |target| allocator.free(target); + + const open_flags = std.meta.intToEnum(vfs.OpenFlags, flags) catch return error.InvalidFlags; + const path = try getData(path_ptr, path_len); + defer if (!current_task.kernel) allocator.free(path); + + const node = try vfs.open(path, true, open_flags, open_args); + errdefer vfs.close(node.*); + return (try current_task.addVFSHandle(node)) orelse panic(null, "Failed to add a VFS handle to current_task\n", .{}); +} + +/// Read data from an open vfs file +/// +/// Arguments: +/// node_handle: usize - The file handle returned from the open syscall +/// buff_ptr: usize ` - The user/kernel address of the buffer to put the read data in +/// buff_len: usize - The size of the buffer +/// ignored1: usize - Ignored +/// ignored2: usize - Ignored +/// +/// Return: usize +/// The number of bytes read and put into the buffer +/// +/// Error: +/// OutOfBounds - The node handle is outside of the maximum per process +/// TooBig - The buffer is bigger than what a user process is allowed to give the kernel +/// NotAFile - The handle does not correspond to a file +/// NotOpened - The handle doesn't correspond to an opened file +/// Refer to vfs.FileNode.read and vmm.VirtualMemoryManager.copyData for details on what causes other errors +/// +fn handleRead(ctx: *const arch.CpuState, node_handle: usize, buff_ptr: usize, buff_len: usize, ignored1: usize, ignored2: usize) anyerror!usize { + _ = ctx; + _ = ignored1; + _ = ignored2; + if (node_handle >= task.VFS_HANDLES_PER_PROCESS) + return error.OutOfBounds; + const real_handle = @intCast(task.Handle, node_handle); + if (buff_len > USER_MAX_DATA_LEN) { + return Error.TooBig; + } + + const current_task = scheduler.current_task; + const node_opt = current_task.getVFSHandle(real_handle) catch panic(@errorReturnTrace(), "Failed to get VFS node for handle {}\n", .{real_handle}); + if (node_opt) |node| { + const file = switch (node.*) { + .File => |*f| f, + else => return error.NotAFile, + }; + var buff = if (current_task.kernel) @intToPtr([*]u8, buff_ptr)[0..buff_len] else try allocator.alloc(u8, buff_len); + defer if (!current_task.kernel) allocator.free(buff); + + const bytes_read = try file.read(buff); + // TODO: A more performant method would be mapping in the user memory and using that directly. Then we wouldn't need to allocate or copy the buffer + if (!current_task.kernel) try vmm.kernel_vmm.copyData(current_task.vmm, true, buff, buff_ptr); + return bytes_read; + } + + return error.NotOpened; +} + +/// Write data from to open vfs file +/// +/// Arguments: +/// node_handle: usize - The file handle returned from the open syscall +/// buff_ptr: usize ` - The user/kernel address of the buffer containing the data to write +/// buff_len: usize - The size of the buffer +/// ignored1: usize - Ignored +/// ignored2: usize - Ignored +/// +/// Return: usize +/// The number of bytes written +/// +/// Error: +/// OutOfBounds - The node handle is outside of the maximum per process +/// TooBig - The buffer is bigger than what a user process is allowed to give the kernel +/// NotAFile - The handle does not correspond to a file +/// NotOpened - The handle doesn't correspond to an opened file +/// Refer to vfs.FileNode.read and vmm.VirtualMemoryManager.copyData for details on what causes other errors +/// +fn handleWrite(ctx: *const arch.CpuState, node_handle: usize, buff_ptr: usize, buff_len: usize, ignored1: usize, ignored2: usize) anyerror!usize { + _ = ctx; + _ = ignored1; + _ = ignored2; + if (node_handle >= task.VFS_HANDLES_PER_PROCESS) + return error.OutOfBounds; + const real_handle = @intCast(task.Handle, node_handle); + + const current_task = scheduler.current_task; + const node_opt = current_task.getVFSHandle(real_handle) catch panic(@errorReturnTrace(), "Failed to get VFS node for handle {}\n", .{real_handle}); + if (node_opt) |node| { + const file = switch (node.*) { + .File => |*f| f, + else => return error.NotAFile, + }; + + // TODO: A more performant method would be mapping in the user memory and using that directly. Then we wouldn't need to allocate or copy the buffer + var buff = try getData(buff_ptr, buff_len); + defer if (!current_task.kernel) allocator.free(buff); + return try file.write(buff); + } + + return error.NotOpened; +} + +/// +/// Close an open vfs node. What it means to "close" depends on the underlying file system, but often it will cause the file to be committed to disk or for a network socket to be closed +/// +/// Arguments: +/// node_handle: usize - The handle to close +/// ignored1..4: usize - Ignored +/// +/// Return: void +/// +/// Error: +/// OutOfBounds - The node handle is outside of the maximum per process +/// NotOpened - The node handle hasn't been opened +fn handleClose(ctx: *const arch.CpuState, node_handle: usize, ignored1: usize, ignored2: usize, ignored3: usize, ignored4: usize) anyerror!usize { + _ = ctx; + _ = ignored1; + _ = ignored2; + _ = ignored3; + _ = ignored4; + if (node_handle >= task.VFS_HANDLES_PER_PROCESS) + return error.OutOfBounds; + const real_handle = @intCast(task.Handle, node_handle); + const current_task = scheduler.current_task; + const node_opt = current_task.getVFSHandle(real_handle) catch panic(@errorReturnTrace(), "Failed to get VFS node for handle {}\n", .{real_handle}); + if (node_opt) |node| { + current_task.clearVFSHandle(real_handle) catch |e| return switch (e) { + error.VFSHandleNotSet, error.OutOfBounds => error.NotOpened, + }; + vfs.close(node.*); + } + return error.NotOpened; +} + +pub fn handleTest1(ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) anyerror!usize { // Suppress unused variable warnings _ = ctx; _ = arg1; @@ -104,12 +407,12 @@ pub fn handleTest1(ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: us return 0; } -pub fn handleTest2(ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize { +pub fn handleTest2(ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) anyerror!usize { _ = ctx; return arg1 + arg2 + arg3 + arg4 + arg5; } -pub fn handleTest3(ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) Error!usize { +pub fn handleTest3(ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: usize, arg4: usize, arg5: usize) anyerror!usize { // Suppress unused variable warnings _ = ctx; _ = arg1; @@ -117,18 +420,351 @@ pub fn handleTest3(ctx: *const arch.CpuState, arg1: usize, arg2: usize, arg3: us _ = arg3; _ = arg4; _ = arg5; - return std.mem.Allocator.Error.OutOfMemory; + return error.OutOfMemory; +} + +fn testInitMem(comptime num_vmm_entries: usize, alloc: std.mem.Allocator, map_all: bool) !std.heap.FixedBufferAllocator { + // handleOpen requires that the name passed is mapped in the VMM + // Allocate them within a buffer so we know the start and end address to give to the VMM + var buffer = try alloc.alloc(u8, num_vmm_entries * vmm.BLOCK_SIZE); + var fixed_buffer_allocator = std.heap.FixedBufferAllocator.init(buffer[0..]); + + vmm.kernel_vmm = try vmm.VirtualMemoryManager(arch.VmmPayload).init(@ptrToInt(fixed_buffer_allocator.buffer.ptr), @ptrToInt(fixed_buffer_allocator.buffer.ptr) + buffer.len, alloc, arch.VMM_MAPPER, arch.KERNEL_VMM_PAYLOAD); + // The PMM is required as well + const mem_profile = mem.MemProfile{ + .vaddr_end = undefined, + .vaddr_start = undefined, + .physaddr_start = undefined, + .physaddr_end = undefined, + .mem_kb = num_vmm_entries * vmm.BLOCK_SIZE / 1024, + .fixed_allocator = undefined, + .virtual_reserved = &[_]mem.Map{}, + .physical_reserved = &[_]mem.Range{}, + .modules = &[_]mem.Module{}, + }; + pmm.init(&mem_profile, alloc); + // Set the whole VMM space as mapped so all address within the buffer allocator will be considered valid + if (map_all) _ = try vmm.kernel_vmm.alloc(num_vmm_entries, null, .{ .kernel = true, .writable = true, .cachable = true }); + return fixed_buffer_allocator; +} + +fn testDeinitMem(alloc: std.mem.Allocator, buffer_allocator: std.heap.FixedBufferAllocator) void { + alloc.free(buffer_allocator.buffer); + vmm.kernel_vmm.deinit(); + pmm.deinit(); } test "getHandler" { try std.testing.expectEqual(Syscall.Test1.getHandler(), handleTest1); try std.testing.expectEqual(Syscall.Test2.getHandler(), handleTest2); try std.testing.expectEqual(Syscall.Test3.getHandler(), handleTest3); + try std.testing.expectEqual(Syscall.Open.getHandler(), handleOpen); + try std.testing.expectEqual(Syscall.Close.getHandler(), handleClose); + try std.testing.expectEqual(Syscall.Read.getHandler(), handleRead); + try std.testing.expectEqual(Syscall.Write.getHandler(), handleWrite); } test "handle" { const state = arch.CpuState.empty(); try std.testing.expectEqual(@as(usize, 0), try handle(.Test1, &state, 0, 0, 0, 0, 0)); try std.testing.expectEqual(@as(usize, 1 + 2 + 3 + 4 + 5), try handle(.Test2, &state, 1, 2, 3, 4, 5)); - try std.testing.expectError(Error.OutOfMemory, handle(.Test3, &state, 0, 0, 0, 0, 0)); + try std.testing.expectError(error.OutOfMemory, handle(.Test3, &state, 0, 0, 0, 0, 0)); +} + +test "handleOpen" { + allocator = std.testing.allocator; + var testfs = try vfs.testInitFs(allocator); + defer allocator.destroy(testfs); + defer testfs.deinit(); + + testfs.instance = 1; + try vfs.setRoot(testfs.tree.val); + + var fixed_buffer_allocator = try testInitMem(1, allocator, true); + var buffer_allocator = fixed_buffer_allocator.allocator(); + defer testDeinitMem(allocator, fixed_buffer_allocator); + + scheduler.current_task = try task.Task.create(0, true, undefined, allocator, true); + defer scheduler.current_task.destroy(allocator); + var current_task = scheduler.current_task; + + const empty = arch.CpuState.empty(); + + // Creating a file + var name1 = try buffer_allocator.dupe(u8, "/abc.txt"); + var test_handle = @intCast(task.Handle, try handleOpen(&empty, @ptrToInt(name1.ptr), name1.len, @enumToInt(vfs.OpenFlags.CREATE_FILE), 0, undefined)); + var test_node = (try current_task.getVFSHandle(test_handle)).?; + try testing.expectEqual(testfs.tree.children.items.len, 1); + var tree = testfs.tree.children.items[0]; + try testing.expect(tree.val.isFile() and test_node.isFile()); + try testing.expectEqual(&test_node.File, &tree.val.File); + try testing.expect(std.mem.eql(u8, tree.name, "abc.txt")); + try testing.expectEqual(tree.data, null); + try testing.expectEqual(tree.children.items.len, 0); + + // Creating a dir + var name2 = try buffer_allocator.dupe(u8, "/def"); + test_handle = @intCast(task.Handle, try handleOpen(&empty, @ptrToInt(name2.ptr), name2.len, @enumToInt(vfs.OpenFlags.CREATE_DIR), 0, undefined)); + test_node = (try current_task.getVFSHandle(test_handle)).?; + try testing.expectEqual(testfs.tree.children.items.len, 2); + tree = testfs.tree.children.items[1]; + try testing.expect(tree.val.isDir() and test_node.isDir()); + try testing.expectEqual(&test_node.Dir, &tree.val.Dir); + try testing.expect(std.mem.eql(u8, tree.name, "def")); + try testing.expectEqual(tree.data, null); + try testing.expectEqual(tree.children.items.len, 0); + + // Creating a file under a new dir + var name3 = try buffer_allocator.dupe(u8, "/def/ghi.zig"); + test_handle = @intCast(task.Handle, try handleOpen(&empty, @ptrToInt(name3.ptr), name3.len, @enumToInt(vfs.OpenFlags.CREATE_FILE), 0, undefined)); + test_node = (try current_task.getVFSHandle(test_handle)).?; + try testing.expectEqual(testfs.tree.children.items[1].children.items.len, 1); + tree = testfs.tree.children.items[1].children.items[0]; + try testing.expect(tree.val.isFile() and test_node.isFile()); + try testing.expectEqual(&test_node.File, &tree.val.File); + try testing.expect(std.mem.eql(u8, tree.name, "ghi.zig")); + try testing.expectEqual(tree.data, null); + try testing.expectEqual(tree.children.items.len, 0); + + // Opening an existing file + test_handle = @intCast(task.Handle, try handleOpen(&empty, @ptrToInt(name3.ptr), name3.len, @enumToInt(vfs.OpenFlags.NO_CREATION), 0, undefined)); + test_node = (try current_task.getVFSHandle(test_handle)).?; + try testing.expectEqual(testfs.tree.children.items[1].children.items.len, 1); + try testing.expect(test_node.isFile()); + try testing.expectEqual(&test_node.File, &tree.val.File); +} + +test "handleRead" { + allocator = std.testing.allocator; + var testfs = try vfs.testInitFs(allocator); + defer allocator.destroy(testfs); + defer testfs.deinit(); + + testfs.instance = 1; + try vfs.setRoot(testfs.tree.val); + + var fixed_buffer_allocator = try testInitMem(1, allocator, true); + var buffer_allocator = fixed_buffer_allocator.allocator(); + defer testDeinitMem(allocator, fixed_buffer_allocator); + scheduler.current_task = try task.Task.create(0, true, &vmm.kernel_vmm, allocator, true); + defer scheduler.current_task.destroy(allocator); + _ = scheduler.current_task; + + const empty = arch.CpuState.empty(); + + var test_file_path = try buffer_allocator.dupe(u8, "/foo.txt"); + var test_file = @intCast(task.Handle, try handleOpen(&empty, @ptrToInt(test_file_path.ptr), test_file_path.len, @enumToInt(vfs.OpenFlags.CREATE_FILE), 0, undefined)); + var f_data = &testfs.tree.children.items[0].data; + var str = "test123"; + f_data.* = try testing.allocator.dupe(u8, str); + + var buffer: [str.len]u8 = undefined; + { + const length = try handleRead(&empty, test_file, @ptrToInt(&buffer[0]), buffer.len, 0, undefined); + try testing.expect(std.mem.eql(u8, str, buffer[0..length])); + } + + { + const length = try handleRead(&empty, test_file, @ptrToInt(&buffer[0]), buffer.len + 1, 0, undefined); + try testing.expect(std.mem.eql(u8, str, buffer[0..length])); + } + + { + const length = try handleRead(&empty, test_file, @ptrToInt(&buffer[0]), buffer.len + 3, 0, undefined); + try testing.expect(std.mem.eql(u8, str, buffer[0..length])); + } + + { + const length = try handleRead(&empty, test_file, @ptrToInt(&buffer[0]), buffer.len - 1, 0, undefined); + try testing.expect(std.mem.eql(u8, str[0 .. str.len - 1], buffer[0..length])); + } + + { + const length = try handleRead(&empty, test_file, @ptrToInt(&buffer[0]), 0, 0, undefined); + try testing.expect(std.mem.eql(u8, str[0..0], buffer[0..length])); + } + // Try reading from a symlink + var args = try buffer_allocator.create(vfs.OpenArgs); + args.* = vfs.OpenArgs{ .symlink_target = test_file_path }; + var link = try buffer_allocator.dupe(u8, "/link"); + var test_link = @intCast(task.Handle, try handleOpen(&empty, @ptrToInt(link.ptr), link.len, @enumToInt(vfs.OpenFlags.CREATE_SYMLINK), @ptrToInt(args), undefined)); + { + const length = try handleRead(&empty, test_link, @ptrToInt(&buffer[0]), buffer.len, 0, undefined); + try testing.expect(std.mem.eql(u8, str[0..str.len], buffer[0..length])); + } +} + +test "handleRead errors" { + allocator = std.testing.allocator; + var testfs = try vfs.testInitFs(allocator); + { + defer allocator.destroy(testfs); + defer testfs.deinit(); + + testfs.instance = 1; + try vfs.setRoot(testfs.tree.val); + + const empty = arch.CpuState.empty(); + + // The data we pass to handleRead needs to be mapped within the VMM, so we need to know their address + // Allocating the data within a fixed buffer allocator is the best way to know the address of the data + var fixed_buffer_allocator = try testInitMem(3, allocator, true); + var buffer_allocator = fixed_buffer_allocator.allocator(); + defer testDeinitMem(allocator, fixed_buffer_allocator); + + scheduler.current_task = try task.Task.create(0, true, &vmm.kernel_vmm, allocator, true); + defer scheduler.current_task.destroy(allocator); + + // Invalid file handle + try testing.expectError(error.OutOfBounds, handleRead(&empty, task.VFS_HANDLES_PER_PROCESS, 0, 0, 0, 0)); + try testing.expectError(error.OutOfBounds, handleRead(&empty, task.VFS_HANDLES_PER_PROCESS + 1, 0, 0, 0, 0)); + + // Unopened file + try testing.expectError(error.NotOpened, handleRead(&empty, 0, 0, 0, 0, 0)); + try testing.expectError(error.NotOpened, handleRead(&empty, 1, 0, 0, 0, 0)); + try testing.expectError(error.NotOpened, handleRead(&empty, task.VFS_HANDLES_PER_PROCESS - 1, 0, 0, 0, 0)); + + // Reading from a dir + const name = try buffer_allocator.dupe(u8, "/dir"); + const node = try handleOpen(&empty, @ptrToInt(name.ptr), name.len, @enumToInt(vfs.OpenFlags.CREATE_DIR), 0, 0); + try testing.expectError(error.NotAFile, handleRead(&empty, node, 0, 0, 0, 0)); + + // User buffer is too big + const name2 = try buffer_allocator.dupe(u8, "/file.txt"); + const node2 = try handleOpen(&empty, @ptrToInt(name2.ptr), name2.len, @enumToInt(vfs.OpenFlags.CREATE_FILE), 0, 0); + scheduler.current_task.kernel = false; + try testing.expectError(Error.TooBig, handleRead(&empty, node2, 0, USER_MAX_DATA_LEN + 1, 0, 0)); + } + try testing.expect(!testing.allocator_instance.detectLeaks()); +} + +test "handleWrite" { + allocator = std.testing.allocator; + var testfs = try vfs.testInitFs(allocator); + defer allocator.destroy(testfs); + defer testfs.deinit(); + + testfs.instance = 1; + try vfs.setRoot(testfs.tree.val); + + var fixed_buffer_allocator = try testInitMem(1, allocator, true); + var buffer_allocator = fixed_buffer_allocator.allocator(); + defer testDeinitMem(allocator, fixed_buffer_allocator); + + scheduler.current_task = try task.Task.create(0, true, &vmm.kernel_vmm, allocator, true); + defer scheduler.current_task.destroy(allocator); + + const empty = arch.CpuState.empty(); + + // Open test file + const name = try buffer_allocator.dupe(u8, "/abc.txt"); + const node = try handleOpen(&empty, @ptrToInt(name.ptr), name.len, @enumToInt(vfs.OpenFlags.CREATE_FILE), 0, undefined); + + // Write + const data = try buffer_allocator.dupe(u8, "test_data 123"); + const res = try handleWrite(&empty, node, @ptrToInt(data.ptr), data.len, 0, 0); + try testing.expectEqual(res, data.len); + try testing.expectEqualSlices(u8, data, testfs.tree.children.items[0].data.?); + + // Write to a file in a folder + const name2 = try buffer_allocator.dupe(u8, "/dir"); + _ = try handleOpen(&empty, @ptrToInt(name2.ptr), name2.len, @enumToInt(vfs.OpenFlags.CREATE_DIR), 0, undefined); + const name3 = try buffer_allocator.dupe(u8, "/dir/def.txt"); + const node3 = try handleOpen(&empty, @ptrToInt(name3.ptr), name3.len, @enumToInt(vfs.OpenFlags.CREATE_FILE), 0, undefined); + const data2 = try buffer_allocator.dupe(u8, "some more test data!"); + const res2 = try handleWrite(&empty, node3, @ptrToInt(data2.ptr), data2.len, 0, 0); + try testing.expectEqual(res2, data2.len); + try testing.expectEqualSlices(u8, data2, testfs.tree.children.items[1].children.items[0].data.?); +} + +test "handleWrite errors" { + allocator = std.testing.allocator; + var testfs = try vfs.testInitFs(allocator); + { + defer allocator.destroy(testfs); + defer testfs.deinit(); + + testfs.instance = 1; + try vfs.setRoot(testfs.tree.val); + + const empty = arch.CpuState.empty(); + + // The data we pass to handleWrite needs to be mapped within the VMM, so we need to know their address + // Allocating the data within a fixed buffer allocator is the best way to know the address of the data + var fixed_buffer_allocator = try testInitMem(3, allocator, true); + var buffer_allocator = fixed_buffer_allocator.allocator(); + defer testDeinitMem(allocator, fixed_buffer_allocator); + + scheduler.current_task = try task.Task.create(0, true, &vmm.kernel_vmm, allocator, true); + defer scheduler.current_task.destroy(allocator); + + // Invalid file handle + try testing.expectError(error.OutOfBounds, handleWrite(&empty, task.VFS_HANDLES_PER_PROCESS, 0, 0, 0, 0)); + try testing.expectError(error.OutOfBounds, handleWrite(&empty, task.VFS_HANDLES_PER_PROCESS + 1, 0, 0, 0, 0)); + + // Unopened file + try testing.expectError(error.NotOpened, handleWrite(&empty, 0, 0, 0, 0, 0)); + try testing.expectError(error.NotOpened, handleWrite(&empty, 1, 0, 0, 0, 0)); + try testing.expectError(error.NotOpened, handleWrite(&empty, task.VFS_HANDLES_PER_PROCESS - 1, 0, 0, 0, 0)); + + // Writing to a dir + const name = try buffer_allocator.dupe(u8, "/dir"); + const node = try handleOpen(&empty, @ptrToInt(name.ptr), name.len, @enumToInt(vfs.OpenFlags.CREATE_DIR), 0, 0); + try testing.expectError(error.NotAFile, handleWrite(&empty, node, 0, 0, 0, 0)); + + // User buffer is too big + const name2 = try buffer_allocator.dupe(u8, "/file.txt"); + const node2 = try handleOpen(&empty, @ptrToInt(name2.ptr), name2.len, @enumToInt(vfs.OpenFlags.CREATE_FILE), 0, 0); + scheduler.current_task.kernel = false; + try testing.expectError(Error.TooBig, handleWrite(&empty, node2, 0, USER_MAX_DATA_LEN + 1, 0, 0)); + } + try testing.expect(!testing.allocator_instance.detectLeaks()); +} + +test "handleOpen errors" { + allocator = std.testing.allocator; + var testfs = try vfs.testInitFs(allocator); + { + defer allocator.destroy(testfs); + defer testfs.deinit(); + + testfs.instance = 1; + try vfs.setRoot(testfs.tree.val); + + const empty = arch.CpuState.empty(); + + // The data we pass to handleOpen needs to be mapped within the VMM, so we need to know their address + // Allocating the data within a fixed buffer allocator is the best way to know the address of the data + var fixed_buffer_allocator = try testInitMem(3, allocator, false); + var buffer_allocator = fixed_buffer_allocator.allocator(); + defer testDeinitMem(allocator, fixed_buffer_allocator); + + scheduler.current_task = try task.Task.create(0, true, &vmm.kernel_vmm, allocator, true); + defer scheduler.current_task.destroy(allocator); + + // Check opening with no free file handles left + const free_handles = scheduler.current_task.file_handles.num_free_entries; + scheduler.current_task.file_handles.num_free_entries = 0; + try testing.expectError(Error.NoMoreFSHandles, handleOpen(&empty, 0, 0, 0, 0, 0)); + scheduler.current_task.file_handles.num_free_entries = free_handles; + + // Using a path that is too long + scheduler.current_task.kernel = false; + try testing.expectError(Error.TooBig, handleOpen(&empty, 0, USER_MAX_DATA_LEN + 1, 0, 0, 0)); + + // Unallocated user address + const test_alloc = try buffer_allocator.alloc(u8, 1); + // The kernel VMM and task VMM need to have their buffers mapped, so we'll temporarily use the buffer allocator since it operates within a known address space + allocator = buffer_allocator; + try testing.expectError(error.NotAllocated, handleOpen(&empty, @ptrToInt(test_alloc.ptr), 1, 0, 0, 0)); + allocator = std.testing.allocator; + + // Unallocated kernel address + scheduler.current_task.kernel = true; + try testing.expectError(error.NotAllocated, handleOpen(&empty, @ptrToInt(test_alloc.ptr), 1, 0, 0, 0)); + + // Invalid flag enum value + try testing.expectError(error.InvalidFlags, handleOpen(&empty, @ptrToInt(test_alloc.ptr), 1, 999, 0, 0)); + } + try testing.expect(!testing.allocator_instance.detectLeaks()); } diff --git a/src/kernel/task.zig b/src/kernel/task.zig index e60fba9..cb50c32 100644 --- a/src/kernel/task.zig +++ b/src/kernel/task.zig @@ -1,6 +1,7 @@ const std = @import("std"); const expectEqual = std.testing.expectEqual; const expectError = std.testing.expectError; +const expect = std.testing.expect; const builtin = @import("builtin"); const is_test = builtin.is_test; const build_options = @import("build_options"); @@ -11,6 +12,7 @@ const pmm = @import("pmm.zig"); const mem = @import("mem.zig"); const elf = @import("elf.zig"); const bitmap = @import("bitmap.zig"); +const vfs = @import("filesystem/vfs.zig"); const Allocator = std.mem.Allocator; const log = std.log.scoped(.task); @@ -18,6 +20,12 @@ const log = std.log.scoped(.task); /// as we cannot deallocate this. extern var KERNEL_STACK_START: *u32; +/// The number of vfs handles that a process can have +pub const VFS_HANDLES_PER_PROCESS = std.math.maxInt(Handle); + +/// A vfs handle. 65k is probably a good limit for the number of files a task can have open at once so we use u16 as the type +pub const Handle = u16; + /// The function type for the entry point. pub const EntryPoint = usize; @@ -27,11 +35,18 @@ const PidBitmap = bitmap.Bitmap(1024, usize); /// The list of PIDs that have been allocated. var all_pids = PidBitmap.init(1024, null) catch unreachable; +const FileHandleBitmap = bitmap.Bitmap(1024, usize); + /// The default stack size of a task. Currently this is set to a page size. pub const STACK_SIZE: u32 = arch.MEMORY_BLOCK_SIZE / @sizeOf(u32); /// The task control block for storing all the information needed to save and restore a task. pub const Task = struct { + pub const Error = error{ + /// The supplied vfs handle hasn't been allocated + VFSHandleNotSet, + }; + const Self = @This(); /// The unique task identifier @@ -52,6 +67,12 @@ pub const Task = struct { /// The virtual memory manager belonging to the task vmm: *vmm.VirtualMemoryManager(arch.VmmPayload), + /// The list of file handles for this process + file_handles: FileHandleBitmap, + + /// The mapping between file handles and file nodes + file_handle_mapping: std.hash_map.AutoHashMap(Handle, *vfs.Node), + /// /// Create a task. This will allocate a PID and the stack. The stack will be set up as a /// kernel task. As this is a new task, the stack will need to be initialised with the CPU @@ -90,6 +111,8 @@ pub const Task = struct { .stack_pointer = if (!alloc_kernel_stack) 0 else @ptrToInt(&k_stack[STACK_SIZE - 1]), .kernel = kernel, .vmm = task_vmm, + .file_handles = FileHandleBitmap.init(null, null) catch unreachable, + .file_handle_mapping = std.hash_map.AutoHashMap(Handle, *vfs.Node).init(allocator), }; try arch.initTask(task, entry_point, allocator, alloc_kernel_stack); @@ -146,8 +169,97 @@ pub const Task = struct { if (!self.kernel) { allocator.free(self.user_stack); } + self.file_handle_mapping.deinit(); allocator.destroy(self); } + + /// + /// Get the VFS node associated with a VFS handle. + /// + /// Arguments: + /// IN self: *Self - The pointer to self. + /// IN handle: Handle - The handle to get the node for. Must have been returned from addVFSHandle. + /// + /// Return: *vfs.Node + /// The node associated with the handle. + /// + /// Error: bitmap.BitmapError + /// See Bitmap. + /// + pub fn getVFSHandle(self: Self, handle: Handle) bitmap.BitmapError!?*vfs.Node { + return self.file_handle_mapping.get(handle); + } + + /// + /// Check if the task has free handles to allocate. + /// + /// Arguments: + /// IN self: Self - The self. + /// + /// Return: bool + /// True if there are free handles, else false. + /// + pub fn hasFreeVFSHandle(self: Self) bool { + return self.file_handles.num_free_entries > 0; + } + + /// + /// Add a handle associated with a node. The node can later be retrieved with getVFSHandle. + /// + /// Arguments: + /// IN self: *Self - The pointer to self. + /// IN node: *vfs.Node - The node to associate with the returned handle. + /// + /// Return: Handle + /// The handle now associated with the vfs node. + /// + /// Error: std.mem.Allocator.Error + /// + pub fn addVFSHandle(self: *Self, node: *vfs.Node) std.mem.Allocator.Error!?Handle { + if (self.file_handles.setFirstFree()) |handle| { + const real_handle = @intCast(Handle, handle); + try self.file_handle_mapping.put(real_handle, node); + return real_handle; + } + return null; + } + + /// + /// Check if the task has a certain handle registered. + /// + /// Arguments: + /// IN self: Self - The self. + /// IN handle: Handle - The handle to check. + /// + /// Return: bool + /// True if the handle has been registered to this task, else false. + /// + /// Error: bitmap.BitmapError + /// See Bitmap. + /// + pub fn hasVFSHandle(self: Self, handle: Handle) bitmap.BitmapError!bool { + return self.file_handles.isSet(handle); + } + + /// + /// Clear a registered handle and de-associate the node from it. + /// + /// Arguments: + /// IN self: *Self - The pointer to self. + /// IN handle: Handle - The handle to clear. Must have been registered before. + /// + /// Error: bitmap.BitmapError || Error + /// bitmap.BitmapError.* - See bitmap.BitmapError + /// Error.VFSHandleNotSet - The handle has not previously been registered + /// + pub fn clearVFSHandle(self: *Self, handle: Handle) (bitmap.BitmapError || Error)!void { + if (try self.hasVFSHandle(handle)) { + try self.file_handles.clearEntry(handle); + _ = self.file_handle_mapping.remove(handle); + } else { + return Error.VFSHandleNotSet; + } + } }; /// @@ -381,3 +493,75 @@ test "create doesn't allocate kernel stack" { try std.testing.expectEqualSlices(usize, task.kernel_stack, &[_]usize{}); try std.testing.expectEqual(task.stack_pointer, 0); } + +test "addVFSHandle" { + var task = try Task.create(0, true, undefined, std.testing.allocator, false); + defer task.destroy(std.testing.allocator); + var node1 = vfs.Node{ .Dir = .{ .fs = undefined, .mount = null } }; + var node2 = vfs.Node{ .File = .{ .fs = undefined } }; + + const handle1 = (try task.addVFSHandle(&node1)) orelse return error.FailedToAddVFSHandle; + try expectEqual(handle1, 0); + try expectEqual(&node1, task.file_handle_mapping.get(handle1).?); + try expectEqual(true, try task.file_handles.isSet(handle1)); + + const handle2 = (try task.addVFSHandle(&node2)) orelse return error.FailedToAddVFSHandle; + try expectEqual(handle2, 1); + try expectEqual(&node2, task.file_handle_mapping.get(handle2).?); + try expectEqual(true, try task.file_handles.isSet(handle2)); +} + +test "hasFreeVFSHandle" { + var task = try Task.create(0, true, undefined, std.testing.allocator, false); + defer task.destroy(std.testing.allocator); + var node1 = vfs.Node{ .Dir = .{ .fs = undefined, .mount = null } }; + + try expect(task.hasFreeVFSHandle()); + + _ = (try task.addVFSHandle(&node1)) orelse return error.FailedToAddVFSHandle; + try expect(task.hasFreeVFSHandle()); + + var i: usize = 0; + const free_entries = task.file_handles.num_free_entries; + while (i < free_entries) : (i += 1) { + try expect(task.hasFreeVFSHandle()); + _ = task.file_handles.setFirstFree(); + } + try expect(!task.hasFreeVFSHandle()); +} + +test "getVFSHandle" { + var task = try Task.create(0, true, undefined, std.testing.allocator, false); + defer task.destroy(std.testing.allocator); + var node1 = vfs.Node{ .Dir = .{ .fs = undefined, .mount = null } }; + var node2 = vfs.Node{ .File = .{ .fs = undefined } }; + + const handle1 = (try task.addVFSHandle(&node1)) orelse return error.FailedToAddVFSHandle; + try expectEqual(&node1, (try task.getVFSHandle(handle1)).?); + + const handle2 = (try task.addVFSHandle(&node2)) orelse return error.FailedToAddVFSHandle; + try expectEqual(&node2, (try task.getVFSHandle(handle2)).?); + try expectEqual(&node1, (try task.getVFSHandle(handle1)).?); + + try expectEqual(task.getVFSHandle(handle2 + 1), null); +} + +test "clearVFSHandle" { + var task = try Task.create(0, true, undefined, std.testing.allocator, false); + defer task.destroy(std.testing.allocator); + var node1 = vfs.Node{ .Dir = .{ .fs = undefined, .mount = null } }; + var node2 = vfs.Node{ .File = .{ .fs = undefined } }; + + const handle1 = (try task.addVFSHandle(&node1)) orelse return error.FailedToAddVFSHandle; + const handle2 = (try task.addVFSHandle(&node2)) orelse return error.FailedToAddVFSHandle; + + try task.clearVFSHandle(handle1); + try expectEqual(false, try task.hasVFSHandle(handle1)); + + try task.clearVFSHandle(handle2); + try expectEqual(false, try task.hasVFSHandle(handle2)); + + try expectError(Task.Error.VFSHandleNotSet, task.clearVFSHandle(handle2 + 1)); + try expectError(Task.Error.VFSHandleNotSet, task.clearVFSHandle(handle2)); + try expectError(Task.Error.VFSHandleNotSet, task.clearVFSHandle(handle1)); +} From 6b03e8dfae216a2bf2a97b117d4f8575847643c7 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Mon, 24 Jun 2024 20:46:59 +0200 Subject: [PATCH 5/8] Ignore .zig-cache --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index f5ce0f2..6fc65e6 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ # Zig ignore **/zig-cache/ +**/.zig-cache/ **/zig-out/ **/build/ **/build-*/ From d3f278fc59c3405244f34f37018f3ad981c59824 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Mon, 24 Jun 2024 20:47:27 +0200 Subject: [PATCH 6/8] Build steps now silent, unclear if fixed --- build.zig | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/build.zig b/build.zig index d63a392..82c68a8 100644 --- a/build.zig +++ b/build.zig @@ -18,7 +18,7 @@ const Fat32 = @import("mkfat32.zig").Fat32; const x86_i686 = CrossTarget{ .cpu_arch = .i386, .os_tag = .freestanding, - .cpu_model = .{ .explicit = &Target.x86.cpu._i686 }, + .cpu_model = .{ .explicit = &Target.x86.cpu.i686 }, }; pub fn build(b: *Builder) !void { @@ -156,7 +156,7 @@ pub fn build(b: *Builder) !void { try qemu_args_al.append("none"); } - var qemu_args = qemu_args_al.toOwnedSlice(); + const qemu_args = qemu_args_al.toOwnedSlice(); const rt_step = RuntimeStep.create(b, test_mode, qemu_args); rt_step.step.dependOn(&make_iso.step); @@ -217,7 +217,7 @@ const Fat32BuilderStep = struct { /// Fat32.Error - If there was an error creating the FAT image. This will be invalid options. /// fn make(step: *Step) (error{EndOfStream} || File.OpenError || File.ReadError || File.WriteError || File.SeekError || Fat32.Error)!void { - const self = @fieldParentPtr(Fat32BuilderStep, "step", step); + const self: *Fat32BuilderStep = @fieldParentPtr("step", step); // Open the out file const image = try std.fs.cwd().createFile(self.out_file_path, .{ .read = true }); @@ -293,7 +293,7 @@ const RamdiskStep = struct { // First write the number of files/headers std.debug.assert(self.files.len < std.math.maxInt(Usize)); - try ramdisk.writer().writeInt(Usize, @truncate(Usize, self.files.len), endian); + try ramdisk.writer().writeInt(Usize, @truncate(self.files.len), endian); var current_offset: usize = 0; for (self.files) |file_path| { // Open, and read the file. Can get the size from this as well @@ -305,14 +305,14 @@ const RamdiskStep = struct { // Write the header and file content to the ramdisk // Name length std.debug.assert(file_path[file_name_index..].len < std.math.maxInt(Usize)); - try ramdisk.writer().writeInt(Usize, @truncate(Usize, file_path[file_name_index..].len), endian); + try ramdisk.writer().writeInt(Usize, @truncate(file_path[file_name_index..].len), endian); // Name try ramdisk.writer().writeAll(file_path[file_name_index..]); // Length std.debug.assert(file_content.len < std.math.maxInt(Usize)); - try ramdisk.writer().writeInt(Usize, @truncate(Usize, file_content.len), endian); + try ramdisk.writer().writeInt(Usize, @truncate(file_content.len), endian); // File contest try ramdisk.writer().writeAll(file_content); @@ -333,7 +333,7 @@ const RamdiskStep = struct { /// Errors for opening, reading and writing to and from files and for allocating memory. /// fn make(step: *Step) Error!void { - const self = @fieldParentPtr(RamdiskStep, "step", step); + const self: *RamdiskStep = @fieldParentPtr("step", step); switch (self.target.getCpuArch()) { .i386 => try writeRamdisk(u32, self), else => unreachable, From 98d876b18c727c49276f712faa544ed83b52f956 Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Mon, 24 Jun 2024 20:47:54 +0200 Subject: [PATCH 7/8] Autofixes --- mkfat32.zig | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/mkfat32.zig b/mkfat32.zig index 4340fab..821d0b3 100644 --- a/mkfat32.zig +++ b/mkfat32.zig @@ -311,13 +311,13 @@ pub const Fat32 = struct { return switch (image_size) { 0...35 * 512 - 1 => Error.TooSmall, - 35 * 512...64 * MB - 1 => @intCast(u8, std.math.max(512, bytes_per_sector) / bytes_per_sector), - 64 * MB...128 * MB - 1 => @intCast(u8, std.math.max(1024, bytes_per_sector) / bytes_per_sector), - 128 * MB...256 * MB - 1 => @intCast(u8, std.math.max(2048, bytes_per_sector) / bytes_per_sector), - 256 * MB...8 * GB - 1 => @intCast(u8, std.math.max(4096, bytes_per_sector) / bytes_per_sector), - 8 * GB...16 * GB - 1 => @intCast(u8, std.math.max(8192, bytes_per_sector) / bytes_per_sector), - 16 * GB...32 * GB - 1 => @intCast(u8, std.math.max(16384, bytes_per_sector) / bytes_per_sector), - 32 * GB...2 * TB - 1 => @intCast(u8, std.math.max(32768, bytes_per_sector) / bytes_per_sector), + 35 * 512...64 * MB - 1 => @intCast(std.math.max(512, bytes_per_sector) / bytes_per_sector), + 64 * MB...128 * MB - 1 => @intCast(std.math.max(1024, bytes_per_sector) / bytes_per_sector), + 128 * MB...256 * MB - 1 => @intCast(std.math.max(2048, bytes_per_sector) / bytes_per_sector), + 256 * MB...8 * GB - 1 => @intCast(std.math.max(4096, bytes_per_sector) / bytes_per_sector), + 8 * GB...16 * GB - 1 => @intCast(std.math.max(8192, bytes_per_sector) / bytes_per_sector), + 16 * GB...32 * GB - 1 => @intCast(std.math.max(16384, bytes_per_sector) / bytes_per_sector), + 32 * GB...2 * TB - 1 => @intCast(std.math.max(32768, bytes_per_sector) / bytes_per_sector), else => Error.TooLarge, }; } @@ -354,7 +354,7 @@ pub const Fat32 = struct { fn createSerialNumber() u32 { // TODO: Get the actual date. Currently there is no std lib for human readable date. const year = 2020; - const month = 09; + const month = 9; const day = 27; const hour = 13; const minute = 46; @@ -541,14 +541,14 @@ pub const Fat32 = struct { } // See: https://board.flatassembler.net/topic.php?t=12680 - var sectors_per_fat = @intCast(u32, (image_size - getReservedSectors() + (2 * options.cluster_size)) / ((options.cluster_size * (options.sector_size / 4)) + 2)); + var sectors_per_fat: u8 = @intCast((image_size - getReservedSectors() + (2 * options.cluster_size)) / ((options.cluster_size * (options.sector_size / 4)) + 2)); // round up sectors sectors_per_fat = (sectors_per_fat + options.sector_size - 1) / options.sector_size; return Header{ .bytes_per_sector = options.sector_size, .sectors_per_cluster = options.cluster_size, - .total_sectors = @intCast(u32, @divExact(image_size, options.sector_size)), + .total_sectors = @intCast(@divExact(image_size, options.sector_size)), .sectors_per_fat = sectors_per_fat, .serial_number = createSerialNumber(), .volume_label = options.volume_name, From 66d15e8945738e3f0991272115f3f59a1fb4f47f Mon Sep 17 00:00:00 2001 From: Imbus <> Date: Mon, 24 Jun 2024 21:10:31 +0200 Subject: [PATCH 8/8] No brakes on the fix train --- build.zig | 16 ++++++++-------- test/runtime_test.zig | 4 ++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/build.zig b/build.zig index 82c68a8..b744903 100644 --- a/build.zig +++ b/build.zig @@ -4,10 +4,10 @@ const builtin = @import("builtin"); const rt = @import("test/runtime_test.zig"); const RuntimeStep = rt.RuntimeStep; const Allocator = std.mem.Allocator; -const Builder = std.build.Builder; +// const Builder = std.build.Builder; const Step = std.build.Step; const Target = std.Target; -const CrossTarget = std.zig.CrossTarget; +const CrossTarget = std.Target.Query; const fs = std.fs; const File = fs.File; const Mode = std.builtin.Mode; @@ -16,12 +16,12 @@ const ArrayList = std.ArrayList; const Fat32 = @import("mkfat32.zig").Fat32; const x86_i686 = CrossTarget{ - .cpu_arch = .i386, + .cpu_arch = .x86, .os_tag = .freestanding, .cpu_model = .{ .explicit = &Target.x86.cpu.i686 }, }; -pub fn build(b: *Builder) !void { +pub fn build(b: *std.Build) !void { const target = b.standardTargetOptions(.{ .whitelist = &[_]CrossTarget{x86_i686}, .default_target = x86_i686 }); const arch = switch (target.getCpuArch()) { .i386 => "x86", @@ -197,7 +197,7 @@ const Fat32BuilderStep = struct { step: Step, /// The builder pointer, also all you need to know - builder: *Builder, + builder: *std.Build, /// The path to where the ramdisk will be written to. out_file_path: []const u8, @@ -237,7 +237,7 @@ const Fat32BuilderStep = struct { /// Return: *Fat32BuilderStep /// The FAT32 builder step pointer to add to the build process. /// - pub fn create(builder: *Builder, options: Fat32.Options, out_file_path: []const u8) *Fat32BuilderStep { + pub fn create(builder: *std.Build, options: Fat32.Options, out_file_path: []const u8) *Fat32BuilderStep { const fat32_builder_step = builder.allocator.create(Fat32BuilderStep) catch unreachable; fat32_builder_step.* = .{ .step = Step.init(.custom, builder.fmt("Fat32BuilderStep", .{}), builder.allocator, make), @@ -255,7 +255,7 @@ const RamdiskStep = struct { step: Step, /// The builder pointer, also all you need to know - builder: *Builder, + builder: *std.Build, /// The target for the build target: CrossTarget, @@ -352,7 +352,7 @@ const RamdiskStep = struct { /// Return: *RamdiskStep /// The ramdisk step pointer to add to the build process. /// - pub fn create(builder: *Builder, target: CrossTarget, files: []const []const u8, out_file_path: []const u8) *RamdiskStep { + pub fn create(builder: *std.Build, target: CrossTarget, files: []const []const u8, out_file_path: []const u8) *RamdiskStep { const ramdisk_step = builder.allocator.create(RamdiskStep) catch unreachable; ramdisk_step.* = .{ .step = Step.init(.custom, builder.fmt("Ramdisk", .{}), builder.allocator, make), diff --git a/test/runtime_test.zig b/test/runtime_test.zig index 6795daf..c9a14e1 100644 --- a/test/runtime_test.zig +++ b/test/runtime_test.zig @@ -193,7 +193,7 @@ pub const RuntimeStep = struct { /// Error.TestFailed - The error if the test failed. /// fn make(step: *Step) (Thread.SpawnError || ChildProcess.SpawnError || Allocator.Error || Error)!void { - const self = @fieldParentPtr(RuntimeStep, "step", step); + const self: RuntimeStep = @fieldParentPtr("step", step); // Create the qemu process self.os_proc = try ChildProcess.init(self.argv, self.builder.allocator); @@ -254,7 +254,7 @@ pub const RuntimeStep = struct { }; // put line in the queue - var node = self.builder.allocator.create(Node) catch unreachable; + const node = self.builder.allocator.create(Node) catch unreachable; node.* = .{ .next = null, .data = line }; self.msg_queue.put(node); }