fix: correct Intel GGTT setup and DP AUX EDID

This commit is contained in:
2026-07-30 19:28:09 +01:00
parent a965e67c93
commit 4627ac92fd
3 changed files with 288 additions and 121 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once #pragma once
#define MONTAUK_BUILD_NUMBER 27 #define MONTAUK_BUILD_NUMBER 30
+270 -118
View File
@@ -76,6 +76,7 @@ namespace Drivers::Graphics::IntelGPU {
static uint32_t g_transTimingBase = TRANS_TIMING_A; static uint32_t g_transTimingBase = TRANS_TIMING_A;
static uint32_t g_transConfReg = TRANS_CONF_A; static uint32_t g_transConfReg = TRANS_CONF_A;
static uint32_t g_transDdiReg = TRANS_DDI_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 uint8_t g_connectorType = montauk::abi::DISPLAY_CONNECTOR_UNKNOWN;
static bool g_connectorConnected = false; static bool g_connectorConnected = false;
static bool g_edidValid = false; static bool g_edidValid = false;
@@ -83,6 +84,12 @@ namespace Drivers::Graphics::IntelGPU {
static uint32_t g_pwmCtlReg = 0; static uint32_t g_pwmCtlReg = 0;
static char g_connectorName[32] = "Unknown"; static char g_connectorName[32] = "Unknown";
static char g_monitorName[64] = "Unknown display"; 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 // Register access helpers
@@ -426,6 +433,192 @@ namespace Drivers::Graphics::IntelGPU {
return sum == 0; 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) {
for (int retry = 0; retry < 16; retry++) {
uint8_t reply = 0xFF;
int result = AuxTransfer(request, 0x50, 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_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) { static bool ParseDetailedTiming(const uint8_t* dtd, DisplayMode& mode) {
uint32_t clock10KHz = (uint32_t)dtd[0] | ((uint32_t)dtd[1] << 8); uint32_t clock10KHz = (uint32_t)dtd[0] | ((uint32_t)dtd[1] << 8);
if (clock10KHz == 0) return false; if (clock10KHz == 0) return false;
@@ -460,7 +653,17 @@ namespace Drivers::Graphics::IntelGPU {
static void ParseEdid() { static void ParseEdid() {
uint64_t edidSize = 0; uint64_t edidSize = 0;
const uint8_t* edid = ::Graphics::Framebuffer::GetEdid(&edidSize); const uint8_t* edid = ::Graphics::Framebuffer::GetEdid(&edidSize);
if (!edid || edidSize < 128) return; 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, static constexpr uint8_t header[8] = {0x00, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0x00}; 0xFF, 0xFF, 0xFF, 0x00};
if (memcmp(edid, header, sizeof(header)) != 0 || !EdidBlockValid(edid)) { if (memcmp(edid, header, sizeof(header)) != 0 || !EdidBlockValid(edid)) {
@@ -560,6 +763,7 @@ namespace Drivers::Graphics::IntelGPU {
g_transDdiReg = trans.ddi; g_transDdiReg = trans.ddi;
g_connectorConnected = (ReadReg(trans.conf) & PIPECONF_ENABLE) != 0; g_connectorConnected = (ReadReg(trans.conf) & PIPECONF_ENABLE) != 0;
uint32_t ddi = ReadReg(trans.ddi); uint32_t ddi = ReadReg(trans.ddi);
g_auxControlReg = 0;
if (g_gpuGen <= 7) { if (g_gpuGen <= 7) {
if (ReadReg(LVDS) & LVDS_PORT_ENABLE) { if (ReadReg(LVDS) & LVDS_PORT_ENABLE) {
@@ -595,6 +799,11 @@ namespace Drivers::Graphics::IntelGPU {
} else { } else {
g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP; g_connectorType = montauk::abi::DISPLAY_CONNECTOR_DP;
CopyText(g_connectorName, sizeof(g_connectorName), "DP-"); CopyText(g_connectorName, sizeof(g_connectorName), "DP-");
static constexpr uint32_t auxControls[] = {
DP_AUX_A_CTL, DP_AUX_B_CTL, DP_AUX_C_CTL, DP_AUX_D_CTL,
};
if (port < sizeof(auxControls) / sizeof(auxControls[0]))
g_auxControlReg = auxControls[port];
} }
uint64_t len = 0; uint64_t len = 0;
while (len + 1 < sizeof(g_connectorName) && g_connectorName[len]) len++; while (len + 1 < sizeof(g_connectorName) && g_connectorName[len]) len++;
@@ -720,22 +929,17 @@ namespace Drivers::Graphics::IntelGPU {
uint8_t dev = g_gpuInfo.pciDevice; uint8_t dev = g_gpuInfo.pciDevice;
uint8_t func = g_gpuInfo.pciFunction; uint8_t func = g_gpuInfo.pciFunction;
// Read GMCH_CTL to determine GTT size // Read GGC/GMCH_CTL to determine the GGTT modification window.
uint16_t gmchCtl = Pci::LegacyRead16(bus, dev, func, (uint8_t)PCI_REG_GMCH_CTL); uint16_t gmchCtl = Pci::LegacyRead16(bus, dev, func, (uint8_t)PCI_REG_GMCH_CTL);
uint8_t gttSizeBits = (gmchCtl >> 8) & 0x3;
uint64_t gttSizeBytes = 0; uint64_t gttSizeBytes = 0;
uint64_t gttOffset = 0;
if (g_gpuGen >= 8) { if (g_gpuGen >= 8) {
// Gen 8+ has a different encoding for GTT size uint8_t ggms = (gmchCtl >> 6) & 0x3;
switch (gttSizeBits) { if (ggms != 0) gttSizeBytes = (1ull << ggms) * 1024 * 1024;
case 0: gttSizeBytes = 0; break; // No GTT gttOffset = 8 * 1024 * 1024;
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 { } else {
uint8_t gttSizeBits = (gmchCtl >> 8) & 0x3;
// Gen 5-7 // Gen 5-7
switch (gttSizeBits) { switch (gttSizeBits) {
case 0: gttSizeBytes = 0; break; // No GTT case 0: gttSizeBytes = 0; break; // No GTT
@@ -744,12 +948,13 @@ namespace Drivers::Graphics::IntelGPU {
case 3: gttSizeBytes = 2 * 1024 * 1024; break; // Depends on gen, default 2MB case 3: gttSizeBytes = 2 * 1024 * 1024; break; // Depends on gen, default 2MB
default: gttSizeBytes = 1024 * 1024; break; default: gttSizeBytes = 1024 * 1024; break;
} }
gttOffset = 2 * 1024 * 1024;
} }
if (gttSizeBytes == 0) { if (gttSizeBytes == 0) {
// If hardware reports no GTT, assume 1MB as a safe fallback KernelLogStream(ERROR, "IntelGPU") << "GGC reports no GGTT (GGC="
gttSizeBytes = 1024 * 1024; << base::hex << (uint64_t)gmchCtl << ")";
KernelLogStream(WARNING, "IntelGPU") << "GMCH_CTL reports no GTT, assuming 1MB"; return false;
} }
g_gpuInfo.gttSize = gttSizeBytes; g_gpuInfo.gttSize = gttSizeBytes;
@@ -757,9 +962,7 @@ namespace Drivers::Graphics::IntelGPU {
KernelLogStream(INFO, "IntelGPU") << "GMCH_CTL: " << base::hex << (uint64_t)gmchCtl KernelLogStream(INFO, "IntelGPU") << "GMCH_CTL: " << base::hex << (uint64_t)gmchCtl
<< ", GTT size: " << base::dec << (gttSizeBytes / 1024) << " KB"; << ", GTT size: " << base::dec << (gttSizeBytes / 1024) << " KB";
// The GTT entries reside at BAR0 + 2MB (offset 0x200000) uint64_t gttPhys = g_gpuInfo.mmioPhys + gttOffset;
// 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 // Map the GTT region (it may overlap with already-mapped MMIO, but we map
// additional pages beyond the initial 2MB MMIO mapping) // additional pages beyond the initial 2MB MMIO mapping)
@@ -770,6 +973,7 @@ namespace Drivers::Graphics::IntelGPU {
} }
g_gttBase = (volatile void*)Memory::HHDM(gttPhys); g_gttBase = (volatile void*)Memory::HHDM(gttPhys);
g_ggtt = g_gttBase;
// Calculate number of GTT entries // Calculate number of GTT entries
if (g_gpuGen >= 8) { if (g_gpuGen >= 8) {
@@ -779,6 +983,7 @@ namespace Drivers::Graphics::IntelGPU {
// 32-bit PTEs // 32-bit PTEs
g_gttEntryCount = gttSizeBytes / sizeof(uint32_t); g_gttEntryCount = gttSizeBytes / sizeof(uint32_t);
} }
g_ggttEntries = g_gttEntryCount;
KernelLogStream(INFO, "IntelGPU") << "GTT at physical " << base::hex << gttPhys KernelLogStream(INFO, "IntelGPU") << "GTT at physical " << base::hex << gttPhys
<< ", " << base::dec << g_gttEntryCount << " entries" << ", " << base::dec << g_gttEntryCount << " entries"
@@ -804,10 +1009,10 @@ namespace Drivers::Graphics::IntelGPU {
// ========================================================================= // =========================================================================
static bool SetupFramebuffer() { static bool SetupFramebuffer() {
// Map the firmware framebuffer's contiguous physical pages through our GTT. // The boot framebuffer can be the CPU-visible graphics aperture rather
// This keeps the same physical memory that the firmware set up (contiguous // than the scanout memory itself. On Gen 8+ retain the firmware's GGTT
// pages, already HHDM-mapped), so both kernel and userspace access continue // mapping and its surface offset; remapping the aperture address as a
// to work via the original virtual/physical addresses. No copy is needed. // backing page would create an invalid translation.
uint32_t* fwFb = ::Graphics::Framebuffer::GetBase(); uint32_t* fwFb = ::Graphics::Framebuffer::GetBase();
if (fwFb == nullptr) { if (fwFb == nullptr) {
KernelLogStream(ERROR, "IntelGPU") << "No firmware framebuffer available"; KernelLogStream(ERROR, "IntelGPU") << "No firmware framebuffer available";
@@ -823,33 +1028,36 @@ namespace Drivers::Graphics::IntelGPU {
return false; 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) { if (g_gpuGen >= 8) {
volatile uint64_t* gtt64 = (volatile uint64_t*)g_gttBase; g_fbGttOffset = ReadReg(DSPASURF) & ~0xFFFull;
for (uint64_t i = 0; i < pageCount; i++) { uint64_t first = g_fbGttOffset >> 12;
gtt64[i] = MakeGttPte64(fwFbPhys + i * 0x1000); 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;
} }
// Flush GTT writes KernelLogStream(OK, "IntelGPU") << "Preserving firmware GGTT scanout at +"
(void)gtt64[pageCount - 1]; << base::hex << g_fbGttOffset << " (CPU aperture " << fwFbPhys << ")";
} else { } 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; volatile uint32_t* gtt32 = (volatile uint32_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) { for (uint64_t i = 0; i < pageCount; i++) {
gtt32[i] = MakeGttPte32(fwFbPhys + i * 0x1000); gtt32[i] = MakeGttPte32(fwFbPhys + i * 0x1000);
} }
// Flush GTT writes // Flush GTT writes
(void)gtt32[pageCount - 1]; (void)gtt32[pageCount - 1];
g_fbGttOffset = 0;
} }
// Keep using the same framebuffer memory // Keep using the same framebuffer memory
g_fbBase = fwFb; g_fbBase = fwFb;
g_fbPhysBase = fwFbPhys; g_fbPhysBase = fwFbPhys;
g_fbGttOffset = 0; // Starting at GTT entry 0 => offset 0 KernelLogStream(OK, "IntelGPU") << "Framebuffer ready: " << base::dec << pageCount
<< " pages, CPU phys=" << base::hex << fwFbPhys;
KernelLogStream(OK, "IntelGPU") << "Framebuffer mapped through GTT: " << base::dec << pageCount
<< " pages, phys=" << base::hex << fwFbPhys;
return true; return true;
} }
@@ -877,8 +1085,7 @@ namespace Drivers::Graphics::IntelGPU {
// in the hardware's native format (bytes on Gen <9, 64-byte units on Gen 9+). // 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+. // Writing our byte-converted g_fbPitch would corrupt it on Gen 9+.
// Write the GTT base offset to DSPASURF - this triggers the plane update // Reassert the firmware surface offset to flush any plane update.
// Since we mapped at GTT entry 0, the offset is 0
uint32_t surfAddr = (uint32_t)g_fbGttOffset; uint32_t surfAddr = (uint32_t)g_fbGttOffset;
WriteReg(DSPASURF, surfAddr); WriteReg(DSPASURF, surfAddr);
@@ -898,14 +1105,8 @@ namespace Drivers::Graphics::IntelGPU {
// The GTT window lives in the upper half of GTTMMADR (BAR0): // 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 6-7: 4 MB BAR, GTT at +2 MB, 32-bit entries
// Gen 8+: 16 MB BAR, GGTT at +8 MB, 64-bit entries // Gen 8+: 16 MB BAR, GGTT at +8 MB, 64-bit entries
// The legacy init path above writes at +2 MB for all generations; on // Initialization maps this window once and preserves the firmware's active
// Gen 8+ those writes land in register space, and scanout keeps working // scanout PTEs. Page flipping only proceeds after those entries validate.
// only because the firmware's real GGTT entries (stored in stolen RAM,
// which survives S3 in self-refresh) are never actually touched. Page
// flipping needs entries the display engine really reads, so it uses the
// correct window and refuses to run unless the firmware's own scanout
// PTEs are visible there (valid bits set). On any failure page flipping
// stays off and the driver behaves exactly as before.
static void MicroDelay(int us) { static void MicroDelay(int us) {
// Simple busy-wait; us is approximate // Simple busy-wait; us is approximate
@@ -928,35 +1129,9 @@ namespace Drivers::Graphics::IntelGPU {
} }
static bool MapCorrectGgtt() { static bool MapCorrectGgtt() {
if (g_gpuGen < 8) { if (!g_gttBase || g_gttEntryCount == 0) return false;
// The legacy mapping at BAR0 + 2 MB is already the real GTT
g_ggtt = g_gttBase; g_ggtt = g_gttBase;
g_ggttEntries = g_gttEntryCount; g_ggttEntries = g_gttEntryCount;
return g_ggtt != nullptr;
}
// Gen 8+: GGC.GGMS moved to bits 7:6 (0 = none, 1/2/3 = 2/4/8 MB)
uint16_t gmchCtl = Pci::LegacyRead16(g_gpuInfo.pciBus, g_gpuInfo.pciDevice,
g_gpuInfo.pciFunction, (uint8_t)PCI_REG_GMCH_CTL);
uint8_t ggms = (gmchCtl >> 6) & 0x3;
if (ggms == 0) {
KernelLogStream(WARNING, "IntelGPU") << "GGC reports no GGTT (GGC="
<< base::hex << (uint64_t)gmchCtl << "), page flip unavailable";
return false;
}
uint64_t ggttBytes = (1ull << ggms) * 1024 * 1024;
uint64_t ggttPhys = g_gpuInfo.mmioPhys + 8 * 1024 * 1024;
for (uint64_t off = 0; off < ggttBytes; off += 0x1000) {
Memory::VMM::g_paging->MapMMIO(ggttPhys + off, Memory::HHDM(ggttPhys + off));
}
g_ggtt = (volatile void*)Memory::HHDM(ggttPhys);
g_ggttEntries = ggttBytes / sizeof(uint64_t);
KernelLogStream(INFO, "IntelGPU") << "GGTT window at BAR0+8MB (phys "
<< base::hex << ggttPhys << "), " << base::dec << (ggttBytes / 1024)
<< " KB, " << g_ggttEntries << " entries";
return true; return true;
} }
@@ -1017,45 +1192,25 @@ namespace Drivers::Graphics::IntelGPU {
static bool FindAndMapGgttRange() { static bool FindAndMapGgttRange() {
uint64_t pages = (g_fbSize + 0xFFF) >> 12; uint64_t pages = (g_fbSize + 0xFFF) >> 12;
constexpr uint64_t guard = 16; constexpr uint64_t guard = 16;
uint64_t need = pages + 2 * guard;
// Firmware does not leave unused GGTT entries invalid: it points the
// ENTIRE table at a scratch page, so free entries are valid but all
// hold one identical PTE value (HW-confirmed on Raptor Lake, where a
// scan for invalid entries found none in 1M entries). Sample the tail
// of the table to learn the scratch value; a non-uniform tail means
// the layout is not what we expect, so bail.
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 not uniform (entry -"
<< base::dec << probe << " != " << base::hex << scratchPte
<< "), page flip unavailable";
return false;
}
}
// Search the upper half for a run of scratch-backed (or invalid)
// entries. Never overlap the active scanout range.
uint64_t avoidFirst = g_fbGttOffsetA >> 12; uint64_t avoidFirst = g_fbGttOffsetA >> 12;
uint64_t avoidLast = (g_fbGttOffsetA + g_fbSize - 1) >> 12; uint64_t avoidLast = (g_fbGttOffsetA + g_fbSize - 1) >> 12;
uint64_t runStart = 0, run = 0; // Montauk owns the GPU after firmware handoff. Reserve a deterministic
bool found = false; // high-aperture range for its second scanout buffer instead of trying
for (uint64_t idx = g_ggttEntries / 2; idx < g_ggttEntries; idx++) { // to infer firmware ownership from scratch-PTE patterns. Keep guards
if (idx >= avoidFirst && idx <= avoidLast) { run = 0; continue; } // around the allocation and never overlap the live firmware surface.
uint64_t pte = ReadGgttPte(idx); uint64_t base = g_ggttEntries / 2 + guard;
if ((pte & 1) && pte != scratchPte) { run = 0; continue; } if (base <= avoidLast + guard) base = avoidLast + guard + 1;
if (run == 0) runStart = idx; base = (base + guard - 1) & ~(guard - 1);
if (++run >= need) { found = true; break; } if (base < guard || base + pages + guard > g_ggttEntries
} || !(base + pages - 1 < avoidFirst || base > avoidLast)) {
if (!found) { KernelLogStream(WARNING, "IntelGPU") << "No safe owned GGTT range for "
KernelLogStream(WARNING, "IntelGPU") << "No free GGTT run of " << base::dec << base::dec << pages << " back-buffer pages";
<< need << " entries in upper half, page flip unavailable";
return false; 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++) { for (uint64_t i = 0; i < pages; i++) {
WriteGgttPte(base + i, g_buf1Phys + i * 0x1000); WriteGgttPte(base + i, g_buf1Phys + i * 0x1000);
} }
@@ -1073,9 +1228,10 @@ namespace Drivers::Graphics::IntelGPU {
} }
g_buf1GttOffset = base << 12; g_buf1GttOffset = base << 12;
KernelLogStream(OK, "IntelGPU") << "Buffer 1 mapped at GGTT+" << base::hex KernelLogStream(OK, "IntelGPU") << "Claimed buffer 1 at GGTT+" << base::hex
<< g_buf1GttOffset << " (" << base::dec << pages << " pages, phys " << g_buf1GttOffset << " (" << base::dec << pages << " pages, phys "
<< base::hex << g_buf1Phys << ")"; << base::hex << g_buf1Phys << ", replaced PTEs "
<< replacedFirst << ".." << replacedLast << ")";
return true; return true;
} }
@@ -1679,16 +1835,11 @@ namespace Drivers::Graphics::IntelGPU {
// 2. Disable VGA plane (firmware may have re-enabled it during POST) // 2. Disable VGA plane (firmware may have re-enabled it during POST)
DisableVga(); DisableVga();
// 3. Reprogram GTT entries (hardware lost all GTT state during S3) // 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; uint64_t pageCount = (g_fbSize + 0xFFF) / 0x1000;
if (g_gpuGen >= 8) { if (g_gpuGen < 8) {
volatile uint64_t* gtt64 = (volatile uint64_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) {
gtt64[i] = MakeGttPte64(g_fbPhysBase + i * 0x1000);
}
// Flush GTT writes by reading back the last entry
(void)gtt64[pageCount - 1];
} else {
volatile uint32_t* gtt32 = (volatile uint32_t*)g_gttBase; volatile uint32_t* gtt32 = (volatile uint32_t*)g_gttBase;
for (uint64_t i = 0; i < pageCount; i++) { for (uint64_t i = 0; i < pageCount; i++) {
gtt32[i] = MakeGttPte32(g_fbPhysBase + i * 0x1000); gtt32[i] = MakeGttPte32(g_fbPhysBase + i * 0x1000);
@@ -1697,8 +1848,9 @@ namespace Drivers::Graphics::IntelGPU {
(void)gtt32[pageCount - 1]; (void)gtt32[pageCount - 1];
} }
KernelLogStream(DEBUG, "IntelGPU") << "GTT reprogrammed: " << base::dec << pageCount KernelLogStream(DEBUG, "IntelGPU") << (g_gpuGen >= 8
<< " pages (" << (g_gpuGen >= 8 ? "64-bit" : "32-bit") << " PTEs)"; ? "Firmware GGTT scanout preserved"
: "Legacy GTT framebuffer mappings restored");
// 4. Re-enable the display pipe. After S3, the pipe may be off even // 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 // though the firmware lit the backlight. Wait for it to become
+15
View File
@@ -284,6 +284,21 @@ namespace Drivers::Graphics::IntelGPU {
static constexpr uint32_t TRANS_DDI_PORT_MASK = (0xFu << 27); static constexpr uint32_t TRANS_DDI_PORT_MASK = (0xFu << 27);
static constexpr uint32_t TRANS_DDI_MODE_MASK = (0x7u << 24); static constexpr uint32_t TRANS_DDI_MODE_MASK = (0x7u << 24);
// DisplayPort AUX channels. Each port has one control register followed
// by five big-endian data registers.
static constexpr uint32_t DP_AUX_A_CTL = 0x64010;
static constexpr uint32_t DP_AUX_B_CTL = 0x64110;
static constexpr uint32_t DP_AUX_C_CTL = 0x64210;
static constexpr uint32_t DP_AUX_D_CTL = 0x64310;
static constexpr uint32_t DP_AUX_DATA_DELTA = 4;
static constexpr uint32_t DP_AUX_SEND_BUSY = (1u << 31);
static constexpr uint32_t DP_AUX_DONE = (1u << 30);
static constexpr uint32_t DP_AUX_INTERRUPT = (1u << 29);
static constexpr uint32_t DP_AUX_TIMEOUT_ERROR = (1u << 28);
static constexpr uint32_t DP_AUX_TIMEOUT_MAX = (3u << 26);
static constexpr uint32_t DP_AUX_RECEIVE_ERROR = (1u << 25);
static constexpr uint32_t DP_AUX_MESSAGE_SHIFT = 20;
// PCH/CPU panel PWM. Firmware owns the frequency; the driver only changes // PCH/CPU panel PWM. Firmware owns the frequency; the driver only changes
// the duty field and preserves enable/polarity/reserved bits. // the duty field and preserves enable/polarity/reserved bits.
static constexpr uint32_t BLC_PWM_CPU_CTL2 = 0x48250; static constexpr uint32_t BLC_PWM_CPU_CTL2 = 0x48250;