/* * 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 #include #include #include #include #include #include #include #include #include #include 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 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) // Page flip state static volatile void* g_ggtt = nullptr; // GGTT at the gen-correct BAR0 offset static uint64_t g_ggttEntries = 0; static uint64_t g_fbGttOffsetA = 0; // GGTT offset of buffer 0 (firmware FB) static uint32_t* g_buf1Virt = nullptr; // buffer 1 kernel VA (HHDM, WC) static uint64_t g_buf1Phys = 0; static uint64_t g_buf1Pages = 0; // allocation/mapping bound static uint64_t g_buf1GttOffset = 0; // GGTT offset of buffer 1 (in bytes) static bool g_flipSupported = false; static int g_frontBuffer = 0; static int g_flipOwnerPid = -1; // Serializes surface programming, flip-owner teardown, and timing changes. // Without this, a process exit or modeset on another CPU can rewrite // DSPASURF/PIPE state in the middle of a desktop flip transaction. static kcp::Mutex g_flipLock; // Modesetting/display-management state. The firmware-trained port and // link are deliberately retained; the driver owns the active transcoder, // timing catalogue, panel PWM, and validated timing transactions. static constexpr int MaxDisplayModes = 32; struct ModeRecord { DisplayMode timing; uint32_t refreshMilliHz; uint32_t flags; }; static ModeRecord g_modes[MaxDisplayModes] = {}; static int g_modeCount = 0; static int g_currentMode = -1; static uint32_t g_transTimingBase = TRANS_TIMING_A; static uint32_t g_transConfReg = TRANS_CONF_A; static uint32_t g_transDdiReg = TRANS_DDI_A; static uint32_t g_auxControlReg = 0; static uint8_t g_connectorType = montauk::abi::DISPLAY_CONNECTOR_UNKNOWN; static bool g_connectorConnected = false; static bool g_edidValid = false; static int g_ddiPort = -1; // DDI port driving the output, -1 unknown static char g_connectorName[32] = "Unknown"; static char g_monitorName[64] = "Unknown display"; static uint8_t g_auxEdid[256] = {}; static uint64_t g_auxEdidSize = 0; static uint32_t g_auxLastStatus = 0; static uint8_t g_auxLastReply = 0xFF; static uint64_t ReadGgttPte(uint64_t index); // ========================================================================= // 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); } static void CopyText(char* dst, uint64_t cap, const char* src) { if (!dst || cap == 0) return; uint64_t i = 0; if (src) { while (i + 1 < cap && src[i]) { dst[i] = src[i]; i++; } } dst[i] = '\0'; } static void CopyEdidText(char* dst, uint64_t cap, const uint8_t* src, int len) { if (!dst || cap == 0) return; int end = len; while (end > 0 && (src[end - 1] == ' ' || src[end - 1] == '\n' || src[end - 1] == '\r' || src[end - 1] == '\0')) { end--; } uint64_t out = 0; for (int i = 0; i < end && out + 1 < cap; i++) { uint8_t ch = src[i]; dst[out++] = (ch >= 0x20 && ch <= 0x7e) ? (char)ch : '?'; } dst[out] = '\0'; } // ========================================================================= // 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 - infer generation from device ID range. // Getting this wrong is catastrophic (32-bit vs 64-bit GTT PTEs), // so use known Intel device ID patterns. uint16_t did = found->DeviceId; uint8_t inferredGen; if (did >= 0xA700 || (did >= 0x4600 && did < 0x4700) || (did >= 0x5600 && did < 0x5700) || (did >= 0x7D00 && did < 0x7E00)) { inferredGen = 12; // Raptor Lake, Alder Lake, DG2, Meteor Lake range } else if ((did >= 0x8A00 && did < 0x8B00) || (did >= 0x9A00 && did < 0x9B00)) { inferredGen = 12; // Ice Lake / Tiger Lake range } else if ((did >= 0x3E00 && did < 0x3F00) || (did >= 0x5900 && did < 0x5A00) || (did >= 0x9B00 && did < 0x9C00) || (did >= 0x1900 && did < 0x1A00)) { inferredGen = 9; // Skylake / Kaby Lake / Coffee Lake / Comet Lake } else if (did >= 0x1600 && did < 0x1700) { inferredGen = 8; // Broadwell } else { inferredGen = 7; // Older (Haswell and below) } g_gpuInfo.gen = inferredGen; g_gpuInfo.name = "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 << " - inferred gen " << base::dec << (uint64_t)inferredGen << ", attempting generic initialization"; } g_gpuGen = g_gpuInfo.gen; return true; } // This driver intentionally uses register polling for display latches and // does not own the GPU's many generation-specific interrupt sources. Mask // PCI delivery before touching display state: otherwise firmware-left MSI // enables can route an unhandled GT interrupt and create an interrupt // storm even though Montauk never requested one. static void DisableInterruptDelivery() { uint8_t bus = g_gpuInfo.pciBus; uint8_t dev = g_gpuInfo.pciDevice; uint8_t func = g_gpuInfo.pciFunction; if (Pci::DisableInterruptDelivery(bus, dev, func)) { KernelLogStream(WARNING, "IntelGPU") << "Disabled pre-enabled GPU MSI/MSI-X; using bounded latch polling"; } } // ========================================================================= // 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 uint64_t mmioPhys = Pci::ReadBar0(bus, dev, func); 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 Pci::EnableBusMaster(bus, dev, func); 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::Framebuffer::GetWidth(); g_fbHeight = ::Graphics::Framebuffer::GetHeight(); g_fbPitch = ::Graphics::Framebuffer::GetPitch(); 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), and reject geometry // whose size arithmetic could wrap before it reaches the allocator. if (g_fbPitch == 0) { g_fbPitch = g_fbWidth * 4; KernelLogStream(WARNING, "IntelGPU") << "Stride not available, assuming " << base::dec << g_fbPitch << " bytes"; } if (g_fbWidth > (~0ULL / 4) || g_fbPitch < g_fbWidth * 4 || g_fbHeight > (~0ULL / g_fbPitch)) { KernelLogStream(ERROR, "IntelGPU") << "Invalid or overflowing framebuffer geometry"; return false; } 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; } // ========================================================================= // Connector discovery, EDID, and mode catalogue // ========================================================================= static uint32_t ModeRefreshMilliHz(const DisplayMode& mode) { uint64_t pixels = (uint64_t)mode.htotal * mode.vtotal; if (pixels == 0 || mode.pixelClock == 0) return 0; return (uint32_t)(((uint64_t)mode.pixelClock * 1000000ULL + pixels / 2) / pixels); } static bool SameMode(const ModeRecord& a, const DisplayMode& mode, uint32_t refreshMilliHz) { if (a.timing.hdisplay != mode.hdisplay || a.timing.vdisplay != mode.vdisplay) return false; if (a.timing.htotal && mode.htotal && (a.timing.htotal != mode.htotal || a.timing.vtotal != mode.vtotal)) return false; uint32_t ar = a.refreshMilliHz; uint32_t diff = ar > refreshMilliHz ? ar - refreshMilliHz : refreshMilliHz - ar; return ar == 0 || refreshMilliHz == 0 || diff <= 500; } static int AddMode(const DisplayMode& mode, uint32_t refreshMilliHz, uint32_t flags) { if (mode.hdisplay < 320 || mode.vdisplay < 200 || mode.hdisplay > 16384 || mode.vdisplay > 16384) return -1; if (refreshMilliHz == 0) refreshMilliHz = ModeRefreshMilliHz(mode); for (int i = 0; i < g_modeCount; i++) { if (SameMode(g_modes[i], mode, refreshMilliHz)) { g_modes[i].flags |= flags; if (g_modes[i].timing.pixelClock == 0 && mode.pixelClock) g_modes[i].timing = mode; return i; } } if (g_modeCount >= MaxDisplayModes) return -1; int index = g_modeCount++; g_modes[index].timing = mode; g_modes[index].refreshMilliHz = refreshMilliHz; g_modes[index].flags = flags; return index; } static void AddSimpleMode(uint32_t width, uint32_t height, uint32_t refreshHz, uint32_t flags = 0) { DisplayMode mode = {}; mode.hdisplay = width; mode.vdisplay = height; AddMode(mode, refreshHz * 1000, flags); } static bool EdidBlockValid(const uint8_t* block) { uint8_t sum = 0; for (int i = 0; i < 128; i++) sum = (uint8_t)(sum + block[i]); return sum == 0; } static uint32_t PackAuxBytes(const uint8_t* bytes, int count) { uint32_t value = 0; if (count > 4) count = 4; for (int i = 0; i < count; i++) value |= (uint32_t)bytes[i] << ((3 - i) * 8); return value; } static void UnpackAuxBytes(uint32_t value, uint8_t* bytes, int count) { if (count > 4) count = 4; for (int i = 0; i < count; i++) bytes[i] = (uint8_t)(value >> ((3 - i) * 8)); } // Submit one native or I2C-over-AUX packet. The active firmware display // has already powered and trained this channel; Montauk only uses it for // read-only monitor discovery. static int AuxTransfer(uint8_t request, uint32_t address, const uint8_t* writeData, int writeSize, uint8_t* readData, int readCapacity, uint8_t* reply) { if (g_auxControlReg == 0 || writeSize < 0 || writeSize > 16 || readCapacity < 0 || readCapacity > 16) return -1; g_auxLastReply = 0xFF; uint8_t packet[20] = {}; packet[0] = (uint8_t)((request << 4) | ((address >> 16) & 0x0F)); packet[1] = (uint8_t)(address >> 8); packet[2] = (uint8_t)address; bool read = (request & 1) != 0; int payloadSize = read ? readCapacity : writeSize; int sendBytes = payloadSize ? 4 : 3; if (payloadSize) packet[3] = (uint8_t)(payloadSize - 1); if (!read && writeSize) { memcpy(packet + 4, writeData, (uint64_t)writeSize); sendBytes += writeSize; } for (int attempt = 0; attempt < 5; attempt++) { int busyWait = 100000; while (((g_auxLastStatus = ReadReg(g_auxControlReg)) & DP_AUX_SEND_BUSY) && --busyWait) asm volatile("pause"); if (!busyWait) return -1; for (int i = 0; i < sendBytes; i += 4) { WriteReg(g_auxControlReg + DP_AUX_DATA_DELTA + (uint32_t)(i / 4) * 4, PackAuxBytes(packet + i, sendBytes - i)); } // Skylake and later derive the AUX clock automatically. The pulse // lengths below are within the documented hardware ranges. uint32_t control = DP_AUX_SEND_BUSY | DP_AUX_DONE | DP_AUX_INTERRUPT | DP_AUX_TIMEOUT_ERROR | DP_AUX_TIMEOUT_MAX | DP_AUX_RECEIVE_ERROR | ((uint32_t)sendBytes << DP_AUX_MESSAGE_SHIFT) | (17u << 5) | 31u; WriteReg(g_auxControlReg, control); int completeWait = 1000000; uint32_t status; do { status = ReadReg(g_auxControlReg); g_auxLastStatus = status; if (!(status & DP_AUX_SEND_BUSY)) break; asm volatile("pause"); } while (--completeWait); if (!completeWait) return -1; WriteReg(g_auxControlReg, status | DP_AUX_DONE | DP_AUX_TIMEOUT_ERROR | DP_AUX_RECEIVE_ERROR); if (!(status & DP_AUX_DONE) || (status & (DP_AUX_TIMEOUT_ERROR | DP_AUX_RECEIVE_ERROR))) continue; int received = (int)((status >> DP_AUX_MESSAGE_SHIFT) & 0x1F); if (received <= 0 || received > 20) continue; uint8_t response[20] = {}; for (int i = 0; i < received; i += 4) { uint32_t value = ReadReg(g_auxControlReg + DP_AUX_DATA_DELTA + (uint32_t)(i / 4) * 4); UnpackAuxBytes(value, response + i, received - i); } if (reply) *reply = response[0] >> 4; g_auxLastReply = response[0] >> 4; if (!read) return writeSize; int bytes = received - 1; if (bytes > readCapacity) bytes = readCapacity; if (bytes > 0) memcpy(readData, response + 1, (uint64_t)bytes); return bytes; } return -1; } static bool AuxI2cTransfer(uint8_t request, const uint8_t* writeData, int writeSize, uint8_t* readData, int readSize, uint8_t i2cAddress = 0x50) { for (int retry = 0; retry < 16; retry++) { uint8_t reply = 0xFF; int result = AuxTransfer(request, i2cAddress, writeData, writeSize, readData, readSize, &reply); uint8_t nativeReply = reply & 0x3; uint8_t i2cReply = reply & 0xC; if (result == (readSize ? readSize : writeSize) && nativeReply == 0 && i2cReply == 0) return true; if (nativeReply != 2 && i2cReply != 8 && result >= 0) return false; for (int delay = 0; delay < 40000; delay++) asm volatile("pause"); } return false; } static bool ReadDpEdidBlock(uint8_t offset, uint8_t* block) { // Keep the I2C transaction open while setting the EEPROM offset and // reading the block in the AUX controller's 16-byte payload chunks. if (!AuxI2cTransfer(0x4, nullptr, 0, nullptr, 0)) return false; if (!AuxI2cTransfer(0x4, &offset, 1, nullptr, 0)) return false; if (!AuxI2cTransfer(0x5, nullptr, 0, nullptr, 0)) return false; for (int pos = 0; pos < 128; pos += 16) { uint8_t request = (pos + 16 < 128) ? 0x5 : 0x1; if (!AuxI2cTransfer(request, nullptr, 0, block + pos, 16)) return false; } return true; } static bool ProbeAuxChannel(uint32_t controlReg, uint8_t* revision) { g_auxControlReg = controlReg; uint8_t reply = 0xFF; uint8_t dpcd = 0; int result = AuxTransfer(0x9, 0, nullptr, 0, &dpcd, 1, &reply); if (result != 1 || (reply & 0x3) != 0 || dpcd < 0x10 || dpcd > 0x30) return false; if (revision) *revision = dpcd; return true; } static bool ReadDpEdid() { g_auxEdidSize = 0; if ((g_connectorType != montauk::abi::DISPLAY_CONNECTOR_DP && g_connectorType != montauk::abi::DISPLAY_CONNECTOR_EDP) || g_auxControlReg == 0) return false; uint32_t preferred = g_auxControlReg; uint32_t candidates[] = { preferred, DP_AUX_A_CTL, DP_AUX_B_CTL, DP_AUX_C_CTL, DP_AUX_D_CTL, }; for (uint32_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) { bool duplicate = false; for (uint32_t j = 0; j < i; j++) { if (candidates[j] == candidates[i]) duplicate = true; } if (duplicate) continue; uint8_t revision = 0; if (!ProbeAuxChannel(candidates[i], &revision)) { KernelLogStream(DEBUG, "IntelGPU") << "DP AUX probe failed at " << base::hex << candidates[i] << " (status=" << g_auxLastStatus << ", reply=" << (uint64_t)g_auxLastReply << ")"; continue; } KernelLogStream(INFO, "IntelGPU") << "DP AUX channel at " << base::hex << candidates[i] << ", DPCD revision " << (uint64_t)revision; if (!ReadDpEdidBlock(0, g_auxEdid) || !EdidBlockValid(g_auxEdid)) { KernelLogStream(WARNING, "IntelGPU") << "EDID read failed on DP AUX " << base::hex << candidates[i] << " (status=" << g_auxLastStatus << ", reply=" << (uint64_t)g_auxLastReply << ")"; continue; } g_auxEdidSize = 128; if (g_auxEdid[126] != 0 && ReadDpEdidBlock(128, g_auxEdid + 128) && EdidBlockValid(g_auxEdid + 128)) g_auxEdidSize = 256; return true; } g_auxControlReg = preferred; return false; } static bool ParseDetailedTiming(const uint8_t* dtd, DisplayMode& mode) { uint32_t clock10KHz = (uint32_t)dtd[0] | ((uint32_t)dtd[1] << 8); if (clock10KHz == 0) return false; uint32_t hactive = dtd[2] | ((uint32_t)(dtd[4] & 0xF0) << 4); uint32_t hblank = dtd[3] | ((uint32_t)(dtd[4] & 0x0F) << 8); uint32_t vactive = dtd[5] | ((uint32_t)(dtd[7] & 0xF0) << 4); uint32_t vblank = dtd[6] | ((uint32_t)(dtd[7] & 0x0F) << 8); uint32_t hsyncOffset = dtd[8] | ((uint32_t)(dtd[11] & 0xC0) << 2); uint32_t hsyncWidth = dtd[9] | ((uint32_t)(dtd[11] & 0x30) << 4); uint32_t vsyncOffset = ((dtd[10] >> 4) & 0x0F) | ((uint32_t)(dtd[11] & 0x0C) << 2); uint32_t vsyncWidth = (dtd[10] & 0x0F) | ((uint32_t)(dtd[11] & 0x03) << 4); if (!hactive || !vactive || !hblank || !vblank) return false; mode = {}; mode.hdisplay = hactive; mode.hsyncStart = hactive + hsyncOffset; mode.hsyncEnd = mode.hsyncStart + hsyncWidth; mode.htotal = hactive + hblank; mode.vdisplay = vactive; mode.vsyncStart = vactive + vsyncOffset; mode.vsyncEnd = mode.vsyncStart + vsyncWidth; mode.vtotal = vactive + vblank; mode.pixelClock = clock10KHz * 10; mode.hsyncPositive = (dtd[17] & (1u << 1)) != 0; mode.vsyncPositive = (dtd[17] & (1u << 2)) != 0; return true; } static void ParseEdid() { uint64_t edidSize = 0; const uint8_t* edid = ::Graphics::Framebuffer::GetEdid(&edidSize); if (!edid || edidSize < 128) { if (!ReadDpEdid()) { KernelLogStream(INFO, "IntelGPU") << "EDID unavailable from boot data" << (g_auxControlReg ? " and DP AUX" : ""); return; } edid = g_auxEdid; edidSize = g_auxEdidSize; KernelLogStream(OK, "IntelGPU") << "Read " << base::dec << edidSize << " bytes of EDID over DP AUX"; } static constexpr uint8_t header[8] = {0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00}; if (memcmp(edid, header, sizeof(header)) != 0 || !EdidBlockValid(edid)) { KernelLogStream(WARNING, "IntelGPU") << "Boot EDID failed validation"; return; } g_edidValid = true; // Established timings from EDID 1.3 section 3.8. if (edid[35] & 0x20) AddSimpleMode(640, 480, 60); if (edid[35] & 0x08) AddSimpleMode(640, 480, 72); if (edid[35] & 0x04) AddSimpleMode(640, 480, 75); if (edid[35] & 0x02) AddSimpleMode(800, 600, 56); if (edid[35] & 0x01) AddSimpleMode(800, 600, 60); if (edid[36] & 0x80) AddSimpleMode(800, 600, 72); if (edid[36] & 0x40) AddSimpleMode(800, 600, 75); if (edid[36] & 0x08) AddSimpleMode(1024, 768, 60); if (edid[36] & 0x04) AddSimpleMode(1024, 768, 70); if (edid[36] & 0x02) AddSimpleMode(1024, 768, 75); if (edid[36] & 0x01) AddSimpleMode(1280, 1024, 75); // Standard timings carry geometry and refresh but no porch/sync data. for (int off = 38; off < 54; off += 2) { if (edid[off] == 0x01 && edid[off + 1] == 0x01) continue; uint32_t width = ((uint32_t)edid[off] + 31) * 8; uint32_t aspect = (edid[off + 1] >> 6) & 3; uint32_t height = aspect == 0 ? width * 10 / 16 : aspect == 1 ? width * 3 / 4 : aspect == 2 ? width * 4 / 5 : width * 9 / 16; AddSimpleMode(width, height, (edid[off + 1] & 0x3F) + 60); } // Base-block detailed timings and monitor name descriptor. bool firstDetailed = true; for (int off = 54; off + 18 <= 126; off += 18) { DisplayMode mode; if (ParseDetailedTiming(edid + off, mode)) { AddMode(mode, 0, firstDetailed ? montauk::abi::DISPLAY_MODE_PREFERRED : 0); firstDetailed = false; } else if (edid[off + 3] == 0xFC) { CopyEdidText(g_monitorName, sizeof(g_monitorName), edid + off + 5, 13); } } // CTA-861 extension detailed timings. Other extension types are kept // untouched; their mode encodings need format-specific parsers. uint32_t blocks = 1 + edid[126]; if (blocks * 128 > edidSize) blocks = (uint32_t)(edidSize / 128); for (uint32_t block = 1; block < blocks; block++) { const uint8_t* ext = edid + block * 128; if (!EdidBlockValid(ext) || ext[0] != 0x02) continue; int start = ext[2]; if (start == 0 || start < 4 || start > 126) continue; for (int off = start; off + 18 <= 127; off += 18) { DisplayMode mode; if (ParseDetailedTiming(ext + off, mode)) AddMode(mode, 0, 0); } } KernelLogStream(OK, "IntelGPU") << "EDID: " << g_monitorName << ", " << base::dec << (uint64_t)g_modeCount << " modes"; } struct TranscoderCandidate { uint32_t timing; uint32_t conf; uint32_t ddi; const char* name; bool edp; }; static void DiscoverActiveTranscoder() { static constexpr TranscoderCandidate candidates[] = { {TRANS_TIMING_EDP, TRANS_CONF_EDP, TRANS_DDI_EDP, "eDP-1", true}, {TRANS_TIMING_A, TRANS_CONF_A, TRANS_DDI_A, "Pipe A", false}, {TRANS_TIMING_B, TRANS_CONF_B, TRANS_DDI_B, "Pipe B", false}, {TRANS_TIMING_C, TRANS_CONF_C, TRANS_DDI_C, "Pipe C", false}, }; int selected = -1; for (int pass = 0; pass < 2 && selected < 0; pass++) { for (int i = 0; i < (int)(sizeof(candidates) / sizeof(candidates[0])); i++) { uint32_t conf = ReadReg(candidates[i].conf); uint32_t ddi = ReadReg(candidates[i].ddi); if (!(conf & PIPECONF_ENABLE)) continue; if (pass == 0 && !(ddi & TRANS_DDI_ENABLE)) continue; selected = i; break; } } if (selected < 0) selected = 1; // conservative legacy Pipe A fallback const auto& trans = candidates[selected]; g_transTimingBase = trans.timing; g_transConfReg = trans.conf; g_transDdiReg = trans.ddi; g_connectorConnected = (ReadReg(trans.conf) & PIPECONF_ENABLE) != 0; uint32_t ddi = ReadReg(trans.ddi); g_auxControlReg = 0; g_ddiPort = -1; if (g_gpuGen <= 7) { // Haswell (listed as Gen 7) has the eDP transcoder; Ironlake to // Ivy Bridge drive eDP from the CPU's port A. if (trans.edp || (ReadReg(DP_A) & (1u << 31))) { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_EDP; CopyText(g_connectorName, sizeof(g_connectorName), "eDP-1"); g_ddiPort = 0; } else if (ReadReg(LVDS) & LVDS_PORT_ENABLE) { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_LVDS; CopyText(g_connectorName, sizeof(g_connectorName), "LVDS-1"); } else if (ReadReg(ADPA) & ADPA_DAC_ENABLE) { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_VGA; CopyText(g_connectorName, sizeof(g_connectorName), "VGA-1"); } else if ((ReadReg(DP_B) | ReadReg(DP_C) | ReadReg(DP_D)) & (1u << 31)) { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP; CopyText(g_connectorName, sizeof(g_connectorName), "DP-1"); } else { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_HDMI; CopyText(g_connectorName, sizeof(g_connectorName), "HDMI-1"); } } else if (trans.edp) { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_EDP; CopyText(g_connectorName, sizeof(g_connectorName), "eDP-1"); g_ddiPort = 0; g_auxControlReg = DP_AUX_A_CTL; } else { uint32_t mode = (ddi & TRANS_DDI_MODE_MASK) >> 24; // Gen12 widened the port select to bits 30:27 and stores port + 1 // (0 = none); earlier DDI parts store the port in bits 30:28. // Gen12 numbering puts the Type-C ports from D (index 3) onward. int port; if (g_gpuGen >= 12) { uint32_t field = (ddi & TRANS_DDI_PORT_MASK) >> 27; port = field ? (int)field - 1 : -1; } else { port = (int)((ddi >> 28) & 0x7); } g_ddiPort = port; static const char* const legacyNames[] = {"A", "B", "C", "D", "E", "F"}; static const char* const tcNames[] = {"TC1", "TC2", "TC3", "TC4", "TC5", "TC6"}; const char* suffix = "unknown"; if (port >= 0 && g_gpuGen >= 12) { if (port < 3) suffix = legacyNames[port]; else if (port - 3 < 6) suffix = tcNames[port - 3]; } else if (port >= 0 && port < 6) { suffix = legacyNames[port]; } if (mode == 0) { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_HDMI; CopyText(g_connectorName, sizeof(g_connectorName), "HDMI-"); } else if (mode == 1) { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DVI; CopyText(g_connectorName, sizeof(g_connectorName), "DVI-"); } else { g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP; CopyText(g_connectorName, sizeof(g_connectorName), "DP-"); // AUX channels are laid out per port, Type-C ones included. if (port >= 0 && port <= 8) g_auxControlReg = DP_AUX_A_CTL + (uint32_t)port * 0x100; } uint64_t len = 0; while (len + 1 < sizeof(g_connectorName) && g_connectorName[len]) len++; CopyText(g_connectorName + len, sizeof(g_connectorName) - len, suffix); } KernelLogStream(OK, "IntelGPU") << "Active transcoder " << trans.name << ", connector " << g_connectorName << (g_connectorConnected ? " connected" : " disconnected"); } static DisplayMode CaptureActiveMode() { DisplayMode mode = {}; uint32_t htotal = ReadReg(g_transTimingBase + 0x00); uint32_t hsync = ReadReg(g_transTimingBase + 0x08); uint32_t vtotal = ReadReg(g_transTimingBase + 0x0C); uint32_t vsync = ReadReg(g_transTimingBase + 0x14); mode.hdisplay = (htotal & 0xFFFF) + 1; mode.htotal = ((htotal >> 16) & 0xFFFF) + 1; mode.hsyncStart = (hsync & 0xFFFF) + 1; mode.hsyncEnd = ((hsync >> 16) & 0xFFFF) + 1; mode.vdisplay = (vtotal & 0xFFFF) + 1; mode.vtotal = ((vtotal >> 16) & 0xFFFF) + 1; mode.vsyncStart = (vsync & 0xFFFF) + 1; mode.vsyncEnd = ((vsync >> 16) & 0xFFFF) + 1; return mode; } static void SelectCurrentMode() { DisplayMode active = CaptureActiveMode(); if (active.hdisplay == 0 || active.hdisplay > 16384 || active.vdisplay == 0 || active.vdisplay > 16384) { active.hdisplay = (uint32_t)g_fbWidth; active.vdisplay = (uint32_t)g_fbHeight; } // Match the register totals against a detailed EDID timing to recover // the firmware-selected pixel clock exactly. for (int i = 0; i < g_modeCount; i++) { const auto& timing = g_modes[i].timing; if (timing.hdisplay == active.hdisplay && timing.vdisplay == active.vdisplay && timing.htotal == active.htotal && timing.vtotal == active.vtotal && timing.pixelClock != 0) { active.pixelClock = timing.pixelClock; active.hsyncPositive = timing.hsyncPositive; active.vsyncPositive = timing.vsyncPositive; g_currentMode = i; break; } } uint32_t refresh = ModeRefreshMilliHz(active); if (refresh == 0) refresh = 60000; if (g_currentMode < 0) g_currentMode = AddMode(active, refresh, 0); if (g_currentMode < 0 && g_modeCount == MaxDisplayModes) { // A pathological EDID can fill the catalogue before the firmware // mode is considered. The active mode is never optional: retain // it by replacing the final non-preferred catalogue entry. int replacement = MaxDisplayModes - 1; for (int i = MaxDisplayModes - 1; i >= 0; i--) { if (!(g_modes[i].flags & montauk::abi::DISPLAY_MODE_PREFERRED)) { replacement = i; break; } } g_modes[replacement].timing = active; g_modes[replacement].refreshMilliHz = refresh; g_modes[replacement].flags = 0; g_currentMode = replacement; } if (g_currentMode < 0) return; g_modes[g_currentMode].flags |= montauk::abi::DISPLAY_MODE_CURRENT | montauk::abi::DISPLAY_MODE_DRIVER_VALID; uint32_t activeClock = g_modes[g_currentMode].timing.pixelClock; for (int i = 0; i < g_modeCount; i++) { const auto& mode = g_modes[i].timing; if (mode.hdisplay == active.hdisplay && mode.vdisplay == active.vdisplay && mode.pixelClock != 0 && activeClock != 0 && mode.pixelClock == activeClock) { g_modes[i].flags |= montauk::abi::DISPLAY_MODE_DRIVER_VALID; } } } // ========================================================================= // Backlight // ========================================================================= // South-display family. The PWM register layout and the GMBUS pin map // follow the PCH, not the GPU: a Coffee Lake on Cannon Point and a Kaby // Lake on Sunrise Point are both Gen 9 but have incompatible PWM blocks. enum class PchFamily : uint8_t { Unknown, Ibx, Lpt, Cnp, Icp }; enum class BacklightMethod : uint8_t { None, PwmIlk, PwmLpt, PwmCnp, DpcdAux, DdcCi }; static PchFamily g_pch = PchFamily::Unknown; static BacklightMethod g_blMethod = BacklightMethod::None; static uint32_t g_blFreqReg = 0; // PWM period register static uint32_t g_blDutyReg = 0; // PWM duty register static uint32_t g_blMax = 0; // DPCD / DDC full-scale value static int g_blPercent = -1; // last level read or written static bool g_dpcdModePending = false; // panel still on its PWM pin static bool g_ddcCandidate = false; // external monitor, DDC/CI untried static uint64_t g_ddcRetryMs = 0; // earliest re-probe after a failure static uint64_t g_ddcLastMs = 0; // MCCS needs 50ms between commands // Serializes backlight access: DDC/CI and DPCD changes are multi-step // AUX/GMBUS transactions that must not interleave. static kcp::Mutex g_blLock; // Lowest level a panel is driven to. A zero duty cycle turns some panels // fully dark, which leaves nothing on screen to turn them back up with. static constexpr int MinPanelPercent = 1; // MCCS DDC/CI (VESA Monitor Control Command Set over I2C address 0x37). static constexpr uint8_t DdcAddress = 0x37; static constexpr uint8_t DdcHostAddress = 0x51; static constexpr uint8_t DdcVcpBrightness = 0x10; static constexpr uint64_t DdcCommandGapMs = 50; static constexpr uint64_t DdcReplyDelayMs = 40; static constexpr uint64_t DdcRetryIntervalMs = 60000; static constexpr uint32_t SOUTH_DSPCLK_GATE_D = 0xC2020; static constexpr uint32_t PCH_GMBUSUNIT_CLOCK_GATE_DISABLE = (1u << 31); static PchFamily DetectPch() { auto& devices = Pci::GetDevices(); for (uint64_t i = 0; i < devices.size(); i++) { const auto& d = devices[i]; if (d.VendorId != VendorIntel || d.Bus != 0 || d.Device != 0x1F || d.Function != 0) continue; // Device-ID families as i915 groups them (id & 0xFF80). switch (d.DeviceId & 0xFF80) { case 0x3B00: case 0x1C00: case 0x1E00: return PchFamily::Ibx; case 0x8C00: case 0x9C00: case 0x8C80: case 0x9C80: // LPT/WPT case 0xA100: case 0x9D00: case 0xA280: case 0xA380: // SPT/KBP/CMP-V return PchFamily::Lpt; case 0xA300: case 0x9D80: case 0x0280: case 0x0680: // CNP/CMP return PchFamily::Cnp; default: break; } break; } // Ice Point and everything after it share the Cannon Point PWM block // and the Ice Point pin map, and new PCH IDs keep appearing. return g_gpuGen >= 11 ? PchFamily::Icp : PchFamily::Unknown; } static int RawToPercent(uint32_t raw, uint32_t maximum) { if (maximum == 0) return -1; if (raw > maximum) raw = maximum; return (int)(((uint64_t)raw * 100 + maximum / 2) / maximum); } static uint32_t PercentToRaw(int percent, uint32_t maximum) { return (uint32_t)(((uint64_t)maximum * (uint32_t)percent + 50) / 100); } static void ShortSpin() { for (int i = 0; i < 20000; i++) asm volatile("pause"); } // ---- Panel PWM ---------------------------------------------------------- static bool PwmState(uint32_t& maximum, uint32_t& duty) { uint32_t freq = ReadReg(g_blFreqReg); uint32_t value = ReadReg(g_blDutyReg); if (freq == 0xFFFFFFFF) return false; switch (g_blMethod) { case BacklightMethod::PwmCnp: maximum = freq; duty = value; break; case BacklightMethod::PwmLpt: maximum = value >> 16; duty = value & 0xFFFF; break; case BacklightMethod::PwmIlk: maximum = freq >> 16; duty = value & 0xFFFF; break; default: return false; } return maximum != 0; } static void PwmWriteDuty(uint32_t duty) { if (g_blMethod == BacklightMethod::PwmCnp) { WriteReg(g_blDutyReg, duty); } else { uint32_t value = ReadReg(g_blDutyReg); WriteReg(g_blDutyReg, (value & 0xFFFF0000u) | (duty & 0xFFFF)); } (void)ReadReg(g_blDutyReg); } // Adopt the PWM controller firmware left driving the panel. A controller // that is not enabled is not what lights this panel, so it is skipped. static bool DiscoverPwmBacklight() { bool cnpLayout = g_pch == PchFamily::Cnp || g_pch == PchFamily::Icp; if (g_pch == PchFamily::Unknown && g_gpuGen >= 8) { // Unrecognized bridge: the LPT layout has nothing at DUTY1, so a // plausible duty there identifies the CNP block. uint32_t freq = ReadReg(BXT_BLC_PWM_FREQ1); uint32_t duty = ReadReg(BXT_BLC_PWM_DUTY1); cnpLayout = duty != 0 && duty != 0xFFFFFFFF && duty <= freq; } if (cnpLayout) { // BXT/CNP firmware may route the panel through either controller. for (uint32_t c = 0; c < 2; c++) { uint32_t delta = c * BXT_BLC_PWM_CONTROLLER_DELTA; uint32_t ctl = ReadReg(BXT_BLC_PWM_CTL1 + delta); uint32_t freq = ReadReg(BXT_BLC_PWM_FREQ1 + delta); if (ctl == 0xFFFFFFFF || !(ctl & BLC_PWM_ENABLE) || freq == 0 || freq == 0xFFFFFFFF) continue; g_blMethod = BacklightMethod::PwmCnp; g_blFreqReg = BXT_BLC_PWM_FREQ1 + delta; g_blDutyReg = BXT_BLC_PWM_DUTY1 + delta; return true; } return false; } if (g_pch == PchFamily::Lpt || g_gpuGen >= 8) { uint32_t ctl1 = ReadReg(BLC_PWM_PCH_CTL1); uint32_t ctl2 = ReadReg(BLC_PWM_PCH_CTL2); if (ctl1 == 0xFFFFFFFF || !(ctl1 & BLC_PWM_ENABLE) || (ctl2 >> 16) == 0) return false; g_blMethod = BacklightMethod::PwmLpt; g_blFreqReg = BLC_PWM_PCH_CTL2; g_blDutyReg = BLC_PWM_PCH_CTL2; return true; } uint32_t pch2 = ReadReg(BLC_PWM_PCH_CTL2); uint32_t cpu2 = ReadReg(BLC_PWM_CPU_CTL2); if (pch2 == 0xFFFFFFFF || (pch2 >> 16) == 0 || !(cpu2 & BLC_PWM_ENABLE)) return false; g_blMethod = BacklightMethod::PwmIlk; g_blFreqReg = BLC_PWM_PCH_CTL2; g_blDutyReg = BLC_PWM_CPU_CTL; return true; } // ---- eDP DPCD backlight ------------------------------------------------- // Native AUX read or write of DPCD registers, retrying sink DEFERs. static bool DpcdAccess(bool write, uint32_t address, uint8_t* data, int size) { for (int attempt = 0; attempt < 8; attempt++) { uint8_t reply = 0xFF; int result = write ? AuxTransfer(0x8, address, data, size, nullptr, 0, &reply) : AuxTransfer(0x9, address, nullptr, 0, data, size, &reply); if (result == size && (reply & 0x3) == 0) return true; if (result < 0 || (reply & 0x3) != 0x2) return false; ShortSpin(); } return false; } // Before Gen 12 the eDP output has its own transcoder; from Gen 12 it is // an ordinary DDI in DP mode, so ask the sink. Only eDP sinks implement // the 0x700 eDP register block or EDP_CONFIGURATION_CAP. static void DetectEmbeddedDp() { if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_DP || g_auxControlReg == 0) return; uint8_t edpRev = 0; uint8_t edpConfig = 0; bool edp = DpcdAccess(false, DPCD_EDP_REV, &edpRev, 1) && edpRev != 0; if (!edp) edp = DpcdAccess(false, 0x00D, &edpConfig, 1) && edpConfig != 0; if (!edp) return; g_connectorType = montauk::abi::DISPLAY_CONNECTOR_EDP; CopyText(g_connectorName, sizeof(g_connectorName), "eDP-1"); KernelLogStream(INFO, "IntelGPU") << "DDI output identified as eDP (eDP DPCD rev " << base::hex << (uint64_t)edpRev << ")"; } static int DpcdBrightnessBytes() { return g_blMax > 0xFF ? 2 : 1; } // Returns 0 when the panel offers no AUX brightness control, 1 when it // does but is still driven from its PWM pin, 2 when DPCD already owns it. static int ProbeDpcdBacklight() { if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_EDP || g_auxControlReg == 0) return 0; uint8_t caps[3] = {}; if (!DpcdAccess(false, DPCD_EDP_REV, caps, 3)) return 0; if (caps[0] == 0 || !(caps[2] & EDP_BLCAP_AUX_SET)) return 0; uint8_t mode = 0; if (!DpcdAccess(false, DPCD_EDP_BACKLIGHT_MODE, &mode, 1)) return 0; g_blMax = (caps[2] & EDP_BLCAP_BYTE_COUNT_16) ? 0xFFFF : 0xFF; return (mode & EDP_BLMODE_MASK) == EDP_BLMODE_DPCD ? 2 : 1; } static int DpcdReadPercent() { uint8_t bytes[2] = {}; int count = DpcdBrightnessBytes(); if (!DpcdAccess(false, DPCD_EDP_BRIGHTNESS_MSB, bytes, count)) return -1; uint32_t raw = count == 2 ? ((uint32_t)bytes[0] << 8 | bytes[1]) : bytes[0]; return RawToPercent(raw, g_blMax); } static bool DpcdWritePercent(int percent) { uint32_t raw = PercentToRaw(percent, g_blMax); if (raw == 0) raw = 1; uint8_t bytes[2]; int count = DpcdBrightnessBytes(); if (count == 2) { bytes[0] = (uint8_t)(raw >> 8); bytes[1] = (uint8_t)raw; } else { bytes[0] = (uint8_t)raw; } if (!DpcdAccess(true, DPCD_EDP_BRIGHTNESS_MSB, bytes, count)) return false; if (!g_dpcdModePending) return true; // First change on a panel that was on its PWM pin: the level is // already programmed, so switching the source cannot flash it dark. uint8_t mode = 0; if (!DpcdAccess(false, DPCD_EDP_BACKLIGHT_MODE, &mode, 1)) return false; mode = (uint8_t)((mode & ~EDP_BLMODE_MASK) | EDP_BLMODE_DPCD); if (!DpcdAccess(true, DPCD_EDP_BACKLIGHT_MODE, &mode, 1)) return false; g_dpcdModePending = false; return true; } // ---- GMBUS (HDMI/DVI DDC) ----------------------------------------------- static int GmbusPinForPort(int port) { if (port < 0) return -1; switch (g_pch) { case PchFamily::Icp: { // Combo PHY ports use pins 1-3; Type-C ports start at pin 9. // Gen 11 numbers its first Type-C port C, Gen 12 numbers it D. int firstTc = g_gpuGen >= 12 ? 3 : 2; if (port < firstTc) return port + 1; int pin = 9 + (port - firstTc); return pin <= 14 ? pin : -1; } case PchFamily::Cnp: return port == 1 ? 1 : port == 2 ? 2 : port == 3 ? 4 : -1; default: return port == 1 ? 5 : port == 2 ? 4 : port == 3 ? 6 : -1; } } static bool GmbusWait(uint32_t bit) { uint64_t start = Timekeeping::GetMilliseconds(); for (;;) { uint32_t status = ReadReg(GMBUS2); if (status & GMBUS_SATOER) return false; if (status & bit) return true; if (Timekeeping::GetMilliseconds() - start > 50) return false; asm volatile("pause"); } } static bool GmbusWaitIdle() { uint64_t start = Timekeeping::GetMilliseconds(); while (ReadReg(GMBUS2) & GMBUS_ACTIVE) { if (Timekeeping::GetMilliseconds() - start > 50) return false; asm volatile("pause"); } return true; } // One complete I2C message (START .. STOP) at 100 kHz. static bool GmbusTransfer(int pin, uint8_t address, bool read, uint8_t* data, int size) { if (pin < 0 || size <= 0 || size > 256) return false; uint32_t gate = 0; if (g_pch == PchFamily::Cnp) { // Cannon Point drops GMBUS transfers while the unit clock gates. gate = ReadReg(SOUTH_DSPCLK_GATE_D); WriteReg(SOUTH_DSPCLK_GATE_D, gate | PCH_GMBUSUNIT_CLOCK_GATE_DISABLE); } WriteReg(GMBUS0, (uint32_t)pin); uint32_t command = GMBUS_SW_RDY | GMBUS_CYCLE_WAIT | ((uint32_t)size << GMBUS_BYTE_COUNT_SHIFT) | ((uint32_t)address << 1) | (read ? GMBUS_SLAVE_READ : 0); bool ok = true; int pos = 0; if (read) { WriteReg(GMBUS1, command); while (ok && pos < size) { ok = GmbusWait(GMBUS_HW_RDY); if (!ok) break; uint32_t value = ReadReg(GMBUS3); for (int i = 0; i < 4 && pos < size; i++, pos++) data[pos] = (uint8_t)(value >> (8 * i)); } } else { // The first dword is loaded before the command starts the cycle. uint32_t value = 0; for (int i = 0; i < 4 && pos < size; i++, pos++) value |= (uint32_t)data[pos] << (8 * i); WriteReg(GMBUS3, value); WriteReg(GMBUS1, command); while (ok && pos < size) { ok = GmbusWait(GMBUS_HW_RDY); if (!ok) break; value = 0; for (int i = 0; i < 4 && pos < size; i++, pos++) value |= (uint32_t)data[pos] << (8 * i); WriteReg(GMBUS3, value); } } if (ok) ok = GmbusWait(GMBUS_HW_WAIT_PHASE); if (ok) { WriteReg(GMBUS1, GMBUS_SW_RDY | GMBUS_CYCLE_STOP); ok = GmbusWaitIdle(); } else { // NAK or timeout: clear the error, let the bus go idle. WriteReg(GMBUS1, GMBUS_SW_CLR_INT); (void)GmbusWaitIdle(); WriteReg(GMBUS1, 0); } WriteReg(GMBUS0, 0); if (g_pch == PchFamily::Cnp) WriteReg(SOUTH_DSPCLK_GATE_D, gate); return ok; } // ---- DDC/CI (external monitors) ----------------------------------------- static bool DdcUsesAux() { return g_connectorType == montauk::abi::DISPLAY_CONNECTOR_DP; } static bool DdcWrite(uint8_t* data, int size) { if (DdcUsesAux()) { if (size > 16) return false; bool ok = AuxI2cTransfer(0x4, data, size, nullptr, 0, DdcAddress); // Address-only write without MOT ends the I2C transaction. bool stopped = AuxI2cTransfer(0x0, nullptr, 0, nullptr, 0, DdcAddress); return ok && stopped; } return GmbusTransfer(GmbusPinForPort(g_ddiPort), DdcAddress, false, data, size); } static bool DdcRead(uint8_t* data, int size) { if (DdcUsesAux()) { if (size > 16) return false; if (!AuxI2cTransfer(0x5, nullptr, 0, nullptr, 0, DdcAddress)) return false; return AuxI2cTransfer(0x1, nullptr, 0, data, size, DdcAddress); } return GmbusTransfer(GmbusPinForPort(g_ddiPort), DdcAddress, true, data, size); } static void DdcPace() { uint64_t now = Timekeeping::GetMilliseconds(); if (g_ddcLastMs != 0 && now - g_ddcLastMs < DdcCommandGapMs) Timekeeping::Sleep(DdcCommandGapMs - (now - g_ddcLastMs)); } // Send an MCCS command: host source address, length, payload, checksum. // The checksum covers the monitor's 8-bit write address (0x6E) too. static bool DdcCommand(const uint8_t* payload, int length) { uint8_t packet[16]; if (length + 3 > (int)sizeof(packet)) return false; packet[0] = DdcHostAddress; packet[1] = (uint8_t)(0x80 | length); uint8_t checksum = (uint8_t)(DdcAddress << 1) ^ packet[0] ^ packet[1]; for (int i = 0; i < length; i++) { packet[2 + i] = payload[i]; checksum ^= payload[i]; } packet[2 + length] = checksum; DdcPace(); bool ok = DdcWrite(packet, length + 3); g_ddcLastMs = Timekeeping::GetMilliseconds(); return ok; } static bool DdcGetBrightness(uint32_t& current, uint32_t& maximum) { uint8_t request[2] = {0x01, DdcVcpBrightness}; // Get VCP Feature if (!DdcCommand(request, 2)) return false; Timekeeping::Sleep(DdcReplyDelayMs); // Reply: 6E 88 02 rc 10 type maxH maxL curH curL checksum. The // checksum covers the host's virtual address 0x50. uint8_t reply[11] = {}; bool ok = DdcRead(reply, sizeof(reply)); g_ddcLastMs = Timekeeping::GetMilliseconds(); if (!ok) return false; uint8_t checksum = 0x50; for (int i = 0; i < 10; i++) checksum ^= reply[i]; if (checksum != reply[10] || reply[1] != 0x88 || reply[2] != 0x02 || reply[3] != 0x00 || reply[4] != DdcVcpBrightness) return false; maximum = (uint32_t)reply[6] << 8 | reply[7]; current = (uint32_t)reply[8] << 8 | reply[9]; return maximum != 0; } static bool DdcSetBrightness(uint32_t value) { uint8_t request[4] = {0x03, DdcVcpBrightness, // Set VCP Feature (uint8_t)(value >> 8), (uint8_t)value}; return DdcCommand(request, 4); } // DDC/CI takes tens of milliseconds and needs the scheduler, so it is // probed on first use from a syscall rather than during boot. static bool EnsureDdc() { if (g_blMethod == BacklightMethod::DdcCi) return true; if (!g_ddcCandidate) return false; uint64_t now = Timekeeping::GetMilliseconds(); if (g_ddcRetryMs != 0 && now < g_ddcRetryMs) return false; for (int attempt = 0; attempt < 3; attempt++) { uint32_t current = 0, maximum = 0; if (!DdcGetBrightness(current, maximum)) continue; g_blMethod = BacklightMethod::DdcCi; g_blMax = maximum; g_blPercent = RawToPercent(current, maximum); KernelLogStream(OK, "IntelGPU") << "Monitor brightness via DDC/CI (" << base::dec << (uint64_t)current << "/" << (uint64_t)maximum << ")"; return true; } g_ddcRetryMs = Timekeeping::GetMilliseconds() + DdcRetryIntervalMs; KernelLogStream(INFO, "IntelGPU") << "Monitor did not answer DDC/CI brightness query"; return false; } // ---- Discovery and dispatch --------------------------------------------- static void DiscoverBacklight() { g_blMethod = BacklightMethod::None; g_blPercent = -1; g_dpcdModePending = false; g_ddcCandidate = false; g_pch = DetectPch(); DetectEmbeddedDp(); bool panel = g_connectorType == montauk::abi::DISPLAY_CONNECTOR_EDP || g_connectorType == montauk::abi::DISPLAY_CONNECTOR_LVDS; if (panel) { // A panel firmware already put in DPCD mode ignores its PWM pin. // Otherwise PWM is what firmware lit it with; take DPCD over only // when there is no live PWM controller. int dpcd = ProbeDpcdBacklight(); if (dpcd == 2) { g_blMethod = BacklightMethod::DpcdAux; } else if (!DiscoverPwmBacklight() && dpcd == 1) { g_blMethod = BacklightMethod::DpcdAux; g_dpcdModePending = true; g_blPercent = 100; } } else if (g_gpuGen >= 8 && g_ddiPort >= 0 && (g_connectorType == montauk::abi::DISPLAY_CONNECTOR_HDMI || g_connectorType == montauk::abi::DISPLAY_CONNECTOR_DVI || (g_connectorType == montauk::abi::DISPLAY_CONNECTOR_DP && g_auxControlReg != 0))) { g_ddcCandidate = DdcUsesAux() || GmbusPinForPort(g_ddiPort) >= 0; } const char* pch = g_pch == PchFamily::Ibx ? "IBX" : g_pch == PchFamily::Lpt ? "LPT" : g_pch == PchFamily::Cnp ? "CNP" : g_pch == PchFamily::Icp ? "ICP+" : "unknown"; if (g_blMethod != BacklightMethod::None) { KernelLogStream(INFO, "IntelGPU") << "Backlight: " << BrightnessMethod() << (g_dpcdModePending ? " (on first change)" : "") << ", PCH " << pch; } else { KernelLogStream(INFO, "IntelGPU") << "Backlight: " << (g_ddcCandidate ? "DDC/CI probe deferred" : "not exposed") << ", PCH " << pch; } } static int BrightnessLocked(int percent) { if (percent > 100) percent = 100; switch (g_blMethod) { case BacklightMethod::PwmIlk: case BacklightMethod::PwmLpt: case BacklightMethod::PwmCnp: { uint32_t maximum = 0, duty = 0; if (!PwmState(maximum, duty)) return -1; if (percent >= 0) { if (percent < MinPanelPercent) percent = MinPanelPercent; uint32_t raw = PercentToRaw(percent, maximum); PwmWriteDuty(raw ? raw : 1); if (!PwmState(maximum, duty)) return -2; } g_blPercent = RawToPercent(duty, maximum); return g_blPercent; } case BacklightMethod::DpcdAux: { if (percent < 0) { if (g_dpcdModePending) return g_blPercent; int level = DpcdReadPercent(); if (level >= 0) g_blPercent = level; return g_blPercent; } if (percent < MinPanelPercent) percent = MinPanelPercent; if (!DpcdWritePercent(percent)) return -2; int level = DpcdReadPercent(); g_blPercent = level >= 0 ? level : percent; return g_blPercent; } case BacklightMethod::None: if (!EnsureDdc()) return -1; [[fallthrough]]; case BacklightMethod::DdcCi: { // Monitors answer queries slowly, and the OSD is the only // other writer, so the last known level stands in. if (percent < 0) return g_blPercent; if (!DdcSetBrightness(PercentToRaw(percent, g_blMax))) return -2; g_blPercent = percent; return g_blPercent; } } return -1; } static void InitializeDisplayManagement() { g_modeCount = 0; g_currentMode = -1; g_edidValid = false; CopyText(g_monitorName, sizeof(g_monitorName), "Unknown display"); DiscoverActiveTranscoder(); ParseEdid(); SelectCurrentMode(); DiscoverBacklight(); } // ========================================================================= // GTT Initialization // ========================================================================= static bool InitializeGtt() { uint8_t bus = g_gpuInfo.pciBus; uint8_t dev = g_gpuInfo.pciDevice; uint8_t func = g_gpuInfo.pciFunction; // Read GGC/GMCH_CTL to determine the GGTT modification window. uint16_t gmchCtl = Pci::LegacyRead16(bus, dev, func, (uint8_t)PCI_REG_GMCH_CTL); uint64_t gttSizeBytes = 0; uint64_t gttOffset = 0; if (g_gpuGen >= 8) { uint8_t ggms = (gmchCtl >> 6) & 0x3; if (ggms != 0) gttSizeBytes = (1ull << ggms) * 1024 * 1024; gttOffset = 8 * 1024 * 1024; } else { uint8_t gttSizeBits = (gmchCtl >> 8) & 0x3; // 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; } gttOffset = 2 * 1024 * 1024; } if (gttSizeBytes == 0) { KernelLogStream(ERROR, "IntelGPU") << "GGC reports no GGTT (GGC=" << base::hex << (uint64_t)gmchCtl << ")"; return false; } g_gpuInfo.gttSize = gttSizeBytes; KernelLogStream(INFO, "IntelGPU") << "GMCH_CTL: " << base::hex << (uint64_t)gmchCtl << ", GTT size: " << base::dec << (gttSizeBytes / 1024) << " KB"; uint64_t gttPhys = g_gpuInfo.mmioPhys + gttOffset; // 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); g_ggtt = g_gttBase; // 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); } g_ggttEntries = g_gttEntryCount; KernelLogStream(INFO, "IntelGPU") << "GTT at physical " << base::hex << gttPhys << ", " << base::dec << g_gttEntryCount << " entries" << (g_gpuGen >= 8 ? " (64-bit PTEs)" : " (32-bit PTEs)"); // 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"; return true; } // ========================================================================= // Framebuffer Allocation and Setup // ========================================================================= static bool SetupFramebuffer() { // The boot framebuffer can be the CPU-visible graphics aperture rather // than the scanout memory itself. On Gen 8+ retain the firmware's GGTT // mapping and its surface offset; remapping the aperture address as a // backing page would create an invalid translation. uint32_t* fwFb = ::Graphics::Framebuffer::GetBase(); 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; } if (g_gpuGen >= 8) { g_fbGttOffset = ReadReg(DSPASURF) & ~0xFFFull; uint64_t first = g_fbGttOffset >> 12; uint64_t last = (g_fbGttOffset + g_fbSize - 1) >> 12; if (last >= g_gttEntryCount || !(ReadGgttPte(first) & 1) || !(ReadGgttPte(last) & 1)) { KernelLogStream(ERROR, "IntelGPU") << "Firmware scanout is not backed by valid GGTT entries"; return false; } KernelLogStream(OK, "IntelGPU") << "Preserving firmware GGTT scanout at +" << base::hex << g_fbGttOffset << " (CPU aperture " << fwFbPhys << ")"; } else { KernelLogStream(INFO, "IntelGPU") << "Mapping " << base::dec << pageCount << " firmware FB pages through GTT (phys base " << base::hex << fwFbPhys << ")"; 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]; g_fbGttOffset = 0; } // Keep using the same framebuffer memory g_fbBase = fwFb; g_fbPhysBase = fwFbPhys; KernelLogStream(OK, "IntelGPU") << "Framebuffer ready: " << base::dec << pageCount << " pages, CPU 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+. // Reassert the firmware surface offset to flush any plane update. 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; } // ========================================================================= // Page flipping: GGTT at the generation-correct offset // ========================================================================= // // The GTT window lives in the upper half of GTTMMADR (BAR0): // Gen 6-7: 4 MB BAR, GTT at +2 MB, 32-bit entries // Gen 8+: 16 MB BAR, GGTT at +8 MB, 64-bit entries // Initialization maps this window once and preserves the firmware's active // scanout PTEs. Page flipping only proceeds after those entries validate. static uint64_t ReadGgttPte(uint64_t index) { if (g_gpuGen >= 8) return ((volatile uint64_t*)g_ggtt)[index]; return ((volatile uint32_t*)g_ggtt)[index]; } static int ReadLiveBufferIndex() { if (!g_mmioBase) return -1; uint32_t live = ReadReg(DSPASURFLIVE) & ~0xFFFu; if (live == (uint32_t)g_fbGttOffsetA) return 0; if (live == (uint32_t)g_buf1GttOffset) return 1; return -1; } // Polling-only latch wait. This is safe during both ordinary syscalls and // ExitProcess teardown because it never enters the scheduler. Keeping // flips independent of GPU MSI is deliberate: MSI would route every // firmware-enabled GPU source, not just the one source this driver owns. // Three observed frames are the normal timeout. Laptop panel self-refresh // can transiently stop the scanline counter, so also enforce a wall-clock // bound: one million uncached MMIO reads can otherwise hold the desktop in // SYS_FBFLIP long enough to look permanently frozen (cursor included). static bool WaitForLiveBuffer(int index) { uint32_t expected = (uint32_t)((index == 0) ? g_fbGttOffsetA : g_buf1GttOffset); uint32_t previous = ReadReg(PIPE_DSL) & 0x1FFFu; uint64_t started = Timekeeping::GetMilliseconds(); int frameWraps = 0; for (int i = 0; i < 200000; i++) { uint32_t live = ReadReg(DSPASURFLIVE); if (live == 0xFFFFFFFFu) return false; if ((live & ~0xFFFu) == expected) return true; uint32_t current = ReadReg(PIPE_DSL) & 0x1FFFu; if (current < previous && ++frameWraps >= 3) return false; previous = current; if ((i & 0x3FF) == 0 && Timekeeping::GetMilliseconds() - started >= 100) return false; asm volatile("pause"); } return false; } static void WriteGgttPte(uint64_t index, uint64_t physAddr) { if (g_gpuGen >= 8) { ((volatile uint64_t*)g_ggtt)[index] = MakeGttPte64(physAddr); } else { ((volatile uint32_t*)g_ggtt)[index] = MakeGttPte32(physAddr); } } static bool MapCorrectGgtt() { if (!g_gttBase || g_gttEntryCount == 0) return false; g_ggtt = g_gttBase; g_ggttEntries = g_gttEntryCount; return true; } static bool VerifyFirmwareScanoutPtes() { // The active scanout surface must be backed by valid PTEs in the // window we mapped; if not, our idea of where the GGTT lives is wrong // and writing to it could corrupt arbitrary state. g_fbGttOffsetA = ReadReg(DSPASURF) & ~0xFFFull; uint64_t firstIdx = g_fbGttOffsetA >> 12; uint64_t lastIdx = (g_fbGttOffsetA + g_fbSize - 1) >> 12; if (lastIdx >= g_ggttEntries) { KernelLogStream(WARNING, "IntelGPU") << "Scanout range exceeds GGTT (" << base::dec << lastIdx << " >= " << g_ggttEntries << ")"; return false; } uint64_t checks[3] = { firstIdx, (firstIdx + lastIdx) / 2, lastIdx }; for (int i = 0; i < 3; i++) { uint64_t pte = ReadGgttPte(checks[i]); if (!(pte & 1)) { KernelLogStream(WARNING, "IntelGPU") << "Scanout PTE " << base::dec << checks[i] << " invalid (" << base::hex << pte << ") - GGTT window not where expected, page flip unavailable"; return false; } } KernelLogStream(OK, "IntelGPU") << "Firmware scanout PTEs verified (surface at GGTT+" << base::hex << g_fbGttOffsetA << ", PTE[" << base::dec << firstIdx << "]=" << base::hex << ReadGgttPte(firstIdx) << ")"; return true; } static bool AllocateBackBuffer() { uint64_t pages = (g_fbSize + 0xFFF) >> 12; if (pages == 0 || pages > 0x7FFFFFFFULL) { KernelLogStream(WARNING, "IntelGPU") << "Invalid second scanout buffer page count " << base::dec << pages; return false; } void* virt = Memory::g_pfa->ReallocConsecutive(nullptr, (int)pages); if (!virt) { KernelLogStream(WARNING, "IntelGPU") << "Failed to allocate " << base::dec << pages << " contiguous pages for the second scanout buffer"; return false; } uint64_t phys = Memory::SubHHDM(virt); if ((pages - 1) > (UINT64_MAX - phys) / 0x1000ULL) { Memory::g_pfa->Free(virt, (int)pages); KernelLogStream(WARNING, "IntelGPU") << "Second scanout buffer physical range overflows"; return false; } uint64_t lastPhys = phys + (pages - 1) * 0x1000ULL; if (!GgttCanAddress(phys, g_gpuGen) || !GgttCanAddress(lastPhys, g_gpuGen)) { Memory::g_pfa->Free(virt, (int)pages); KernelLogStream(WARNING, "IntelGPU") << "Second scanout buffer lies outside the GPU physical-address width (" << base::hex << phys << ".." << lastPhys << ")"; return false; } g_buf1Phys = phys; g_buf1Virt = (uint32_t*)virt; g_buf1Pages = pages; // Display scanout does not snoop the CPU cache: remap the buffer // write-combining before touching it, same as the firmware FB. for (uint64_t i = 0; i < pages; i++) { Memory::VMM::g_paging->MapWC(g_buf1Phys + i * 0x1000, Memory::HHDM(g_buf1Phys + i * 0x1000)); } Memory::VMM::FlushTLB(); memset(virt, 0, pages * 0x1000); return true; } static bool FindAndMapGgttRange() { uint64_t pages = (g_fbSize + 0xFFF) >> 12; constexpr uint64_t guard = 16; uint64_t need = pages + 2 * guard; uint64_t avoidFirst = g_fbGttOffsetA >> 12; uint64_t avoidLast = (g_fbGttOffsetA + g_fbSize - 1) >> 12; // Firmware normally fills unused GGTT entries with one scratch-page // PTE. Verify that invariant at the tail, then claim only a complete // scratch-backed (or invalid) run. Never overwrite arbitrary valid // mappings merely because they happen to sit in the upper half. if (g_ggttEntries < 32 || need > g_ggttEntries / 2) return false; uint64_t scratchPte = ReadGgttPte(g_ggttEntries - 1); for (uint64_t probe = 2; probe <= 32; probe++) { if (ReadGgttPte(g_ggttEntries - probe) != scratchPte) { KernelLogStream(WARNING, "IntelGPU") << "GGTT tail is owned/non-uniform; page flip unavailable"; return false; } } uint64_t runStart = 0; uint64_t run = 0; bool found = false; for (uint64_t idx = g_ggttEntries / 2; idx < g_ggttEntries; idx++) { if (idx >= avoidFirst && idx <= avoidLast) { run = 0; continue; } uint64_t pte = ReadGgttPte(idx); if ((pte & 1) && pte != scratchPte) { run = 0; continue; } if (run == 0) runStart = idx; if (++run >= need) { found = true; break; } } if (!found) { KernelLogStream(WARNING, "IntelGPU") << "No verified free GGTT run for " << base::dec << pages << " back-buffer pages"; return false; } uint64_t base = runStart + guard; uint64_t replacedFirst = ReadGgttPte(base); uint64_t replacedLast = ReadGgttPte(base + pages - 1); for (uint64_t i = 0; i < pages; i++) { WriteGgttPte(base + i, g_buf1Phys + i * 0x1000); } // Read back first and last entry to confirm the writes stuck uint64_t expectFirst = (g_gpuGen >= 8) ? MakeGttPte64(g_buf1Phys) : MakeGttPte32(g_buf1Phys); uint64_t lastPhys = g_buf1Phys + (pages - 1) * 0x1000; uint64_t expectLast = (g_gpuGen >= 8) ? MakeGttPte64(lastPhys) : MakeGttPte32(lastPhys); if (ReadGgttPte(base) != expectFirst || ReadGgttPte(base + pages - 1) != expectLast) { KernelLogStream(WARNING, "IntelGPU") << "GGTT read-back mismatch at entry " << base::dec << base << ", page flip unavailable"; return false; } g_buf1GttOffset = base << 12; KernelLogStream(OK, "IntelGPU") << "Claimed buffer 1 at GGTT+" << base::hex << g_buf1GttOffset << " (" << base::dec << pages << " pages, phys " << base::hex << g_buf1Phys << ", replaced PTEs " << replacedFirst << ".." << replacedLast << ")"; return true; } static void SetupPageFlip() { if (!MapCorrectGgtt()) return; if (!VerifyFirmwareScanoutPtes()) return; if (!AllocateBackBuffer()) return; if (!FindAndMapGgttRange()) return; g_flipSupported = true; int live = ReadLiveBufferIndex(); g_frontBuffer = (live >= 0) ? live : 0; KernelLogStream(OK, "IntelGPU") << "Page flip ready: 2 buffers, bounded SURFLIVE polling"; } static bool FirmwareFramebufferMatchesGpu() { uint64_t fwWidth = ::Graphics::Framebuffer::GetWidth(); uint64_t fwHeight = ::Graphics::Framebuffer::GetHeight(); uint64_t fwPitch = ::Graphics::Framebuffer::GetPitch(); bool match = g_fbWidth == fwWidth && g_fbHeight == fwHeight && g_fbPitch == fwPitch; if (match) return true; // Userspace obtains its drawing dimensions from the firmware // framebuffer API. A differently-sized GPU allocation would make the // second mapping overrun (or alias only part of) the backing buffer. KernelLogStream(ERROR, "IntelGPU") << "GPU dimensions differ from firmware; unsafe framebuffer setup skipped"; 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; return false; } // ========================================================================= // Public API // ========================================================================= // ========================================================================= // Probe (called by PCI driver matching framework) // ========================================================================= bool Probe(const Pci::PciDevice& dev) { if (g_initialized) return false; // Fill in PCI location g_gpuInfo.pciBus = dev.Bus; g_gpuInfo.pciDevice = dev.Device; g_gpuInfo.pciFunction = dev.Function; g_gpuInfo.deviceId = dev.DeviceId; // Match device ID against supported table const DeviceInfo* matchedInfo = nullptr; for (int j = 0; j < SupportedDeviceCount; j++) { if (dev.DeviceId == SupportedDevices[j].deviceId) { matchedInfo = &SupportedDevices[j]; break; } } if (matchedInfo != nullptr) { g_gpuInfo.gen = matchedInfo->gen; g_gpuInfo.name = matchedInfo->name; KernelLogStream(OK, "IntelGPU") << "Found " << matchedInfo->name << " (device " << base::hex << (uint64_t)dev.DeviceId << ")" << " at PCI " << (uint64_t)dev.Bus << ":" << (uint64_t)dev.Device << "." << (uint64_t)dev.Function; } else { uint16_t did = dev.DeviceId; uint8_t inferredGen; if (did >= 0xA700 || (did >= 0x4600 && did < 0x4700) || (did >= 0x5600 && did < 0x5700) || (did >= 0x7D00 && did < 0x7E00)) { inferredGen = 12; } else if ((did >= 0x8A00 && did < 0x8B00) || (did >= 0x9A00 && did < 0x9B00)) { inferredGen = 12; } else if ((did >= 0x3E00 && did < 0x3F00) || (did >= 0x5900 && did < 0x5A00) || (did >= 0x9B00 && did < 0x9C00) || (did >= 0x1900 && did < 0x1A00)) { inferredGen = 9; } else if (did >= 0x1600 && did < 0x1700) { inferredGen = 8; } else { inferredGen = 7; } g_gpuInfo.gen = inferredGen; g_gpuInfo.name = "Intel GPU"; KernelLogStream(WARNING, "IntelGPU") << "Unknown Intel display controller " << "(device " << base::hex << (uint64_t)dev.DeviceId << ")" << " at PCI " << (uint64_t)dev.Bus << ":" << (uint64_t)dev.Device << "." << (uint64_t)dev.Function << " - inferred gen " << base::dec << (uint64_t)inferredGen << ", attempting generic initialization"; } g_gpuGen = g_gpuInfo.gen; DisableInterruptDelivery(); if (!MapMmio()) { KernelLogStream(ERROR, "IntelGPU") << "Failed to map MMIO region"; return false; } DisableVga(); if (!ReadDisplayState()) { KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state"; return false; } if (!FirmwareFramebufferMatchesGpu()) return false; InitializeDisplayManagement(); if (!InitializeGtt()) { KernelLogStream(ERROR, "IntelGPU") << "Failed to initialize GTT"; return false; } if (!SetupFramebuffer()) { KernelLogStream(ERROR, "IntelGPU") << "Failed to set up framebuffer"; return false; } ProgramDisplayPlane(); g_initialized = true; SetupPageFlip(); KernelLogStream(OK, "IntelGPU") << "Initialization complete: " << base::dec << g_fbWidth << "x" << g_fbHeight << " @ " << base::hex << (uint64_t)g_fbBase; return true; } // ========================================================================= // Initialize (standalone fallback path) // ========================================================================= void Initialize() { KernelLogStream(INFO, "IntelGPU") << "Scanning for Intel integrated graphics..."; // Step 1: Detect GPU on PCI bus if (!DetectGpu()) { return; } DisableInterruptDelivery(); // 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; } if (!FirmwareFramebufferMatchesGpu()) return; InitializeDisplayManagement(); // 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; 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; } // ========================================================================= // Display management public API // ========================================================================= static bool WaitTranscoderState(bool enabled) { for (int i = 0; i < 200000; i++) { bool state = (ReadReg(g_transConfReg) & PIPECONF_STATE) != 0; if (state == enabled) return true; asm volatile("pause"); } return false; } struct TimingSnapshot { uint32_t htotal, hblank, hsync; uint32_t vtotal, vblank, vsync; }; static TimingSnapshot ReadTimingSnapshot() { return { ReadReg(g_transTimingBase + 0x00), ReadReg(g_transTimingBase + 0x04), ReadReg(g_transTimingBase + 0x08), ReadReg(g_transTimingBase + 0x0C), ReadReg(g_transTimingBase + 0x10), ReadReg(g_transTimingBase + 0x14), }; } static void WriteTimingSnapshot(const TimingSnapshot& timing) { WriteReg(g_transTimingBase + 0x00, timing.htotal); WriteReg(g_transTimingBase + 0x04, timing.hblank); WriteReg(g_transTimingBase + 0x08, timing.hsync); WriteReg(g_transTimingBase + 0x0C, timing.vtotal); WriteReg(g_transTimingBase + 0x10, timing.vblank); WriteReg(g_transTimingBase + 0x14, timing.vsync); } static uint32_t PackTiming(uint32_t start, uint32_t end) { return ((end - 1) << 16) | (start - 1); } static bool ApplyModeTiming(const DisplayMode& mode) { if (!g_connectorConnected || mode.hdisplay == 0 || mode.vdisplay == 0 || mode.htotal <= mode.hdisplay || mode.vtotal <= mode.vdisplay || mode.hsyncStart < mode.hdisplay || mode.hsyncEnd <= mode.hsyncStart || mode.hsyncEnd > mode.htotal || mode.vsyncStart < mode.vdisplay || mode.vsyncEnd <= mode.vsyncStart || mode.vsyncEnd > mode.vtotal) return false; TimingSnapshot oldTiming = ReadTimingSnapshot(); uint32_t oldConf = ReadReg(g_transConfReg); uint32_t oldPlane = ReadReg(DSPACNTR); uint64_t oldSurface = (g_flipSupported && g_frontBuffer == 1) ? g_buf1GttOffset : (g_flipSupported ? g_fbGttOffsetA : g_fbGttOffset); // A timing-only modeset retains the firmware-trained DDI, PLL and link. // Disable fetch and the transcoder, program all timing registers as one // transaction, then restore them in the inverse order. if (oldPlane & DISP_ENABLE) { WriteReg(DSPACNTR, oldPlane & ~DISP_ENABLE); WriteReg(DSPASURF, (uint32_t)oldSurface); (void)ReadReg(DSPASURF); } WriteReg(g_transConfReg, oldConf & ~PIPECONF_ENABLE); if (!WaitTranscoderState(false)) { WriteReg(DSPACNTR, oldPlane); WriteReg(DSPASURF, (uint32_t)oldSurface); return false; } TimingSnapshot next = { PackTiming(mode.hdisplay, mode.htotal), PackTiming(mode.hdisplay, mode.htotal), PackTiming(mode.hsyncStart, mode.hsyncEnd), PackTiming(mode.vdisplay, mode.vtotal), PackTiming(mode.vdisplay, mode.vtotal), PackTiming(mode.vsyncStart, mode.vsyncEnd), }; WriteTimingSnapshot(next); WriteReg(g_transConfReg, oldConf | PIPECONF_ENABLE); if (!WaitTranscoderState(true)) { // Roll back while the transcoder is stopped. A failed timing must // never leave the panel black merely because userspace requested it. WriteReg(g_transConfReg, oldConf & ~PIPECONF_ENABLE); (void)WaitTranscoderState(false); WriteTimingSnapshot(oldTiming); WriteReg(g_transConfReg, oldConf); (void)WaitTranscoderState((oldConf & PIPECONF_ENABLE) != 0); WriteReg(DSPACNTR, oldPlane); WriteReg(DSPASURF, (uint32_t)oldSurface); (void)ReadReg(DSPASURF); return false; } WriteReg(DSPACNTR, oldPlane); WriteReg(DSPASURF, (uint32_t)oldSurface); (void)ReadReg(DSPASURF); return true; } bool GetDisplayInfo(montauk::abi::DisplayInfo* out) { if (!out) return false; memset(out, 0, sizeof(*out)); out->brightness = -1; out->currentMode = -1; if (!g_initialized) return false; out->capabilities = montauk::abi::DISPLAY_CAP_MODESET; if (g_edidValid) out->capabilities |= montauk::abi::DISPLAY_CAP_EDID; if (g_flipSupported) out->capabilities |= montauk::abi::DISPLAY_CAP_PAGE_FLIP; for (int i = 0; i < g_modeCount; i++) { if (i != g_currentMode && (g_modes[i].flags & montauk::abi::DISPLAY_MODE_DRIVER_VALID)) { out->capabilities |= montauk::abi::DISPLAY_CAP_SAFE_SWITCH; break; } } out->modeCount = (uint32_t)g_modeCount; out->currentMode = g_currentMode; out->brightness = Brightness(-1); if (out->brightness >= 0) out->capabilities |= montauk::abi::DISPLAY_CAP_BRIGHTNESS; out->deviceId = g_gpuInfo.deviceId; out->generation = g_gpuInfo.gen; out->connectorType = g_connectorType; out->connected = g_connectorConnected ? 1 : 0; CopyText(out->gpuName, sizeof(out->gpuName), g_gpuInfo.name ? g_gpuInfo.name : "Intel GPU"); CopyText(out->connectorName, sizeof(out->connectorName), g_connectorName); CopyText(out->monitorName, sizeof(out->monitorName), g_monitorName); return true; } int GetDisplayModes(montauk::abi::DisplayModeInfo* out, int maxCount) { if (!g_initialized || maxCount < 0) return -1; int count = g_modeCount < maxCount ? g_modeCount : maxCount; if (count > 0 && !out) return -1; for (int i = 0; i < count; i++) { out[i].width = g_modes[i].timing.hdisplay; out[i].height = g_modes[i].timing.vdisplay; out[i].refreshMilliHz = g_modes[i].refreshMilliHz; out[i].pixelClockKHz = g_modes[i].timing.pixelClock; out[i].flags = g_modes[i].flags; out[i]._pad = 0; } return count; } int SetDisplayMode(int modeIndex) { if (!g_initialized || modeIndex < 0 || modeIndex >= g_modeCount) return -1; if (g_currentMode < 0 || g_currentMode >= g_modeCount) return -1; if (!(g_modes[modeIndex].flags & montauk::abi::DISPLAY_MODE_DRIVER_VALID)) return -2; if (modeIndex == g_currentMode) return modeIndex; g_flipLock.Acquire(); // Link rate and framebuffer geometry remain fixed. Only timings with // the same active area and pixel clock are safe without retraining the // firmware-established HDMI/DP/eDP link. const DisplayMode& current = g_modes[g_currentMode].timing; const DisplayMode& requested = g_modes[modeIndex].timing; if (requested.hdisplay != current.hdisplay || requested.vdisplay != current.vdisplay || requested.pixelClock == 0 || requested.pixelClock != current.pixelClock) { g_flipLock.Release(); return -2; } if (!ApplyModeTiming(requested)) { g_flipLock.Release(); KernelLogStream(ERROR, "IntelGPU") << "Modeset transaction failed; previous mode restored"; return -3; } g_modes[g_currentMode].flags &= ~montauk::abi::DISPLAY_MODE_CURRENT; g_currentMode = modeIndex; g_modes[g_currentMode].flags |= montauk::abi::DISPLAY_MODE_CURRENT; g_flipLock.Release(); KernelLogStream(OK, "IntelGPU") << "Mode set to " << base::dec << (uint64_t)requested.hdisplay << "x" << (uint64_t)requested.vdisplay << " @ " << (uint64_t)(g_modes[modeIndex].refreshMilliHz / 1000) << "Hz"; return modeIndex; } int Brightness(int percent) { if (!g_initialized) return -1; if (g_blMethod == BacklightMethod::None && !g_ddcCandidate) return -1; g_blLock.Acquire(); int result = BrightnessLocked(percent); g_blLock.Release(); return result; } uint8_t ActiveConnectorType() { return g_connectorType; } const char* BrightnessMethod() { switch (g_blMethod) { case BacklightMethod::PwmIlk: case BacklightMethod::PwmLpt: case BacklightMethod::PwmCnp: return "PWM"; case BacklightMethod::DpcdAux: return "eDP AUX"; case BacklightMethod::DdcCi: return "DDC/CI"; case BacklightMethod::None: break; } return nullptr; } // ========================================================================= // Page flip public API // ========================================================================= bool FlipSupported() { return g_flipSupported; } uint64_t GetBufferPhys(int index) { if (index == 0) return g_fbPhysBase; if (index == 1 && g_flipSupported) return g_buf1Phys; return 0; } uint64_t GetBufferPageCount(int index) { if (index == 0) return (g_fbSize + 0xFFF) >> 12; if (index == 1 && g_flipSupported) return g_buf1Pages; return 0; } int GetFrontBuffer() { g_flipLock.Acquire(); int live = ReadLiveBufferIndex(); if (live >= 0) g_frontBuffer = live; g_flipLock.Release(); return live; } int AcquireFlip(int pid) { if (!g_flipSupported || pid < 0) return -1; g_flipLock.Acquire(); int oldOwner = g_flipOwnerPid; g_flipOwnerPid = pid; int live = ReadLiveBufferIndex(); if (live < 0) { // Recover from an unrecognised/pending firmware surface by putting // the legacy buffer back synchronously before userspace draws. WriteReg(DSPASURF, (uint32_t)g_fbGttOffsetA); (void)ReadReg(DSPASURF); if (WaitForLiveBuffer(0)) live = 0; } if (live >= 0) g_frontBuffer = live; g_flipLock.Release(); if (oldOwner != pid) { KernelLogStream(DEBUG, "IntelGPU") << "Flip owner " << base::dec << (uint64_t)(oldOwner < 0 ? 0xFFFFFFFFu : (uint32_t)oldOwner) << " -> " << (uint64_t)pid; } KernelLogStream(DEBUG, "IntelGPU") << "Flip acquired by pid " << base::dec << (uint64_t)pid << " (live=" << (uint64_t)(live < 0 ? 0xFFFFFFFFu : (uint32_t)live) << ")"; return live; } int64_t Flip(int index, bool waitVsync, int pid) { if (!g_flipSupported || index < 0 || index > 1 || pid < 0) return -1; g_flipLock.Acquire(); int oldOwner = g_flipOwnerPid; g_flipOwnerPid = pid; // Scanout memory is exposed through WC mappings. Drain userspace's // completed frame before programming the display-plane surface. asm volatile("sfence" ::: "memory"); uint64_t gttOff = (index == 0) ? g_fbGttOffsetA : g_buf1GttOffset; WriteReg(DSPASURF, (uint32_t)gttOff); (void)ReadReg(DSPASURF); if (waitVsync) { // DSPASURFLIVE is authoritative and avoids taking ownership of // the GPU's shared interrupt domain merely to wait for vblank. if (!WaitForLiveBuffer(index)) { int live = ReadLiveBufferIndex(); if (live >= 0) g_frontBuffer = live; uint32_t programmed = ReadReg(DSPASURF); uint32_t liveSurface = ReadReg(DSPASURFLIVE); g_flipLock.Release(); if (oldOwner != pid) { KernelLogStream(DEBUG, "IntelGPU") << "Flip owner " << base::dec << (uint64_t)(oldOwner < 0 ? 0xFFFFFFFFu : (uint32_t)oldOwner) << " -> " << (uint64_t)pid; } KernelLogStream(WARNING, "IntelGPU") << "Flip latch timeout: requested=" << base::dec << (uint64_t)index << " live=" << (uint64_t)(live < 0 ? 0xFFFFFFFFu : (uint32_t)live) << " DSPASURF=" << base::hex << (uint64_t)programmed << " DSPASURFLIVE=" << (uint64_t)liveSurface; return -1; } g_frontBuffer = index; } else { int live = ReadLiveBufferIndex(); if (live >= 0) g_frontBuffer = live; } g_flipLock.Release(); if (oldOwner != pid) { KernelLogStream(DEBUG, "IntelGPU") << "Flip owner " << base::dec << (uint64_t)(oldOwner < 0 ? 0xFFFFFFFFu : (uint32_t)oldOwner) << " -> " << (uint64_t)pid; } return index; } void OnProcessExit(int pid) { if (!g_flipSupported) return; g_flipLock.Acquire(); if (pid != g_flipOwnerPid) { g_flipLock.Release(); return; } g_flipOwnerPid = -1; uint32_t programmedBefore = ReadReg(DSPASURF); uint32_t liveBefore = ReadReg(DSPASURFLIVE); WriteReg(DSPASURF, (uint32_t)g_fbGttOffsetA); (void)ReadReg(DSPASURF); bool liveAtZero = WaitForLiveBuffer(0); bool restored = liveAtZero; int live = ReadLiveBufferIndex(); if (live >= 0) g_frontBuffer = live; uint32_t programmedAfter = ReadReg(DSPASURF); uint32_t liveAfter = ReadReg(DSPASURFLIVE); g_flipLock.Release(); KernelLogStream(restored ? INFO : ERROR, "IntelGPU") << "Flip owner pid " << base::dec << (uint64_t)pid << " exited; restore buffer 0 " << (restored ? "latched" : "timed out") << " (before surf/live=" << base::hex << (uint64_t)programmedBefore << "/" << (uint64_t)liveBefore << ", after=" << (uint64_t)programmedAfter << "/" << (uint64_t)liveAfter << ")"; } void PanicRestoreScanout() { // g_frontBuffer is only a software cache and can be stale precisely // when the GPU or kernel is failing. Always request buffer 0 so the // panic console cannot be left painting an invisible surface. if (!g_flipSupported || !g_mmioBase) return; WriteReg(DSPASURF, (uint32_t)g_fbGttOffsetA); (void)ReadReg(DSPASURF); g_frontBuffer = 0; } void Reinitialize() { if (!g_initialized || !g_mmioBase) return; KernelLogStream(INFO, "IntelGPU") << "Reinitializing display after S3 resume" << " (gen " << base::dec << (uint64_t)g_gpuGen << ")"; // 1. Re-enable PCI memory space and bus mastering. S3 resets the PCI // command register, so MMIO writes to the GPU are silently dropped // until we re-enable these bits. Pci::EnableBusMaster(g_gpuInfo.pciBus, g_gpuInfo.pciDevice, g_gpuInfo.pciFunction); DisableInterruptDelivery(); // Verify PCI memory space is accessible by reading back command register uint16_t pciCmd = Pci::LegacyRead16(g_gpuInfo.pciBus, g_gpuInfo.pciDevice, g_gpuInfo.pciFunction, (uint8_t)Pci::PCI_REG_COMMAND); if (!(pciCmd & Pci::PCI_CMD_MEM_SPACE)) { KernelLogStream(ERROR, "IntelGPU") << "PCI memory space not enabled after restore (cmd=" << base::hex << (uint64_t)pciCmd << ")"; return; } // Sanity-check MMIO access: read a register and verify we don't get 0xFFFFFFFF // (which indicates the device is not responding on the PCI bus) uint32_t testRead = ReadReg(PIPEACONF); if (testRead == 0xFFFFFFFF) { KernelLogStream(ERROR, "IntelGPU") << "MMIO reads return 0xFFFFFFFF - GPU not responding"; return; } KernelLogStream(DEBUG, "IntelGPU") << "PCI memory space verified (cmd=" << base::hex << (uint64_t)pciCmd << ")"; // 2. Disable VGA plane (firmware may have re-enabled it during POST) DisableVga(); // 3. Restore legacy framebuffer mappings. Gen 8+ firmware scanout uses // an aperture mapping whose backing physical pages are not described // by the boot framebuffer address, so preserve the restored entries. uint64_t pageCount = (g_fbSize + 0xFFF) / 0x1000; if (g_gpuGen < 8) { volatile uint32_t* gtt32 = (volatile uint32_t*)g_gttBase; for (uint64_t i = 0; i < pageCount; i++) { gtt32[i] = MakeGttPte32(g_fbPhysBase + i * 0x1000); } // Flush GTT writes by reading back the last entry (void)gtt32[pageCount - 1]; } KernelLogStream(DEBUG, "IntelGPU") << (g_gpuGen >= 8 ? "Firmware GGTT scanout preserved" : "Legacy GTT framebuffer mappings restored"); // 4. Re-enable the display pipe. After S3, the pipe may be off even // though the firmware lit the backlight. Wait for it to become // active before programming the display plane. uint32_t pipeConf = ReadReg(PIPEACONF); if (!(pipeConf & PIPECONF_ENABLE)) { WriteReg(PIPEACONF, pipeConf | PIPECONF_ENABLE); // Wait for pipe to become active (PIPECONF_STATE bit 30) for (int i = 0; i < 100000; i++) { if (ReadReg(PIPEACONF) & PIPECONF_STATE) break; asm volatile("pause"); } if (!(ReadReg(PIPEACONF) & PIPECONF_STATE)) { KernelLogStream(WARNING, "IntelGPU") << "Pipe A did not become active after enable"; } } // 5. Reprogram display plane to point at our GTT-mapped framebuffer ProgramDisplayPlane(); // 6. Restore page-flip state: rewrite buffer 1's GGTT entries and // re-show whichever buffer was front when we suspended. if (g_flipSupported) { uint64_t pages = (g_fbSize + 0xFFF) >> 12; uint64_t baseIdx = g_buf1GttOffset >> 12; for (uint64_t i = 0; i < pages; i++) { WriteGgttPte(baseIdx + i, g_buf1Phys + i * 0x1000); } (void)ReadGgttPte(baseIdx + pages - 1); if (g_frontBuffer != 0) { WriteReg(DSPASURF, (uint32_t)g_buf1GttOffset); (void)ReadReg(DSPASURF); } KernelLogStream(DEBUG, "IntelGPU") << "Page flip state restored (front=" << base::dec << (uint64_t)g_frontBuffer << ")"; } // 7. Firmware POST resets the PWM duty to its own default. Put back // the level the user chose (register writes only; no AUX here). uint32_t pwmMax = 0, pwmDuty = 0; if (g_blPercent > 0 && PwmState(pwmMax, pwmDuty)) PwmWriteDuty(PercentToRaw(g_blPercent, pwmMax)); KernelLogStream(OK, "IntelGPU") << "Display restored after S3 resume"; } };