Enabling S3 sleep on a DeskMini X300

TL;DR: Follow the README in https://github.com/lorenz/asrock-x300-s3-fix

The ASRock DeskMini X300 is a very affordable barebone computer for everything that doesn't require more than 8C/16T or a necessitates an external GPU. Equipped with a 5700G, 32 GiB of RAM and a 512GiB of fast NVMe flash they are fairly powerful machines at a very affordable price point (~800$) while also being space-efficient at around 2l volume, power-efficient (measured at around 7W in idle on Linux) and quiet (at least with a Noctua L9a fitted).

They do however have one giant flaw: Standby (ACPI S3) is just straight-up unavailable out-of-the-box with CPUs starting from the 4000 series. Windows won't even show you the button, Linux attempts to use a S2idle which doesn't do much because the hardware has no S2idle controllers available. It turns the screen off and lowers power consumption by around 1W, but that's about it. The power LED and fan keep running. This is now documented by ASRock, but only on their detailed specifications tab and literally on the last row.

When I got my first batch of them I thought about sending them back because Desktop PCs without ACPI S3 aren't really useable. But first I wanted to try and see if I could enable ACPI S3 on these boxes. Because fundamentally there really isn't much on there that could screw with S3. The X300 chipset isn't really a chipset, but pretty much denotes the lack of a chipset. The only portion of it that's actually physical is a marker chip telling the CPU/SoC that there is no actual chipset present. So basically the only things that are even involved are the CPU/SoC itself, memory, the SuperIO chip and the firmware/BIOS. But all of these components individually support S3, at least if you're not running the CPU with TSME (Transparent System Memory Encryption) enabled. And since the sister platform Jupiter X300 (also by ASRock) supports S3 it's extremely unlikely that the hardware physically can't do it. The reason for the issue is in the firmware/BIOS.

What's interesting about the issue is that the operating systems are aware of the fact that S3 is unsupported. This means that the firmware explicitly chose to pass an ACPI table with S3 disabled. So I dumped the ACPI table responsible for most of the original ACPI power handling, the Differentiated System Description Table (DSDT). I tried decompiling the table with acpica, but the decompiler refused because the table contained broken definitions. I patched the decompiler to accept the broken declarations and saw something very interesting

    Name (_S0, Package (0x04)  // _S0_: S0 System State
    {
        Zero, 
        Zero, 
        Zero, 
        Zero
    })
    Name (XS3, Package (0x04)
    {
        0x03, 
        Zero, 
        Zero, 
        Zero
    })
    Name (_S4, Package (0x04)  // _S4_: S4 System State
    {
        0x04, 
        Zero, 
        Zero, 
        Zero
    })
    Name (_S5, Package (0x04)  // _S5_: S5 System State
    {
        0x05, 
        Zero, 
        Zero, 
        Zero
    })

For the S0 (powered on), S4 (suspend-to-disk) and S5 (powered off) power states, there is a valid entry (see the ACPI sleeping state docs), but for S3 there is an X in front of it, which is not a defined ACPI resource name. Because this platform supports S3 on older CPUs and the entry still exists I wrote a patch which replaced the XS3 resource name with _S3 which is the proper one and recompiled the DSDT table using acpica's iasl. But ACPI tables are provided by firmware, so just having a fixed one doesn't really get you far. Luckily Linux provides a facility to overwrite firmware ACPI tables using a special type of CPIO archive / initramfs. The compiled ACPI table needs to be located in the CPIO archive at kernel/firmware/acpi/dsdt.aml. Then this CPIO archive needs to be passed to your bootloader of choice before the normal initramfs. There's one more caviat which bit me the first time I tried this: ACPI tables have a version number. Linux will only load the override table if its version is higher than the one provided by firmware. Another small patch to the DefinitionBlock of the table source later, Linux loaded my modified DSDT table:

ACPI: Table Upgrade: override [DSDT-ALASKA-  A M I ]
ACPI: DSDT 0x00000000BB235000 Physical table override, new table: 0x00000000BD632000

And unsurprisingly it now showed S3 as supported:

ACPI: PM: (supports S0 S3 S4 S5)

Now I needed to actually test it since I only forced ACPI to expose the capability and didn't yet fix any possible bugs with S3. But to my surprise just running systemctl suspend suspended the machine and pressing the power button resumed it again without any obvious bugs. A stress test with 100 sleep/wake cycles didn't reveal any issues at least on my Cezanne-based Ryzen 5700G and with TSME disabled in firmware. Success!

I also looked into modifying the firmware itself to correct the ACPI table there but I didn't find the responsible EFI module. Modern EFI firmware is one massive pile of code and at least for AMD firmware there isn't much public research/documentation available yet. So for now I'm injecting my fix via Linux's ACPI table override feature. If someone wants to have a go at it, the firmware is here.

A few notes on the constraints of this hack:

  1. This is unsupported by both the vendor (ASRock) and me. I am not responsible for dead hardware or eaten cats. And depending on your jurisdiction it might void your warranty.
  2. Any change to firmware settings can change the underlying ACPI tables. Since the patched one is not taken from the firmware you then have an inconsistent set of ACPI tables loaded which can lead to unpredictable behavior and in extreme cases even hardware damage. The only "safe" way to do this is to never change firmware settings or update the firmware after you've injected the patch. Otherwise you need to remove the hack first, change the settings and then redo the whole procedure.
  3. This is only confirmed to work on Cezanne-based APUs (so the Ryzen 5x00G series) and Renoir-based APUs (Ryzen 4x50G, thanks Z3NOX) with TSME disabled. It might very well not work with other APUs.
  4. It only works if your operating system supports overriding ACPI tables. I don't know how to do that on Windows and have never tested it.

If you have one of those boxes and you want to apply my fix, there is a repo at https://github.com/lorenz/asrock-x300-s3-fix which contains instructions on how to use it.