Files
MontaukOS/kernel/src/Api/Graphics.hpp
T

216 lines
8.7 KiB
C++

/*
* Graphics.hpp
* SYS_FBINFO, SYS_FBMAP, SYS_TERMSIZE, SYS_TERMSCALE syscalls
* Copyright (c) 2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
#include <Sched/Scheduler.hpp>
#include <Memory/Paging.hpp>
#include <Memory/HHDM.hpp>
#include <Graphics/Framebuffer.hpp>
#include <Drivers/Graphics/IntelGPU.hpp>
#include <ACPI/AcpiVideo.hpp>
#include <ACPI/WmiEcBacklight.hpp>
#include <Terminal/Terminal.hpp>
#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));
}
};