diff --git a/kernel/src/Drivers/Net/E1000E.cpp b/kernel/src/Drivers/Net/E1000E.cpp index abb059f..9fd66d0 100644 --- a/kernel/src/Drivers/Net/E1000E.cpp +++ b/kernel/src/Drivers/Net/E1000E.cpp @@ -735,6 +735,10 @@ namespace Drivers::Net::E1000E { return g_initialized; } + bool RequiresPolling() { + return g_initialized && g_pollingMode; + } + void SetRxCallback(RxCallback callback) { g_rxCallback = callback; } diff --git a/kernel/src/Drivers/Net/E1000E.hpp b/kernel/src/Drivers/Net/E1000E.hpp index 7034690..2f71ea0 100644 --- a/kernel/src/Drivers/Net/E1000E.hpp +++ b/kernel/src/Drivers/Net/E1000E.hpp @@ -147,6 +147,9 @@ namespace Drivers::Net::E1000E { // Check if the device was found and initialized bool IsInitialized(); + // Returns true only when the driver had to fall back to timer polling. + bool RequiresPolling(); + // RX callback type: called with (packet data, length) using RxCallback = void(*)(const uint8_t* data, uint16_t length); diff --git a/kernel/src/Drivers/USB/Xhci.cpp b/kernel/src/Drivers/USB/Xhci.cpp index 872e202..315bcb9 100644 --- a/kernel/src/Drivers/USB/Xhci.cpp +++ b/kernel/src/Drivers/USB/Xhci.cpp @@ -49,6 +49,7 @@ namespace Drivers::USB::Xhci { // Hot-plug deferred work static volatile bool g_hotplugPending[MAX_PORTS] = {}; + static volatile bool g_deferredWorkPending = false; static bool g_hotplugProcessing = false; // MMIO region pointers @@ -302,6 +303,7 @@ namespace Drivers::USB::Xhci { // Defer enumeration to ProcessDeferredWork (called from timer tick) if (g_bootScanComplete && portId >= 1 && portId <= g_maxPorts) { g_hotplugPending[portId - 1] = true; + g_deferredWorkPending = true; } break; } @@ -723,15 +725,20 @@ namespace Drivers::USB::Xhci { return g_initialized; } + bool HasDeferredWork() { + return g_initialized && g_deferredWorkPending; + } + // ------------------------------------------------------------------------- // ProcessDeferredWork - handle hot-plug outside interrupt context // Called from timer tick (same pattern as E1000E::Poll) // ------------------------------------------------------------------------- void ProcessDeferredWork() { - if (!g_initialized || !g_bootScanComplete) return; + if (!g_initialized || !g_bootScanComplete || !g_deferredWorkPending) return; if (g_hotplugProcessing) return; g_hotplugProcessing = true; + g_deferredWorkPending = false; for (uint32_t port = 0; port < g_maxPorts; port++) { if (!g_hotplugPending[port]) continue; diff --git a/kernel/src/Drivers/USB/Xhci.hpp b/kernel/src/Drivers/USB/Xhci.hpp index 5d89a01..7d5b4f5 100644 --- a/kernel/src/Drivers/USB/Xhci.hpp +++ b/kernel/src/Drivers/USB/Xhci.hpp @@ -292,6 +292,7 @@ namespace Drivers::USB::Xhci { void Initialize(); bool Probe(const Pci::PciDevice& dev); bool IsInitialized(); + bool HasDeferredWork(); // Deferred hot-plug processing (call from timer tick, not interrupt context) void ProcessDeferredWork(); diff --git a/kernel/src/Main.cpp b/kernel/src/Main.cpp index 7a37884..81f4dc6 100644 --- a/kernel/src/Main.cpp +++ b/kernel/src/Main.cpp @@ -201,11 +201,11 @@ extern "C" void kmain() { if (bspCpu && bspCpu->hasMwait) { static volatile uint64_t s_bspIdleMonitor = 0; for (;;) { - Hal::IdleWait(&s_bspIdleMonitor); + Timekeeping::IdleOnce(true, &s_bspIdleMonitor); } } else { for (;;) { - asm volatile("hlt"); + Timekeeping::IdleOnce(false); } } } diff --git a/kernel/src/Sched/Scheduler.cpp b/kernel/src/Sched/Scheduler.cpp index 8837697..ed575ef 100644 --- a/kernel/src/Sched/Scheduler.cpp +++ b/kernel/src/Sched/Scheduler.cpp @@ -443,6 +443,46 @@ namespace Sched { schedLock.Release(); } + bool HasReadyProcesses() { + return readyCount > 0; + } + + void RunBspMaintenance() { + schedLock.Acquire(); + uint64_t now = Timekeeping::GetTicks(); + for (int i = 0; i < MaxProcesses; i++) { + if (processTable[i].state == ProcessState::Blocked && + processTable[i].sleepUntilTick != 0 && + now >= processTable[i].sleepUntilTick) { + processTable[i].sleepUntilTick = 0; + processTable[i].waitingForPid = -1; + processTable[i].waitingOnObject = nullptr; + processTable[i].state = ProcessState::Ready; + readyCount++; + } + } + schedLock.Release(); + + ReclaimTerminated(); + } + + uint64_t GetNextDeadlineTick() { + uint64_t nextDeadline = 0; + + schedLock.Acquire(); + for (int i = 0; i < MaxProcesses; i++) { + if (processTable[i].state != ProcessState::Blocked) continue; + uint64_t deadline = processTable[i].sleepUntilTick; + if (deadline == 0) continue; + if (nextDeadline == 0 || deadline < nextDeadline) { + nextDeadline = deadline; + } + } + schedLock.Release(); + + return nextDeadline; + } + void Schedule() { auto* cpu = Smp::GetCurrentCpuData(); @@ -532,28 +572,12 @@ namespace Sched { schedLock.Release(); } - void Tick() { + void Tick(uint32_t elapsedMs) { auto* cpu = Smp::GetCurrentCpuData(); // BSP: wake sleeping processes and reclaim terminated slots if (cpu->cpuIndex == 0) { - schedLock.Acquire(); - uint64_t now = Timekeeping::GetTicks(); - for (int i = 0; i < MaxProcesses; i++) { - if (processTable[i].state == ProcessState::Blocked && - processTable[i].sleepUntilTick != 0 && - now >= processTable[i].sleepUntilTick) { - processTable[i].sleepUntilTick = 0; - processTable[i].waitingForPid = -1; - processTable[i].waitingOnObject = nullptr; - processTable[i].state = ProcessState::Ready; - readyCount++; - } - } - schedLock.Release(); - - // Reclaim terminated process memory (BSP only, once per tick) - ReclaimTerminated(); + RunBspMaintenance(); } int slot = cpu->currentSlot; @@ -577,8 +601,10 @@ namespace Sched { return; } - if (processTable[slot].sliceRemaining > 0) { - processTable[slot].sliceRemaining--; + if (processTable[slot].sliceRemaining > elapsedMs) { + processTable[slot].sliceRemaining -= elapsedMs; + } else { + processTable[slot].sliceRemaining = 0; } if (processTable[slot].sliceRemaining == 0) { diff --git a/kernel/src/Sched/Scheduler.hpp b/kernel/src/Sched/Scheduler.hpp index 921c4ca..54dfe5e 100644 --- a/kernel/src/Sched/Scheduler.hpp +++ b/kernel/src/Sched/Scheduler.hpp @@ -41,7 +41,7 @@ namespace Sched { uint64_t savedRsp; uint64_t stackBase; // Bottom of allocated kernel stack (lowest address) uint64_t entryPoint; - uint64_t sliceRemaining; // Ticks left in current time slice + uint64_t sliceRemaining; // Milliseconds left in current time slice uint64_t pml4Phys; // Physical address of per-process PML4 uint64_t kernelStackTop; // Top of kernel stack (for TSS RSP0 / SYSCALL) uint64_t userStackTop; // User-space stack top @@ -87,8 +87,12 @@ namespace Sched { int Spawn(const char* vfsPath, const char* args = nullptr); void Schedule(); - // Called from the APIC timer handler on every tick (per-CPU). - void Tick(); + // True when there is runnable work somewhere in the process table. + bool HasReadyProcesses(); + + // Called from the APIC timer handler with the elapsed time for that CPU's + // tick interval. The BSP runs at 1 ms; APs may use a coarser interval. + void Tick(uint32_t elapsedMs = 1); // Get the PID of the currently running process (-1 if idle) int GetCurrentPid(); @@ -112,6 +116,14 @@ namespace Sched { // timeoutMs == 0 means wait indefinitely. void BlockOnObject(void* object, uint64_t timeoutMs = 0); + // BSP-only scheduler housekeeping: wake expired sleepers and reclaim + // terminated process resources. + void RunBspMaintenance(); + + // Return the earliest blocked sleep/object timeout deadline in ticks, + // or 0 when no timed waits are pending. + uint64_t GetNextDeadlineTick(); + // Wake any processes blocked on the given object. void WakeObjectWaiters(void* object); diff --git a/kernel/src/Timekeeping/ApicTimer.cpp b/kernel/src/Timekeeping/ApicTimer.cpp index b056478..6526dfa 100644 --- a/kernel/src/Timekeeping/ApicTimer.cpp +++ b/kernel/src/Timekeeping/ApicTimer.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -33,30 +34,90 @@ namespace Timekeeping { // APIC timer divide configuration values static constexpr uint32_t DIVIDE_BY_16 = 0x03; - // Timer tick rate: 1000 Hz (1 ms per tick) - static constexpr uint32_t TIMER_HZ = 1000; + // The BSP keeps a 1 ms tick for timekeeping and sleep deadlines. + // APs use a coarser 10 ms scheduler tick to avoid waking idle cores + // 1000 times per second with no useful work to do. + static constexpr uint32_t BSP_TICK_INTERVAL_MS = 1; + static constexpr uint32_t BSP_TIMER_HZ = 1000 / BSP_TICK_INTERVAL_MS; + static constexpr uint32_t AP_TICK_INTERVAL_MS = 10; + // Without a reschedule IPI, the BSP still needs a short periodic safety net + // while idle so input- and IPC-driven wakeups do not feel laggy. + static constexpr uint32_t BSP_IDLE_MAX_INTERVAL_MS = 2; // Global state static std::atomic g_tickCount{0}; static uint32_t g_ticksPerMs = 0; static bool g_schedEnabled = false; + static volatile bool g_bspIdleOneShotArmed = false; + static uint32_t g_bspIdleOneShotMs = 0; + static uint32_t g_bspIdleInitialCount = 0; - // Timer IRQ handler: BSP handles timekeeping+polling, all CPUs run scheduler + static uint32_t CountForIntervalMs(uint32_t intervalMs) { + return g_ticksPerMs * intervalMs; + } + + static void ProgramTimer(bool periodic, uint32_t intervalMs) { + uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER); + if (periodic) { + lvt |= LVT_PERIODIC; + } + + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16); + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt); + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, + CountForIntervalMs(intervalMs)); + } + + static void ProgramBspPeriodicTimer() { + ProgramTimer(true, BSP_TICK_INTERVAL_MS); + } + + static void ProgramBspIdleOneShotTimer(uint32_t intervalMs) { + g_bspIdleOneShotMs = intervalMs; + g_bspIdleInitialCount = CountForIntervalMs(intervalMs); + g_bspIdleOneShotArmed = true; + + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16); + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, + (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER)); + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_bspIdleInitialCount); + } + + static void WaitForInterrupt(bool hasMwait, volatile uint64_t* monitorAddr) { + if (hasMwait && monitorAddr != nullptr) { + Hal::IdleWait(monitorAddr); + } else { + asm volatile("hlt"); + } + } + + // Timer IRQ handler: BSP handles timekeeping and the few timer-driven + // fallbacks that are still required; APs only run scheduler accounting. static void TimerHandler(uint8_t) { auto* cpu = Smp::GetCurrentCpuData(); + uint32_t schedElapsedMs = (cpu->cpuIndex == 0) ? BSP_TICK_INTERVAL_MS : AP_TICK_INTERVAL_MS; if (cpu->cpuIndex == 0) { - // BSP: increment global tick count and poll devices - g_tickCount.fetch_add(1, std::memory_order_relaxed); + if (g_bspIdleOneShotArmed) { + schedElapsedMs = g_bspIdleOneShotMs; + g_bspIdleOneShotArmed = false; + ProgramBspPeriodicTimer(); + } - Drivers::Net::E1000E::Poll(); - Drivers::USB::Xhci::ProcessDeferredWork(); + g_tickCount.fetch_add(schedElapsedMs, std::memory_order_relaxed); + + if (Drivers::Net::E1000E::RequiresPolling()) { + Drivers::Net::E1000E::Poll(); + } + if (Drivers::USB::Xhci::HasDeferredWork()) { + Drivers::USB::Xhci::ProcessDeferredWork(); + } Drivers::USB::HidKeyboard::Tick(); } if (g_schedEnabled) { - Sched::Tick(); + Sched::Tick(schedElapsedMs); } } @@ -129,16 +190,11 @@ namespace Timekeeping { Hal::RegisterIrqHandler(Hal::IRQ_TIMER, TimerHandler); // Configure APIC timer: periodic mode, vector 32 - uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC; - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16); - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt); + ProgramBspPeriodicTimer(); - // Set initial count for 1ms intervals (1000 Hz tick rate) - uint32_t initialCount = g_ticksPerMs; - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, initialCount); - - KernelLogStream(OK, "Timer") << "APIC timer started: " << base::dec << (uint64_t)TIMER_HZ - << " Hz periodic, initial count=" << (uint64_t)initialCount; + KernelLogStream(OK, "Timer") << "APIC timer started: BSP " << base::dec + << (uint64_t)BSP_TIMER_HZ << " Hz periodic, initial count=" + << (uint64_t)CountForIntervalMs(BSP_TICK_INTERVAL_MS); } void ApicTimerReinitialize() { @@ -150,10 +206,8 @@ namespace Timekeeping { // Reprogram the APIC timer registers (they were lost during S3). // The calibrated g_ticksPerMs value is still valid (it's in RAM). // The IRQ handler registration also survives (it's a function pointer array in RAM). - uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC; - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16); - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt); - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_ticksPerMs); + g_bspIdleOneShotArmed = false; + ProgramBspPeriodicTimer(); KernelLogStream(OK, "Timer") << "APIC timer restarted after S3 resume"; } @@ -176,11 +230,67 @@ namespace Timekeeping { // identical. This avoids PIT contention during AP boot. if (g_ticksPerMs == 0) return; - // Configure periodic timer at 1000 Hz (same vector as BSP) - uint32_t lvt = (Hal::IRQ_VECTOR_BASE + Hal::IRQ_TIMER) | LVT_PERIODIC; - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_DIVIDE, DIVIDE_BY_16); - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_LVT, lvt); - Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, g_ticksPerMs); + // Configure a coarser periodic timer on APs. The scheduler still gets + // a 10 ms time slice, but idle APs stop taking 1000 timer interrupts/sec. + ProgramTimer(true, AP_TICK_INTERVAL_MS); + } + + void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) { + auto* cpu = Smp::GetCurrentCpuData(); + if (cpu == nullptr || cpu->cpuIndex != 0 || !g_schedEnabled || g_ticksPerMs == 0) { + WaitForInterrupt(hasMwait, monitorAddr); + return; + } + + Sched::RunBspMaintenance(); + + if (Sched::HasReadyProcesses()) { + Sched::Schedule(); + return; + } + + uint32_t waitMs = BSP_TICK_INTERVAL_MS; + waitMs = BSP_IDLE_MAX_INTERVAL_MS; + + uint64_t now = GetTicks(); + uint64_t nextDeadline = Sched::GetNextDeadlineTick(); + if (nextDeadline != 0) { + if (nextDeadline <= now) { + waitMs = BSP_TICK_INTERVAL_MS; + } else { + uint64_t untilDeadline = nextDeadline - now; + if (untilDeadline < waitMs) { + waitMs = (uint32_t)untilDeadline; + } + } + } + + if (waitMs == 0) { + waitMs = BSP_TICK_INTERVAL_MS; + } + + asm volatile("cli" ::: "memory"); + ProgramBspIdleOneShotTimer(waitMs); + asm volatile("sti" ::: "memory"); + + WaitForInterrupt(hasMwait, monitorAddr); + + asm volatile("cli" ::: "memory"); + if (g_bspIdleOneShotArmed) { + uint32_t currentCount = Hal::LocalApic::ReadRegister(Hal::LocalApic::REG_TIMER_CURRENT); + uint32_t elapsedTicks = (g_bspIdleInitialCount > currentCount) + ? (g_bspIdleInitialCount - currentCount) + : 0; + uint32_t elapsedMs = (g_ticksPerMs > 0) ? (elapsedTicks / g_ticksPerMs) : 0; + if (elapsedMs > 0) { + g_tickCount.fetch_add(elapsedMs, std::memory_order_relaxed); + } + + g_bspIdleOneShotArmed = false; + Hal::LocalApic::WriteRegister(Hal::LocalApic::REG_TIMER_INITIAL, 0); + ProgramBspPeriodicTimer(); + } + asm volatile("sti" ::: "memory"); } void Sleep(uint64_t ms) { diff --git a/kernel/src/Timekeeping/ApicTimer.hpp b/kernel/src/Timekeeping/ApicTimer.hpp index a8b5b84..a46dac7 100644 --- a/kernel/src/Timekeeping/ApicTimer.hpp +++ b/kernel/src/Timekeeping/ApicTimer.hpp @@ -28,6 +28,10 @@ namespace Timekeeping { // Enable scheduler tick (called after scheduler is initialized) void EnableSchedulerTick(); + // Enter one idle wait cycle for the current CPU. The BSP uses a + // one-shot LAPIC timer while idle; APs keep their existing simple wait. + void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr = nullptr); + // Busy-wait sleep for the given number of milliseconds void Sleep(uint64_t ms); }; diff --git a/programs/src/printers/main.cpp b/programs/src/printers/main.cpp index 64f912c..a662be3 100644 --- a/programs/src/printers/main.cpp +++ b/programs/src/printers/main.cpp @@ -267,6 +267,36 @@ static void text_fit(const char* src, char* out, int out_len, int max_w) { montauk::strncpy(out, ell, out_len - 1); } +static void format_resolved_transport_line(const IppUri& uri, char* out, int out_len) { + if (!out || out_len <= 0) return; + out[0] = '\0'; + + const char* transport = uri.use_tls ? "TLS" : "Plain TCP"; + uint32_t ip = 0; + if (!parse_ipv4_literal(uri.host, &ip)) + ip = g_app.probe_caps.resolved_ip; + + if (ip != 0) { + char ip_text[20]; + format_ipv4(ip_text, sizeof(ip_text), ip); + snprintf(out, out_len, "Resolved: %s Transport: %s", ip_text, transport); + return; + } + + if (host_looks_like_mdns(uri.host)) { + snprintf(out, out_len, + "Resolved: unavailable (.local/mDNS not supported yet) Transport: %s", + transport); + return; + } + + if (g_app.probe_valid) + snprintf(out, out_len, "Resolved: unavailable Transport: %s", transport); + else + snprintf(out, out_len, "Resolved: run Probe to resolve current printer Transport: %s", + transport); +} + static bool main_mouse_in_rect(const Rect& rect) { return rect.contains(g_app.mouse_x, g_app.mouse_y); } @@ -807,17 +837,7 @@ static void render_details(Canvas& c, const Layout& lo, const mtk::Theme& theme) c.text(x, y, line, dim); y += sfh + 4; - uint32_t ip = 0; - if (resolve_host(normalized.host, &ip)) { - char ip_text[20]; - format_ipv4(ip_text, sizeof(ip_text), ip); - snprintf(line, sizeof(line), "Resolved: %s Transport: %s", ip_text, - normalized.use_tls ? "TLS" : "Plain TCP"); - } else if (host_looks_like_mdns(normalized.host)) { - safe_copy(line, sizeof(line), "Resolved: unavailable (.local/mDNS not supported yet)"); - } else { - safe_copy(line, sizeof(line), "Resolved: unavailable"); - } + format_resolved_transport_line(normalized, line, sizeof(line)); text_fit(line, line, sizeof(line), w); c.text(x, y, line, dim); y += sfh + 4; @@ -943,8 +963,6 @@ static void render_details(Canvas& c, const Layout& lo, const mtk::Theme& theme) } static void render() { - refresh_state(); - mtk::StandaloneHost host(&g_win); Canvas c = host.canvas(); mtk::Theme theme = printers_theme();