diff --git a/kernel/drivers/input/ps2mouse.cpp b/kernel/drivers/input/ps2mouse.cpp index 7bf81575..8702859f 100644 --- a/kernel/drivers/input/ps2mouse.cpp +++ b/kernel/drivers/input/ps2mouse.cpp @@ -345,14 +345,20 @@ void IrqHandler() duetos::sched::WaitQueueWakeOne(&g_readers); } -} // namespace - -void Ps2MouseInit() +// Polled 8042 controller + aux-device bring-up (steps 1-5). Split +// out so Ps2MouseInit can run the WHOLE dialogue under a single +// interrupts-disabled window with one clean exit. This matters: +// Ps2MouseInit runs AFTER Ps2KeyboardInit has already unmasked +// IRQ 1, so every non-aux controller response we poll for here +// (the 0xA9 test result, ReadConfigByte's reply, device ACKs) +// also raises the keyboard IRQ — the keyboard ISR then reads +// port 0x60 first and our poll loop spins to its cap forever. +// On QEMU the race window happened not to bite; on VirtualBox +// the controller's IRQ timing makes the steal deterministic, +// which is exactly the "port-2 self-test no response" boot bail. +// Returns true iff the mouse ACKed enable-reporting. +bool Ps2MouseControllerBringup() { - static constinit bool s_initialised = false; - KASSERT(!s_initialised, "drivers/ps2mouse", "Ps2MouseInit called twice"); - s_initialised = true; - // Step 1: enable the aux channel. The keyboard driver's // ControllerInit disabled it during its bring-up; re-enable // before anything else. @@ -370,13 +376,13 @@ void Ps2MouseInit() if (!TryWaitOutputFull(&port2_test)) { core::Log(core::LogLevel::Warn, "drivers/ps2mouse", "port-2 self-test no response (no PS/2 mouse?)"); - return; + return false; } if (port2_test != kResponseTestPort2Pass) { core::LogWithValue(core::LogLevel::Warn, "drivers/ps2mouse", "port-2 self-test failed (no PS/2 mouse?)", port2_test); - return; + return false; } // Step 3: device reset + set defaults. The mouse's "set defaults" @@ -387,7 +393,7 @@ void Ps2MouseInit() if (!MouseSendAndAck(kMouseCmdSetDefaults)) { core::Log(core::LogLevel::Warn, "drivers/ps2mouse", "set-defaults (0xF6) not ACKed — mouse disabled"); - return; + return false; } // Step 4: enable data reporting. Without this, the mouse stays @@ -395,7 +401,7 @@ void Ps2MouseInit() if (!MouseSendAndAck(kMouseCmdEnableReporting)) { core::Log(core::LogLevel::Warn, "drivers/ps2mouse", "enable-reporting (0xF4) not ACKed — mouse disabled"); - return; + return false; } // Step 5: flip on the aux-channel IRQ + active clock in the @@ -404,10 +410,44 @@ void Ps2MouseInit() config |= kConfigPort2IrqEnable; config = static_cast(config & ~kConfigPort2ClockDisable); WriteConfigByte(config); + return true; +} + +} // namespace + +void Ps2MouseInit() +{ + static constinit bool s_initialised = false; + KASSERT(!s_initialised, "drivers/ps2mouse", "Ps2MouseInit called twice"); + s_initialised = true; + + // Run steps 1-6 with interrupts disabled. Ps2KeyboardInit has + // already unmasked IRQ 1, so without this the live keyboard ISR + // races every non-aux controller byte we poll for below and the + // mouse never initialises (the VirtualBox "port-2 self-test no + // response" bail). Save/restore IF rather than unconditionally + // STI so a future caller that runs with interrupts already off + // isn't surprised. The whole dialogue is bounded spin-polls + + // register writes — no sleep / block — so a CLI window is safe. + constexpr u64 kRflagsIf = 1ULL << 9; + const bool irqs_were_on = (arch::ReadRflags() & kRflagsIf) != 0; + arch::Cli(); + + if (!Ps2MouseControllerBringup()) + { + if (irqs_were_on) + { + arch::Sti(); + } + return; + } // Step 6: route through the IOAPIC + IDT. Identical shape to // the keyboard. Note that IRQ 12 may or may not have a MADT - // override on real hardware — IsaIrqToGsi handles that. + // override on real hardware — IsaIrqToGsi handles that. Still + // under CLI: these are IDT / IOAPIC register writes, and the + // mouse IRQ can't be delivered until the route below lands + // anyway. arch::IdtSetGate(kMouseVector, reinterpret_cast(&isr_44)); arch::IrqInstall(kMouseVector, &IrqHandler); const u32 gsi = acpi::IsaIrqToGsi(kMouseIsaIrq); @@ -416,6 +456,14 @@ void Ps2MouseInit() g_available = true; + // Route is live; the polled dialogue is done. Re-enable + // interrupts (if the caller had them on) before the logging + // tail so the CLI window stays as tight as the bug fix needs. + if (irqs_were_on) + { + arch::Sti(); + } + duetos::core::LogWithValue(duetos::core::LogLevel::Info, "drivers/ps2mouse", "routed isa_irq", kMouseIsaIrq); duetos::core::LogWithValue(duetos::core::LogLevel::Info, "drivers/ps2mouse", " gsi", gsi); duetos::core::LogWithValue(duetos::core::LogLevel::Info, "drivers/ps2mouse", " vector", kMouseVector); diff --git a/kernel/security/login.cpp b/kernel/security/login.cpp index d86f78ae..35a940f6 100644 --- a/kernel/security/login.cpp +++ b/kernel/security/login.cpp @@ -40,9 +40,11 @@ namespace duetos::core using duetos::drivers::video::ConsoleWrite; using duetos::drivers::video::ConsoleWriteChar; using duetos::drivers::video::ConsoleWriteln; +using duetos::drivers::video::FramebufferBeginCompose; using duetos::drivers::video::FramebufferDrawRect; using duetos::drivers::video::FramebufferDrawString; using duetos::drivers::video::FramebufferDropShadow; +using duetos::drivers::video::FramebufferEndCompose; using duetos::drivers::video::FramebufferFillRect; using duetos::drivers::video::FramebufferFillRectGradient; using duetos::drivers::video::FramebufferGet; @@ -422,6 +424,14 @@ void DrawField(u32 x, u32 y, u32 w, u32 h, const char* text, u32 len, bool mask, void GuiRepaint() { const GuiLayout l = ComputeLayout(); + // Compose the whole panel offscreen so the 1 Hz ui-ticker + // repaint lands in a single blit. Without this the full-screen + // gradient clear is visible on its own for a frame on + // un-coalesced host framebuffers (VBox), reading as a 1 Hz + // flicker. Mirrors DesktopCompose's BeginCompose/EndCompose/ + // Present flow; no-op fallback to direct mode if the shadow + // allocator is unavailable. + FramebufferBeginCompose(); DrawBackground(l); DrawPanel(l); @@ -473,6 +483,10 @@ void GuiRepaint() FramebufferDrawString(16, y_hint, "DEFAULT ACCOUNTS: ADMIN/ADMIN GUEST/(EMPTY)", 0x00C0D0E0, kBgBottom); } + // Flush the offscreen shadow surface to the live framebuffer in + // one row-by-row copy (no-op if BeginCompose fell back to direct + // mode). + FramebufferEndCompose(); // Push the freshly-painted login surface to the active backend // (virtio-gpu TRANSFER_TO_HOST_2D + RESOURCE_FLUSH; no-op for // direct firmware-handoff framebuffers). Without this the