diff --git a/dev/check-edid.py b/dev/check-edid.py index 01947d7..2eeb22a 100644 --- a/dev/check-edid.py +++ b/dev/check-edid.py @@ -77,6 +77,8 @@ with tempfile.TemporaryDirectory(prefix='qemu-edid-probe-') as temporary: (144000, 60000, 1920, 1080, 144), (144000, 0, 1600, 900, 144), (0, 144000, 1920, 1080, 144), + (50000, 0, 1920, 1080, 50), + (255000, 0, 1280, 800, 255), ]: data = subprocess.check_output([str(directory / 'probe'), *map(str, (configured, frontend, width, height))]) assert all(sum(data[offset:offset + 128]) % 256 == 0 for offset in range(0, len(data), 128)), 'invalid EDID checksum' @@ -93,6 +95,6 @@ with tempfile.TemporaryDirectory(prefix='qemu-edid-probe-') as temporary: assert ranges[5] <= actual <= ranges[6], f'vertical range {ranges[5:7]} excludes {actual}' horizontal_khz = clock / (horizontal + hblank) / 1000 assert ranges[7] <= horizontal_khz <= ranges[8], f'horizontal range {ranges[7:9]} excludes {horizontal_khz}' - if expected <= 75: + if configured == 0 and expected <= 75: assert ranges[5:9] == bytes((50, 125, 30, 160)), 'legacy monitor range changed' print(f'PASS configured={configured}, frontend={frontend}: {width}x{height}@{actual:.6f}, ranges={list(ranges[5:9])}') diff --git a/docs/verification.md b/docs/verification.md index 02c21ea..d24c3fb 100644 --- a/docs/verification.md +++ b/docs/verification.md @@ -112,7 +112,11 @@ The probe applies the tracked patch to four relevant source files in a temporary directory, compiles the actual EDID and virtio GPU generator functions with a small dependency harness, and decodes the result. It checks 60/75 Hz fallback, 144 Hz configuration, a conflicting 60 Hz frontend request, resized dimensions, -checksums and monitor ranges. At 1920×1080/144000 mHz the preferred timing is +checksums and monitor ranges. It also checks the supported vertical-refresh +boundaries: 1920×1080/50000 mHz and 1280×800/255000 mHz. Range endpoints +use floor/ceiling of the encoded timing after pixel-clock rounding, so the +49.998895 Hz lower-boundary timing is included by a 49 Hz minimum. Normal +automatic 60/75 Hz cases retain the legacy 50–125 Hz range. At 1920×1080/144000 mHz the preferred timing is 143.997231 Hz after EDID clock rounding. This is an advertised guest mode, not measured frame delivery or proof of a 144 Hz physical output. diff --git a/patches/qemu-virtio-refresh-rate.patch b/patches/qemu-virtio-refresh-rate.patch index 868e226..0c7e160 100644 --- a/patches/qemu-virtio-refresh-rate.patch +++ b/patches/qemu-virtio-refresh-rate.patch @@ -33,13 +33,13 @@ for (output_idx = 0, node = g->conf.outputs; --- a/hw/display/edid-generate.c +++ b/hw/display/edid-generate.c -@@ -220,17 +220,17 @@ +@@ -220,17 +220,20 @@ desc[5 + len] = '\n'; } -static void edid_desc_ranges(uint8_t *desc) -+static void edid_desc_ranges(uint8_t *desc, uint32_t refresh_rate, -+ uint32_t horizontal_rate) ++static void edid_desc_ranges(uint8_t *desc, uint64_t clock, ++ uint32_t htotal, uint32_t vtotal) { edid_desc_type(desc, 0xfd); @@ -50,22 +50,25 @@ - /* horizontal (30 -> 160 kHz) */ - desc[7] = 30; - desc[8] = 160; -+ /* Preserve the legacy ranges and include the preferred mode. */ -+ desc[5] = 50; -+ desc[6] = MIN(255, MAX(125, DIV_ROUND_UP(refresh_rate, 1000))); ++ /* Enclose the encoded timing after its 10 kHz pixel-clock rounding, ++ * while retaining legacy ranges whenever they already contain it. */ ++ uint64_t vperiod = (uint64_t)htotal * vtotal; ++ uint64_t hperiod = (uint64_t)htotal * 1000; ++ desc[5] = MIN(50, clock / vperiod); ++ desc[6] = MIN(255, MAX(125, DIV_ROUND_UP(clock, vperiod))); + -+ desc[7] = 30; -+ desc[8] = MIN(255, MAX(160, DIV_ROUND_UP(horizontal_rate, 1000))); ++ desc[7] = MIN(30, clock / hperiod); ++ desc[8] = MIN(255, MAX(160, DIV_ROUND_UP(clock, hperiod))); /* max dot clock (2550 MHz) */ desc[9] = 2550 / 10; -@@ -513,7 +513,8 @@ +@@ -513,7 +516,8 @@ * so dta descriptor offsets don't move any more. */ - edid_desc_ranges(desc); -+ edid_desc_ranges(desc, refresh_rate, -+ timings.clock * 10000 / (info->prefx + timings.xblank)); ++ edid_desc_ranges(desc, (uint64_t)timings.clock * 10000, ++ info->prefx + timings.xblank, info->prefy + timings.yblank); desc = edid_desc_next(edid, dta, desc); if (desc && info->name) {