feat: userspace overhaul, Intel GPU driver, and more

This commit is contained in:
2026-02-19 15:46:49 +01:00
parent d355d376f9
commit cae7dd352e
55 changed files with 9080 additions and 270 deletions
+65 -2
View File
@@ -18,6 +18,7 @@
#include <Libraries/String.hpp>
#include <Drivers/PS2/Keyboard.hpp>
#include <Net/Icmp.hpp>
#include <Net/Dns.hpp>
#include <Net/Socket.hpp>
#include <Net/ByteOrder.hpp>
#include <Net/NetConfig.hpp>
@@ -209,11 +210,18 @@ namespace Zenith {
* Graphics::Cursor::GetFramebufferPitch();
uint64_t numPages = (fbSize + 0xFFF) / 0x1000;
Kt::KernelLogStream(Kt::INFO, "FbMap") << "fbPhys=" << kcp::hex << fbPhys
<< " size=" << kcp::dec << fbSize
<< " pages=" << numPages
<< " (" << Graphics::Cursor::GetFramebufferWidth()
<< "x" << Graphics::Cursor::GetFramebufferHeight()
<< " pitch=" << Graphics::Cursor::GetFramebufferPitch() << ")";
// Map at a fixed user VA
constexpr uint64_t userVa = 0x50000000ULL;
for (uint64_t i = 0; i < numPages; i++) {
Memory::VMM::Paging::MapUserIn(
Memory::VMM::Paging::MapUserInWC(
proc->pml4Phys,
fbPhys + i * 0x1000,
userVa + i * 0x1000
@@ -335,6 +343,7 @@ namespace Zenith {
}
out->_pad[0] = 0;
out->_pad[1] = 0;
out->dnsServer = Net::GetDnsServer();
}
static int Sys_SetNetCfg(const NetCfg* in) {
@@ -342,6 +351,7 @@ namespace Zenith {
Net::SetIpAddress(in->ipAddress);
Net::SetSubnetMask(in->subnetMask);
Net::SetGateway(in->gateway);
Net::SetDnsServer(in->dnsServer);
return 0;
}
@@ -372,6 +382,53 @@ namespace Zenith {
return Fs::Vfs::VfsCreate(path);
}
// ---- Terminal scaling ----
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));
}
// ---- DNS resolve ----
static int64_t Sys_Resolve(const char* hostname) {
uint32_t ip = Net::Dns::Resolve(hostname);
return (int64_t)ip;
}
// ---- Random number generation ----
// Uses RDTSC mixed with xorshift64* PRNG for entropy.
// RDRAND is intentionally avoided: some firmware disables the RDRAND
// hardware unit while CPUID still advertises support (bit 30 of ECX),
// causing #UD on real hardware. RDTSC-based entropy is sufficient for
// seeding BearSSL's PRNG for TLS session keys.
static int64_t Sys_GetRandom(uint8_t* buf, uint64_t len) {
uint64_t tsc;
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
uint64_t state = tsc;
for (uint64_t i = 0; i < len; i += 8) {
asm volatile("rdtsc; shl $32, %%rdx; or %%rdx, %%rax" : "=a"(tsc) :: "rdx");
state ^= tsc;
state ^= state >> 12;
state ^= state << 25;
state ^= state >> 27;
uint64_t val = state * 0x2545F4914F6CDD1DULL;
uint64_t remaining = len - i;
uint64_t toCopy = remaining < 8 ? remaining : 8;
for (uint64_t j = 0; j < toCopy; j++)
buf[i + j] = (uint8_t)(val >> (j * 8));
}
return (int64_t)len;
}
// ---- Dispatch ----
extern "C" int64_t SyscallDispatch(SyscallFrame* frame) {
@@ -486,6 +543,12 @@ namespace Zenith {
frame->arg3, frame->arg4);
case SYS_FCREATE:
return (int64_t)Sys_FCreate((const char*)frame->arg1);
case SYS_TERMSCALE:
return Sys_TermScale(frame->arg1, frame->arg2);
case SYS_RESOLVE:
return Sys_Resolve((const char*)frame->arg1);
case SYS_GETRANDOM:
return Sys_GetRandom((uint8_t*)frame->arg1, frame->arg2);
default:
return -1;
}
@@ -512,7 +575,7 @@ namespace Zenith {
Hal::WriteMSR(Hal::IA32_FMASK, 0x200);
Kt::KernelLogStream(Kt::OK, "Syscall") << "SYSCALL/SYSRET initialized (LSTAR="
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 43 syscalls)";
<< kcp::hex << (uint64_t)SyscallEntry << kcp::dec << ", 46 syscalls)";
}
}
+4
View File
@@ -54,6 +54,9 @@ namespace Zenith {
static constexpr uint64_t SYS_RECVFROM = 40;
static constexpr uint64_t SYS_FWRITE = 41;
static constexpr uint64_t SYS_FCREATE = 42;
static constexpr uint64_t SYS_TERMSCALE = 43;
static constexpr uint64_t SYS_RESOLVE = 44;
static constexpr uint64_t SYS_GETRANDOM = 45;
static constexpr int SOCK_TCP = 1;
static constexpr int SOCK_UDP = 2;
@@ -88,6 +91,7 @@ namespace Zenith {
uint32_t gateway; // network byte order
uint8_t macAddress[6];
uint8_t _pad[2];
uint32_t dnsServer; // network byte order
};
struct KeyEvent {
+6
View File
@@ -32,6 +32,12 @@ void Panic(const char *meditationString, System::PanicFrame* frame) {
if (frame->InterruptVector == 0xE) {
auto pf_frame = (System::PageFaultPanicFrame*)frame;
frame = (System::PanicFrame*)&pf_frame->IP;
// CR2 holds the faulting virtual address for page faults
uint64_t cr2;
asm volatile("mov %%cr2, %0" : "=r"(cr2));
PrintBoxedHex(kerr, "Faulting Address (CR2)", cr2, boxWidth);
PrintBoxedLine(kerr, "Page Fault Error:", boxWidth, true);
PrintBoxedDec(kerr, "Present", pf_frame->PageFaultError.Present, boxWidth);
PrintBoxedDec(kerr, "Write", pf_frame->PageFaultError.Write, boxWidth);
+564
View File
@@ -0,0 +1,564 @@
/*
* IntelGPU.cpp
* Intel integrated graphics (i915) modesetting driver
* Scans PCI for Intel display controllers, maps MMIO, initializes GTT,
* and sets up a framebuffer using the firmware's existing display timings.
* Copyright (c) 2025 Daniel Hammer
*/
#include "IntelGPU.hpp"
#include <Pci/Pci.hpp>
#include <Terminal/Terminal.hpp>
#include <CppLib/Stream.hpp>
#include <Memory/HHDM.hpp>
#include <Memory/Paging.hpp>
#include <Memory/PageFrameAllocator.hpp>
#include <Io/IoPort.hpp>
#include <Graphics/Cursor.hpp>
using namespace Kt;
namespace Drivers::Graphics::IntelGPU {
// =========================================================================
// Driver state
// =========================================================================
static bool g_initialized = false;
static GpuInfo g_gpuInfo = {};
static volatile uint8_t* g_mmioBase = nullptr;
// GTT state
static volatile void* g_gttBase = nullptr; // Virtual address of GTT entries
static uint64_t g_gttEntryCount = 0; // Number of GTT entries
static uint64_t g_scratchPagePhys = 0; // Physical address of scratch page
static uint8_t g_gpuGen = 0; // Cached generation number
// Framebuffer state
static uint32_t* g_fbBase = nullptr; // HHDM virtual address of first FB page
static uint64_t g_fbPhysBase = 0; // Physical address of first FB page
static uint64_t g_fbWidth = 0;
static uint64_t g_fbHeight = 0;
static uint64_t g_fbPitch = 0; // Stride in bytes
static uint64_t g_fbSize = 0; // Total framebuffer size in bytes
static uint64_t g_fbGttOffset = 0; // GTT offset where FB starts (in bytes)
// =========================================================================
// Register access helpers
// =========================================================================
static void WriteReg(uint32_t reg, uint32_t val) {
*(volatile uint32_t*)(g_mmioBase + reg) = val;
}
static uint32_t ReadReg(uint32_t reg) {
return *(volatile uint32_t*)(g_mmioBase + reg);
}
// =========================================================================
// PCI Detection
// =========================================================================
static bool DetectGpu() {
auto& devices = Pci::GetDevices();
const Pci::PciDevice* found = nullptr;
const DeviceInfo* matchedInfo = nullptr;
for (uint64_t i = 0; i < devices.size(); i++) {
if (devices[i].VendorId != VendorIntel) {
continue;
}
if (devices[i].ClassCode != ClassDisplay) {
continue;
}
// Found an Intel display controller; try to match device ID
found = &devices[i];
for (int j = 0; j < SupportedDeviceCount; j++) {
if (devices[i].DeviceId == SupportedDevices[j].deviceId) {
matchedInfo = &SupportedDevices[j];
break;
}
}
// Stop at first Intel display controller
break;
}
if (found == nullptr) {
KernelLogStream(WARNING, "IntelGPU") << "No Intel display controller found";
return false;
}
g_gpuInfo.pciBus = found->Bus;
g_gpuInfo.pciDevice = found->Device;
g_gpuInfo.pciFunction = found->Function;
g_gpuInfo.deviceId = found->DeviceId;
if (matchedInfo != nullptr) {
g_gpuInfo.gen = matchedInfo->gen;
g_gpuInfo.name = matchedInfo->name;
KernelLogStream(OK, "IntelGPU") << "Found " << matchedInfo->name
<< " (device " << base::hex << (uint64_t)found->DeviceId << ")"
<< " at PCI " << (uint64_t)found->Bus << ":"
<< (uint64_t)found->Device << "." << (uint64_t)found->Function;
} else {
// Unknown device ID - accept generically but warn
g_gpuInfo.gen = 7; // Assume gen 7 as a safe default
g_gpuInfo.name = "Unknown Intel GPU";
KernelLogStream(WARNING, "IntelGPU") << "Unknown Intel display controller "
<< "(device " << base::hex << (uint64_t)found->DeviceId << ")"
<< " at PCI " << (uint64_t)found->Bus << ":"
<< (uint64_t)found->Device << "." << (uint64_t)found->Function
<< " - attempting generic initialization";
}
g_gpuGen = g_gpuInfo.gen;
return true;
}
// =========================================================================
// BAR0 MMIO Mapping
// =========================================================================
static bool MapMmio() {
uint8_t bus = g_gpuInfo.pciBus;
uint8_t dev = g_gpuInfo.pciDevice;
uint8_t func = g_gpuInfo.pciFunction;
// Read BAR0
uint32_t bar0Low = Pci::LegacyRead32(bus, dev, func, (uint8_t)PCI_REG_BAR0);
uint64_t mmioPhys = bar0Low & 0xFFFFFFF0u; // Mask off type/prefetchable bits
// Check for 64-bit BAR (bit 2 of BAR type field)
if (bar0Low & 0x04) {
uint32_t bar0High = Pci::LegacyRead32(bus, dev, func, (uint8_t)(PCI_REG_BAR0 + 4));
mmioPhys |= ((uint64_t)bar0High << 32);
}
g_gpuInfo.mmioPhys = mmioPhys;
g_gpuInfo.mmioSize = 0x200000; // Map 2MB of MMIO space
KernelLogStream(INFO, "IntelGPU") << "BAR0 physical: " << base::hex << mmioPhys;
// Map 2MB of MMIO space (0x200 pages)
for (uint64_t offset = 0; offset < 0x200000; offset += 0x1000) {
Memory::VMM::g_paging->MapMMIO(mmioPhys + offset, Memory::HHDM(mmioPhys + offset));
}
g_mmioBase = (volatile uint8_t*)Memory::HHDM(mmioPhys);
KernelLogStream(OK, "IntelGPU") << "MMIO mapped at virtual " << base::hex << (uint64_t)g_mmioBase;
// Enable memory space and bus master in PCI command register
uint16_t pciCmd = Pci::LegacyRead16(bus, dev, func, (uint8_t)PCI_REG_COMMAND);
pciCmd |= PCI_CMD_MEM_SPACE | PCI_CMD_BUS_MASTER;
Pci::LegacyWrite16(bus, dev, func, (uint8_t)PCI_REG_COMMAND, pciCmd);
KernelLogStream(OK, "IntelGPU") << "PCI memory space and bus mastering enabled";
return true;
}
// =========================================================================
// VGA Disable
// =========================================================================
static void DisableVga() {
// Check if VGA plane is already disabled (bit 31 of VGACNTRL).
// On modern Intel iGPUs with eDP/LVDS panels, the firmware typically
// disables VGA when it sets up GOP. Skip if already done.
uint32_t vgaCtrl = ReadReg(VGACNTRL);
if (vgaCtrl & VGACNTRL_DISABLE) {
KernelLogStream(INFO, "IntelGPU") << "VGA plane already disabled by firmware";
return;
}
// Step 1: Disable VGA screen via the VGA sequencer I/O ports
// SR01 bit 5 must be set BEFORE disabling the VGA plane register
Io::Out8(0x01, 0x3C4);
uint8_t sr01 = Io::In8(0x3C5);
sr01 |= (1 << 5);
Io::Out8(0x01, 0x3C4);
Io::Out8(sr01, 0x3C5);
// Step 2: Set bit 31 of VGACNTRL MMIO register to disable VGA display plane
vgaCtrl |= VGACNTRL_DISABLE;
WriteReg(VGACNTRL, vgaCtrl);
// Step 3: Read back to flush the write
(void)ReadReg(VGACNTRL);
KernelLogStream(OK, "IntelGPU") << "VGA plane disabled";
}
// =========================================================================
// Read Current Display State
// =========================================================================
static bool ReadDisplayState() {
// Read pipe A configuration
uint32_t pipeConf = ReadReg(PIPEACONF);
bool pipeEnabled = (pipeConf & PIPECONF_ENABLE) != 0;
// Read plane A control
uint32_t dspaCntr = ReadReg(DSPACNTR);
bool planeEnabled = (dspaCntr & DISP_ENABLE) != 0;
// Read current surface address and stride
uint32_t dspaSurf = ReadReg(DSPASURF);
uint32_t dspaStride = ReadReg(DSPASTRIDE);
// Read timing registers
uint32_t htotal = ReadReg(HTOTAL_A);
uint32_t vtotal = ReadReg(VTOTAL_A);
uint32_t pipeSrc = ReadReg(PIPEASRC);
KernelLogStream(INFO, "IntelGPU") << "Pipe A: "
<< (pipeEnabled ? "ENABLED" : "DISABLED")
<< ", Plane A: " << (planeEnabled ? "ENABLED" : "DISABLED");
KernelLogStream(INFO, "IntelGPU") << "DSPASURF: " << base::hex << (uint64_t)dspaSurf
<< ", DSPASTRIDE: " << (uint64_t)dspaStride;
KernelLogStream(INFO, "IntelGPU") << "HTOTAL_A: " << base::hex << (uint64_t)htotal
<< ", VTOTAL_A: " << (uint64_t)vtotal
<< ", PIPEASRC: " << (uint64_t)pipeSrc;
// Extract resolution from PIPEASRC (preferred) or timing registers
if (pipeSrc != 0) {
// PIPEASRC: bits [31:16] = horizontal size - 1, bits [15:0] = vertical size - 1
g_fbWidth = ((pipeSrc >> 16) & 0xFFFF) + 1;
g_fbHeight = (pipeSrc & 0xFFFF) + 1;
} else if (pipeEnabled) {
// Fallback to timing registers
g_fbWidth = (htotal & 0xFFF) + 1;
g_fbHeight = (vtotal & 0xFFF) + 1;
}
// Read stride from hardware.
// On Gen 9+ (Skylake and later), PLANE_STRIDE (same offset as DSPASTRIDE)
// stores the stride in 64-byte units, not bytes. Detect this by checking
// whether the raw value is too small to be a byte stride.
if (dspaStride != 0) {
g_fbPitch = dspaStride;
if (g_fbWidth > 0 && g_fbPitch < g_fbWidth * 4) {
KernelLogStream(INFO, "IntelGPU") << "DSPASTRIDE=" << base::dec << g_fbPitch
<< " is in 64-byte units (Gen 9+), converting to bytes";
g_fbPitch *= 64;
}
}
// If we still don't have valid dimensions, fall back to the firmware framebuffer
if (g_fbWidth == 0 || g_fbHeight == 0 || g_fbPitch == 0) {
g_fbWidth = ::Graphics::Cursor::GetFramebufferWidth();
g_fbHeight = ::Graphics::Cursor::GetFramebufferHeight();
g_fbPitch = ::Graphics::Cursor::GetFramebufferPitch();
KernelLogStream(INFO, "IntelGPU") << "Using firmware framebuffer dimensions: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< " pitch=" << g_fbPitch;
} else {
KernelLogStream(INFO, "IntelGPU") << "Detected resolution: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< " pitch=" << g_fbPitch;
}
if (g_fbWidth == 0 || g_fbHeight == 0) {
KernelLogStream(ERROR, "IntelGPU") << "Could not determine display resolution";
return false;
}
// Ensure pitch is at least width * 4 (BGRX8888)
if (g_fbPitch == 0) {
g_fbPitch = g_fbWidth * 4;
KernelLogStream(WARNING, "IntelGPU") << "Stride not available, assuming "
<< base::dec << g_fbPitch << " bytes";
}
g_fbSize = g_fbHeight * g_fbPitch;
KernelLogStream(OK, "IntelGPU") << "Display state: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< ", stride=" << g_fbPitch
<< ", FB size=" << g_fbSize << " bytes";
return true;
}
// =========================================================================
// GTT Initialization
// =========================================================================
static bool InitializeGtt() {
uint8_t bus = g_gpuInfo.pciBus;
uint8_t dev = g_gpuInfo.pciDevice;
uint8_t func = g_gpuInfo.pciFunction;
// Read GMCH_CTL to determine GTT size
uint16_t gmchCtl = Pci::LegacyRead16(bus, dev, func, (uint8_t)PCI_REG_GMCH_CTL);
uint8_t gttSizeBits = (gmchCtl >> 8) & 0x3;
uint64_t gttSizeBytes = 0;
if (g_gpuGen >= 8) {
// Gen 8+ has a different encoding for GTT size
switch (gttSizeBits) {
case 0: gttSizeBytes = 0; break; // No GTT
case 1: gttSizeBytes = 2 * 1024 * 1024; break; // 2MB
case 2: gttSizeBytes = 4 * 1024 * 1024; break; // 4MB
case 3: gttSizeBytes = 8 * 1024 * 1024; break; // 8MB
default: gttSizeBytes = 2 * 1024 * 1024; break;
}
} else {
// Gen 5-7
switch (gttSizeBits) {
case 0: gttSizeBytes = 0; break; // No GTT
case 1: gttSizeBytes = 1024 * 1024; break; // 1MB
case 2: gttSizeBytes = 2 * 1024 * 1024; break; // 2MB
case 3: gttSizeBytes = 2 * 1024 * 1024; break; // Depends on gen, default 2MB
default: gttSizeBytes = 1024 * 1024; break;
}
}
if (gttSizeBytes == 0) {
// If hardware reports no GTT, assume 1MB as a safe fallback
gttSizeBytes = 1024 * 1024;
KernelLogStream(WARNING, "IntelGPU") << "GMCH_CTL reports no GTT, assuming 1MB";
}
g_gpuInfo.gttSize = gttSizeBytes;
KernelLogStream(INFO, "IntelGPU") << "GMCH_CTL: " << base::hex << (uint64_t)gmchCtl
<< ", GTT size: " << base::dec << (gttSizeBytes / 1024) << " KB";
// The GTT entries reside at BAR0 + 2MB (offset 0x200000)
// This is correct for most Intel generations
uint64_t gttPhys = g_gpuInfo.mmioPhys + 0x200000;
// Map the GTT region (it may overlap with already-mapped MMIO, but we map
// additional pages beyond the initial 2MB MMIO mapping)
uint64_t gttMapSize = gttSizeBytes;
// Ensure we map at least up to the GTT region end
for (uint64_t offset = 0; offset < gttMapSize; offset += 0x1000) {
Memory::VMM::g_paging->MapMMIO(gttPhys + offset, Memory::HHDM(gttPhys + offset));
}
g_gttBase = (volatile void*)Memory::HHDM(gttPhys);
// Calculate number of GTT entries
if (g_gpuGen >= 8) {
// 64-bit PTEs
g_gttEntryCount = gttSizeBytes / sizeof(uint64_t);
} else {
// 32-bit PTEs
g_gttEntryCount = gttSizeBytes / sizeof(uint32_t);
}
KernelLogStream(INFO, "IntelGPU") << "GTT at physical " << base::hex << gttPhys
<< ", " << base::dec << g_gttEntryCount << " entries"
<< (g_gpuGen >= 8 ? " (64-bit PTEs)" : " (32-bit PTEs)");
// Allocate a scratch page (zeroed) for future use
void* scratchPageVirt = Memory::g_pfa->AllocateZeroed();
g_scratchPagePhys = Memory::SubHHDM(scratchPageVirt);
// Do NOT clear the entire GTT here. The firmware has active GTT mappings
// that the display engine is currently scanning out from. Clearing them
// would cause the display to go black (or worse) before we remap.
// Instead, we only write the entries we need in SetupFramebuffer().
KernelLogStream(OK, "IntelGPU") << "GTT ready: " << base::dec << g_gttEntryCount
<< " entries, scratch page at " << base::hex << g_scratchPagePhys;
return true;
}
// =========================================================================
// Framebuffer Allocation and Setup
// =========================================================================
static bool SetupFramebuffer() {
// Map the firmware framebuffer's contiguous physical pages through our GTT.
// This keeps the same physical memory that the firmware set up (contiguous
// pages, already HHDM-mapped), so both kernel and userspace access continue
// to work via the original virtual/physical addresses. No copy is needed.
uint32_t* fwFb = ::Graphics::Cursor::GetFramebufferBase();
if (fwFb == nullptr) {
KernelLogStream(ERROR, "IntelGPU") << "No firmware framebuffer available";
return false;
}
uint64_t fwFbPhys = Memory::SubHHDM((uint64_t)fwFb);
uint64_t pageCount = (g_fbSize + 0xFFF) / 0x1000;
if (pageCount > g_gttEntryCount) {
KernelLogStream(ERROR, "IntelGPU") << "Framebuffer requires " << base::dec << pageCount
<< " pages but GTT only has " << g_gttEntryCount << " entries";
return false;
}
KernelLogStream(INFO, "IntelGPU") << "Mapping " << base::dec << pageCount
<< " firmware FB pages through GTT (phys base " << base::hex << fwFbPhys << ")";
// Program GTT entries to point to the firmware FB's contiguous physical pages
if (g_gpuGen >= 8) {
volatile uint64_t* gtt64 = (volatile uint64_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) {
gtt64[i] = MakeGttPte64(fwFbPhys + i * 0x1000);
}
// Flush GTT writes
(void)gtt64[pageCount - 1];
} else {
volatile uint32_t* gtt32 = (volatile uint32_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) {
gtt32[i] = MakeGttPte32(fwFbPhys + i * 0x1000);
}
// Flush GTT writes
(void)gtt32[pageCount - 1];
}
// Keep using the same framebuffer memory
g_fbBase = fwFb;
g_fbPhysBase = fwFbPhys;
g_fbGttOffset = 0; // Starting at GTT entry 0 => offset 0
KernelLogStream(OK, "IntelGPU") << "Framebuffer mapped through GTT: " << base::dec << pageCount
<< " pages, phys=" << base::hex << fwFbPhys;
return true;
}
static void ProgramDisplayPlane() {
// Preserve the firmware's DSPACNTR value entirely. The firmware already
// configured the correct pixel format, pipe assignment, and tiling mode.
// We only need to point DSPASURF to our GTT-mapped framebuffer.
uint32_t dspaCntr = ReadReg(DSPACNTR);
uint32_t fmtBits = (dspaCntr & DISP_FORMAT_MASK) >> DISP_FORMAT_SHIFT;
uint32_t oldDspaSurf = ReadReg(DSPASURF);
KernelLogStream(INFO, "IntelGPU") << "Preserving firmware DSPACNTR: " << base::hex
<< (uint64_t)dspaCntr << " (format=" << (uint64_t)fmtBits << ")";
KernelLogStream(INFO, "IntelGPU") << "Firmware DSPASURF was: " << base::hex
<< (uint64_t)oldDspaSurf;
// Ensure the plane is enabled (it should already be)
if (!(dspaCntr & DISP_ENABLE)) {
dspaCntr |= DISP_ENABLE;
WriteReg(DSPACNTR, dspaCntr);
}
// Do NOT write DSPASTRIDE — the firmware already set the correct value
// in the hardware's native format (bytes on Gen <9, 64-byte units on Gen 9+).
// Writing our byte-converted g_fbPitch would corrupt it on Gen 9+.
// Write the GTT base offset to DSPASURF - this triggers the plane update
// Since we mapped at GTT entry 0, the offset is 0
uint32_t surfAddr = (uint32_t)g_fbGttOffset;
WriteReg(DSPASURF, surfAddr);
// Read back to flush
(void)ReadReg(DSPASURF);
KernelLogStream(OK, "IntelGPU") << "Display plane A: DSPASURF="
<< base::hex << (uint64_t)surfAddr
<< " (was " << (uint64_t)oldDspaSurf << ")"
<< ", stride=" << base::dec << g_fbPitch;
}
// =========================================================================
// Public API
// =========================================================================
void Initialize() {
KernelLogStream(INFO, "IntelGPU") << "Scanning for Intel integrated graphics...";
// Step 1: Detect GPU on PCI bus
if (!DetectGpu()) {
return;
}
// Step 2: Map BAR0 MMIO region
if (!MapMmio()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to map MMIO region";
return;
}
// Step 3: Disable VGA plane
DisableVga();
// Step 4: Read current display state from firmware
if (!ReadDisplayState()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
return;
}
// Step 5: Initialize GTT
if (!InitializeGtt()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to initialize GTT";
return;
}
// Step 6: Map firmware framebuffer pages through GTT
if (!SetupFramebuffer()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to set up framebuffer";
return;
}
// Step 7: Program the display plane to use our GTT-mapped framebuffer
ProgramDisplayPlane();
g_initialized = true;
// Diagnostic: compare GPU-detected values with firmware/Limine values
uint64_t fwWidth = ::Graphics::Cursor::GetFramebufferWidth();
uint64_t fwHeight = ::Graphics::Cursor::GetFramebufferHeight();
uint64_t fwPitch = ::Graphics::Cursor::GetFramebufferPitch();
if (g_fbWidth != fwWidth || g_fbHeight != fwHeight || g_fbPitch != fwPitch) {
KernelLogStream(WARNING, "IntelGPU") << "GPU dimensions differ from firmware!";
KernelLogStream(WARNING, "IntelGPU") << " GPU: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< " pitch=" << g_fbPitch;
KernelLogStream(WARNING, "IntelGPU") << " Firmware: "
<< base::dec << fwWidth << "x" << fwHeight
<< " pitch=" << fwPitch;
}
KernelLogStream(OK, "IntelGPU") << "Initialization complete: "
<< base::dec << g_fbWidth << "x" << g_fbHeight
<< " @ " << base::hex << (uint64_t)g_fbBase;
}
bool IsInitialized() {
return g_initialized;
}
const GpuInfo* GetGpuInfo() {
return &g_gpuInfo;
}
uint32_t* GetFramebufferBase() {
return g_fbBase;
}
uint64_t GetFramebufferPhysBase() {
return g_fbPhysBase;
}
uint64_t GetWidth() {
return g_fbWidth;
}
uint64_t GetHeight() {
return g_fbHeight;
}
uint64_t GetPitch() {
return g_fbPitch;
}
};
+420
View File
@@ -0,0 +1,420 @@
/*
* IntelGPU.hpp
* Intel integrated graphics (i915) modesetting driver
* Supports Gen 5 (Ironlake) through Gen 12 (Tiger Lake / Alder Lake)
* Copyright (c) 2025 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Drivers::Graphics::IntelGPU {
// =========================================================================
// PCI identification
// =========================================================================
static constexpr uint16_t VendorIntel = 0x8086;
static constexpr uint8_t ClassDisplay = 0x03;
static constexpr uint8_t SubclassVGA = 0x00;
// PCI config space offsets
static constexpr uint16_t PCI_REG_BAR0 = 0x10;
static constexpr uint16_t PCI_REG_BAR2 = 0x18;
static constexpr uint16_t PCI_REG_COMMAND = 0x04;
static constexpr uint16_t PCI_CMD_MEM_SPACE = (1 << 1);
static constexpr uint16_t PCI_CMD_BUS_MASTER = (1 << 2);
// Graphics control register (PCI config offset 0x50 on SNB+)
static constexpr uint16_t PCI_REG_GMCH_CTL = 0x50;
// Supported Intel GPU device IDs (representative subset)
struct DeviceInfo {
uint16_t deviceId;
uint8_t gen; // Intel graphics generation (5-12)
const char* name;
};
static constexpr DeviceInfo SupportedDevices[] = {
// Gen 5 - Ironlake
{0x0042, 5, "Ironlake Desktop"},
{0x0046, 5, "Ironlake Mobile"},
// Gen 6 - Sandy Bridge
{0x0102, 6, "Sandy Bridge GT1 Desktop"},
{0x0112, 6, "Sandy Bridge GT2 Desktop"},
{0x0122, 6, "Sandy Bridge GT2 Desktop"},
{0x0106, 6, "Sandy Bridge GT1 Mobile"},
{0x0116, 6, "Sandy Bridge GT2 Mobile"},
{0x0126, 6, "Sandy Bridge GT2 Mobile"},
{0x010A, 6, "Sandy Bridge GT1 Server"},
// Gen 7 - Ivy Bridge
{0x0152, 7, "Ivy Bridge GT1 Desktop"},
{0x0162, 7, "Ivy Bridge GT2 Desktop"},
{0x0156, 7, "Ivy Bridge GT1 Mobile"},
{0x0166, 7, "Ivy Bridge GT2 Mobile"},
{0x015A, 7, "Ivy Bridge GT1 Server"},
{0x016A, 7, "Ivy Bridge GT2 Server"},
// Gen 7.5 - Haswell
{0x0402, 7, "Haswell GT1 Desktop"},
{0x0412, 7, "Haswell GT2 Desktop"},
{0x0422, 7, "Haswell GT3 Desktop"},
{0x0406, 7, "Haswell GT1 Mobile"},
{0x0416, 7, "Haswell GT2 Mobile"},
{0x0426, 7, "Haswell GT3 Mobile"},
{0x0A06, 7, "Haswell ULT GT1"},
{0x0A16, 7, "Haswell ULT GT2"},
{0x0A26, 7, "Haswell ULT GT3"},
{0x0D12, 7, "Haswell CRW GT2"},
{0x0D22, 7, "Haswell CRW GT3"},
// Gen 8 - Broadwell
{0x1602, 8, "Broadwell GT1"},
{0x1612, 8, "Broadwell GT2"},
{0x1616, 8, "Broadwell GT2 Mobile"},
{0x1622, 8, "Broadwell GT3"},
{0x1626, 8, "Broadwell GT3 Mobile"},
{0x162A, 8, "Broadwell GT3 Server"},
// Gen 9 - Skylake
{0x1902, 9, "Skylake GT1 Desktop"},
{0x1906, 9, "Skylake GT1 Mobile"},
{0x1912, 9, "Skylake GT2 Desktop"},
{0x1916, 9, "Skylake GT2 Mobile"},
{0x191E, 9, "Skylake GT2 Mobile"},
{0x1926, 9, "Skylake GT3 Mobile"},
{0x1932, 9, "Skylake GT4 Desktop"},
// Gen 9.5 - Kaby Lake / Coffee Lake
{0x5902, 9, "Kaby Lake GT1 Desktop"},
{0x5912, 9, "Kaby Lake GT2 Desktop"},
{0x5916, 9, "Kaby Lake GT2 Mobile"},
{0x5926, 9, "Kaby Lake GT3 Mobile"},
{0x3E90, 9, "Coffee Lake GT1 Desktop"},
{0x3E92, 9, "Coffee Lake GT2 Desktop"},
{0x3EA0, 9, "Coffee Lake GT3"},
{0x3E91, 9, "Coffee Lake GT2 Desktop"},
{0x3E98, 9, "Coffee Lake GT2 Desktop"},
{0x9B41, 9, "Comet Lake GT2"},
{0x9BA5, 9, "Comet Lake GT2 Mobile"},
// Gen 11 - Ice Lake
{0x8A52, 11, "Ice Lake GT2"},
{0x8A56, 11, "Ice Lake GT2 Mobile"},
{0x8A5A, 11, "Ice Lake GT1.5"},
{0x8A5C, 11, "Ice Lake GT1"},
// Gen 12 - Tiger Lake
{0x9A49, 12, "Tiger Lake GT2"},
{0x9A78, 12, "Tiger Lake GT2"},
{0x9A40, 12, "Tiger Lake GT2"},
// Gen 12 - Alder Lake
{0x4626, 12, "Alder Lake GT2"},
{0x4680, 12, "Alder Lake-S GT1"},
{0x4692, 12, "Alder Lake-S GT1"},
{0x46A6, 12, "Alder Lake-P GT2"},
};
static constexpr int SupportedDeviceCount = sizeof(SupportedDevices) / sizeof(SupportedDevices[0]);
// =========================================================================
// MMIO register offsets (relative to BAR0)
// =========================================================================
// --- VGA control ---
static constexpr uint32_t VGACNTRL = 0x71400;
static constexpr uint32_t VGACNTRL_DISABLE = (1u << 31);
// --- DPLL (Display PLL) ---
static constexpr uint32_t DPLL_A = 0x06014;
static constexpr uint32_t DPLL_B = 0x06018;
static constexpr uint32_t FPA0 = 0x06040;
static constexpr uint32_t FPA1 = 0x06044;
static constexpr uint32_t FPB0 = 0x06048;
static constexpr uint32_t FPB1 = 0x0604C;
// DPLL control bits
static constexpr uint32_t DPLL_VCO_ENABLE = (1u << 31);
static constexpr uint32_t DPLL_VGA_MODE_DIS = (1u << 28);
static constexpr uint32_t DPLL_MODE_DAC_SDVO = (1u << 26);
static constexpr uint32_t DPLL_MODE_LVDS = (2u << 26);
// FP register fields
static constexpr uint32_t FP_N_DIV_SHIFT = 16;
static constexpr uint32_t FP_N_DIV_MASK = 0x3F0000;
static constexpr uint32_t FP_M1_DIV_SHIFT = 8;
static constexpr uint32_t FP_M1_DIV_MASK = 0x003F00;
static constexpr uint32_t FP_M2_DIV_SHIFT = 0;
static constexpr uint32_t FP_M2_DIV_MASK = 0x00003F;
// --- Display timing registers (Pipe A) ---
static constexpr uint32_t HTOTAL_A = 0x60000;
static constexpr uint32_t HBLANK_A = 0x60004;
static constexpr uint32_t HSYNC_A = 0x60008;
static constexpr uint32_t VTOTAL_A = 0x6000C;
static constexpr uint32_t VBLANK_A = 0x60010;
static constexpr uint32_t VSYNC_A = 0x60014;
static constexpr uint32_t PIPEASRC = 0x6001C;
// --- Display timing registers (Pipe B) ---
static constexpr uint32_t HTOTAL_B = 0x61000;
static constexpr uint32_t HBLANK_B = 0x61004;
static constexpr uint32_t HSYNC_B = 0x61008;
static constexpr uint32_t VTOTAL_B = 0x6100C;
static constexpr uint32_t VBLANK_B = 0x61010;
static constexpr uint32_t VSYNC_B = 0x61014;
static constexpr uint32_t PIPEBSRC = 0x6101C;
// --- Pipe configuration ---
static constexpr uint32_t PIPEACONF = 0x70008;
static constexpr uint32_t PIPEBCONF = 0x71008;
static constexpr uint32_t PIPECONF_ENABLE = (1u << 31);
static constexpr uint32_t PIPECONF_STATE = (1u << 30);
static constexpr uint32_t PIPECONF_8BPC = (0u << 5);
static constexpr uint32_t PIPECONF_10BPC = (1u << 5);
static constexpr uint32_t PIPECONF_6BPC = (2u << 5);
static constexpr uint32_t PIPECONF_12BPC = (3u << 5);
// --- Display plane control (Plane A, pre-Skylake i9xx-style) ---
static constexpr uint32_t DSPACNTR = 0x70180;
static constexpr uint32_t DSPALINOFF = 0x70184;
static constexpr uint32_t DSPASTRIDE = 0x70188;
static constexpr uint32_t DSPAPOS = 0x7018C;
static constexpr uint32_t DSPASIZE = 0x70190;
static constexpr uint32_t DSPASURF = 0x7019C;
static constexpr uint32_t DSPATILEOFF = 0x701A4;
// --- Display plane control (Plane B) ---
static constexpr uint32_t DSPBCNTR = 0x71180;
static constexpr uint32_t DSPBLINOFF = 0x71184;
static constexpr uint32_t DSPBSTRIDE = 0x71188;
static constexpr uint32_t DSPBPOS = 0x7118C;
static constexpr uint32_t DSPBSIZE = 0x71190;
static constexpr uint32_t DSPBSURF = 0x7119C;
static constexpr uint32_t DSPBTILEOFF = 0x711A4;
// DSPCNTR bits
static constexpr uint32_t DISP_ENABLE = (1u << 31);
static constexpr uint32_t DISP_GAMMA_ENABLE = (1u << 30);
static constexpr uint32_t DISP_FORMAT_SHIFT = 26;
static constexpr uint32_t DISP_FORMAT_MASK = (0xFu << 26);
static constexpr uint32_t DISP_FORMAT_BGRX8888 = (0x6u << 26); // 32bpp BGRX (no alpha)
static constexpr uint32_t DISP_FORMAT_BGRA8888 = (0x7u << 26); // 32bpp BGRA (with alpha)
static constexpr uint32_t DISP_FORMAT_RGBX8888 = (0xEu << 26); // 32bpp RGBX
static constexpr uint32_t DISP_FORMAT_BGR565 = (0x5u << 26); // 16bpp BGR 5:6:5
static constexpr uint32_t DISP_FORMAT_BGRX1010102 = (0xAu << 26);
static constexpr uint32_t DISP_PIPE_B_SELECT = (1u << 24);
static constexpr uint32_t DISP_TILED = (1u << 10);
// --- Cursor plane (Pipe A) ---
static constexpr uint32_t CURACNTR = 0x70080;
static constexpr uint32_t CURABASE = 0x70084;
static constexpr uint32_t CURAPOS = 0x70088;
// --- Output connectors ---
static constexpr uint32_t ADPA = 0x61100; // Analog Display Port (VGA/CRT)
static constexpr uint32_t DVOB = 0x61140; // DVO-B
static constexpr uint32_t DVOC = 0x61160; // DVO-C
static constexpr uint32_t LVDS = 0x61180; // LVDS panel
static constexpr uint32_t DP_B = 0x64100; // DisplayPort B
static constexpr uint32_t DP_C = 0x64200; // DisplayPort C
static constexpr uint32_t DP_D = 0x64300; // DisplayPort D
static constexpr uint32_t HDMI_B = 0x61140; // HDMI-B (same as DVOB on some gens)
static constexpr uint32_t HDMI_C = 0x61160; // HDMI-C (same as DVOC on some gens)
// ADPA bits
static constexpr uint32_t ADPA_DAC_ENABLE = (1u << 31);
static constexpr uint32_t ADPA_PIPE_B_SELECT = (1u << 30);
static constexpr uint32_t ADPA_HSYNC_ACTIVE_LOW = (1u << 3);
static constexpr uint32_t ADPA_VSYNC_ACTIVE_LOW = (1u << 4);
// LVDS bits
static constexpr uint32_t LVDS_PORT_ENABLE = (1u << 31);
static constexpr uint32_t LVDS_PIPE_B_SELECT = (1u << 30);
// --- GMBUS (I2C for EDID) ---
static constexpr uint32_t GMBUS0 = 0x5100; // Clock/port select
static constexpr uint32_t GMBUS1 = 0x5104; // Command/status
static constexpr uint32_t GMBUS2 = 0x5108; // Status
static constexpr uint32_t GMBUS3 = 0x510C; // Data buffer
static constexpr uint32_t GMBUS4 = 0x5110; // Interrupt mask
static constexpr uint32_t GMBUS5 = 0x5120; // 2-byte index
// --- Hardware status page ---
static constexpr uint32_t HWS_PGA = 0x02080;
// --- Fence registers (tiling) ---
static constexpr uint32_t FENCE_REG_BASE = 0x02000; // Gen 2-3
static constexpr uint32_t FENCE_REG_965_BASE = 0x03000; // Gen 4+
// =========================================================================
// GTT (Graphics Translation Table)
// =========================================================================
// GTT PTE format for Gen 6/7 (Sandy Bridge through Haswell) - 32-bit entries
// Bits [31:12] = physical page address [31:12]
// Bits [10:4] = physical page address [38:32]
// Bits [3:1] = cacheability
// Bit [0] = valid
static constexpr uint32_t GTT_PTE_VALID = (1u << 0);
static constexpr uint32_t GTT_PTE_WB_LLC = (3u << 1); // Write-back LLC cache
static constexpr uint32_t GTT_PTE_UNCACHED = (0u << 1);
// Gen 8+ uses 64-bit GTT PTEs
static constexpr uint64_t GTT_PTE64_VALID = (1ULL << 0);
// Helper: Build a Gen 6/7 GTT PTE from a physical address
static inline uint32_t MakeGttPte32(uint64_t physAddr) {
uint32_t pte = (uint32_t)(physAddr & 0xFFFFF000u); // bits [31:12]
pte |= (uint32_t)((physAddr >> 28) & 0x7F0u); // bits [38:32] -> PTE [10:4]
pte |= GTT_PTE_VALID;
return pte;
}
// Helper: Build a Gen 8+ GTT PTE from a physical address
static inline uint64_t MakeGttPte64(uint64_t physAddr) {
return (physAddr & ~0xFFFULL) | GTT_PTE64_VALID;
}
// =========================================================================
// DPLL clock calculation structures
// =========================================================================
struct DpllParams {
uint32_t n;
uint32_t m1;
uint32_t m2;
uint32_t p1;
uint32_t p2;
};
// DPLL parameter limits (Gen 6 / Sandy Bridge)
struct DpllLimits {
uint32_t nMin, nMax;
uint32_t m1Min, m1Max;
uint32_t m2Min, m2Max;
uint32_t p1Min, p1Max;
uint32_t p2Slow, p2Fast; // P2 values for slow/fast pixel clocks
uint32_t p2Threshold; // kHz threshold between slow/fast P2
uint32_t vcoMin, vcoMax; // VCO range in kHz
uint32_t refClock; // Reference clock in kHz
};
// Sandy Bridge / Ivy Bridge DAC/SDVO limits
static constexpr DpllLimits SNB_DAC_LIMITS = {
.nMin = 1, .nMax = 5,
.m1Min = 12, .m1Max = 22,
.m2Min = 5, .m2Max = 9,
.p1Min = 1, .p1Max = 8,
.p2Slow = 10, .p2Fast = 5,
.p2Threshold = 225000,
.vcoMin = 1750000, .vcoMax = 3500000,
.refClock = 120000,
};
// Sandy Bridge / Ivy Bridge LVDS limits
static constexpr DpllLimits SNB_LVDS_LIMITS = {
.nMin = 1, .nMax = 3,
.m1Min = 12, .m1Max = 22,
.m2Min = 5, .m2Max = 9,
.p1Min = 1, .p1Max = 8,
.p2Slow = 14, .p2Fast = 7,
.p2Threshold = 225000,
.vcoMin = 1750000, .vcoMax = 3500000,
.refClock = 120000,
};
// =========================================================================
// Display mode timing
// =========================================================================
struct DisplayMode {
uint32_t hdisplay; // Horizontal active pixels
uint32_t hsyncStart;
uint32_t hsyncEnd;
uint32_t htotal;
uint32_t vdisplay; // Vertical active lines
uint32_t vsyncStart;
uint32_t vsyncEnd;
uint32_t vtotal;
uint32_t pixelClock; // in kHz
bool hsyncPositive;
bool vsyncPositive;
};
// Common mode timings (CVT/DMT standard)
static constexpr DisplayMode MODE_1920x1080_60 = {
.hdisplay = 1920, .hsyncStart = 2008, .hsyncEnd = 2052, .htotal = 2200,
.vdisplay = 1080, .vsyncStart = 1084, .vsyncEnd = 1089, .vtotal = 1125,
.pixelClock = 148500,
.hsyncPositive = true, .vsyncPositive = true,
};
static constexpr DisplayMode MODE_1280x720_60 = {
.hdisplay = 1280, .hsyncStart = 1390, .hsyncEnd = 1430, .htotal = 1650,
.vdisplay = 720, .vsyncStart = 725, .vsyncEnd = 730, .vtotal = 750,
.pixelClock = 74250,
.hsyncPositive = true, .vsyncPositive = true,
};
static constexpr DisplayMode MODE_1024x768_60 = {
.hdisplay = 1024, .hsyncStart = 1048, .hsyncEnd = 1184, .htotal = 1344,
.vdisplay = 768, .vsyncStart = 771, .vsyncEnd = 777, .vtotal = 806,
.pixelClock = 65000,
.hsyncPositive = false, .vsyncPositive = false,
};
static constexpr DisplayMode MODE_800x600_60 = {
.hdisplay = 800, .hsyncStart = 840, .hsyncEnd = 968, .htotal = 1056,
.vdisplay = 600, .vsyncStart = 601, .vsyncEnd = 605, .vtotal = 628,
.pixelClock = 40000,
.hsyncPositive = true, .vsyncPositive = true,
};
// =========================================================================
// Detected GPU information
// =========================================================================
struct GpuInfo {
uint16_t deviceId;
uint8_t gen;
const char* name;
uint8_t pciBus;
uint8_t pciDevice;
uint8_t pciFunction;
uint64_t mmioPhys; // BAR0 physical address
uint64_t mmioSize; // BAR0 region size
uint64_t gmadrPhys; // BAR2 physical (aperture)
uint64_t gttSize; // GTT size in bytes
uint32_t stolenMb; // Stolen memory in MB
};
// =========================================================================
// Public API
// =========================================================================
// Initialize the Intel GPU driver (scans PCI, maps MMIO, sets up GTT + FB)
void Initialize();
// Check if an Intel GPU was found and initialized
bool IsInitialized();
// Get detected GPU information
const GpuInfo* GetGpuInfo();
// Framebuffer access (returns HHDM virtual address)
uint32_t* GetFramebufferBase();
// Framebuffer physical base (for userspace mapping)
uint64_t GetFramebufferPhysBase();
// Framebuffer dimensions
uint64_t GetWidth();
uint64_t GetHeight();
uint64_t GetPitch();
};
+14
View File
@@ -6,6 +6,7 @@
#include "Cursor.hpp"
#include <Terminal/Terminal.hpp>
#include <Memory/HHDM.hpp>
namespace Graphics::Cursor {
@@ -30,4 +31,17 @@ namespace Graphics::Cursor {
uint64_t GetFramebufferHeight() { return g_FbHeight; }
uint64_t GetFramebufferPitch() { return g_FbPitch; }
void SetFramebuffer(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch) {
g_FbBase = base;
g_FbWidth = width;
g_FbHeight = height;
g_FbPitch = pitch;
Kt::KernelLogStream(Kt::OK, "Graphics") << "Framebuffer switched ("
<< (uint64_t)g_FbWidth << "x" << (uint64_t)g_FbHeight << ")";
}
uint64_t GetFramebufferPhysBase() {
return Memory::SubHHDM((uint64_t)g_FbBase);
}
};
+3
View File
@@ -17,4 +17,7 @@ namespace Graphics::Cursor {
uint64_t GetFramebufferHeight();
uint64_t GetFramebufferPitch();
void SetFramebuffer(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch);
uint64_t GetFramebufferPhysBase();
};
+29
View File
@@ -26,5 +26,34 @@ namespace Hal {
static constexpr uint32_t IA32_STAR = 0xC0000081;
static constexpr uint32_t IA32_LSTAR = 0xC0000082;
static constexpr uint32_t IA32_FMASK = 0xC0000084;
static constexpr uint32_t IA32_PAT = 0x00000277;
// PAT memory type encodings
static constexpr uint8_t PAT_UC = 0x00; // Uncacheable
static constexpr uint8_t PAT_WC = 0x01; // Write Combining
static constexpr uint8_t PAT_WT = 0x04; // Write Through
static constexpr uint8_t PAT_WP = 0x05; // Write Protect
static constexpr uint8_t PAT_WB = 0x06; // Write Back
static constexpr uint8_t PAT_UCM = 0x07; // UC- (UC minus)
// Program PAT so entry 1 = WC (default is WT).
// PAT index is selected by PTE bits: PAT(bit7) | PCD(bit4) | PWT(bit3)
// Entry 0 (000) = WB — normal memory (unchanged)
// Entry 1 (001) = WC — framebuffers (was WT)
// Entry 2 (010) = UC- — (unchanged)
// Entry 3 (011) = UC — MMIO registers (unchanged)
// Entries 4-7: unchanged from defaults
inline void InitializePAT() {
uint64_t pat =
((uint64_t)PAT_WB << 0) | // Entry 0: WB
((uint64_t)PAT_WC << 8) | // Entry 1: WC (was WT)
((uint64_t)PAT_UCM << 16) | // Entry 2: UC-
((uint64_t)PAT_UC << 24) | // Entry 3: UC
((uint64_t)PAT_WB << 32) | // Entry 4: WB
((uint64_t)PAT_WT << 40) | // Entry 5: WT
((uint64_t)PAT_UCM << 48) | // Entry 6: UC-
((uint64_t)PAT_UC << 56); // Entry 7: UC
WriteMSR(IA32_PAT, pat);
}
}
+45 -2
View File
@@ -35,9 +35,11 @@
#include <Drivers/PS2/Mouse.hpp>
#include <Drivers/Net/E1000.hpp>
#include <Drivers/Net/E1000E.hpp>
#include <Drivers/Graphics/IntelGPU.hpp>
#include <Net/Net.hpp>
#include <CppLib/BoxUI.hpp>
#include <Graphics/Cursor.hpp>
#include <Hal/MSR.hpp>
#include <Fs/Ramdisk.hpp>
#include <Fs/Vfs.hpp>
#include <Sched/Scheduler.hpp>
@@ -138,7 +140,38 @@ extern "C" void kmain() {
Memory::VMM::g_paging = &g_paging;
g_paging.Init((uint64_t)&KernelStartSymbol, ((uint64_t)&KernelEndSymbol - (uint64_t)&KernelStartSymbol), memmap_request.response);
// Reprogram PAT so entry 1 = Write-Combining (default is Write-Through).
// Must be done after paging init and before any WC mappings.
Hal::InitializePAT();
Kt::KernelLogStream(OK, "Hal") << "PAT reprogrammed (entry 1 = WC)";
#endif
// Initialize Cursor early so we can WC-map the framebuffer before
// the bulk of boot logging begins (ACPI, PCI, drivers, etc.)
Graphics::Cursor::Initialize(framebuffer);
#if defined (__x86_64__)
// Map framebuffer as Write-Combining immediately for faster screen writes.
// All subsequent log output benefits from WC burst transfers.
{
uint64_t fbPhys = Graphics::Cursor::GetFramebufferPhysBase();
uint64_t fbSize = Graphics::Cursor::GetFramebufferHeight()
* Graphics::Cursor::GetFramebufferPitch();
uint64_t numPages = (fbSize + 0xFFF) / 0x1000;
for (uint64_t i = 0; i < numPages; i++) {
uint64_t phys = fbPhys + i * 0x1000;
g_paging.MapWC(phys, Memory::HHDM(phys));
}
asm volatile("mov %%cr3, %%rax; mov %%rax, %%cr3" ::: "rax", "memory");
Kt::KernelLogStream(OK, "Graphics") << "Framebuffer mapped as Write-Combining ("
<< kcp::dec << numPages << " pages)";
}
#endif
Hal::ACPI g_acpi((Hal::ACPI::XSDP*)Memory::HHDM(rsdp_request.response->address));
#if defined (__x86_64__)
@@ -147,6 +180,18 @@ extern "C" void kmain() {
Pci::Initialize(g_acpi.GetXSDT());
// Intel GPU driver — initialize right after PCI so the native
// driver takes over early and all subsequent logs use it.
Drivers::Graphics::IntelGPU::Initialize();
if (Drivers::Graphics::IntelGPU::IsInitialized()) {
Graphics::Cursor::SetFramebuffer(
Drivers::Graphics::IntelGPU::GetFramebufferBase(),
Drivers::Graphics::IntelGPU::GetWidth(),
Drivers::Graphics::IntelGPU::GetHeight(),
Drivers::Graphics::IntelGPU::GetPitch()
);
}
Timekeeping::ApicTimerInitialize();
Drivers::PS2::Initialize();
@@ -199,8 +244,6 @@ extern "C" void kmain() {
};
Fs::Vfs::RegisterDrive(0, &ramdiskDriver);
Graphics::Cursor::Initialize(framebuffer);
Hal::LoadTSS();
Zenith::InitializeSyscalls();
+55
View File
@@ -94,6 +94,26 @@ namespace Memory::VMM {
pageEntry->Address = physicalAddress >> 12;
}
void Paging::MapWC(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Value that isn't page-aligned passed as address to Paging::MapWC!", nullptr);
}
VirtualAddress virtualAddressObj(virtualAddress);
auto PML3 = HandleLevel(virtualAddressObj, PML4, 4);
auto PML2 = HandleLevel(virtualAddressObj, PML3, 3);
auto PML1 = HandleLevel(virtualAddressObj, PML2, 2);
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&PML1->entries[virtualAddressObj.GetPageIndex()]);
pageEntry->Present = true;
pageEntry->Writable = true;
pageEntry->WriteThrough = true; // PWT=1, PCD=0 → PAT entry 1 = WC
pageEntry->Address = physicalAddress >> 12;
}
void Paging::MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Value that isn't page-aligned passed as address to Paging::MapMMIO!", nullptr);
@@ -185,6 +205,41 @@ namespace Memory::VMM {
pageEntry->Address = physicalAddress >> 12;
}
void Paging::MapUserInWC(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress) {
if (virtualAddress % 0x1000 != 0 || physicalAddress % 0x1000 != 0) {
Panic("Non-aligned address in Paging::MapUserInWC!", nullptr);
}
VirtualAddress va(virtualAddress);
auto walkLevel = [](PageTable* table, uint64_t index) -> PageTable* {
PageTableEntry* entry = (PageTableEntry*)Memory::HHDM(&table->entries[index]);
if (!entry->Present) {
entry->Present = true;
entry->Writable = true;
entry->Supervisor = 1;
uint64_t newPhys = Memory::SubHHDM((uint64_t)Memory::g_pfa->AllocateZeroed());
entry->Address = newPhys >> 12;
return (PageTable*)newPhys;
} else {
entry->Supervisor = 1;
return (PageTable*)(entry->Address << 12);
}
};
PageTable* pml4 = (PageTable*)pml4Phys;
auto pml3 = walkLevel(pml4, va.GetL4Index());
auto pml2 = walkLevel(pml3, va.GetL3Index());
auto pml1 = walkLevel(pml2, va.GetL2Index());
PageTableEntry* pageEntry = (PageTableEntry*)Memory::HHDM(&pml1->entries[va.GetPageIndex()]);
pageEntry->Present = true;
pageEntry->Writable = true;
pageEntry->Supervisor = 1;
pageEntry->WriteThrough = true; // PWT=1, PCD=0 → PAT entry 1 = WC
pageEntry->Address = physicalAddress >> 12;
}
std::uint64_t Paging::GetPhysAddr(std::uint64_t pml4, std::uint64_t virtualAddress, bool use40BitL1) {
VirtualAddress virtualAddressObj(virtualAddress);
+4
View File
@@ -94,6 +94,7 @@ public:
void Init(std::uint64_t kernelBaseVirt, std::uint64_t kernelSize, limine_memmap_response* memMap);
void Map(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
void MapMMIO(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
void MapWC(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
void MapUser(std::uint64_t physicalAddress, std::uint64_t virtualAddress);
static std::uint64_t GetPhysAddr(std::uint64_t PML4, std::uint64_t virtualAddress, bool use40BitL1 = false);
std::uint64_t GetPhysAddr(std::uint64_t virtualAddress);
@@ -104,6 +105,9 @@ public:
// Map a page into an arbitrary PML4 (specified by physical address) with User bit set.
static void MapUserIn(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);
// Map a page into an arbitrary PML4 with User + Write-Combining attributes.
static void MapUserInWC(std::uint64_t pml4Phys, std::uint64_t physicalAddress, std::uint64_t virtualAddress);
};
extern Paging* g_paging;
+375
View File
@@ -0,0 +1,375 @@
/*
* Dns.cpp
* DNS resolver (kernel-level, RFC 1035)
* Copyright (c) 2025-2026 Daniel Hammer
*/
#include "Dns.hpp"
#include <Net/Udp.hpp>
#include <Net/ByteOrder.hpp>
#include <Net/NetConfig.hpp>
#include <Libraries/Memory.hpp>
#include <Libraries/String.hpp>
#include <Timekeeping/ApicTimer.hpp>
#include <Sched/Scheduler.hpp>
#include <Terminal/Terminal.hpp>
namespace Net::Dns {
// ---- DNS packet constants ----
static constexpr uint16_t DNS_PORT = 53;
static constexpr uint16_t DNS_FLAGS_RD = 0x0100; // Recursion Desired
static constexpr uint16_t DNS_QTYPE_A = 1;
static constexpr uint16_t DNS_QCLASS_IN = 1;
// ---- Simple cache ----
static constexpr int CACHE_SIZE = 8;
struct CacheEntry {
char hostname[128];
uint32_t ip;
uint32_t ttl; // TTL in seconds
uint64_t timestamp; // ms when cached
bool valid;
};
static CacheEntry g_cache[CACHE_SIZE] = {};
static bool streq(const char* a, const char* b) {
while (*a && *b) {
if (*a != *b) return false;
a++; b++;
}
return *a == *b;
}
static uint32_t CacheLookup(const char* hostname) {
uint64_t now = Timekeeping::GetMilliseconds();
for (int i = 0; i < CACHE_SIZE; i++) {
if (!g_cache[i].valid) continue;
if (!streq(g_cache[i].hostname, hostname)) continue;
// Check TTL
uint64_t elapsed = (now - g_cache[i].timestamp) / 1000;
if (elapsed < g_cache[i].ttl) {
return g_cache[i].ip;
}
// Expired
g_cache[i].valid = false;
return 0;
}
return 0;
}
static void CacheStore(const char* hostname, uint32_t ip, uint32_t ttl) {
if (ttl == 0) ttl = 60; // Minimum 60s TTL
// Find free or oldest slot
int slot = 0;
uint64_t oldestTime = ~0ULL;
for (int i = 0; i < CACHE_SIZE; i++) {
if (!g_cache[i].valid) { slot = i; break; }
if (g_cache[i].timestamp < oldestTime) {
oldestTime = g_cache[i].timestamp;
slot = i;
}
}
CacheEntry& e = g_cache[slot];
// Copy hostname
int len = 0;
while (hostname[len] && len < 126) { e.hostname[len] = hostname[len]; len++; }
e.hostname[len] = '\0';
e.ip = ip;
e.ttl = ttl;
e.timestamp = Timekeeping::GetMilliseconds();
e.valid = true;
}
// ---- DNS query building ----
// Encode a hostname as DNS labels: "example.com" -> "\x07example\x03com\x00"
// Returns number of bytes written, or 0 on error.
static int EncodeName(const char* hostname, uint8_t* out, int maxLen) {
int outPos = 0;
const char* p = hostname;
while (*p) {
// Find the next dot or end
const char* dot = p;
while (*dot && *dot != '.') dot++;
int labelLen = (int)(dot - p);
if (labelLen == 0 || labelLen > 63) return 0;
if (outPos + 1 + labelLen >= maxLen) return 0;
out[outPos++] = (uint8_t)labelLen;
for (int i = 0; i < labelLen; i++) {
out[outPos++] = (uint8_t)p[i];
}
p = dot;
if (*p == '.') p++;
}
if (outPos >= maxLen) return 0;
out[outPos++] = 0; // Root label terminator
return outPos;
}
// Build a DNS query packet. Returns total packet length, or 0 on error.
static int BuildQuery(uint16_t id, const char* hostname, uint8_t* packet, int maxLen) {
if (maxLen < 12) return 0;
// Header (12 bytes)
packet[0] = (uint8_t)(id >> 8);
packet[1] = (uint8_t)(id & 0xFF);
packet[2] = (uint8_t)(DNS_FLAGS_RD >> 8); // Flags high: RD=1
packet[3] = (uint8_t)(DNS_FLAGS_RD & 0xFF); // Flags low
packet[4] = 0; packet[5] = 1; // QDCOUNT = 1
packet[6] = 0; packet[7] = 0; // ANCOUNT = 0
packet[8] = 0; packet[9] = 0; // NSCOUNT = 0
packet[10] = 0; packet[11] = 0; // ARCOUNT = 0
// Question section
int nameLen = EncodeName(hostname, packet + 12, maxLen - 12 - 4);
if (nameLen == 0) return 0;
int pos = 12 + nameLen;
if (pos + 4 > maxLen) return 0;
// QTYPE = A (1)
packet[pos++] = 0;
packet[pos++] = DNS_QTYPE_A;
// QCLASS = IN (1)
packet[pos++] = 0;
packet[pos++] = DNS_QCLASS_IN;
return pos;
}
// ---- DNS response parsing ----
// Skip over a DNS name in the packet (handles compression pointers).
// Returns the new offset, or -1 on error.
static int SkipName(const uint8_t* packet, int packetLen, int offset) {
int maxJumps = 32; // prevent infinite loops
bool jumped = false;
int returnOffset = -1;
while (offset < packetLen && maxJumps > 0) {
uint8_t len = packet[offset];
if (len == 0) {
// End of name
offset++;
return jumped ? returnOffset : offset;
}
if ((len & 0xC0) == 0xC0) {
// Compression pointer
if (offset + 1 >= packetLen) return -1;
if (!jumped) returnOffset = offset + 2;
offset = ((len & 0x3F) << 8) | packet[offset + 1];
jumped = true;
maxJumps--;
continue;
}
// Regular label
offset += 1 + len;
maxJumps--;
}
return -1;
}
struct DnsAnswer {
uint32_t ip;
uint32_t ttl;
bool found;
};
// Parse a DNS response and extract the first A record.
static DnsAnswer ParseResponse(uint16_t expectedId, const uint8_t* packet, int packetLen) {
DnsAnswer result = {0, 0, false};
if (packetLen < 12) return result;
// Check ID
uint16_t id = ((uint16_t)packet[0] << 8) | packet[1];
if (id != expectedId) return result;
// Check QR bit (must be response)
if (!(packet[2] & 0x80)) return result;
// Check RCODE (must be 0 = no error)
uint8_t rcode = packet[3] & 0x0F;
if (rcode != 0) return result;
uint16_t qdcount = ((uint16_t)packet[4] << 8) | packet[5];
uint16_t ancount = ((uint16_t)packet[6] << 8) | packet[7];
// Skip question section
int offset = 12;
for (uint16_t i = 0; i < qdcount; i++) {
offset = SkipName(packet, packetLen, offset);
if (offset < 0) return result;
offset += 4; // QTYPE + QCLASS
if (offset > packetLen) return result;
}
// Parse answers
for (uint16_t i = 0; i < ancount; i++) {
offset = SkipName(packet, packetLen, offset);
if (offset < 0 || offset + 10 > packetLen) return result;
uint16_t atype = ((uint16_t)packet[offset] << 8) | packet[offset + 1];
// uint16_t aclass = ((uint16_t)packet[offset + 2] << 8) | packet[offset + 3];
uint32_t attl = ((uint32_t)packet[offset + 4] << 24) |
((uint32_t)packet[offset + 5] << 16) |
((uint32_t)packet[offset + 6] << 8) |
((uint32_t)packet[offset + 7]);
uint16_t rdlen = ((uint16_t)packet[offset + 8] << 8) | packet[offset + 9];
offset += 10;
if (offset + rdlen > packetLen) return result;
if (atype == DNS_QTYPE_A && rdlen == 4) {
// A record: 4-byte IPv4 address (already in network byte order)
result.ip = ((uint32_t)packet[offset])
| ((uint32_t)packet[offset + 1] << 8)
| ((uint32_t)packet[offset + 2] << 16)
| ((uint32_t)packet[offset + 3] << 24);
result.ttl = attl;
result.found = true;
return result;
}
offset += rdlen;
}
return result;
}
// ---- Resolve state (shared with UDP callback) ----
static volatile bool g_gotResponse = false;
static volatile uint16_t g_currentId = 0;
static uint8_t g_responseBuffer[512];
static volatile int g_responseLen = 0;
static void DnsRecvCallback(uint32_t srcIp, uint16_t srcPort,
uint16_t dstPort,
const uint8_t* data, uint16_t length) {
(void)srcIp;
(void)srcPort;
(void)dstPort;
if (g_gotResponse) return; // Already got a response
if (length > sizeof(g_responseBuffer)) length = sizeof(g_responseBuffer);
memcpy(g_responseBuffer, data, length);
g_responseLen = length;
g_gotResponse = true;
}
// ---- Simple PRNG for transaction IDs ----
static uint16_t g_nextId = 0x4E53; // "NS"
static uint16_t NextId() {
g_nextId = g_nextId * 25173 + 13849;
return g_nextId;
}
// ---- Check if string is already an IP address ----
static bool IsIpAddress(const char* s) {
int dotCount = 0;
bool hasDigit = false;
for (int i = 0; s[i]; i++) {
if (s[i] >= '0' && s[i] <= '9') {
hasDigit = true;
} else if (s[i] == '.') {
if (!hasDigit) return false;
dotCount++;
hasDigit = false;
} else {
return false;
}
}
return hasDigit && dotCount == 3;
}
// ---- Public API ----
uint32_t Resolve(const char* hostname, uint32_t timeoutMs) {
if (hostname == nullptr || hostname[0] == '\0') return 0;
// Don't try to resolve IP addresses
if (IsIpAddress(hostname)) return 0;
// Check cache first
uint32_t cached = CacheLookup(hostname);
if (cached != 0) return cached;
// Check DNS server is configured
uint32_t dnsServer = Net::GetDnsServer();
if (dnsServer == 0) return 0;
// Pick a local port for receiving the response (ephemeral range)
uint16_t localPort = 10000 + (NextId() % 50000);
uint16_t txId = NextId();
// Build DNS query
uint8_t queryPacket[512];
int queryLen = BuildQuery(txId, hostname, queryPacket, sizeof(queryPacket));
if (queryLen == 0) return 0;
// Reset response state
g_gotResponse = false;
g_responseLen = 0;
g_currentId = txId;
// Bind our receive port
if (!Net::Udp::Bind(localPort, DnsRecvCallback)) {
// Port might be in use, try another
localPort = 10000 + (NextId() % 50000);
if (!Net::Udp::Bind(localPort, DnsRecvCallback)) {
return 0;
}
}
// Send the query to DNS server port 53
bool sent = Net::Udp::Send(dnsServer, localPort, DNS_PORT, queryPacket, (uint16_t)queryLen);
if (!sent) {
Net::Udp::Unbind(localPort);
return 0;
}
// Wait for response with timeout
uint64_t start = Timekeeping::GetMilliseconds();
while (!g_gotResponse) {
if (Timekeeping::GetMilliseconds() - start >= timeoutMs) {
Net::Udp::Unbind(localPort);
return 0;
}
Sched::Schedule();
}
// Unbind the port
Net::Udp::Unbind(localPort);
// Parse the response
DnsAnswer answer = ParseResponse(txId, g_responseBuffer, g_responseLen);
if (!answer.found) return 0;
// Cache the result
CacheStore(hostname, answer.ip, answer.ttl);
return answer.ip;
}
}
+16
View File
@@ -0,0 +1,16 @@
/*
* Dns.hpp
* DNS resolver (kernel-level)
* Copyright (c) 2025-2026 Daniel Hammer
*/
#pragma once
#include <cstdint>
namespace Net::Dns {
// Resolve a hostname to an IPv4 address.
// Returns the IP in network byte order, or 0 on failure.
uint32_t Resolve(const char* hostname, uint32_t timeoutMs = 5000);
}
+4
View File
@@ -12,6 +12,7 @@ namespace Net {
static uint32_t g_ipAddress = Ipv4Addr(10, 0, 68, 99);
static uint32_t g_subnetMask = Ipv4Addr(255, 255, 255, 0);
static uint32_t g_gateway = Ipv4Addr(10, 0, 68, 1);
static uint32_t g_dnsServer = Ipv4Addr(10, 0, 68, 1);
uint32_t GetIpAddress() { return g_ipAddress; }
void SetIpAddress(uint32_t ip) { g_ipAddress = ip; }
@@ -22,6 +23,9 @@ namespace Net {
uint32_t GetGateway() { return g_gateway; }
void SetGateway(uint32_t gw) { g_gateway = gw; }
uint32_t GetDnsServer() { return g_dnsServer; }
void SetDnsServer(uint32_t dns) { g_dnsServer = dns; }
bool IsLocalSubnet(uint32_t destIp) {
return (destIp & g_subnetMask) == (g_ipAddress & g_subnetMask);
}
+4
View File
@@ -27,6 +27,10 @@ namespace Net {
uint32_t GetGateway();
void SetGateway(uint32_t gw);
// Get/set the DNS server (network byte order)
uint32_t GetDnsServer();
void SetDnsServer(uint32_t dns);
// Check if a destination IP is on the local subnet
bool IsLocalSubnet(uint32_t destIp);
-3
View File
@@ -255,9 +255,6 @@ namespace Net::Tcp {
// Send ACK
SendSegment(conn, FLAG_ACK, nullptr, 0);
KernelLogStream(INFO, "Net") << "TCP connection established to port "
<< base::dec << (uint64_t)conn->RemotePort;
}
}
break;
+124 -2
View File
@@ -5,16 +5,131 @@
*/
#include "Terminal.hpp"
#define FLANTERM_IN_FLANTERM
#include "../Libraries/flanterm/src/flanterm_backends/fb.h"
#include "../Libraries/flanterm/src/flanterm.h"
#include "../Libraries/String.hpp"
#include "../Libraries/Memory.hpp"
#include <CppLib/CString.hpp>
namespace Kt {
flanterm_context *ctx;
std::size_t g_terminal_width = 0;
// Maximum grid cells allocated at init (scale 1,1). Used to validate
// that a requested scale does not exceed the original buffer capacity.
static std::size_t g_max_grid_cells = 0;
// Custom plot_char that works for any font_scale_x/y >= 1.
// This is the same algorithm as flanterm's plot_char_scaled_uncanvas
// but lives outside fb.c so we can install it after rescaling.
static void plot_char_universal(struct flanterm_context *_ctx,
struct flanterm_fb_char *c,
size_t x, size_t y) {
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)_ctx;
if (x >= _ctx->cols || y >= _ctx->rows) {
return;
}
uint32_t default_bg = fbctx->default_bg;
uint32_t bg = c->bg == 0xffffffff ? default_bg : c->bg;
uint32_t fg = c->fg == 0xffffffff ? fbctx->default_fg : c->fg;
x = fbctx->offset_x + x * fbctx->glyph_width;
y = fbctx->offset_y + y * fbctx->glyph_height;
bool *glyph = &fbctx->font_bool[c->c * fbctx->font_height * fbctx->font_width];
// Only ROTATE_0 is used in ZenithOS
volatile uint32_t *dest = fbctx->framebuffer + x + y * (fbctx->pitch / 4);
size_t stride = fbctx->pitch / 4;
for (size_t gy = 0; gy < fbctx->glyph_height; gy++) {
size_t fy = gy / fbctx->font_scale_y;
volatile uint32_t *fb_line = dest;
bool *glyph_pointer = glyph + (fy * fbctx->font_width);
for (size_t fx = 0; fx < fbctx->font_width; fx++) {
for (size_t i = 0; i < fbctx->font_scale_x; i++) {
*fb_line = *glyph_pointer ? fg : bg;
fb_line++;
}
glyph_pointer++;
}
dest += stride;
}
}
void Rescale(std::size_t scale_x, std::size_t scale_y) {
if (scale_x == 0) scale_x = 1;
if (scale_y == 0) scale_y = 1;
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
// Calculate new dimensions
size_t new_glyph_w = fbctx->font_width * scale_x;
size_t new_glyph_h = fbctx->font_height * scale_y;
size_t new_cols = fbctx->width / new_glyph_w;
size_t new_rows = fbctx->height / new_glyph_h;
if (new_cols == 0 || new_rows == 0) return;
// Ensure the new grid fits within original buffer allocation
if (new_cols * new_rows > g_max_grid_cells) return;
// Update scale and glyph dimensions
fbctx->font_scale_x = scale_x;
fbctx->font_scale_y = scale_y;
fbctx->glyph_width = new_glyph_w;
fbctx->glyph_height = new_glyph_h;
// Update terminal grid dimensions
ctx->cols = new_cols;
ctx->rows = new_rows;
// Center the text area
fbctx->offset_x = (fbctx->width % new_glyph_w) / 2;
fbctx->offset_y = (fbctx->height % new_glyph_h) / 2;
// Install our universal plot_char
fbctx->plot_char = plot_char_universal;
// Reinitialize grid data (reuse existing buffers)
for (size_t i = 0; i < new_rows * new_cols; i++) {
fbctx->grid[i].c = ' ';
fbctx->grid[i].fg = fbctx->text_fg;
fbctx->grid[i].bg = fbctx->text_bg;
}
fbctx->queue_i = 0;
memset(fbctx->queue, 0, new_rows * new_cols * sizeof(struct flanterm_fb_queue_item));
memset(fbctx->map, 0, new_rows * new_cols * sizeof(struct flanterm_fb_queue_item *));
// Clear the framebuffer
for (size_t y = 0; y < fbctx->height; y++) {
volatile uint32_t *row = fbctx->framebuffer + y * (fbctx->pitch / 4);
for (size_t x = 0; x < fbctx->width; x++) {
row[x] = fbctx->default_bg;
}
}
// Reset terminal state and refresh
flanterm_context_reinit(ctx);
flanterm_full_refresh(ctx);
}
std::size_t GetFontScaleX() {
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
return fbctx->font_scale_x;
}
std::size_t GetFontScaleY() {
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
return fbctx->font_scale_y;
}
void UpdatePanelBar(CString panelText) {
kout << "\033[s";
kout << "\033[H";
@@ -48,13 +163,20 @@ namespace Kt {
NULL, NULL,
NULL, NULL,
NULL, 0, 0, 1,
0, 0,
1, 1,
0,
FLANTERM_FB_ROTATE_0
);
g_terminal_width = width;
// Store max grid cells for rescale buffer bounds checking
g_max_grid_cells = ctx->cols * ctx->rows;
// Install our universal plot_char so rescaling works at any scale
struct flanterm_fb_context *fbctx = (struct flanterm_fb_context *)ctx;
fbctx->plot_char = plot_char_universal;
UpdatePanelBar("Initializing...");
kout << "\n\n\n";
}
@@ -73,4 +195,4 @@ namespace Kt {
}
}
};
};
+4
View File
@@ -44,6 +44,10 @@ namespace Kt
void UpdatePanelBar(const char* panelText);
void Rescale(std::size_t font_scale_x, std::size_t font_scale_y);
std::size_t GetFontScaleX();
std::size_t GetFontScaleY();
inline base base_custom(int custom)
{
return (base)custom;