preface: Posting these online since it sounds like these notes are somewhat interesting based on a few folks I've shared with. These are semi-rough notes that I basically wrote for myself in case I ever needed to revisit this fix, so keep that in mind.
I recently bought an LG ULTRAGEAR monitor secondhand off of a coworker. I really love it and it's been great so far, but I ran into some minor issues with it in Linux. It works great on both Mac and Windows, but on Linux it displays just a black panel until I use the second monitor to go in and reduce the refresh rate down to 60 Hz.
This has worked decent so far but there's some issues:
- It doesn't work while linux is booting up. The motherboards boot sequence is visible just fine, but as soon as control is handed over to Linux and I'd normally see a splash screen while I'm waiting for my login window, I see nothing.
- It doesn't work on the login screen. This would be fine if login consistently worked on my second screen, but I need to manually switch the cables between my work computer and the desktop for the second screen and sometimes I don't feel like doing that. Even when I switch the cables, the second screen seems to be moody and doesn't always show the login screen either.
- Once I've logged in and fixed the settings on my second screen it seems to go fine, unless I actually unplug the second screen. If I do, it looks like the graphics settings go reset back to default (settings that don't work) and I lose the main monitor too.
Since I was able to fix the issue by manually reducing the refresh rate, my guess is that the issue is really about Linux insisting on defaulting the monitor to a mode that it doesn't support: 3440 x 1440 143.923 Hz
. I started looking online to do some research and I found a lot of articles and forum posts about how to sync up your logged-in display preferences with your boot sequence preferences & your greeter preferences, but that was mostly around fixing resolution, position, and orientation of monitors and it only worked for specific monitor connections.
In my case, it wasn't so much about the positioning of the monitors, the default mode that my monitor seemed to want to be in just didn't seem to work at all. Regardless, I tried the suggestions around creating an xrandr script and having it run when the greeter starts, but it seems that my monitor didn't care and the xrandr script wasn't doing anything at all. I also tried updating my version of Linux Mint and my Linux kernel to the latest versions, neither of those solved the issue.
Since this monitor did work out of the box when hooked up to the Macbook or when I boot this computer into Windows, I knew it was possible for this monitor to work properly.
I landed on the arch wiki for xrandr, xorg, and kernel mode setting and started to get the idea that my monitor was possibly reporting the wrong mode to the OS. This eventually lead me to this mailing list post which had me start to look closer at the EDID. Running edid-decode /sys/devices/pci0000:00/0000:00:03.1/0000:08:00.0/drm/card0/card0-DP-2/edid
I got this output:
edid-decode (hex):
00 ff ff ff ff ff ff 00 1e 6d 4b 77 b5 42 02 00
09 1e 01 04 b5 50 21 78 9f f6 75 af 4e 42 ab 26
0e 50 54 21 09 00 71 40 81 80 81 c0 a9 c0 b3 00
d1 c0 81 00 d1 cf da a7 70 50 d5 a0 34 50 90 20
3a 30 20 4f 31 00 00 1a 00 00 00 fd 00 30 90 e1
e1 50 01 0a 20 20 20 20 20 20 00 00 00 fc 00 4c
47 20 55 4c 54 52 41 47 45 41 52 0a 00 00 00 ff
00 30 30 39 4e 54 5a 4e 34 43 31 34 39 0a 02 34
02 03 30 71 23 09 07 07 47 10 04 03 01 1f 13 12
83 01 00 00 e3 05 c0 00 e2 00 6a e6 06 05 01 61
61 3d 6d 1a 00 00 02 05 30 90 00 04 61 3d 61 3d
4e d4 70 d0 d0 a0 32 50 30 20 3a 00 20 4f 31 00
00 1a 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ee
70 12 79 00 00 03 01 14 66 38 01 86 6f 0d ef 00
2f 80 1f 00 9f 05 45 00 02 00 09 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00 0b 90
----------------
Block 0, Base EDID:
EDID Structure Version & Revision: 1.4
Vendor & Product Identification:
Manufacturer: GSM
Model: 30539
Serial Number: 148149
Made in: week 9 of 2020
Basic Display Parameters & Features:
Digital display
Bits per primary color channel: 10
DisplayPort interface
Maximum image size: 80 cm x 33 cm
Gamma: 2.20
DPMS levels: Standby
Supported color formats: RGB 4:4:4, YCrCb 4:4:4, YCrCb 4:2:2
Default (sRGB) color space is primary color space
First detailed timing includes the native pixel format and preferred refresh rate
Display is continuous frequency
Color Characteristics:
Red : 0.6865, 0.3076
Green: 0.2587, 0.6699
Blue : 0.1494, 0.0576
White: 0.3134, 0.3291
Established Timings I & II:
DMT 0x04: 640x480 59.940 Hz 4:3 31.469 kHz 25.175 MHz
DMT 0x09: 800x600 60.317 Hz 4:3 37.879 kHz 40.000 MHz
DMT 0x10: 1024x768 60.004 Hz 4:3 48.363 kHz 65.000 MHz
DMT 0x24: 1280x1024 75.025 Hz 5:4 79.976 kHz 135.000 MHz
Standard Timings:
CVT : 1152x864 59.959 Hz 4:3 53.783 kHz 81.750 MHz (EDID 1.4 source)
GTF : 1152x864 60.000 Hz 4:3 53.700 kHz 81.624 MHz (EDID 1.3 source)
DMT 0x23: 1280x1024 60.020 Hz 5:4 63.981 kHz 108.000 MHz
DMT 0x55: 1280x720 60.000 Hz 16:9 45.000 kHz 74.250 MHz
DMT 0x53: 1600x900 60.000 Hz 16:9 60.000 kHz 108.000 MHz (RB)
DMT 0x3a: 1680x1050 59.954 Hz 16:10 65.290 kHz 146.250 MHz
DMT 0x52: 1920x1080 60.000 Hz 16:9 67.500 kHz 148.500 MHz
DMT 0x1c: 1280x800 59.810 Hz 16:10 49.702 kHz 83.500 MHz
CVT : 1920x1080 74.906 Hz 16:9 84.643 kHz 220.750 MHz (EDID 1.4 source)
GTF : 1920x1080 75.000 Hz 16:9 84.600 kHz 220.637 MHz (EDID 1.3 source)
Detailed Timing Descriptors:
DTD 1: 3440x1440 60.001 Hz 43:18 89.521 kHz 429.700 MHz (800 mm x 335 mm)
Hfront 144 Hsync 800 Hback 416 Hpol P
Vfront 3 Vsync 10 Vback 39 Vpol N
Display Range Limits:
Monitor ranges (Bare Limits): 48-144 Hz V, 225-225 kHz H, max dotclock 800 MHz
Display Product Name: 'LG ULTRAGEAR'
Display Product Serial Number: '009NTZN4C149'
Extension blocks: 2
Checksum: 0x34
----------------
Block 1, CTA-861 Extension Block:
Revision: 3
Basic audio support
Supports YCbCr 4:4:4
Supports YCbCr 4:2:2
Native detailed modes: 1
Audio Data Block:
Linear PCM:
Max channels: 2
Supported sample rates (kHz): 48 44.1 32
Supported sample sizes (bits): 24 20 16
Video Data Block:
VIC 16: 1920x1080 60.000 Hz 16:9 67.500 kHz 148.500 MHz
VIC 4: 1280x720 60.000 Hz 16:9 45.000 kHz 74.250 MHz
VIC 3: 720x480 59.940 Hz 16:9 31.469 kHz 27.000 MHz
VIC 1: 640x480 59.940 Hz 4:3 31.469 kHz 25.175 MHz
VIC 31: 1920x1080 50.000 Hz 16:9 56.250 kHz 148.500 MHz
VIC 19: 1280x720 50.000 Hz 16:9 37.500 kHz 74.250 MHz
VIC 18: 720x576 50.000 Hz 16:9 31.250 kHz 27.000 MHz
Speaker Allocation Data Block:
FL/FR - Front Left/Right
Colorimetry Data Block:
BT2020YCC
BT2020RGB
Video Capability Data Block:
YCbCr quantization: No Data
RGB quantization: Selectable (via AVI Q)
PT scan behavior: Always Underscanned
IT scan behavior: Always Underscanned
CE scan behavior: Always Underscanned
HDR Static Metadata Data Block:
Electro optical transfer functions:
Traditional gamma - SDR luminance range
SMPTE ST2084
Supported static metadata descriptors:
Static metadata type 1
Desired content max luminance: 97 (408.759 cd/m^2)
Desired content max frame-average luminance: 97 (408.759 cd/m^2)
Desired content min luminance: 61 (0.234 cd/m^2)
Vendor-Specific Data Block (AMD), OUI 00-00-1A:
02 05 30 90 00 04 61 3d 61 3d '..0...a=a='
Detailed Timing Descriptors:
DTD 2: 3440x1440 99.990 Hz 43:18 148.986 kHz 543.500 MHz (800 mm x 335 mm)
Hfront 48 Hsync 32 Hback 128 Hpol P
Vfront 3 Vsync 10 Vback 37 Vpol N
Checksum: 0xee
----------------
Block 2, DisplayID Extension Block:
Version: 1.2
Extension Count: 0
Display Product Type: Extension Section
Video Timing Modes Type 1 - Detailed Timings Data Block:
DTD: 3440x1440 143.923 Hz 64:27 217.323 kHz 799.750 MHz (aspect 64:27, no 3D stereo, preferred)
Hfront 48 Hsync 32 Hback 160 Hpol P
Vfront 3 Vsync 10 Vback 57 Vpol N
Checksum: 0x0b
Checksum: 0x90
I am not an EDID expert by any means (although I'm certainly more than a novice after this whole saga), but most of that looks fine to me. Actually the only place where I saw the mode that Linux Mint was defaulting to was in that last block:
Block 2, DisplayID Extension Block:
...
DTD: 3440x1440 143.923 Hz 64:27 217.323 kHz 799.750 MHz (aspect 64:27, no 3D stereo, preferred)
My hypothesis now was that if there was some way for me to change the EDID so that that last block had 60 Hz
instead of 143.923 Hz
, that could fix everything.
I started by trying to read the specs for EDID and DisplayID. Ultimately these were helpful to have on the side, but actually reading and understanding the details of every byte was beginning to confuse me and was starting to take me too long. I started thinking about a faster way to grok this bytestring when I realized there was a program I just used that was really great at parsing edids: edid-decode
.
I found the git repo and cloned it locally. Thankfully, the build instructions were super easy, just one make
later and I had a development build of parse-edid
on disk.
git clone git://linuxtv.org/edid-decode.git
cd edid-decode
make
./edid-decode /sys/devices/pci0000:00/0000:00:03.1/0000:08:00.0/drm/card0/card0-DP-2/edid
Looking at the output, I wanted to hone in on that DisplayID Extension Block
section. I popped the source open in VSCode and started searching around. DisplayID Extension Block
shows up as a constant in the block_name
function, so looking at usages of that brought me to edid_state::parse_extension
. This clearly was a DisplayID block, so I clicked into parse_displayid_block
.
At this point I wanted to orient myself a bit. The function was using things like x[1]
and x[2]
but I knew we weren't thinking about the second and third bytes of the whole EDID. I added a print statement to print out the first 5 bytes of x, just so that I could compare with my hex editor and see where we were.
printf("-- %x %x %x %x %x", x[0], x[1], x[2], x[3], x[4]);
This gave us 70 12 79 00 00
and I could find where that sequence was in my hex editor. In fact, I could also see that the variable assignments lined up with the structure table in the DisplayID Wikipedia page (version
, length
, prod_type
, ext_count
).
Continuing to trace the code, we moved the pointer forward by 5 and then jumped into edid_state::displayid_block
. We know that the next byte tag
is 0x03
which matches the fact that Video Timing Modes Type 1 - Detailed Timings Data Block
is the next section that's printed out. Later down the function, there's a function call to edid_state::parse_displayid_type_1_7_timing
that we enter and this is where the code starts to get exciting.
Actually, the start of the function isn't that bad, but scroll down halfway and you'll find this:
t.hact = 1 + (x[4] | (x[5] << 8));
hbl = 1 + (x[6] | (x[7] << 8));
t.hfp = 1 + (x[8] | ((x[9] & 0x7f) << 8));
t.hsync = 1 + (x[10] | (x[11] << 8));
t.hbp = hbl - t.hfp - t.hsync;
if ((x[9] >> 7) & 0x1)
t.pos_pol_hsync = true;
t.vact = 1 + (x[12] | (x[13] << 8));
vbl = 1 + (x[14] | (x[15] << 8));
t.vfp = 1 + (x[16] | ((x[17] & 0x7f) << 8));
t.vsync = 1 + (x[18] | (x[19] << 8));
t.vbp = vbl - t.vfp - t.vsync;
if ((x[17] >> 7) & 0x1)
t.pos_pol_vsync = true;
if (x[3] & 0x10) {
t.interlaced = true;
t.vfp /= 2;
t.vsync /= 2;
t.vbp /= 2;
}
Oh no, there's no way I'm going to be able to figure out all this byte manipulation in my head. I probably don't need to care about most of this just to figure out how to fix the refresh rate, and I wish there was a way I could just see what all these values get set to in the end.
I don't do much C or C++ development, but I've used gdb
in the past for schoolwork and a really great capture the flag I did once. I probably can remember enough about it to get some useful info out.
First I worked to try and get debug symbols enabled. Turns out this Makefile already has debug symbols turned on, so that wasn't actually necessary. If you ever do need to turn on debug symbols, the flag to remember is -g
for the compiler although in this Makefile I just tossed it onto the WARN_FLAGS
variable since I didn't see an obvious other place for it.
I fired up gdb ./edid-decode
did some quick googling to figure out that setting a breakpoint is break filename.cpp:line-number
and running with arguments is run arg1 arg2
and I landed in the right place.
Breakpoint 1, edid_state::parse_displayid_type_1_7_timing (this=0x5555555b3940 <state>, x=0x5555555b4408 <edid+264> "f8\001\206o", <incomplete sequence \357>, type7=false, block_rev=1, is_cta=false) at parse-displayid-block.cpp:368
368 print_timings(" ", &t, name.c_str(), s.c_str(), true);
(gdb)
Ok, we just got through that wild bit where we set up t
. I'm not entirely sure what I'm expecting since a C struct is just a bunch of bytes, but lets try to print it out:
(gdb) p t
$1 = {hact = 3440, vact = 1440, hratio = 64, vratio = 27, pixclk_khz = 799750, rb = 0, interlaced = false, hfp = 48, hsync = 32,
hbp = 160, pos_pol_hsync = true, vfp = 3, vsync = 10, vbp = 57, pos_pol_vsync = false, hborder = 0, vborder = 0, even_vtotal = false,
no_pol_vsync = false, hsize_mm = 0, vsize_mm = 0, ycbcr420 = false}
Oh wow, that's a lot more readable than I expected. I guess debug symbols go a long way! Some of these values I recognize (3440 and 1440 are the resolution), but I'm not seeing the one most important value I need to fix: the refresh rate of 143.923
. Let's step forward and see if we can figure out how that's calculated:
(gdb) n
DTD: 3440x1440 143.922761 Hz 64:27 217.323 kHz 799.750000 MHz (aspect 64:27, no 3D stereo, preferred)
Hfront 48 Hsync 32 Hback 160 Hpol P
Vfront 3 Vsync 10 Vback 57 Vpol N
369 if (is_cta) {
(gdb)
Oops, we went way too far, all the calculations for the refresh rate just got done without us. I had to go look up the difference between s
and n
and realized that I stepped over all the interesting stuff (n
= next line, s
= step into). Well at least we know the interesting stuff is inside the print_timings
function.
Back in my editor, we go looking and we find another relatively dense function. At this point, I'm looking for other strategies to take shortcuts and I realize that there's a Hz
right after the refresh rate. I do a quick search and I find this printf
:
printf("%s%s: %5ux%-5s %10.6f Hz %3u:%-3u %8.3f kHz %13.6f MHz%s\n",
prefix, type,
t->hact, buf,
refresh,
t->hratio, t->vratio,
out_hor_freq_khz,
pixclk / 1000000.0,
s.c_str());
That actually makes things a lot clearer. We're really looking at where the refresh
variable is set. The definition for that is a bit like this:
double refresh = t->pixclk_khz * 1000.0 / (htotal * vtotal);
if (options[OptNTSC] && fmod(refresh, 6.0) == 0) {
refresh *= ntsc_fact;
}
Ah, so that pixclk_khz
from the struct earlier is actually pretty key. Let me try to see if I do that math, do I get the same number?
What if we add in ntsc_fact
which is 1000.0 / 1001.0
Huh, 161 is close to 143, but neither of these are quite right. I must be missing something.
After a lot of searching, it turned out that htotal
and vtotal
aren't just the resolution as I had first assumed. Here's the relevant bits, with comments added based on what stuff ended up evaluating out to:
if (t->interlaced) // false
vact /= 2;
unsigned hbl = t->hfp + t->hsync + t->hbp + 2 * t->hborder; // 240
unsigned vbl = t->vfp + t->vsync + t->vbp + 2 * t->vborder; // 70
unsigned htotal = t->hact + hbl; // 3680
double vtotal = vact + vbl; // 1510
if (t->even_vtotal) // false
vtotal = vact + t->vfp + t->vsync + t->vbp;
else if (t->interlaced) // false
vtotal = vact + t->vfp + t->vsync + t->vbp + 0.5;
Ok, trying our math again:
There we go! Finally, we understand roughly how that refresh rate that Linux Mint was erroneously using was being derived. Now let's see what we can do to fix things.
At this point, we're trying to work backwards from the end result we want to figure out what bytes need to be updated in the EDID. First, we want that fraction to evaluate out to 60 instead, and we don't want to muck with either of the resolution values. That basically means we should change the pixclk_khz
value:
This is really easy math, but I already have the whole thing plugged into WolframAlpha so it's easy to just change that to an x
and set the whole thing equal to 60:
Computers are great, aren't they?
Ok, so we need t->pixclk_khz
to be 333408
. Where does that get set?
// edid_state::parse_displayid_type_1_7_timing
// type7 = false
t.pixclk_khz = (type7 ? 1 : 10) * (1 + (x[0] + (x[1] << 8) + (x[2] << 16)));
It took me a bit longer than I'd like to admit to realize that this is really just 10 times 1 plus the first 3 bytes of the DisplayID section payload in little endian (look, I don't work with bit manipulation that often). Those bytes in decimal are 102
, 56
, and 1
so let's double check our understanding
Great, that was the value for pixclk_khz
that our debugger showed us earlier. So if we want that to instead be 333408
, what are the values we need for those three bytes?
Rearranging a bit:
It looks like we're gonna have to add a bit of imprecision since we're working with whole numbers here. I'm not entirely sure how this is typically handled, but lets just round up to 33340
and hope that nothing blows up.
There's lots of ways to solve this, but at the core it's a base change to base 8. I like to do this with division and remainders. To get the most significant digit
It turns out
So we know that
We've got our new 3 bytes, so let's use a hex editor to fix the file. 60 130 0
is 0x3c 0x82 0x00
in hex, so we go in and update the first line of our 3rd EDID block:
-70 12 79 00 00 03 01 14 **66 38 01** 86 6f 0d ef 00
+70 12 79 00 00 03 01 14 **3c 82 00** 86 6f 0d ef 00
Let's save and run edid-decode
on the new file. Hopefully it agrees with the change that we made.
Block 2, DisplayID Extension Block:
Version: 1.2
Extension Count: 0
Display Product Type: Extension Section
Video Timing Modes Type 1 - Detailed Timings Data Block:
DTD: 3440x1440 60.000 Hz 64:27 90.601 kHz 333.410 MHz (aspect 64:27, no 3D stereo, preferred)
Hfront 48 Hsync 32 Hback 160 Hpol P
Vfront 3 Vsync 10 Vback 57 Vpol N
Checksum: 0x0b (should be 0xec)
Checksum: 0x90 (should be 0x71)
I do not have the language to explain how excited I was when I saw that 60.000 Hz
in the output! You will have to trust me when I tell you I shouted in excitement!
One slight issue which probably isn't a big deal, but I'm going to fix it anyways: those checksums are off since we modified the file. I think fixing this with math wouldn't be that difficult (based on the spec, the checksums should be chosen such that the sum of all the bytes in the section modulo 256 is 0), but edid-decode
was so kind as to just tell us what they should be, so we can just go and update those lines in the file directly:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 0B 90
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 EC 71
Just a quick check to make sure:
Block 2, DisplayID Extension Block:
Version: 1.2
Extension Count: 0
Display Product Type: Extension Section
Video Timing Modes Type 1 - Detailed Timings Data Block:
DTD: 3440x1440 60.000 Hz 64:27 90.601 kHz 333.410 MHz (aspect 64:27, no 3D stereo, preferred)
Hfront 48 Hsync 32 Hback 160 Hpol P
Vfront 3 Vsync 10 Vback 57 Vpol N
Checksum: 0xec
Checksum: 0x71 (should be 0x90)
Doh! The checksum for Block 2 is part of the bytes considered for the overall checksum, so by fixing it we've caused issues for the final checksum. No matter, we can fix that real quick:
-00 00 00 00 00 00 00 00 00 00 00 00 00 00 EC 71
+00 00 00 00 00 00 00 00 00 00 00 00 00 00 EC 90
Astute readers might notice that the final checksum changed back to 90 just now. This is an interesting artifact of the way that the checksum for EDID works, where all the bytes of a section must be congruent to 0 mod 256. When we fixed the checksum for Block 2, we made the sum of that whole section (including our only changes) cancel out to 0, just like it must have done before for the checksums to be valid.
At this point, we've got a valid updated EDID which seems to pass the parse test for edid-decode
. The real question is if this EDID would work for the monitor or not. Fortunately, it seems that there's a way to force the Linux kernel to ignore the EDID that the monitor self reports and override it with a custom firmware (very convenient for this very situation).
Very briefly, the steps seem to be:
- Copy the new firmware to
/usr/lib/firmware
(call itdesktop_edid.bin
for compatibility with the scripts below) - Create this file:
/etc/initramfs-tools/hooks/desktop_edid.sh
#!/bin/sh
# Copy local EDID monitor description data
PREREQ=""
prereqs()
{
echo "$PREREQ"
}
case $1 in
prereqs)
prereqs
exit 0
;;
esac
. /usr/share/initramfs-tools/hook-functions
EDID_DATA="/usr/lib/firmware/desktop_edid.bin"
if [ ! -f "$EDID_DATA" ]; then
exit 0
fi
add_firmware "$(basename $EDID_DATA)"
exit 0
- Make it executable:
sudo chmod +x /etc/initramfs-tools/hooks/desktop_edid.sh
- Rebuild your initramfs:
sudo update-initramfs -u -v
If all goes well, you should see a log line about copying firmware desktop_edid.bin
. This means that when you next reboot, you can force the kernel to use that firmware.
Let's do a quick test first before making any permanent changes:
- Reboot
- When you get to grub, use
e
to edit the boot configuration for the current boot - Find the linux line which should have something like
quiet splash
at the end of it. Update that line to instead end inquiet splash drm.edid_firmware=desktop_edid.bin
- Press F10 and boot!
After all this I saw a sight I've never seen before. The loading spinner for Linux Mint was showing up on my new monitor. A few seconds later, I was greeted with an even more impressive sight: the login screen popped open on the new monitor. To be honest, I wasn't sure that I believed it would work from the start, but I'm amazed that it actually worked in the end.
- One issue is that now my second monitor isn't displaying anything. When I go into Display Settings, it seems like my computer thinks that both monitors are sending this EDID so it's put the second monitor into the wrong mode. There is a way to override the firmware for only one connector according to the Arch wiki:
drm.edid_firmware=VGA-1:edid/your_edid.bin
so I'm going to need to explore that a bit. - Once I get it working, I'll update the grub files for real and make it permanent.
- One last thing I might consider doing at some point would be to try to overwrite the EDID on the monitor itself. I'd probably want to confirm that my changes are 100% safe and correct before doing this, but that would mean that my system doesn't need any special configuration for this monitor. Any Linux laptop that I plug it into or any port that I plug it into should just work. I've found docs on this online, but it's pretty risky because if I write a bad EDID I can brick the monitor, and if I do the process wrong it sounds like I could actually brick any of the components in my computer... not good!
- EDID and DisplayID formats / specs:
- Arch linux wiki:
- gdb usage:
- new hex editor (that lets you take notes on the file, although I didn't use this as much as I planned)
- Testing the new EDID in my kernel
Here's another way Stephen Wolfram can help you: Reading his blog, I came across his mention of "EDID spoofing boxes," which plug in between computer and monitor or projector and can override incorrect display settings. I've never used one, but maybe it would help you in your situation:
https://writings.stephenwolfram.com/2019/02/seeking-the-productive-life-some-details-of-my-personal-infrastructure/
That page links to a few different EDID boxes.