feat: expand Intel GPU display management
This commit is contained in:
@@ -12,4 +12,4 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#define MONTAUK_BUILD_NUMBER 20
|
||||
#define MONTAUK_BUILD_NUMBER 24
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
// =========================================================================
|
||||
|
||||
@@ -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)
|
||||
// =========================================================================
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user