Files
Zag/src/kern/amd64/impl.zig
T
Loïc Grégoire 619c373d47 all: tidy
2026-10-07 18:03:06 -04:00

249 lines
5.8 KiB
Zig

//! amd64-specific kernel code.
const r = @import("root");
const ke = r.ke;
const kep = ke.private;
const pl = r.pl;
const amd64 = r.arch;
const std = @import("std");
pub const tlb_max_pages = 33;
pub const init = @import("init.zig");
pub const early_init = init.early_init;
pub export var cpu_self_offset: usize linksection(r.percpu) = 0;
pub export var cpu_offsets: [*]usize = undefined;
const ThreadFrame = extern struct {
rbp: u64 align(1),
rbx: u64 align(1),
r12: u64 align(1),
r13: u64 align(1),
r14: u64 align(1),
r15: u64 align(1),
rip: u64 align(1),
};
const Context = ke.Thread.Context;
extern fn asm_thread_entry() void;
extern fn amd64_context_load(td: *ThreadContext) callconv(.c) void;
extern fn amd64_context_switch(
old: *Context,
new: *Context,
lock: *u8,
switching: *bool,
) callconv(.c) void;
extern fn amd64_context_switch_cont_n(
old: *Context,
new: *Context,
lock: *u8,
switching: *bool,
) callconv(.c) void;
extern fn amd64_call_continuation(
td: *ke.Thread,
ctx: *Context,
func: usize,
arg: ?*anyopaque,
) callconv(.c) noreturn;
export fn ki_free_old_stack(stack_top: usize) callconv(.c) void {
kep.thread.stack_cache_free(stack_top);
}
/// Trampoline to match the zig ABI.
export fn ki_continuation_trampoline(
func: usize,
arg: ?*anyopaque,
) noreturn {
ke.ipl.lower(.Passive);
const f: *const fn (?*anyopaque) void = @ptrFromInt(func);
f(arg);
@panic("Continuation returned");
}
pub const ThreadContext = extern struct {
rsp: u64 align(1) = 0xDEADC0D3,
export fn thread_entry(entry: usize, arg: usize) callconv(.c) void {
const entry_fn: *const fn (?*anyopaque) void = @ptrFromInt(entry);
ke.ipl.lower(.Passive);
entry_fn(@ptrFromInt(arg));
}
pub fn init_with_stack(
stack: r.VAddr,
) @This() {
return .{
.rsp = stack,
};
}
pub fn reset(
self: *@This(),
stack_top: r.VAddr,
entry: *const fn (?*anyopaque) void,
arg: ?*anyopaque,
) @This() {
var sp: usize = stack_top;
sp = sp & ~@as(usize, 15);
sp = sp - @sizeOf(ThreadFrame);
sp = (sp & ~@as(usize, 15)) - 8;
const frame: *ThreadFrame = @ptrFromInt(sp);
frame.* = std.mem.zeroes(ThreadFrame);
self.rsp = @intFromPtr(frame);
frame.rip = @intFromPtr(&asm_thread_entry);
frame.r12 = @intFromPtr(entry);
frame.r13 = @intFromPtr(arg);
return self.*;
}
pub fn load(self: *ThreadContext) void {
amd64_context_load(self);
}
};
pub fn switch_normal_to_normal(
old: *Context,
new: *Context,
) void {
const thread: *ke.Thread = @alignCast(@fieldParentPtr("context", old));
amd64_context_switch(
old,
new,
&thread.lock.inner.locked.raw,
&thread.switching.raw,
);
}
pub fn switch_cont_to_normal(
old: *Context,
new: *Context,
) void {
const td: *ke.Thread = @alignCast(@fieldParentPtr("context", old));
amd64_context_switch_cont_n(
old,
new,
&td.lock.inner.locked.raw,
&td.switching.raw,
);
}
pub fn call_continuation(
ctx: *Context,
continuation: ke.Continuation,
) noreturn {
const td: *ke.Thread = @alignCast(@fieldParentPtr("context", ctx));
amd64_call_continuation(
td,
ctx,
@intFromPtr(continuation.func),
continuation.arg,
);
}
inline fn percpu_ptr_for(variable: anytype, cpu: u32) @TypeOf(variable) {
return @ptrFromInt(@intFromPtr(variable) +% cpu_offsets[cpu]);
}
pub inline fn percpu_ptr_other(variable: anytype, id: u32) @TypeOf(variable) {
return percpu_ptr_for(variable, id);
}
pub inline fn halt() void {
asm volatile ("hlt");
}
pub inline fn percpu_ptr(variable: anytype) @TypeOf(variable) {
const offset = asm volatile ("mov %%gs:(%[self]), %[out]"
: [out] "=r" (-> u64),
: [self] "r" (&cpu_self_offset),
);
return @ptrFromInt(offset +% @intFromPtr(variable));
}
pub inline fn set_hardware_ipl(ipl: ke.Ipl) void {
const cr8_value: u64 = switch (ipl) {
.Passive, .Dispatch => 0,
.Device => 13,
.High => 15,
};
asm volatile ("mov %[ipl], %%cr8"
:
: [ipl] "r" (cr8_value),
);
}
pub inline fn enable_interrupts() void {
amd64.sti();
}
pub inline fn disable_interrupts() bool {
const ie = amd64.rflags().interrupt_enable;
amd64.cli();
return ie;
}
pub inline fn restore_interrupts(state: bool) void {
if (state) {
enable_interrupts();
} else {
_ = disable_interrupts();
}
}
pub inline fn flush_full_tlb() void {
const prev_cr3 = amd64.read_cr(3);
// Reload CR3 to flush the entire TLB.
// This works because we assume global pages are never unmapped.
// If they are in the future, this should toggle CR4.PGE instead.
amd64.write_cr(3, prev_cr3);
}
pub inline fn flush_tlb(va: r.VAddr) void {
amd64.invlpg(va);
}
// Inline asm so the compiler has a chance to inline it.
pub inline fn raise_software_ipl(new: ke.Ipl, old: *ke.Ipl) bool {
return asm volatile (
\\movb %%gs:cpu_ipl, %%cl
\\movb %%cl, (%[old_ptr])
\\movb %[new_val], %%gs:cpu_ipl
\\cmpb %%cl, %[new_val]
\\setl %[result]
: [result] "=r" (-> bool),
: [new_val] "r" (new),
[old_ptr] "r" (old),
: .{ .rcx = true, .memory = true });
}
pub inline fn lower_software_ipl(new: ke.Ipl, old: *ke.Ipl) bool {
return asm volatile (
\\movb %%gs:cpu_ipl, %%cl
\\movb %%cl, (%[old_ptr])
\\movb %[new_val], %%gs:cpu_ipl
\\cmpb %[new_val], %%cl
\\setl %[result]
: [result] "=r" (-> bool),
: [new_val] "r" (new),
[old_ptr] "r" (old),
: .{ .rcx = true, .memory = true });
}