/* * Graphics.hpp * SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE syscalls * Copyright (c) 2026 Daniel Hammer */ #pragma once #include #include #include #include #include #include #include #include #include #include "Syscall.hpp" #include "Common.hpp" #include "../Libraries/flanterm/src/flanterm.h" namespace montauk::abi { static void Sys_FbInfo(FbInfo* out) { if (out == nullptr) return; out->width = Graphics::Framebuffer::GetWidth(); out->height = Graphics::Framebuffer::GetHeight(); out->pitch = Graphics::Framebuffer::GetPitch(); out->bpp = 32; out->userAddr = 0; } static uint64_t Sys_FbMap() { auto* proc = Sched::GetCurrentProcessPtr(); if (proc == nullptr) return 0; uint32_t* fbBase = Graphics::Framebuffer::GetBase(); if (fbBase == nullptr) return 0; uint64_t fbPhys = Memory::SubHHDM((uint64_t)fbBase); uint64_t width = Graphics::Framebuffer::GetWidth(); uint64_t height = Graphics::Framebuffer::GetHeight(); uint64_t pitch = Graphics::Framebuffer::GetPitch(); if (width == 0 || height == 0 || pitch == 0 || width > (~0ULL / 4) || pitch < width * 4 || height > (~0ULL / pitch)) { Kt::KernelLogStream(Kt::ERROR, "FbMap") << "Invalid or overflowing framebuffer geometry"; return 0; } uint64_t fbSize = height * pitch; uint64_t numPages = (fbSize + 0xFFF) / 0x1000; Kt::KernelLogStream(Kt::INFO, "FbMap") << "fbPhys=" << kcp::hex << fbPhys << " size=" << kcp::dec << fbSize << " pages=" << numPages << " (" << Graphics::Framebuffer::GetWidth() << "x" << Graphics::Framebuffer::GetHeight() << " pitch=" << Graphics::Framebuffer::GetPitch() << ")"; // Map in the dedicated framebuffer arena, away from the heap, // shared libraries, reusable IPC surfaces, and the user stack. constexpr uint64_t userVa = Sched::UserFramebufferBase; uint64_t mappedBytes = numPages * 0x1000ULL; bool flipSupported = Drivers::Graphics::IntelGPU::FlipSupported(); uint64_t buffers = flipSupported ? 2 : 1; if (mappedBytes > (Sched::UserFramebufferLimit - userVa) / buffers) { Kt::KernelLogStream(Kt::ERROR, "FbMap") << "Framebuffer mappings exceed reserved user VA range"; return 0; } // Never expose more of buffer 1 than the GPU driver allocated. This // independently contains a future geometry-contract regression before // any partial framebuffer mapping is installed in the process. if (flipSupported && (Drivers::Graphics::IntelGPU::GetBufferPhys(1) == 0 || Drivers::Graphics::IntelGPU::GetBufferPageCount(1) < numPages)) { Kt::KernelLogStream(Kt::ERROR, "FbMap") << "Page-flip buffer is smaller than firmware framebuffer"; return 0; } for (uint64_t i = 0; i < numPages; i++) { if (!Memory::VMM::Paging::MapUserInWC( proc->pml4Phys, fbPhys + i * 0x1000, userVa + i * 0x1000 )) { return 0; } } // When page flipping is available, map the second scanout buffer // immediately after the first so a double-buffered client can reach // both. Single-buffer clients never touch the extra pages. if (flipSupported) { uint64_t buf1Phys = Drivers::Graphics::IntelGPU::GetBufferPhys(1); for (uint64_t i = 0; i < numPages; i++) { if (!Memory::VMM::Paging::MapUserInWC( proc->pml4Phys, buf1Phys + i * 0x1000, userVa + (numPages + i) * 0x1000 )) { return 0; } } } return userVa; } // SYS_FBFLIP: point scanout at buffer `index` (0 or 1). The hardware // latches the new surface at vblank, so the flip is tear-free. // index == -1 queries support without side effects. // index == -2 acquires flip ownership and returns the live front buffer. static int64_t Sys_FbFlip(uint64_t index, uint64_t flags) { namespace GPU = Drivers::Graphics::IntelGPU; if ((int64_t)index == -1) { return GPU::FlipSupported() ? 1 : 0; } if (!GPU::FlipSupported()) return UnsupportedOp; auto* proc = Sched::GetCurrentProcessPtr(); if ((int64_t)index == -2) { return proc != nullptr ? GPU::AcquireFlip(proc->pid) : GenericFailure; } if (proc == nullptr) return GenericFailure; return GPU::Flip((int)index, (flags & 1) != 0, proc->pid); } // Firmware brightness interfaces (EC via WMI, ACPI _BCM) drive the // built-in panel, so they apply only while that panel is what is on // screen (or no native driver knows what is): never for an external // monitor, where they would silently dim a closed lid instead. static bool PanelIsActiveOutput() { if (!Drivers::Graphics::IntelGPU::IsInitialized()) return true; uint8_t type = Drivers::Graphics::IntelGPU::ActiveConnectorType(); return type == DISPLAY_CONNECTOR_EDP || type == DISPLAY_CONNECTOR_LVDS; } // Order follows who actually owns the backlight. A firmware that names // the EC as the source has taken it away from the GPU, whose PWM then // still accepts writes that reach nothing; so the EC interface goes // first, then the GPU's own controls, then ACPI _BCM as a last resort. static int DisplayBrightness(int percent) { bool panel = PanelIsActiveOutput(); if (panel) { int rc = Hal::WmiEcBacklight::Brightness(percent); if (rc != -1) return rc; } int rc = Drivers::Graphics::IntelGPU::Brightness(percent); if (rc == -1 && panel) rc = Hal::AcpiVideo::Brightness(percent); return rc; } static int64_t Sys_DisplayInfo(DisplayInfo* out) { if (!Drivers::Graphics::IntelGPU::GetDisplayInfo(out)) return NoDevice; // The driver only knows its own backends; report what a set would use. int level = DisplayBrightness(-1); out->brightness = level >= 0 ? level : -1; if (level >= 0) out->capabilities |= DISPLAY_CAP_BRIGHTNESS; else out->capabilities &= ~DISPLAY_CAP_BRIGHTNESS; return Success; } static int64_t Sys_DisplayModes(DisplayModeInfo* out, int maxCount) { return Drivers::Graphics::IntelGPU::GetDisplayModes(out, maxCount); } static int64_t Sys_DisplaySetMode(int modeIndex) { // The driver answers -2 for a mode that would need link retraining // and -3 for a modeset that failed and was rolled back. int64_t rc = Drivers::Graphics::IntelGPU::SetDisplayMode(modeIndex); if (rc == -2) return UnsupportedOp; if (rc == -3) return IoFailure; return rc; } // percent < 0 queries. Returns 0..100, NoDevice when the active output // has no controllable backlight, IoFailure when the hardware refused. static int64_t Sys_DisplayBrightness(int percent) { int rc = DisplayBrightness(percent); if (rc >= 0) return rc; return rc == -1 ? NoDevice : IoFailure; } static uint64_t Sys_TermSize() { // If the process is redirected to a GUI terminal, return those dimensions auto* proc = Sched::GetCurrentProcessPtr(); if (proc && proc->redirected) { if (proc->termCols > 0 && proc->termRows > 0) { return ((uint64_t)proc->termRows << 32) | ((uint64_t)proc->termCols & 0xFFFFFFFF); } auto* target = GetRedirTarget(proc); if (target && target->termCols > 0 && target->termRows > 0) { return ((uint64_t)target->termRows << 32) | ((uint64_t)target->termCols & 0xFFFFFFFF); } } size_t cols = 0, rows = 0; flanterm_get_dimensions(Kt::ctx, &cols, &rows); return (rows << 32) | (cols & 0xFFFFFFFF); } static int64_t Sys_TermScale(uint64_t scale_x, uint64_t scale_y) { if (scale_x == 0) { return (int64_t)((Kt::GetFontScaleY() << 32) | (Kt::GetFontScaleX() & 0xFFFFFFFF)); } Kt::Rescale((size_t)scale_x, (size_t)scale_y); size_t cols = 0, rows = 0; flanterm_get_dimensions(Kt::ctx, &cols, &rows); return (int64_t)((rows << 32) | (cols & 0xFFFFFFFF)); } };