using System.Runtime.InteropServices; namespace XFESystem.NativeKernel; [StructLayout(LayoutKind.Sequential)] public unsafe struct KernelThread { public ulong State, Id, Reservation, ReservationBytes, StackLow, StackHigh; public delegate* unmanaged Entry; public void* Argument; public ulong Tls, OwnTls, WakeAt; public void* RuntimeThread; public InterruptFrame* Interrupt; public ulong WaitHandle, ExitResult, InterruptStackTop; } [StructLayout(LayoutKind.Sequential)] public unsafe struct SwitchContext { public ulong Rbx, Rbp, R12, R13, R14, R15, Rsp, Rip, Rflags, FsBase, Rax, Rdx; public fixed byte FxState[512]; } [StructLayout(LayoutKind.Sequential)] public struct RuntimeLimitedContext { public ulong Ip, Rsp, Rbp, Rax, Rbx, Rdx, R12, R13, R14, R15; } // Single-BSP scheduler, actual stacks/context switching, no host thread API. public static unsafe class Threads { const uint Capacity = 32; public const ulong InterruptStackBytes = 65536; const ulong Runnable = 1, Running = 2, Waiting = 3, Finished = 4; static KernelThread* _threads; static byte* _contexts; static uint _current; static ulong _nextId; static bool _scheduling; public static bool Ready; public static ulong Switches, Preemptions; public static KernelThread* Current => Ready ? _threads + _current : null; static SwitchContext* Context(uint slot) => (SwitchContext*)(_contexts + slot * 1024); public static bool Initialize() { if (Ready || sizeof(KernelThread) != 128 || sizeof(SwitchContext) != 608) return false; _threads = (KernelThread*)Native.Storage(7); _contexts = (byte*)Native.Storage(8); if (_threads == null || _contexts == null) return false; for (uint i = 0; i < Capacity; i++) { _threads[i] = new KernelThread(); for (uint j = 0; j < 1024; j++) _contexts[i * 1024 + j] = 0; } _current = 0; _nextId = 1; _threads[0].State = Running; _threads[0].Id = _nextId++; _threads[0].StackHigh = Native.BootStackTop(); _threads[0].StackLow = _threads[0].StackHigh - 1024 * 1024; // The boot stack belongs to Limine. Reserve only a guarded IRQ stack for // the BSP thread; new threads place their user and IRQ stacks in one region. ulong interruptRegion = VirtualMemory.Reserve(InterruptStackBytes + 8192, 4096); if (interruptRegion == 0) return false; if (!VirtualMemory.Commit(interruptRegion + 4096, InterruptStackBytes)) { VirtualMemory.Release(interruptRegion, InterruptStackBytes + 8192); return false; } _threads[0].Reservation = interruptRegion; _threads[0].ReservationBytes = InterruptStackBytes + 8192; _threads[0].InterruptStackTop = interruptRegion + 4096 + InterruptStackBytes; if (!Cpu.SetInterruptStack(_threads[0].InterruptStackTop)) return false; Ready = true; return true; } public static ulong Create(delegate* unmanaged entry, void* argument, ulong stackBytes, ulong tls, bool ownTls) { ulong flags = Native.IrqSave(); ulong result = CreateLocked(entry, argument, stackBytes, tls, ownTls); Native.IrqRestore(flags); return result; } static ulong CreateLocked(delegate* unmanaged entry, void* argument, ulong stackBytes, ulong tls, bool ownTls) { if (!Ready || entry == null || stackBytes < 16384 || stackBytes > 32 * 1024 * 1024) return 0; Reap(); uint slot = Capacity; for (uint i = 1; i < Capacity; i++) if (_threads[i].State == 0) { slot = i; break; } if (slot == Capacity) return 0; stackBytes = (stackBytes + 4095) & ~4095UL; ulong regionBytes = stackBytes + InterruptStackBytes + 3 * 4096; ulong reservation = VirtualMemory.Reserve(regionBytes, 4096); ulong interruptLow = reservation + stackBytes + 8192; if (reservation == 0 || !VirtualMemory.Commit(reservation + 4096, stackBytes) || !VirtualMemory.Commit(interruptLow, InterruptStackBytes)) { if (reservation != 0) VirtualMemory.Release(reservation, regionBytes); return 0; } SwitchContext* context = Context(slot); for (uint i = 0; i < 1024; i++) _contexts[slot * 1024 + i] = 0; _threads[slot] = new KernelThread { State = Runnable, Id = _nextId++, Reservation = reservation, ReservationBytes = regionBytes, StackLow = reservation + 4096, StackHigh = reservation + 4096 + stackBytes, Entry = entry, Argument = argument, Tls = tls, OwnTls = ownTls ? 1UL : 0UL, InterruptStackTop = interruptLow + InterruptStackBytes }; context->Rbx = slot; context->Rsp = _threads[slot].StackHigh - 8; context->Rip = Native.ThreadTrampolineAddress(); context->Rflags = 0x202; context->FsBase = tls; Native.ContextInitialize(context); return _threads[slot].Id; } static void Reap() { for (uint i = 1; i < Capacity; i++) { if (i == _current || _threads[i].State != Finished) continue; if (!VirtualMemory.Release(_threads[i].Reservation, _threads[i].ReservationBytes)) Kernel.Fail("THREAD_STACK_RELEASE"); if (_threads[i].OwnTls != 0 && Native.ThreadTlsRelease(_threads[i].Tls) != 1) Kernel.Fail("THREAD_TLS_RELEASE"); _threads[i].State = 0; } } static void WakeExpired() { ulong now = MonotonicClock.Nanoseconds(); for (uint i = 0; i < Capacity; i++) if (_threads[i].State == Waiting && _threads[i].WakeAt != ~0UL && now >= _threads[i].WakeAt) _threads[i].State = Runnable; } static uint Choose() { for (uint step = 1; step <= Capacity; step++) { uint slot = (_current + step) % Capacity; if (_threads[slot].State == Runnable) return slot; } return Capacity; } static void Schedule() { if (!Ready || _scheduling) return; _scheduling = true; Reap(); WakeExpired(); uint old = _current; if (_threads[old].State == Running) _threads[old].State = Runnable; uint next = Choose(); while (next == Capacity) { Native.Idle(); WakeExpired(); next = Choose(); } if (next == old) { _threads[old].State = Running; _scheduling = false; return; } _threads[next].State = Running; _current = next; Switches++; if (!Cpu.SetInterruptStack(_threads[next].InterruptStackTop)) Kernel.Fail("THREAD_INTERRUPT_STACK"); Native.ContextSwitch(Context(old), Context(next)); _scheduling = false; Reap(); } public static void Yield() { ulong flags = Native.IrqSave(); Schedule(); Native.IrqRestore(flags); } public static void Sleep(uint milliseconds) { if (!Ready) return; if (milliseconds == 0) { Yield(); return; } ulong flags = Native.IrqSave(); Current->WakeAt = MonotonicClock.Nanoseconds() + (ulong)milliseconds * 1000000; Current->WaitHandle = 0; Current->State = Waiting; Schedule(); Native.IrqRestore(flags); } public static void ParkEvent(ulong handle, ulong deadline) { if (!Ready) return; Current->WaitHandle = handle; Current->WakeAt = deadline; Current->State = Waiting; Schedule(); Current->WaitHandle = 0; } public static void WakeEvent(ulong handle) { if (!Ready) return; for (uint i = 0; i < Capacity; i++) if (_threads[i].State == Waiting && (_threads[i].WaitHandle == handle || _threads[i].WaitHandle == ~0UL)) _threads[i].State = Runnable; } public static void Timer(InterruptFrame* frame) { LocalApicTimer.Tick(); WakeExpired(); if (!Ready || _scheduling) return; uint slot = _current; ulong frameAddress = (ulong)frame, top = _threads[slot].InterruptStackTop; if (frame == null || frameAddress < top - InterruptStackBytes || frameAddress > top - (ulong)sizeof(InterruptFrame)) Kernel.Fail("IRQ_STACK_BOUNDS"); _threads[slot].Interrupt = frame; Preemptions++; Schedule(); _threads[slot].Interrupt = null; } [UnmanagedCallersOnly(EntryPoint = "xfe_kernel_thread_entry")] public static void Run(ulong slot) { if (!Ready || slot >= Capacity || slot != _current) Kernel.Fail("THREAD_ENTRY"); _scheduling = false; Native.IrqRestore(0x202); uint result = _threads[slot].Entry(_threads[slot].Argument); ulong flags = Native.IrqSave(); if (Native.ThreadRuntimeExit(_threads[slot].RuntimeThread) != 1) Kernel.Fail("RUNTIME_THREAD_EXIT"); _threads[slot].ExitResult = result; _threads[slot].State = Finished; Schedule(); Native.IrqRestore(flags); Kernel.Fail("DEAD_THREAD_RESUMED"); } public static bool StackBounds(void** low, void** high) { if (!Ready || low == null || high == null) return false; *low = (void*)Current->StackLow; *high = (void*)Current->StackHigh; return Current->StackLow < Current->StackHigh; } public static void Attach(void* runtimeThread) { ulong flags = Native.IrqSave(); if (!Ready || runtimeThread == null || (Current->RuntimeThread != null && Current->RuntimeThread != runtimeThread)) Kernel.Fail("RUNTIME_ATTACH"); Current->RuntimeThread = runtimeThread; Native.IrqRestore(flags); } public static int RequestHijack(void* runtimeThread) { if (!Ready || runtimeThread == null) return -1; ulong deadline = MonotonicClock.Nanoseconds() + 5000000000; do { ulong flags = Native.IrqSave(); int result = -1; for (uint slot = 0; slot < Capacity; slot++) { if (_threads[slot].State == 0 || _threads[slot].State == Finished || _threads[slot].RuntimeThread != runtimeThread) continue; RuntimeLimitedContext context; InterruptFrame* frame = _threads[slot].Interrupt; if (frame != null) context = new RuntimeLimitedContext { Ip = frame->Rip, Rsp = frame->Rsp, Rbp = frame->Rbp, Rax = frame->Rax, Rbx = frame->Rbx, Rdx = frame->Rdx, R12 = frame->R12, R13 = frame->R13, R14 = frame->R14, R15 = frame->R15 }; else { SwitchContext* saved = Context(slot); context = new RuntimeLimitedContext { Ip = saved->Rip, Rsp = saved->Rsp, Rbp = saved->Rbp, Rax = saved->Rax, Rbx = saved->Rbx, Rdx = saved->Rdx, R12 = saved->R12, R13 = saved->R13, R14 = saved->R14, R15 = saved->R15 }; } result = Native.TryHijack(runtimeThread, &context); break; } Native.IrqRestore(flags); if (result == 1) return 1; if (result < 0) return -1; Yield(); // inconvenient context: let the real thread run to a GC poll, then retry } while (MonotonicClock.Nanoseconds() < deadline); return 0; } }