fix: enclose rounded preferred timings in EDID ranges

This commit is contained in:
Codex
2026-10-13 04:44:26 +03:00
parent adfce75973
commit e9906a793b
3 changed files with 22 additions and 13 deletions
+3 -1
View File
@@ -77,6 +77,8 @@ with tempfile.TemporaryDirectory(prefix='qemu-edid-probe-') as temporary:
(144000, 60000, 1920, 1080, 144), (144000, 60000, 1920, 1080, 144),
(144000, 0, 1600, 900, 144), (144000, 0, 1600, 900, 144),
(0, 144000, 1920, 1080, 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))]) 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' 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}' assert ranges[5] <= actual <= ranges[6], f'vertical range {ranges[5:7]} excludes {actual}'
horizontal_khz = clock / (horizontal + hblank) / 1000 horizontal_khz = clock / (horizontal + hblank) / 1000
assert ranges[7] <= horizontal_khz <= ranges[8], f'horizontal range {ranges[7:9]} excludes {horizontal_khz}' 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' 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])}') print(f'PASS configured={configured}, frontend={frontend}: {width}x{height}@{actual:.6f}, ranges={list(ranges[5:9])}')
+5 -1
View File
@@ -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 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, small dependency harness, and decodes the result. It checks 60/75 Hz fallback,
144 Hz configuration, a conflicting 60 Hz frontend request, resized dimensions, 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, 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. not measured frame delivery or proof of a 144 Hz physical output.
+14 -11
View File
@@ -33,13 +33,13 @@
for (output_idx = 0, node = g->conf.outputs; for (output_idx = 0, node = g->conf.outputs;
--- a/hw/display/edid-generate.c --- a/hw/display/edid-generate.c
+++ b/hw/display/edid-generate.c +++ b/hw/display/edid-generate.c
@@ -220,17 +220,17 @@ @@ -220,17 +220,20 @@
desc[5 + len] = '\n'; desc[5 + len] = '\n';
} }
-static void edid_desc_ranges(uint8_t *desc) -static void edid_desc_ranges(uint8_t *desc)
+static void edid_desc_ranges(uint8_t *desc, uint32_t refresh_rate, +static void edid_desc_ranges(uint8_t *desc, uint64_t clock,
+ uint32_t horizontal_rate) + uint32_t htotal, uint32_t vtotal)
{ {
edid_desc_type(desc, 0xfd); edid_desc_type(desc, 0xfd);
@@ -50,22 +50,25 @@
- /* horizontal (30 -> 160 kHz) */ - /* horizontal (30 -> 160 kHz) */
- desc[7] = 30; - desc[7] = 30;
- desc[8] = 160; - desc[8] = 160;
+ /* Preserve the legacy ranges and include the preferred mode. */ + /* Enclose the encoded timing after its 10 kHz pixel-clock rounding,
+ desc[5] = 50; + * while retaining legacy ranges whenever they already contain it. */
+ desc[6] = MIN(255, MAX(125, DIV_ROUND_UP(refresh_rate, 1000))); + 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[7] = MIN(30, clock / hperiod);
+ desc[8] = MIN(255, MAX(160, DIV_ROUND_UP(horizontal_rate, 1000))); + desc[8] = MIN(255, MAX(160, DIV_ROUND_UP(clock, hperiod)));
/* max dot clock (2550 MHz) */ /* max dot clock (2550 MHz) */
desc[9] = 2550 / 10; desc[9] = 2550 / 10;
@@ -513,7 +513,8 @@ @@ -513,7 +516,8 @@
* so dta descriptor offsets don't move any more. * so dta descriptor offsets don't move any more.
*/ */
- edid_desc_ranges(desc); - edid_desc_ranges(desc);
+ edid_desc_ranges(desc, refresh_rate, + edid_desc_ranges(desc, (uint64_t)timings.clock * 10000,
+ timings.clock * 10000 / (info->prefx + timings.xblank)); + info->prefx + timings.xblank, info->prefy + timings.yblank);
desc = edid_desc_next(edid, dta, desc); desc = edid_desc_next(edid, dta, desc);
if (desc && info->name) { if (desc && info->name) {