display: pace the frame clock by the panel's EDID refresh rate
Both EDID moments the system has are now captured and carried to the compositor's frame clock: - EFI: the loader derives refresh from the preferred detailed timing (pixel clock / total pixels) while GOP is still alive — the only moment it is readable — and hands it through the boot handoff into the display0 node's DisplayInfo (new refresh_hz field, 0 = unknown). - GPU: the virtio-gpu driver derives the same figure from its own EDID read and carries it in the attach_scanout announce (request.y). updateFrameClock() re-derives the interval from the active backend's info at bring-up and again on every backend change — the boot framebuffer's clock dies with the GOP floor at upgrade, replaced by the GPU's rate. Unknown rate defaults to 60 Hz; the result is clamped to [30, 120] Hz so a mis-parsed EDID can neither starve nor flood the compositor. Rate only, never phase: without vblank, presents still free-run (docs/display-v2.md, 'Fenced is not vsync'). Observed in QEMU: OVMF exposes no EDID for the VGA adapter, so the GOP floor logs 'frame clock 62 Hz (default)' (real firmware does expose it); the virtio-gpu EDID advertises 75 Hz and the upgrade logs 'frame clock 76 Hz (panel EDID)'. The display kernel test asserts refresh_hz rides the seeded node; all 7 display QEMU cases pass.
This commit is contained in:
@@ -61,7 +61,7 @@ pub fn init(device_tree: *const platform.DeviceTree) void {
|
||||
/// [[boot-handoff]], not the device tree), so it is seeded explicitly, after `init`.
|
||||
/// Returns the new device id, or null when there is no framebuffer (headless) or the
|
||||
/// table is full. Idempotent-ish: only ever call once per boot.
|
||||
pub fn seedDisplay(base: u64, width: u32, height: u32, pitch: u32, format: u32) ?u64 {
|
||||
pub fn seedDisplay(base: u64, width: u32, height: u32, pitch: u32, format: u32, refresh_hz: u32) ?u64 {
|
||||
if (base == 0 or width == 0 or height == 0) return null; // headless
|
||||
if (count >= maximum_devices) {
|
||||
dropped += 1;
|
||||
@@ -79,7 +79,7 @@ pub fn seedDisplay(base: u64, width: u32, height: u32, pitch: u32, format: u32)
|
||||
.len = @as(u64, height) * pitch,
|
||||
.flags = device_abi.resource_flag_write_combining,
|
||||
};
|
||||
d.display = .{ .width = width, .height = height, .pitch = pitch, .format = format };
|
||||
d.display = .{ .width = width, .height = height, .pitch = pitch, .format = format, .refresh_hz = refresh_hz };
|
||||
devices[count] = d;
|
||||
display_device = d.id;
|
||||
count += 1;
|
||||
|
||||
@@ -200,8 +200,8 @@ fn kmain(boot_information: *const BootInformation) noreturn {
|
||||
// Publish the loader's framebuffer as a claimable `display` device, so a
|
||||
// user-space display service can take it over the same claim + mmio_map path as
|
||||
// any other hardware (it is not firmware-discovered; it rides the boot handoff).
|
||||
if (devices_broker.seedDisplay(fb.base, fb.width, fb.height, fb.pitch, @intFromEnum(fb.format))) |display_id| {
|
||||
log.print("/system/kernel: framebuffer device {d} seeded ({d}x{d}, pitch {d}, write-combining)\n", .{ display_id, fb.width, fb.height, fb.pitch });
|
||||
if (devices_broker.seedDisplay(fb.base, fb.width, fb.height, fb.pitch, @intFromEnum(fb.format), fb.refresh_hz)) |display_id| {
|
||||
log.print("/system/kernel: framebuffer device {d} seeded ({d}x{d}, pitch {d}, {d} Hz, write-combining)\n", .{ display_id, fb.width, fb.height, fb.pitch, fb.refresh_hz });
|
||||
}
|
||||
|
||||
// Install the device-IRQ trampolines, so a driver's irq_bind has vectors to
|
||||
|
||||
@@ -3404,6 +3404,7 @@ fn displayTest(boot_information: *const BootInformation) void {
|
||||
|
||||
check("the node is class display", d.class == @intFromEnum(device_abi.DeviceClass.display));
|
||||
check("it carries the framebuffer geometry", d.display.width == fb.width and d.display.height == fb.height and d.display.pitch == fb.pitch);
|
||||
check("it carries the panel refresh rate", d.display.refresh_hz == fb.refresh_hz);
|
||||
check("it has exactly one resource", d.resource_count == 1);
|
||||
const r = d.resources[0];
|
||||
check("that resource is a memory window", r.kind == @intFromEnum(device_abi.ResourceKind.memory));
|
||||
|
||||
Reference in New Issue
Block a user