diff --git a/src/ex/fireworks.zig b/src/ex/fireworks.zig index 63f8648..352ea02 100644 --- a/src/ex/fireworks.zig +++ b/src/ex/fireworks.zig @@ -116,6 +116,8 @@ fn particle(param: ?*anyopaque) void { data.act_y = parent_data.act_y; const explosion_range = parent_data.explosion_range; + mm.zone.gpa.destroy(parent_data); + const angle = @rem(rand(), 65536); data.vel_x = mul_fp_fp(cos(angle), rand_fp_sign()) * explosion_range; data.vel_y = mul_fp_fp(sin(angle), rand_fp_sign()) * explosion_range; diff --git a/src/ex/init.zig b/src/ex/init.zig index 340b154..d55ef9c 100644 --- a/src/ex/init.zig +++ b/src/ex/init.zig @@ -8,64 +8,21 @@ const ps = r.ps; const amd64 = @import("arch"); const turnstile = ke.private.turnstile; -var mtx: ke.Mutex = .init(); -var shared_counter: usize = 0; - -var work_item: ex.WorkItem = undefined; -var work_item2: ex.WorkItem = undefined; - -fn mutex_test(_: ?*anyopaque) void { - while (true) { - mtx.acquire(); - shared_counter += 1; - mtx.release(); - } -} - -fn foo(_: ?*anyopaque) void { - std.log.info("foo cpu {}", .{ke.cpu.current()}); - var timer: ke.Timer = undefined; - timer.init(); - ke.timer.set(&timer, std.time.ns_per_ms * 500, null); - _ = ke.wait.wait_one(&timer.hdr, null) catch unreachable; -} - -fn bar(_: ?*anyopaque) void { - std.log.info("bar cpu {}", .{ke.cpu.current()}); -} - pub fn init(boot_info: *pl.BootInfo) void { mm.init(boot_info); + ps.init(); pl.late_init(boot_info); mm.late_init(); ke.sched.late_init(); - ps.init(); ex.private.workqueue.init() catch @panic("e"); - work_item = .init(.Normal, foo, null); - work_item2 = .init(.Normal, bar, null); - - ex.workqueue.enqueue(&work_item); - ex.workqueue.enqueue(&work_item2); - - if (false) { - if (boot_info.framebuffer != null) { - const t = ps.thread.create_kernel(ke.Thread.Priority.default, ex.fireworks.start, boot_info) catch @panic("oom"); - ke.sched.enqueue(&t.kern); - } - - while (true) {} - } - - const ipl = ke.ipl.raise(.Dispatch); - - for (0..10) |_| { - const t = ps.thread.create_kernel(ke.Thread.Priority.default, mutex_test, null) catch @panic("oom"); + if (boot_info.framebuffer != null) { + const t = ps.thread.create_kernel(ke.Thread.Priority.default, ex.fireworks.start, boot_info) catch @panic("oom"); ke.sched.enqueue(&t.kern); } - ke.ipl.lower(ipl); + while (true) {} while (true) { std.atomic.spinLoopHint(); diff --git a/src/kern/amd64/impl.zig b/src/kern/amd64/impl.zig index 4d38a98..ca6227e 100644 --- a/src/kern/amd64/impl.zig +++ b/src/kern/amd64/impl.zig @@ -5,6 +5,8 @@ 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; @@ -114,3 +116,19 @@ pub inline fn restore_interrupts(state: bool) void { pub inline fn send_resched_ipi(target: u32) void { pl.impl.send_resched_ipi(target); } + +pub inline fn send_tlb_ipi(target: u32) void { + pl.impl.send_tlb_ipi(target); +} + +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); +} diff --git a/src/kern/amd64/int.zig b/src/kern/amd64/int.zig index 329f6bc..33ba97a 100644 --- a/src/kern/amd64/int.zig +++ b/src/kern/amd64/int.zig @@ -4,6 +4,7 @@ const r = @import("root"); const ke = r.ke; const ki = ke.private; const pl = r.pl; +const mi = r.mm.private; extern const __interrupt_vectors: [256]usize; @@ -84,6 +85,10 @@ export fn isr_handler_main(frame: *const amd64.IrqFrame) callconv(.{ .x86_64_sys ki.timer.clock(); } + if (frame.intno == 241) { + ki.shootdown.ipi_handler(); + } + if (frame.intno >= 32) { pl.impl.apic.eoi(); } diff --git a/src/kern/dpc.zig b/src/kern/dpc.zig index 9130175..4aa2c7a 100644 --- a/src/kern/dpc.zig +++ b/src/kern/dpc.zig @@ -121,6 +121,8 @@ fn dispatch_queue(cpu: u32) void { sched_cpu.start_timer = false; sched_cpu.preemption_reason = .None; + ki.shootdown.process_shootdowns(); + if (sched_cpu.next_thread != null) { ki.sched.handle_preemption(sched_cpu); } diff --git a/src/kern/init.zig b/src/kern/init.zig index 18776ad..b67c620 100644 --- a/src/kern/init.zig +++ b/src/kern/init.zig @@ -15,6 +15,7 @@ pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void { pl.early_init(boot_info); ki.cpu.init_cpu(0); ki.turnstile.init_turnstiles(); + ki.shootdown.init(); thread0.init(@intFromPtr(&stack), r.kib(16), ke.Thread.Priority.idle, ki.sched.idle, null); thread0.priority = 0; diff --git a/src/kern/private.zig b/src/kern/private.zig index b631386..7ca18d1 100644 --- a/src/kern/private.zig +++ b/src/kern/private.zig @@ -28,3 +28,4 @@ pub const event = @import("event.zig"); pub const turnstile = @import("turnstile.zig"); pub const mutex = @import("mutex.zig"); pub const queue = @import("queue.zig"); +pub const shootdown = @import("shootdown.zig"); diff --git a/src/kern/root.zig b/src/kern/root.zig index e73e264..538daa1 100644 --- a/src/kern/root.zig +++ b/src/kern/root.zig @@ -17,9 +17,11 @@ pub const Console = p.log.Console; pub const CpuLocal = p.cpu.CpuLocal; pub const ExportedCpuLocal = p.cpu.ExportedCpuLocal; pub const CpuMask = p.cpu.CpuMask; +pub const AtomicCpuMask = p.cpu.AtomicCpuMask; pub const Event = p.event.Event; pub const Queue = p.queue.Queue; pub const Mutex = p.mutex.Mutex; +pub const ShootdownState = p.shootdown.ShootdownState; // === Exported Interfaces === pub const panic = p.panic.panic; @@ -72,5 +74,10 @@ pub const cpu = struct { pub const current = p.cpu.current; }; +pub const shootdown = struct { + pub const submit = p.shootdown.submit; + pub const shootdowns = &p.shootdown.shootdowns; +}; + /// Number of CPUs on the system pub var ncpus: usize = 0; diff --git a/src/kern/shootdown.zig b/src/kern/shootdown.zig new file mode 100644 index 0000000..3a29da1 --- /dev/null +++ b/src/kern/shootdown.zig @@ -0,0 +1,199 @@ +//! Kernel-side mechanism for asynchronous TLB shootdown and deferred memory reclamation. + +const std = @import("std"); +const config = @import("config"); +const rtl = @import("rtl"); +const r = @import("root"); +const ke = r.ke; +const ki = ke.private; +const mm = r.mm; + +comptime { + if (!@hasDecl(ki.impl, "send_tlb_ipi")) @compileError("impl must provide send_tlb_ipi"); + if (!@hasDecl(ki.impl, "flush_full_tlb")) @compileError("impl must provide flush_full_tlb"); + if (!@hasDecl(ki.impl, "flush_tlb")) @compileError("impl must provide flush_tlb"); +} + +/// Structure representing a single shootdown request. +pub const ShootdownState = struct { + /// State of the request. + /// This is used as a counter when the request is outstanding, + /// otherwise it is set to the `slot_*` values below. + state: std.atomic.Value(u16), + link: rtl.List.Entry, + /// Base virtual address of the shootdown. + base: r.VAddr, + /// Number of pages to flush. + npages: u32, + /// Additional metadata. + payload: [2]usize, + + pub fn release(self: *ShootdownState) void { + self.state.store(slot_free, .release); + } +}; + +const PerCpu = struct { + /// States attached to this CPU. + states: [64]ShootdownState, + /// States relevant to each CPU. + /// If CPU0 sent a shootdown to CPU1, then CPU0 will have `valid_states[CPU1]` + /// pointing to that shootdown, which will sit in `states`. + valid_states: [config.ncpus]std.atomic.Value(u64), + /// CPUs that have sent this CPU a shootdown. + senders: ke.AtomicCpuMask, +}; + +const slot_free: u16 = std.math.maxInt(u16); +const slot_reserved: u16 = std.math.maxInt(u16) - 1; +const percpu = ke.CpuLocal(PerCpu, undefined); + +pub var shootdowns: ke.Queue = undefined; + +var sync_counter: std.atomic.Value(usize) = .init(0); +var sync_addr: r.VAddr = 0; +var sync_count: usize = 0; +var shootdown_lock: ke.SpinLock = .init(); + +export const tlb_percpu_init linksection(r.percpu_init) = &pcpu_init; + +fn pcpu_init() linksection(r.init) callconv(.c) void { + const local = percpu.local(); + + local.valid_states = @splat(.init(0)); + local.senders = .init(false); + local.states = @splat(.{ .base = 0, .npages = 0, .state = .init(slot_free), .payload = undefined, .link = undefined }); +} + +/// Allocate an invalidation state slot on this CPU. +/// O(N) algorithm but N is tiny. +fn allocate_slot(cpu: *PerCpu) ?usize { + for (0.., &cpu.states) |slot, *state| { + if (state.state.load(.monotonic) != slot_free) continue; + + if (state.state.cmpxchgStrong(slot_free, slot_reserved, .acquire, .monotonic) == null) { + return slot; + } + } + + return null; +} + +fn flush_range(va: r.VAddr, npages: usize) void { + if (npages > ki.impl.tlb_max_pages) { + // Just flush the entire thing. + ki.impl.flush_full_tlb(); + return; + } + + for (0..npages) |i| { + ki.impl.flush_tlb(va + i * mm.page_size); + } +} + +/// Synchronous shootdown path. +fn do_synchronous_shootdown(state: ShootdownState) void { + shootdown_lock.acquire_no_ipl(); + + sync_addr = state.base; + sync_count = state.npages; + sync_counter.store(ke.ncpus - 1, .release); + + const cur = ke.cpu.current(); + + for (0..ke.ncpus) |i| { + if (i == cur) continue; + ki.impl.send_tlb_ipi(@truncate(i)); + } + + while (sync_counter.load(.monotonic) != 0) { + std.atomic.spinLoopHint(); + } + + shootdown_lock.release_no_ipl(); +} + +/// Called by the TLB interrupt handler. +pub fn ipi_handler() void { + flush_range(sync_addr, sync_count); + _ = sync_counter.fetchSub(1, .release); +} + +/// Called in a quiescent state to process the pending shootdowns. +/// This must be called at IPL dispatch. +pub fn process_shootdowns() void { + const cpu = percpu.local(); + const curcpu = ke.cpu.current(); + + if (cpu.senders.is_all(false, .monotonic)) { + // Nothing to do. + return; + } + + var iter = cpu.senders.iter(.monotonic); + + // Go through every sender and get their states. + while (iter.next()) |i| { + if (i == curcpu) continue; + + cpu.senders.clear(i, .monotonic); + + const sender = percpu.remote(@truncate(i)); + + while (true) { + var states = sender.valid_states[curcpu].swap(0, .acquire); + if (states == 0) break; + + while (states != 0) { + // Go through each set bit and invalidate the slot. + const bit = @ctz(states); + states &= ~(@as(u64, 1) << @intCast(bit)); + + const state = &sender.states[bit]; + flush_range(state.base, state.npages); + + if (state.state.fetchSub(1, .release) == 1) { + shootdowns.insert(&state.link, .Tail); + } + } + } + } +} + +/// Submit a shootdown to occur asynchronously on `target_mask`. +/// Returns an error if a synchronous shootdown was done instead. +pub fn submit(state: ShootdownState, target_mask: ke.CpuMask) !void { + const ipl = ke.ipl.raise(.Dispatch); + defer ke.ipl.lower(ipl); + + // Flush on our local TLB. + flush_range(state.base, state.npages); + + // Get a slot and fill it. + const curcpu = ke.cpu.current(); + const cpu = percpu.local(); + const slot = allocate_slot(cpu) orelse { + do_synchronous_shootdown(state); + return error.NoSlot; + }; + + cpu.states[slot] = state; + cpu.states[slot].state.store(@truncate(target_mask.count()), .release); + + var iter = target_mask.iter(); + + while (iter.next()) |bit| { + if (bit == curcpu) continue; + + _ = cpu.valid_states[bit].fetchOr(@as(u64, 1) << @truncate(slot), .release); + + ki.ipl.set_softint_pending(@truncate(bit), .Dispatch); + + // Tell him we have something for him. + percpu.remote(@truncate(bit)).senders.set(curcpu, .release); + } +} + +pub fn init() void { + shootdowns.init(ke.ncpus); +} diff --git a/src/mm/heap.zig b/src/mm/heap.zig index e9f941a..9baac94 100644 --- a/src/mm/heap.zig +++ b/src/mm/heap.zig @@ -16,8 +16,14 @@ pub fn alloc(size: usize) mm.Error!*anyopaque { mi.kernel_space.pmap.map_range_allocating(addr, size, .{ .read = true, .write = true, - .global = true, + .global = false, }); return @ptrFromInt(addr); } + +pub fn free(va: r.VAddr, size: usize) void { + const ipl = mi.kernel_space.lock.acquire(); + mi.tlb.reclaim_range(&mi.kernel_space, va, size); + mi.kernel_space.lock.release(ipl); +} diff --git a/src/mm/init.zig b/src/mm/init.zig index d28cfc2..95f109a 100644 --- a/src/mm/init.zig +++ b/src/mm/init.zig @@ -103,4 +103,5 @@ pub fn init(boot_info: *pl.BootInfo) linksection(r.init) void { pub fn late_init() linksection(r.init) void { mi.zone.late_init(); + mi.tlb.init(); } diff --git a/src/mm/phys.zig b/src/mm/phys.zig index 0beed83..4f0d656 100644 --- a/src/mm/phys.zig +++ b/src/mm/phys.zig @@ -16,13 +16,11 @@ const ke = r.ke; const log = std.log.scoped(.@"mm/phys"); -// === Early state === var bootstrapped = false; var early_alloc_entry_idx: usize = 0; var memory_map: *pl.BootInfo.MemMap = undefined; var early_allocs: usize = 0; -// === Memory allocator state === /// Head for the global treiber stack. const GlobalHead = packed struct(u64) { pfn: mm.Pfn, @@ -41,13 +39,11 @@ const PerCpu = struct { const percpu = ke.CpuLocal(PerCpu, undefined); -const null_pfn: mm.Pfn = std.math.maxInt(mm.Pfn); - // TODO: make this configurable (or dynamic). const batch_size: u8 = 64; var batch_pool: std.atomic.Value(GlobalHead) = .init(.{ - .pfn = null_pfn, + .pfn = mm.null_pfn, .tag = 0, }); @@ -83,7 +79,7 @@ fn pop_from_pool() ?*mm.Page { while (true) { const batch_pfn = old_head.pfn; - if (batch_pfn == null_pfn) { + if (batch_pfn == mm.null_pfn) { return null; } @@ -177,15 +173,15 @@ pub fn free(addr: r.PAddr) void { const page = mm.pfn_to_struct_page(mm.page_to_pfn(addr)); - page.free.next_pfn = null_pfn; - page.free.batch_next = null_pfn; + page.free.next_pfn = mm.null_pfn; + page.free.batch_next = mm.null_pfn; page.free.batch_count = 0; if (cpu.active_batch) |curr| { // Add the page to the current batch. page.free.next_pfn = mm.struct_page_to_pfn(curr); } else { - page.free.next_pfn = null_pfn; + page.free.next_pfn = mm.null_pfn; } cpu.active_batch = page; diff --git a/src/mm/pmap.zig b/src/mm/pmap.zig index b655e0a..2308152 100644 --- a/src/mm/pmap.zig +++ b/src/mm/pmap.zig @@ -103,6 +103,17 @@ pub const PMap = struct { self.map_from(va, size, src); } + /// Unmap a contiguous range of virtual pages. + /// Returns a list threaded through PFNs of the unmapped physical pages if any. + pub fn unmap(self: *Self, va: r.VAddr, size: usize) ?mm.Pfn { + return self.impl.unmap(va, size); + } + + /// Flush the TLB entry of a single page. + pub fn flush(self: *Self, va: r.VAddr) void { + self.impl.flush(va); + } + /// Map a single page. Convenience wrapper around map_contiguous_range. pub fn map_page(self: *Self, va: r.VAddr, pa: r.PAddr, flags: mm.MapFlags) void { self.map_contiguous_range(va, pa, mm.page_size, flags); diff --git a/src/mm/private.zig b/src/mm/private.zig index ff88005..700517f 100644 --- a/src/mm/private.zig +++ b/src/mm/private.zig @@ -7,6 +7,7 @@ pub const vmem = @import("vmem.zig"); pub const heap = @import("heap.zig"); pub const zone = @import("zone.zig"); pub const PMap = pmap.PMap; +pub const tlb = @import("tlb.zig"); const ke = @import("root").ke; @@ -22,4 +23,8 @@ pub const impl = switch (config.arch) { else => @compileError("unsupported architecture"), }; -pub var kernel_space: Space = undefined; +pub var kernel_space: Space = .{ + .pmap = undefined, + .arena = undefined, + .lock = .init(), +}; diff --git a/src/mm/radix_pmap.zig b/src/mm/radix_pmap.zig index cb58532..7d5e280 100644 --- a/src/mm/radix_pmap.zig +++ b/src/mm/radix_pmap.zig @@ -109,6 +109,46 @@ pub fn RadixPmap(comptime Impl: type) type { } } + /// Unmap a contiguous range of virtual pages. + /// Only small pages are supported. + /// Return a pfn that points to the physical pages that were unmapped. + pub fn unmap(self: *Self, va: r.VAddr, size: usize) ?mm.Pfn { + std.debug.assert(std.mem.isAligned(va, mm.page_size)); + + var c = self.cursor(va); + + const npages: usize = size / mm.page_size; + var head_pfn: mm.Pfn = mm.null_pfn; + + for (0..npages) |_| { + var level: usize = num_levels - 1; + blk: while (true) { + const idx = index_for_level(c.va, level); + const pte = c.tables[level][idx]; + + if (!pte.present) break :blk; + + if (level == 0) { + // Found a present entry, add it to the list. + const addr = pte.address(); + const pfn = mm.page_to_pfn(addr); + const page = mm.pfn_to_struct_page(pfn); + page.free.next_pfn = head_pfn; + head_pfn = pfn; + c.tables[level][idx] = std.mem.zeroes(Impl.Pte); + break; + } + + if (!c.walk_down_resolve(level - 1)) return null; + level -= 1; + } + + c.advance(mm.page_size); + } + + return if (head_pfn == mm.null_pfn) null else head_pfn; + } + /// Ensure all intermediate page tables down to `target_level` exist for /// the given virtual address range, allocating them as needed. Useful for /// pre-populating a shared region (e.g. the kernel half of a user pmap) diff --git a/src/mm/root.zig b/src/mm/root.zig index 59bb293..6b43c5b 100644 --- a/src/mm/root.zig +++ b/src/mm/root.zig @@ -15,6 +15,7 @@ pub const Space = private.Space; // 16 TB of maximum physical memory. // This should be fine on consumer hardware for at least a decade :^) pub const Pfn = u32; +pub const null_pfn: Pfn = std.math.maxInt(Pfn); pub const Page = extern union { free: extern struct { diff --git a/src/mm/tlb.zig b/src/mm/tlb.zig new file mode 100644 index 0000000..284b44f --- /dev/null +++ b/src/mm/tlb.zig @@ -0,0 +1,101 @@ +//! MM-level TLB shootdown support + +const r = @import("root"); +const std = @import("std"); +const rtl = @import("rtl"); +const ke = r.ke; +const mm = r.mm; +const mi = mm.private; +const ps = r.ps; + +pub var sync_shootdowns: std.atomic.Value(usize) = .init(0); +pub var async_shootdowns: std.atomic.Value(usize) = .init(0); + +fn free_pages(head_pfn: mm.Pfn) void { + var pfn = head_pfn; + + // Free all the physical pages. + while (pfn != mm.null_pfn) { + const page = mm.pfn_to_struct_page(pfn); + const next = page.free.next_pfn; + mm.phys.free(mm.pfn_to_page(pfn)); + pfn = next; + } +} + +/// Reclaim the memory associated with a state. +/// This can only be done when all CPUs have flushed their TLB. +fn reclaim_state(state: *ke.ShootdownState) void { + const space: *mm.Space = @ptrFromInt(state.payload[0]); + const pfn: u32 = @truncate(state.payload[1]); + const base = state.base; + const npages = state.npages; + + // Reclaim the virtual address space. + const ipl = space.lock.acquire(); + + space.arena.free(base, @as(usize, npages) * mm.page_size) catch unreachable; + space.lock.release(ipl); + + _ = async_shootdowns.fetchAdd(1, .monotonic); + + // Free the physical pages + free_pages(pfn); + + // Release the slot after all copied state has been consumed. + state.release(); +} + +fn worker_thread(_: ?*anyopaque) void { + while (true) { + const entry = ke.shootdown.shootdowns.remove(); + const state: *ke.ShootdownState = @fieldParentPtr("link", entry); + + _ = async_shootdowns.fetchAdd(1, .monotonic); + reclaim_state(state); + } +} + +/// Unmap and flush a virtual range on the given space. +/// Space lock is held on entry and IPL is raised to Dispatch. +pub fn reclaim_range(space: *mm.Space, va: r.VAddr, size: usize) void { + std.debug.assert(std.mem.isAligned(va, mm.page_size)); + + // Unmap the virtual addresses and get the backing physical pages. + const head_pfn = space.pmap.unmap(va, size) orelse { + // If no physical pages, free the VA directly. + space.arena.free(va, size) catch unreachable; + return; + }; + + const state: ke.ShootdownState = .{ + .base = va, + .npages = @truncate(size / mm.page_size), + .link = undefined, + .state = .init(0), + .payload = .{ + @intFromPtr(space), + @intCast(head_pfn), + }, + }; + + var mask: ke.CpuMask = .init(false); + + for (0..ke.ncpus) |i| { + if (i != ke.cpu.current()) { + mask.set(i); + } + } + + ke.shootdown.submit(state, mask) catch { + _ = sync_shootdowns.fetchAdd(1, .monotonic); + space.arena.free(va, size) catch unreachable; + free_pages(head_pfn); + return; + }; +} + +pub fn init() void { + const td = ps.thread.create_kernel(ke.Thread.Priority.default, worker_thread, null) catch unreachable; + ke.sched.enqueue(&td.kern); +} diff --git a/src/mm/zone.zig b/src/mm/zone.zig index 41951c6..adda49b 100644 --- a/src/mm/zone.zig +++ b/src/mm/zone.zig @@ -20,8 +20,10 @@ const Slab = struct { /// Reference count indicating how many objects are in use. refcount: u16, capacity: u16, - /// Base address of the slab. + /// Base address of the slab + its color. base: usize, + /// Base address of the slab allocation. + buf: usize, pub fn bitmap(self: *Slab) [*]u64 { return @ptrFromInt(@intFromPtr(self) + @sizeOf(Slab)); @@ -623,6 +625,7 @@ pub const Zone = struct { if (rem > 0) bm[full_words] = (@as(u64, 1) << @intCast(rem)) - 1; ret.base = @intFromPtr(buf) + self.color; + ret.buf = @intFromPtr(buf); ret.capacity = @intCast(capacity); ret.refcount = 0; @@ -667,7 +670,7 @@ pub const Zone = struct { const alloc_size = @sizeOf(Slab) + Slab.bitmap_bytes(slab.capacity); gpa.free(@as([*]u8, @ptrCast(slab))[0..alloc_size]); - // FIXME: call free_pages on slab.base here! + mm.heap.free(slab.buf, self.slab_size); } else { free_page(@ptrFromInt(std.mem.alignBackward(usize, @intFromPtr(slab), mm.page_size))); } @@ -842,9 +845,8 @@ fn gpa_free( _: usize, ) void { if (buf.len > 2048) { - //const pages = std.mem.alignForward(usize, buf.len, mm.page_size); - // TODO - //mi.heap.free_pages(@ptrCast(buf.ptr), pages); + const pages = std.mem.alignForward(usize, buf.len, mm.page_size); + mm.heap.free(@intFromPtr(buf.ptr), pages); return; } diff --git a/src/platform/pc/impl.zig b/src/platform/pc/impl.zig index 930f2f8..f27381c 100644 --- a/src/platform/pc/impl.zig +++ b/src/platform/pc/impl.zig @@ -109,3 +109,7 @@ pub fn arm_timer(deadline: r.Nanoseconds) void { pub fn send_resched_ipi(target_cpu: u32) void { apic.send_ipi(smp.cpu_id_to_apic_id[target_cpu], 240, 0); } + +pub fn send_tlb_ipi(target_cpu: u32) void { + apic.send_ipi(smp.cpu_id_to_apic_id[target_cpu], 241, 0); +}