From ed324fcf9e990e9074dae37d4ae177d59aef87a8 Mon Sep 17 00:00:00 2001 From: Gabriel Date: Sat, 15 Feb 2025 22:45:32 +0100 Subject: [PATCH] core: Add basic threading code --- core/src/arch/cpu.zig | 39 +++++++++++++++ core/src/arch/thread.zig | 9 ++++ core/src/arch/x86_64/cpu.zig | 3 ++ core/src/arch/x86_64/thread.zig | 74 +++++++++++++++++++++++++++ core/src/thread.zig | 89 +++++++++++++++++++++++++++++++++ 5 files changed, 214 insertions(+) create mode 100644 core/src/arch/cpu.zig create mode 100644 core/src/arch/thread.zig create mode 100644 core/src/arch/x86_64/cpu.zig create mode 100644 core/src/arch/x86_64/thread.zig create mode 100644 core/src/thread.zig diff --git a/core/src/arch/cpu.zig b/core/src/arch/cpu.zig new file mode 100644 index 0000000..2b06a11 --- /dev/null +++ b/core/src/arch/cpu.zig @@ -0,0 +1,39 @@ +const std = @import("std"); +const target = @import("builtin").target; +const thread = @import("../thread.zig"); +const pmm = @import("../pmm.zig"); +const vmm = @import("vmm.zig").arch; + +pub const arch = switch (target.cpu.arch) { + .x86_64 => @import("x86_64/cpu.zig"), + else => { + @compileError("unsupported architecture"); + }, +}; + +// FIXME: single-core hack, we need a proper way to figure which core this is when SMP support is added. +var this_core: *arch.Core = undefined; + +pub fn setupCore(allocator: *pmm.FrameAllocator) !void { + const frame = try pmm.allocFrame(allocator); + + const core: *arch.Core = @ptrFromInt(frame.virtualAddress(vmm.PHYSICAL_MAPPING_BASE)); + core.id = 0; // FIXME: Actually check core id + core.thread_list = .{}; + + const idle_thread = &core.idle_thread.data; + + idle_thread.id = 0; + idle_thread.directory = null; + idle_thread.regs = std.mem.zeroes(@TypeOf(idle_thread.regs)); + thread.arch.initKernelRegisters(&idle_thread.regs); + thread.arch.setAddress(&idle_thread.regs, @intFromPtr(&thread.arch.idleLoop)); + + core.thread_list.append(&core.idle_thread); + + this_core = core; +} + +pub fn thisCore() *arch.Core { + return this_core; +} diff --git a/core/src/arch/thread.zig b/core/src/arch/thread.zig new file mode 100644 index 0000000..acf241c --- /dev/null +++ b/core/src/arch/thread.zig @@ -0,0 +1,9 @@ +const std = @import("std"); +const target = @import("builtin").target; + +pub const arch = switch (target.cpu.arch) { + .x86_64 => @import("x86_64/thread.zig"), + else => { + @compileError("unsupported architecture"); + }, +}; diff --git a/core/src/arch/x86_64/cpu.zig b/core/src/arch/x86_64/cpu.zig new file mode 100644 index 0000000..9e806e2 --- /dev/null +++ b/core/src/arch/x86_64/cpu.zig @@ -0,0 +1,3 @@ +const thread = @import("../../thread.zig"); + +pub const Core = struct { id: u32, thread_list: thread.ThreadList, current_thread: *thread.ThreadControlBlock, idle_thread: thread.ThreadList.Node }; diff --git a/core/src/arch/x86_64/thread.zig b/core/src/arch/x86_64/thread.zig new file mode 100644 index 0000000..7049400 --- /dev/null +++ b/core/src/arch/x86_64/thread.zig @@ -0,0 +1,74 @@ +const std = @import("std"); +const interrupts = @import("interrupts.zig"); + +pub inline fn enterTask(regs: *interrupts.InterruptStackFrame, comptime base: u64, directory: *anyopaque) noreturn { + asm volatile ( + \\ addq %[base], %rsp + \\ push %[ss] + \\ push %[rsp] + \\ push %[rflags] + \\ push %[cs] + \\ push %[rip] + \\ mov %[directory], %cr3 + \\ mov $0, %rax + \\ mov $0, %rbx + \\ mov $0, %rcx + \\ mov $0, %rdx + \\ mov $0, %rsi + \\ mov $0, %rbp + \\ mov $0, %r8 + \\ mov $0, %r9 + \\ mov $0, %r10 + \\ mov $0, %r11 + \\ mov $0, %r12 + \\ mov $0, %r13 + \\ mov $0, %r14 + \\ mov $0, %r15 + \\ iretq + : + : [ss] "r" (regs.ss), + [rsp] "r" (regs.rsp), + [rflags] "r" (regs.rflags), + [cs] "r" (regs.cs), + [rip] "r" (regs.rip), + [arg] "{rdi}" (regs.rdi), + [base] "r" (base), + [directory] "r" (directory), + ); + unreachable; +} + +pub fn idleLoop() callconv(.Naked) noreturn { + asm volatile ( + \\.loop: + \\ sti + \\ hlt + \\ jmp .loop + ); +} + +pub fn setAddress(regs: *interrupts.InterruptStackFrame, address: u64) void { + regs.rip = address; +} + +pub fn setArgument(regs: *interrupts.InterruptStackFrame, argument: u64) void { + regs.rdi = argument; +} + +pub fn setStack(regs: *interrupts.InterruptStackFrame, stack: u64) void { + regs.rsp = stack; +} + +pub fn initKernelRegisters(regs: *interrupts.InterruptStackFrame) void { + regs.* = std.mem.zeroes(interrupts.InterruptStackFrame); + regs.cs = 0x08; + regs.ss = 0x10; + regs.rflags = 1 << 9; // IF (Interrupt enable flag) +} + +pub fn initUserRegisters(regs: *interrupts.InterruptStackFrame) void { + regs.* = std.mem.zeroes(interrupts.InterruptStackFrame); + regs.cs = 0x18 | 3; + regs.ss = 0x20 | 3; + regs.rflags = 1 << 9; // IF (Interrupt enable flag) +} diff --git a/core/src/thread.zig b/core/src/thread.zig new file mode 100644 index 0000000..80aa620 --- /dev/null +++ b/core/src/thread.zig @@ -0,0 +1,89 @@ +const std = @import("std"); +const vmm = @import("arch/vmm.zig").arch; +const interrupts = @import("arch/interrupts.zig").arch; +pub const arch = @import("arch/thread.zig").arch; +const pmm = @import("pmm.zig"); +const cpu = @import("arch/cpu.zig"); + +pub const ThreadState = enum { + Running, +}; + +pub const ThreadControlBlock = struct { + id: u64, + directory: ?*vmm.PageDirectory, + regs: interrupts.InterruptStackFrame, + + ticks: u64, +}; + +pub const ThreadList = std.DoublyLinkedList(ThreadControlBlock); + +var g_threads: ThreadList = .{}; + +const ALLOCATED_TICKS_PER_TASK = 20; + +pub fn enterTask(task: *ThreadControlBlock) noreturn { + cpu.thisCore().current_thread = task; + + task.ticks = ALLOCATED_TICKS_PER_TASK; + + var directory = vmm.readPageDirectory(); + + if (task.directory) |dir| { + directory = vmm.getPhysicalPageDirectory(dir); + } + + arch.enterTask(&task.regs, vmm.PHYSICAL_MAPPING_BASE, @ptrCast(directory)); +} + +pub fn switchTask(regs: *interrupts.InterruptStackFrame, new_task: *ThreadControlBlock) void { + const core = cpu.thisCore(); + + core.current_thread.regs = regs.*; + regs.* = new_task.regs; + + if (new_task.directory) |directory| { + if (vmm.readPageDirectory() != directory) vmm.setPageDirectory(directory); + } + + new_task.ticks = ALLOCATED_TICKS_PER_TASK; + + core.current_thread = new_task; +} + +pub fn scheduleNewTask(regs: *interrupts.InterruptStackFrame) void { + const core = cpu.thisCore(); + + const new_task = core.thread_list.popFirst() orelse return; + core.thread_list.append(new_task); + + switchTask(regs, &new_task.data); +} + +pub fn preempt(regs: *interrupts.InterruptStackFrame) void { + const core = cpu.thisCore(); + + core.current_thread.ticks -= 1; + if (core.current_thread.ticks == 0) { + scheduleNewTask(regs); + } +} + +var next_id: std.atomic.Value(u64) = std.atomic.Value(u64).init(1); + +pub fn addThreadToScheduler(core: *cpu.arch.Core, thread: *ThreadControlBlock) void { + core.thread_list.append(@fieldParentPtr("data", thread)); +} + +pub fn createThreadControlBlock(allocator: *pmm.FrameAllocator) !*ThreadControlBlock { + const frame = try pmm.allocFrame(allocator); + + const node: *ThreadList.Node = @ptrFromInt(frame.virtualAddress(vmm.PHYSICAL_MAPPING_BASE)); + const thread = &node.data; + thread.id = next_id.fetchAdd(1, .seq_cst); + thread.directory = null; + thread.regs = std.mem.zeroes(@TypeOf(thread.regs)); + + return thread; +}