homelab

Two Kernel Bugs and a Missing Bit: Getting Real HDMI 2.1 out of the Steam Machine

Stock SteamOS never turns on FRL, so my Steam Machine's HDMI 2.1 port behaves like HDMI 2.0. Turning it on exposed two amdgpu bugs, and a third patch sends the ALLM flag gamescope never sets.

Pixel art: a small black-and-white cartoon cow sitting on a sofa playing a video game on a big glowing TV, small black games console box with cables, living room

When the Steam Machine arrived, the plan was simple. Plug it into the LG C4, pick 4K at 144 Hz, turn on VRR, done. The box has an HDMI 2.1 port. The TV has four of them. The GPU is RDNA 3. Everything in that chain is sold on 4K120 and up.

So I opened the display settings and looked for 144. It wasn't there.

An HDMI 2.1 port that doesn't do HDMI 2.1

It took me a while to believe this, but stock SteamOS never uses the part of HDMI 2.1 that matters. HDMI 2.1's headline bandwidth comes from FRL (Fixed Rate Link), a new signalling mode that replaces the old TMDS link. Without FRL you're on TMDS, which tops out at 600 MHz. That's HDMI 2.0 bandwidth. You can have 4K60 in full RGB, or 4K120 if you accept 4:2:0 chroma subsampling, which turns small text into mush. 4K144 doesn't show up at all.

The hardware supports FRL. The amdgpu driver supports it. But on SteamOS it sits behind a display feature mask that's switched off by default, so the HDMI 2.1 socket on the back behaves like an HDMI 2.0 one.

Fine, I thought. Turn the mask on with a kernel parameter and move on. That's where the real story starts, because turning FRL on exposed two kernel bugs. Two months later there's a small public package, two kernel patches I'm fairly proud of, a third I didn't expect to write, and an issue sitting with Valve.

Bug one: VRR that never turns on

With the feature mask set (amdgpu.dcfeaturemask=0x402), the TV finally offers 4K144 at 10-bit with HDR. Lovely. You also get this:

vrr_capable: 0
vrr_range:   0-0

I got Valve's kernel source and found the cause in amdgpu_dm_update_freesync_caps(). It checks whether the sink is plain HDMI:

} else if (sink->sink_signal == SIGNAL_TYPE_HDMI_TYPE_A) {

An FRL sink isn't HDMI_TYPE_A. It's SIGNAL_TYPE_HDMI_FRL. So it skips the FreeSync check entirely and the TV is declared VRR-incapable. The fix is a single line that uses the helper already in the tree, dc_is_hdmi_signal(). The same change exists in an out-of-tree HDMI gaming series that Valve's kernel hadn't picked up yet.

Rebuilding the whole kernel takes hours. Rebuilding only amdgpu.ko out of tree took under two minutes. Drop it into /lib/modules/<kernel>/updates/, reboot, and:

vrr_capable: 1
vrr_range:   40-144

Bug two: FRL lost on every hotplug

Victory lasted until I turned the TV off and on again. A hotplug (TV wake, input switch, re-handshake) brought the link back as TMDS, not FRL, and it stayed at 60 Hz until the next reboot.

This took longer. I burned an evening on debug logging that printed nothing. I tried forcing re-detects through debugfs, and tried poking gamescope with uevents. None of it worked. The answer was in link_detection.c: on this GPU family a config flag, skip_frl_pretraining, is always set. When the link is already active it switches detection to a "non-destructive" path. That path can never upgrade the sink to FRL, and gamescope keeps the display lit across hotplugs, so the link always looks active. Every reconnect drops to TMDS.

AMD has already removed that gate upstream ("Reintroduce Force validation link training on all ASICs"), but it hadn't reached SteamOS. Patch 0002 removes the gate. Now an input switch comes back at 4K144 with VRR. The cost is a short blank while FRL retrains, which is what upstream does anyway.

Making it survive SteamOS

A patched kernel module on an immutable, A/B-updated OS has a short life expectancy. So most of the work in the package is plumbing:

  • get.sh, a one-liner that installs everything and builds the module against the exact Valve kernel you're running
  • a boot self-heal that puts the mode and module back after a SteamOS update, and rebuilds if the kernel changed
  • a resume check that confirms FRL came back after sleep

The resume check got its own lesson in humility. Version 1.0.0 decided "is this FRL?" from the pixel clock of the fastest mode. Then someone with an LG C9 commented on the SteamOS issue. On a 120 Hz TV, 4K120 has the same pixel clock over FRL as over TMDS 4:2:0, so the check never saw FRL and blanked the screen five times on every wake. Version 1.0.1 reads the driver's own state instead. There's an HPO: row in amdgpu_dm_dtn_log that only exists while an FRL encoder is active. Facts beat guesses.

The real test came this week: the SteamOS 3.9.5 update (new kernel, new gamescope, new grub). I applied it and kept my hands off. After one extra reboot the self-heal had rebuilt the module, and everything came back on its own: 4K144, VRR 40-144, HDR.

The bit nobody was sending

While adding a proper status readout (it now reports RGB 4:4:4, 10-bit, HDR PQ instead of just "FRL: yes"), I noticed two connector properties side by side:

allm_capable:  1
allm_mode:     0   (Disabled)
content type:  4   (Game)

gamescope tells the driver "this is game content", but never sets allm_mode. ALLM (Auto Low Latency Mode) is the flag that tells the TV to switch into game mode on its own. The TV supports it, the driver supports it, and nobody turns it on. Because gamescope holds DRM master, nothing else can set it while it runs.

Worse, setting a default isn't enough. The driver only works out ALLM when allm_mode changes, and every new stream starts zeroed. So patch 0003 adds a module parameter (amdgpu.hdmi_allm_default, Always On by default) and has the driver evaluate ALLM whenever a stream is created. A small debugfs file reads back what's actually being sent:

allm_active: 1
content_type: 8

(8 is the driver's internal value for "game". It isn't the DRM property's 4. That cost me ten minutes.)

That's in v1.1.0, along with setup.sh allm disabled|dynamic|always-on. The TV was in game mode after boot. I still owe it a proper test: set that input to a different picture mode, reboot, and see if the TV switches back by itself. The proper fix belongs in gamescope, so I've filed it: gamescope#2543.

The controller subplot

The Xbox wireless dongle has its own driver drama. It runs on a patched xone, and a couple of community PRs are now confirmed by other owners on the same dongle revision. One thing I learned the hard way while testing: four quick reboots in a row can leave the dongle's radio stuck (init radio failed: -110). No software reset fixes it. You have to unplug it physically. My own self-heal reloads the driver when no controller shows up, which probably makes this worse when the controller is just switched off. That's next on the list.

Wrapping up

What I'll remember is how close it all was to working. Each bug was one wrong condition: a signal type check, a config flag, a property nobody set. Finding them meant reading the actual source and checking what the driver reports, not trusting what I assumed it does.

Everything is at beaglemoo/steam-machine-hdmi21, with the background on SteamOS#2809. A Decky plugin that shows the link status in the quick access menu is coming once it has its own repo. Ideally Valve turns FRL on by default and makes the whole thing redundant. An HDMI 2.1 port should do HDMI 2.1 out of the box. Until then, the TV finally gets everything it can handle.