feat: expand Intel GPU display management

This commit is contained in:
2026-07-30 17:12:08 +01:00
parent 86de3400a9
commit 471903bafb
25 changed files with 1715 additions and 16 deletions
+1 -1
View File
@@ -12,4 +12,4 @@
#pragma once
#define MONTAUK_BUILD_NUMBER 20
#define MONTAUK_BUILD_NUMBER 24
+16
View File
@@ -96,6 +96,22 @@ namespace montauk::abi {
return GPU::Flip((int)index, (flags & 1) != 0);
}
static int64_t Sys_DisplayInfo(DisplayInfo* out) {
return Drivers::Graphics::IntelGPU::GetDisplayInfo(out) ? 0 : -1;
}
static int64_t Sys_DisplayModes(DisplayModeInfo* out, int maxCount) {
return Drivers::Graphics::IntelGPU::GetDisplayModes(out, maxCount);
}
static int64_t Sys_DisplaySetMode(int modeIndex) {
return Drivers::Graphics::IntelGPU::SetDisplayMode(modeIndex);
}
static int64_t Sys_DisplayBrightness(int percent) {
return Drivers::Graphics::IntelGPU::Brightness(percent);
}
static uint64_t Sys_TermSize() {
// If the process is redirected to a GUI terminal, return those dimensions
auto* proc = Sched::GetCurrentProcessPtr();
+14
View File
@@ -150,6 +150,20 @@ namespace montauk::abi {
return (int64_t)Sys_FbMap();
case SYS_FBFLIP:
return Sys_FbFlip(frame->arg1, frame->arg2);
case SYS_DISPLAYINFO:
if (!UserMemory::Writable<DisplayInfo>(frame->arg1)) return -1;
return Sys_DisplayInfo((DisplayInfo*)frame->arg1);
case SYS_DISPLAYMODES:
// The Intel driver publishes at most 32 modes. Bounding this
// here also keeps the userspace range calculation overflow-free.
if ((int64_t)frame->arg2 < 0 || frame->arg2 > 32) return -1;
if (!UserMemory::Range(frame->arg1,
(uint64_t)frame->arg2 * sizeof(DisplayModeInfo), true)) return -1;
return Sys_DisplayModes((DisplayModeInfo*)frame->arg1, (int)frame->arg2);
case SYS_DISPLAYSETMODE:
return Sys_DisplaySetMode((int)frame->arg1);
case SYS_DISPLAYBRIGHTNESS:
return Sys_DisplayBrightness((int)frame->arg1);
case SYS_GETEXECPATH:
if (!UserMemory::Range(frame->arg2 ? frame->arg1 : frame->arg1, frame->arg2, true)) return -1;
return Sys_GetExecPath((char*)frame->arg1, frame->arg2);
+51
View File
@@ -291,6 +291,12 @@ namespace montauk::abi {
static constexpr uint64_t SYS_STAT = 152;
static constexpr uint64_t SYS_SETUNIXTIME = 153;
/* Graphics.hpp -- display/modesetting control */
static constexpr uint64_t SYS_DISPLAYINFO = 154;
static constexpr uint64_t SYS_DISPLAYMODES = 155;
static constexpr uint64_t SYS_DISPLAYSETMODE = 156;
static constexpr uint64_t SYS_DISPLAYBRIGHTNESS = 157;
// Tunable parameters (for SYS_SDR_SETPARAM / SYS_SDR_GETPARAM).
static constexpr int SDR_PARAM_FREQ = 0; // center frequency, Hz
static constexpr int SDR_PARAM_SAMPLE_RATE = 1; // sample rate, Hz
@@ -351,6 +357,51 @@ namespace montauk::abi {
uint64_t userAddr; // filled by SYS_FBMAP (0 until mapped)
};
enum DisplayConnectorType : uint8_t {
DISPLAY_CONNECTOR_UNKNOWN = 0,
DISPLAY_CONNECTOR_EDP = 1,
DISPLAY_CONNECTOR_DP = 2,
DISPLAY_CONNECTOR_HDMI = 3,
DISPLAY_CONNECTOR_DVI = 4,
DISPLAY_CONNECTOR_VGA = 5,
DISPLAY_CONNECTOR_LVDS = 6,
};
static constexpr uint32_t DISPLAY_CAP_MODESET = 1u << 0;
static constexpr uint32_t DISPLAY_CAP_EDID = 1u << 1;
static constexpr uint32_t DISPLAY_CAP_BRIGHTNESS = 1u << 2;
static constexpr uint32_t DISPLAY_CAP_PAGE_FLIP = 1u << 3;
static constexpr uint32_t DISPLAY_CAP_VBLANK_IRQ = 1u << 4;
static constexpr uint32_t DISPLAY_CAP_SAFE_SWITCH = 1u << 5;
static constexpr uint32_t DISPLAY_MODE_PREFERRED = 1u << 0;
static constexpr uint32_t DISPLAY_MODE_CURRENT = 1u << 1;
static constexpr uint32_t DISPLAY_MODE_DRIVER_VALID = 1u << 2;
struct DisplayModeInfo {
uint32_t width;
uint32_t height;
uint32_t refreshMilliHz;
uint32_t pixelClockKHz;
uint32_t flags;
uint32_t _pad;
};
struct DisplayInfo {
uint32_t capabilities;
uint32_t modeCount;
int32_t currentMode;
int32_t brightness; // 0..100, or -1 when unavailable
uint16_t deviceId;
uint8_t generation;
uint8_t connectorType;
uint8_t connected;
uint8_t _pad0[3];
char gpuName[64];
char connectorName[32];
char monitorName[64];
};
struct SysInfo {
char osName[32];
char osVersion[32];
+7 -1
View File
@@ -65,7 +65,7 @@ namespace montauk::boot {
// An adapter stamps the contract revision it produced into
// BootInfo::contractVersion; Boot.cpp refuses to continue on mismatch.
// Bump this whenever the meaning or layout of BootInfo changes.
static constexpr uint32_t ContractVersion = 1;
static constexpr uint32_t ContractVersion = 2;
// ----------------------------------------------------------------
// Optional-capability advertisement.
@@ -132,6 +132,12 @@ namespace montauk::boot {
uint8_t redMaskSize, redMaskShift;
uint8_t greenMaskSize, greenMaskShift;
uint8_t blueMaskSize, blueMaskShift;
// Optional EDID blob supplied alongside the framebuffer. The pointer
// has bootloader-owned lifetime, so framebuffer consumers must copy
// it during early boot if they need it after reclaim.
const uint8_t* edid; // HHDM/direct-mapped virtual, or nullptr
uint64_t edidSize; // bytes; normally 128 or 256
};
// ----------------------------------------------------------------
@@ -225,6 +225,8 @@ namespace montauk::boot {
out.framebuffer.greenMaskShift = fb->green_mask_shift;
out.framebuffer.blueMaskSize = fb->blue_mask_size;
out.framebuffer.blueMaskShift = fb->blue_mask_shift;
out.framebuffer.edid = reinterpret_cast<const uint8_t*>(fb->edid);
out.framebuffer.edidSize = fb->edid_size;
out.features |= FeatureFramebuffer;
}
+595
View File
@@ -61,6 +61,29 @@ namespace Drivers::Graphics::IntelGPU {
static volatile uint64_t g_vblankCount = 0;
static bool g_vblankIrqReady = false;
// 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 uint8_t g_connectorType = montauk::abi::DISPLAY_CONNECTOR_UNKNOWN;
static bool g_connectorConnected = false;
static bool g_edidValid = false;
static bool g_pwmSupported = false;
static uint32_t g_pwmCtlReg = 0;
static char g_connectorName[32] = "Unknown";
static char g_monitorName[64] = "Unknown display";
// =========================================================================
// Register access helpers
// =========================================================================
@@ -73,6 +96,33 @@ namespace Drivers::Graphics::IntelGPU {
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
// =========================================================================
@@ -317,6 +367,350 @@ namespace Drivers::Graphics::IntelGPU {
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 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) return;
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);
if (g_gpuGen <= 7) {
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");
} else {
uint32_t mode = (ddi & TRANS_DDI_MODE_MASK) >> 24;
uint32_t port = (ddi & TRANS_DDI_PORT_MASK) >> 27;
const char* suffix = port == 0 ? "A" : port == 1 ? "B"
: port == 2 ? "C" : port == 3 ? "D"
: port == 8 ? "TC1" : port == 9 ? "TC2"
: port == 10 ? "TC3" : port == 11 ? "TC4"
: "unknown";
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-");
}
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;
}
}
}
static void DiscoverPwm() {
g_pwmSupported = false;
if (g_connectorType != montauk::abi::DISPLAY_CONNECTOR_EDP
&& g_connectorType != montauk::abi::DISPLAY_CONNECTOR_LVDS)
return;
uint32_t pch = ReadReg(BLC_PWM_PCH_CTL2);
if ((pch >> 16) != 0) {
g_pwmCtlReg = BLC_PWM_PCH_CTL2;
g_pwmSupported = true;
} else {
uint32_t cpu = ReadReg(BLC_PWM_CPU_CTL);
if ((cpu >> 16) != 0) {
g_pwmCtlReg = BLC_PWM_CPU_CTL;
g_pwmSupported = true;
}
}
KernelLogStream(INFO, "IntelGPU") << "Panel PWM "
<< (g_pwmSupported ? "available" : "not exposed");
}
static void InitializeDisplayManagement() {
g_modeCount = 0;
g_currentMode = -1;
g_edidValid = false;
CopyText(g_monitorName, sizeof(g_monitorName), "Unknown display");
DiscoverActiveTranscoder();
ParseEdid();
SelectCurrentMode();
DiscoverPwm();
}
// =========================================================================
// GTT Initialization
// =========================================================================
@@ -850,6 +1244,7 @@ namespace Drivers::Graphics::IntelGPU {
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
return false;
}
InitializeDisplayManagement();
if (!InitializeGtt()) {
KernelLogStream(ERROR, "IntelGPU") << "Failed to initialize GTT";
@@ -910,6 +1305,7 @@ namespace Drivers::Graphics::IntelGPU {
KernelLogStream(ERROR, "IntelGPU") << "Failed to read display state";
return;
}
InitializeDisplayManagement();
// Step 5: Initialize GTT
if (!InitializeGtt()) {
@@ -976,6 +1372,205 @@ namespace Drivers::Graphics::IntelGPU {
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_pwmSupported) out->capabilities |= montauk::abi::DISPLAY_CAP_BRIGHTNESS;
if (g_flipSupported) out->capabilities |= montauk::abi::DISPLAY_CAP_PAGE_FLIP;
if (g_vblankIrqReady) out->capabilities |= montauk::abi::DISPLAY_CAP_VBLANK_IRQ;
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);
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;
// 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)
return -2;
if (!ApplyModeTiming(requested)) {
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;
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 || !g_pwmSupported) return -1;
uint32_t value = ReadReg(g_pwmCtlReg);
uint32_t maximum = value >> 16;
if (maximum == 0) return -1;
if (percent < 0) {
uint32_t duty = value & 0xFFFF;
if (duty > maximum) duty = maximum;
return (int)(((uint64_t)duty * 100 + maximum / 2) / maximum);
}
if (percent > 100) percent = 100;
uint32_t duty = (uint32_t)(((uint64_t)maximum * (uint32_t)percent + 50) / 100);
WriteReg(g_pwmCtlReg, (value & 0xFFFF0000u) | duty);
(void)ReadReg(g_pwmCtlReg);
return Brightness(-1);
}
// =========================================================================
// Page flip public API
// =========================================================================
+37
View File
@@ -8,6 +8,7 @@
#pragma once
#include <cstdint>
#include <Pci/Pci.hpp>
#include <Api/Syscall.hpp>
namespace Drivers::Graphics::IntelGPU {
@@ -265,6 +266,32 @@ namespace Drivers::Graphics::IntelGPU {
static constexpr uint32_t HDMI_B = 0x61140; // HDMI-B (same as DVOB on some gens)
static constexpr uint32_t HDMI_C = 0x61160; // HDMI-C (same as DVOC on some gens)
// Modern transcoder instances. Pipe source/plane registers remain
// pipe-relative, while link timings live in the selected transcoder.
static constexpr uint32_t TRANS_TIMING_A = 0x60000;
static constexpr uint32_t TRANS_TIMING_B = 0x61000;
static constexpr uint32_t TRANS_TIMING_C = 0x62000;
static constexpr uint32_t TRANS_TIMING_EDP = 0x6F000;
static constexpr uint32_t TRANS_CONF_A = 0x70008;
static constexpr uint32_t TRANS_CONF_B = 0x71008;
static constexpr uint32_t TRANS_CONF_C = 0x72008;
static constexpr uint32_t TRANS_CONF_EDP = 0x7F008;
static constexpr uint32_t TRANS_DDI_A = 0x60400;
static constexpr uint32_t TRANS_DDI_B = 0x61400;
static constexpr uint32_t TRANS_DDI_C = 0x62400;
static constexpr uint32_t TRANS_DDI_EDP = 0x6F400;
static constexpr uint32_t TRANS_DDI_ENABLE = (1u << 31);
static constexpr uint32_t TRANS_DDI_PORT_MASK = (0xFu << 27);
static constexpr uint32_t TRANS_DDI_MODE_MASK = (0x7u << 24);
// PCH/CPU panel PWM. Firmware owns the frequency; the driver only changes
// 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_CTL = 0x48254;
static constexpr uint32_t BLC_PWM_PCH_CTL1 = 0xC8250;
static constexpr uint32_t BLC_PWM_PCH_CTL2 = 0xC8254;
static constexpr uint32_t BLC_PWM_ENABLE = (1u << 31);
// ADPA bits
static constexpr uint32_t ADPA_DAC_ENABLE = (1u << 31);
static constexpr uint32_t ADPA_PIPE_B_SELECT = (1u << 30);
@@ -462,6 +489,16 @@ namespace Drivers::Graphics::IntelGPU {
uint64_t GetHeight();
uint64_t GetPitch();
// Display connector, EDID mode catalogue, and modesetting controls.
bool GetDisplayInfo(montauk::abi::DisplayInfo* out);
int GetDisplayModes(montauk::abi::DisplayModeInfo* out, int maxCount);
int SetDisplayMode(int modeIndex);
// percent < 0 queries the current brightness. Otherwise sets 0..100 and
// returns the resulting percentage. Returns -1 when no PWM backlight is
// exposed by the active output.
int Brightness(int percent);
// =========================================================================
// Page flipping (double-buffered scanout)
// =========================================================================
+12
View File
@@ -17,12 +17,20 @@ namespace Graphics::Framebuffer {
static uint64_t g_FbWidth = 0;
static uint64_t g_FbHeight = 0;
static uint64_t g_FbPitch = 0; // in bytes
static uint8_t g_Edid[256] = {};
static uint64_t g_EdidSize = 0;
void Initialize(const montauk::boot::Framebuffer& framebuffer) {
g_FbBase = reinterpret_cast<uint32_t*>(framebuffer.address);
g_FbWidth = framebuffer.width;
g_FbHeight = framebuffer.height;
g_FbPitch = framebuffer.pitch;
g_EdidSize = framebuffer.edidSize;
if (g_EdidSize > sizeof(g_Edid)) g_EdidSize = sizeof(g_Edid);
if (framebuffer.edid == nullptr) g_EdidSize = 0;
for (uint64_t i = 0; i < g_EdidSize; i++) {
g_Edid[i] = framebuffer.edid[i];
}
Kt::KernelLogStream(Kt::OK, "Graphics") << "Framebuffer initialized ("
<< (uint64_t)g_FbWidth << "x" << (uint64_t)g_FbHeight << ")";
@@ -32,6 +40,10 @@ namespace Graphics::Framebuffer {
uint64_t GetWidth() { return g_FbWidth; }
uint64_t GetHeight() { return g_FbHeight; }
uint64_t GetPitch() { return g_FbPitch; }
const uint8_t* GetEdid(uint64_t* size) {
if (size) *size = g_EdidSize;
return g_EdidSize ? g_Edid : nullptr;
}
void Set(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch) {
g_FbBase = base;
+1
View File
@@ -16,6 +16,7 @@ namespace Graphics::Framebuffer {
uint64_t GetWidth();
uint64_t GetHeight();
uint64_t GetPitch();
const uint8_t* GetEdid(uint64_t* size);
void Set(uint32_t* base, uint64_t width, uint64_t height, uint64_t pitch);
uint64_t GetPhysBase();