fix: fix login screen hang when no USB input device plugged in
This commit is contained in:
@@ -29,7 +29,6 @@ namespace Drivers::InputEvents {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void NotifyActivity() {
|
void NotifyActivity() {
|
||||||
Timekeeping::NoteInteractiveActivity();
|
|
||||||
g_serial.fetch_add(1, std::memory_order_release);
|
g_serial.fetch_add(1, std::memory_order_release);
|
||||||
Sched::WakeObjectWaiters(&g_waitObject);
|
Sched::WakeObjectWaiters(&g_waitObject);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,20 +41,12 @@ namespace Timekeeping {
|
|||||||
static constexpr uint32_t BSP_TICK_INTERVAL_MS = 1;
|
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 BSP_TIMER_HZ = 1000 / BSP_TICK_INTERVAL_MS;
|
||||||
static constexpr uint32_t AP_TICK_INTERVAL_MS = 10;
|
static constexpr uint32_t AP_TICK_INTERVAL_MS = 10;
|
||||||
// Keep a bounded periodic wake while otherwise tickless so wall-clock
|
|
||||||
// accounting and typematic repeat still advance even on a fully idle BSP.
|
|
||||||
static constexpr uint32_t BSP_IDLE_MAX_INTERVAL_MS = 50;
|
|
||||||
static constexpr uint32_t BSP_INTERACTIVE_IDLE_MAX_INTERVAL_MS = 1;
|
|
||||||
|
|
||||||
// Global state
|
// Global state
|
||||||
static std::atomic<uint64_t> g_tickCount{0};
|
static std::atomic<uint64_t> g_tickCount{0};
|
||||||
static uint32_t g_ticksPerMs = 0;
|
static uint32_t g_ticksPerMs = 0;
|
||||||
|
|
||||||
static bool g_schedEnabled = false;
|
static bool g_schedEnabled = false;
|
||||||
static volatile bool g_bspIdleOneShotArmed = false;
|
|
||||||
static uint32_t g_bspIdleOneShotMs = 0;
|
|
||||||
static uint32_t g_bspIdleInitialCount = 0;
|
|
||||||
static std::atomic<uint64_t> g_interactiveUntilMs{0};
|
|
||||||
|
|
||||||
static uint32_t CountForIntervalMs(uint32_t intervalMs) {
|
static uint32_t CountForIntervalMs(uint32_t intervalMs) {
|
||||||
return g_ticksPerMs * intervalMs;
|
return g_ticksPerMs * intervalMs;
|
||||||
@@ -76,30 +68,13 @@ namespace Timekeeping {
|
|||||||
ProgramTimer(true, BSP_TICK_INTERVAL_MS);
|
ProgramTimer(true, BSP_TICK_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ProgramBspIdleOneShotTimer(uint32_t intervalMs) {
|
// Timer IRQ handler: BSP runs timekeeping + scheduler accounting; APs
|
||||||
g_bspIdleOneShotMs = intervalMs;
|
// run scheduler accounting only.
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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) {
|
static void TimerHandler(uint8_t) {
|
||||||
auto* cpu = Smp::GetCurrentCpuData();
|
auto* cpu = Smp::GetCurrentCpuData();
|
||||||
uint32_t schedElapsedMs = (cpu->cpuIndex == 0) ? BSP_TICK_INTERVAL_MS : AP_TICK_INTERVAL_MS;
|
uint32_t schedElapsedMs = (cpu->cpuIndex == 0) ? BSP_TICK_INTERVAL_MS : AP_TICK_INTERVAL_MS;
|
||||||
|
|
||||||
if (cpu->cpuIndex == 0) {
|
if (cpu->cpuIndex == 0) {
|
||||||
if (g_bspIdleOneShotArmed) {
|
|
||||||
schedElapsedMs = g_bspIdleOneShotMs;
|
|
||||||
g_bspIdleOneShotArmed = false;
|
|
||||||
ProgramBspPeriodicTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
g_tickCount.fetch_add(schedElapsedMs, std::memory_order_relaxed);
|
g_tickCount.fetch_add(schedElapsedMs, std::memory_order_relaxed);
|
||||||
|
|
||||||
if (Drivers::Net::E1000E::RequiresPolling()) {
|
if (Drivers::Net::E1000E::RequiresPolling()) {
|
||||||
@@ -198,7 +173,6 @@ namespace Timekeeping {
|
|||||||
// Reprogram the APIC timer registers (they were lost during S3).
|
// Reprogram the APIC timer registers (they were lost during S3).
|
||||||
// The calibrated g_ticksPerMs value is still valid (it's in RAM).
|
// 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).
|
// The IRQ handler registration also survives (it's a function pointer array in RAM).
|
||||||
g_bspIdleOneShotArmed = false;
|
|
||||||
ProgramBspPeriodicTimer();
|
ProgramBspPeriodicTimer();
|
||||||
|
|
||||||
KernelLogStream(OK, "Timer") << "APIC timer restarted after S3 resume";
|
KernelLogStream(OK, "Timer") << "APIC timer restarted after S3 resume";
|
||||||
@@ -212,20 +186,6 @@ namespace Timekeeping {
|
|||||||
return g_tickCount.load(std::memory_order_relaxed); // 1 tick = 1 ms at 1000 Hz
|
return g_tickCount.load(std::memory_order_relaxed); // 1 tick = 1 ms at 1000 Hz
|
||||||
}
|
}
|
||||||
|
|
||||||
void NoteInteractiveActivity(uint32_t durationMs) {
|
|
||||||
uint64_t until = GetMilliseconds() + durationMs;
|
|
||||||
uint64_t current = g_interactiveUntilMs.load(std::memory_order_relaxed);
|
|
||||||
|
|
||||||
while (until > current &&
|
|
||||||
!g_interactiveUntilMs.compare_exchange_weak(
|
|
||||||
current, until, std::memory_order_relaxed, std::memory_order_relaxed)) {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool IsInteractiveActivityActive() {
|
|
||||||
return GetMilliseconds() < g_interactiveUntilMs.load(std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EnableSchedulerTick() {
|
void EnableSchedulerTick() {
|
||||||
g_schedEnabled = true;
|
g_schedEnabled = true;
|
||||||
}
|
}
|
||||||
@@ -242,18 +202,20 @@ namespace Timekeeping {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
|
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr) {
|
||||||
|
(void)hasMwait;
|
||||||
|
(void)monitorAddr;
|
||||||
auto* cpu = Smp::GetCurrentCpuData();
|
auto* cpu = Smp::GetCurrentCpuData();
|
||||||
|
|
||||||
// Drain USB hot-plug deferred work from any idle core, not just the BSP.
|
// Drain USB hot-plug deferred work from any idle core, not just the BSP.
|
||||||
// ProcessDeferredWork uses an atomic CAS so only one core runs it at a
|
|
||||||
// time; the rest return immediately. This keeps hot-plug latency bounded
|
|
||||||
// by *any* core's idle gap instead of being pinned to BSP idle.
|
|
||||||
if (Drivers::USB::Xhci::HasDeferredWork()) {
|
if (Drivers::USB::Xhci::HasDeferredWork()) {
|
||||||
Drivers::USB::Xhci::ProcessDeferredWork();
|
Drivers::USB::Xhci::ProcessDeferredWork();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cpu == nullptr || cpu->cpuIndex != 0 || !g_schedEnabled || g_ticksPerMs == 0) {
|
if (cpu == nullptr || cpu->cpuIndex != 0 || !g_schedEnabled || g_ticksPerMs == 0) {
|
||||||
Hal::CpuIdle::Wait(BSP_TICK_INTERVAL_MS, hasMwait, monitorAddr);
|
// Non-BSP or pre-scheduler fallback: plain HLT keeps the LAPIC
|
||||||
|
// timer running in C1 on hardware where MWAIT promotes to deeper
|
||||||
|
// states that freeze the timer (no ARAT in practice).
|
||||||
|
Hal::HaltWithInterruptsDisabled();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,57 +226,20 @@ namespace Timekeeping {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool interactive = IsInteractiveActivityActive();
|
// We deliberately DO NOT reprogram the LAPIC timer to a one-shot here.
|
||||||
uint32_t waitMs = interactive
|
// Some laptop CPUs let MWAIT/HLT drop into a deep C-state where the
|
||||||
? BSP_INTERACTIVE_IDLE_MAX_INTERVAL_MS
|
// LAPIC timer stops firing, even though CPUID.06H:EAX[ARAT] claims
|
||||||
: BSP_IDLE_MAX_INTERVAL_MS;
|
// it doesn't. Symptom: the BSP enters idle and never wakes from its
|
||||||
|
// timer; only external IRQs (PS/2, USB MSI) bring it out, and even
|
||||||
uint64_t now = GetTicks();
|
// those don't advance g_tickCount because the one-shot's current
|
||||||
uint64_t nextDeadline = Sched::GetNextDeadlineTick();
|
// count is frozen. By leaving the boot-time periodic 1ms timer in
|
||||||
uint64_t keyboardDeadline = Drivers::USB::HidKeyboard::GetNextTickDeadline();
|
// place, TimerHandler runs every tick and g_tickCount keeps moving
|
||||||
if (keyboardDeadline != 0 &&
|
// -- which is what RunBspMaintenance needs to wake sleeping procs.
|
||||||
(nextDeadline == 0 || keyboardDeadline < nextDeadline)) {
|
//
|
||||||
nextDeadline = keyboardDeadline;
|
// HLT (vs MWAIT) is the second half: MWAIT EAX=0 is a *hint* and
|
||||||
}
|
// the CPU may go deeper than C1; HLT generally stays in C1, which
|
||||||
|
// is the deepest state guaranteed to keep the LAPIC timer alive.
|
||||||
if (nextDeadline != 0) {
|
Hal::HaltWithInterruptsDisabled();
|
||||||
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);
|
|
||||||
if (interactive) {
|
|
||||||
Hal::CpuIdle::WaitShallowWithInterruptsDisabled(hasMwait, monitorAddr);
|
|
||||||
} else {
|
|
||||||
Hal::CpuIdle::WaitWithInterruptsDisabled(waitMs, hasMwait, monitorAddr);
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
void Sleep(uint64_t ms) {
|
||||||
|
|||||||
@@ -28,15 +28,11 @@ namespace Timekeeping {
|
|||||||
// Enable scheduler tick (called after scheduler is initialized)
|
// Enable scheduler tick (called after scheduler is initialized)
|
||||||
void EnableSchedulerTick();
|
void EnableSchedulerTick();
|
||||||
|
|
||||||
// Enter one idle wait cycle for the current CPU. The BSP uses a
|
// Enter one idle wait cycle for the current CPU. Uses HLT and relies on
|
||||||
// one-shot LAPIC timer while idle; APs keep their existing simple wait.
|
// the boot-time periodic LAPIC timer to wake -- one-shot wake doesn't
|
||||||
|
// survive deep C-states on some laptop CPUs.
|
||||||
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr = nullptr);
|
void IdleOnce(bool hasMwait, volatile uint64_t* monitorAddr = nullptr);
|
||||||
|
|
||||||
// Mark recent latency-sensitive input activity. Idle policy uses this to
|
|
||||||
// stay in shallow, high-responsiveness mode briefly while the user is
|
|
||||||
// moving the pointer or dragging windows.
|
|
||||||
void NoteInteractiveActivity(uint32_t durationMs = 250);
|
|
||||||
|
|
||||||
// Busy-wait sleep for the given number of milliseconds
|
// Busy-wait sleep for the given number of milliseconds
|
||||||
void Sleep(uint64_t ms);
|
void Sleep(uint64_t ms);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user