Enabling the S3 sleep state (suspend-to-RAM) on the Lenovo Yoga 7 AMD Gen 7 (and possibly, others)
The lack of support for the Suspend-to-RAM functionality (ie. S3 sleep state), and its replacement with the disastrous Connected Standby (ie. S0ix sleep state) is a well-known plague on modern laptops.
In this article I’ll describe how to restore support for it on the Lenovo Yoga 7 AMD Gen 7. This method likely applies to other models; for example, the HP ENVY x360) has the same S3 conditional logic in the DSDT.
Content:
Procedure
Preliminary operations
The procedure is generic, and can be performed on any Linux distribution; the difference should be just in the tools package; on Ubuntu, install the acpica-tools
:
$ sudo apt install -y acpica-tools
In order to verify which sleep states the machine supports, run:
# This message comes from the kernel ring buffer, which rotates; if nothing is shown, reboot and
# rerun the command.
#
$ sudo dmesg | grep 'ACPI.*supports S'
[ 0.309933] ACPI: PM: (supports S0 S4 S5)
Dumping and patching the DSDT
Dump the DSDT:
# Can also be achieved via `acpidump -b`, which dumps more data (not required in this context).
#
$ sudo cat /sys/firmware/acpi/tables/DSDT > dsdt.dat
Disassemble it:
$ iasl -d dsdt.dat
The resulting disassembly, dsdt.dsl
, is human readable. On the Lenovo Yoga 7 AMD Gen 7, one can see that the S3 state is supported, but with conditionals:
If ((CNSB == Zero))
{
If ((DAS3 == One))
{
Name (_S3, Package (0x04) // _S3_: S3 System State
{
0x03,
0x03,
Zero,
Zero
})
}
}
I don’t have domain knowledge, however, my educated guess is that this is (primarily) a check whether the option is set in the firmware (the Lenovo Yoga 7 AMD Gen 7 allows the user access only very basic firmware settings, and this is not included).
The fix is simply to remove the conditionals; this can be done with any editor, or with a Perl script:
# A backup file (`dsdt.dsl.bak`) is created.
#
# Regex: remove the four lines before "S3_: S3 System State" and the two lines after; keep the six
# lines in between.
#
perl -0777 -i.bak -pe 's/(.+\n){4}(.+_S3_: S3 System State\n(.+\n){6})(.+\n){2}/$2/m' dsdt.dsl
We also need to bump the DSDT revision, otherwise when booting, the patched DSDT will be overridden (this is not required if patching the kernel):
# Regex: replace the last value of the DSDT table header definition:
#
perl -i -pe 's/^DefinitionBlock.+\K0x00000001/0x00000002/' dsdt.dsl
Now we just reassemble the DSDT:
iasl -tc dsdt.dsl
This will generate multiple files - different override methods require different files.
Overriding the DSDT
There are different approaches to overriding the DSDT. I’ll describe what I’ve tested, and the pros/cons.
Initrd hook
The best method is to add an initrd hook; it’s clean, and it doesn’t require any maintenance:
# Create the initrd image, including the patched DSDT in the approprite directory, which corresponds
# to the `firmware/acpi` subdirectory of the `/sys` virtual filesystem.
#
mkdir -p kernel/firmware/acpi
cp dsdt.aml kernel/firmware/acpi
find kernel | cpio -H newc --create | sudo tee /boot/acpi_override > /dev/null
# Now create the hook. Note that this is not the canonical style for hooks; it's been reduced to the
# simplest form, for clarity.
#
sudo tee /etc/initramfs-tools/hooks/acpi_override << 'SH'
#!/bin/sh
if [ "$1" = prereqs ]; then
echo
else
. /usr/share/initramfs-tools/hook-functions
prepend_earlyinitramfs /boot/acpi_override
fi
SH
sudo chown root: /etc/initramfs-tools/hooks/acpi_override
sudo chmod 755 /etc/initramfs-tools/hooks/acpi_override
# Now update the initramfs (for all the kernels).
#
sudo update-initramfs -k all -u
Patching the kernel
For those who use a patched kernel, it’s just a matter of setting the related configuration symbol(s):
# Run from the kernel source root.
#
scripts/config --set-val CONFIG_ACPI_CUSTOM_DSDT y
scripts/config --set-val CONFIG_ACPI_CUSTOM_DSDT_FILE '"/path/to/dsdt.hex"'
Then recompile and boot. Done!
Prepending an initrd image
This is a method that works, but it’s discouraged, since requires repeating the operation every time the initrd image is regenerated (essentially, for any kernel update).
# Create the initrd image, including the patched DSDT in the approprite directory, which corresponds
# to the `firmware/acpi` subdirectory of the `/sys` virtual filesystem.
#
$ mkdir -p kernel/firmware/acpi
$ cp dsdt.aml kernel/firmware/acpi
$ find kernel | cpio -H newc --create > initrd-patched-dsdt.img
# Backup the initrd for the running kernel, and prepend the initrd image just created, to the
# regular kernel one.
#
$ cp /boot/initrd.img-"$(uname -r)" .
$ cat initrd-patched-dsdt.img initrd.img-"$(uname -r)" | sudo tee /boot/initrd.img-"$(uname -r)" > /dev/null
The source, for the generic method, is in the kernel docs.
GRUB setting (not universal)
Another clean and automatic method is to set the custom initrd image via GRUB. Note that this method has been reported to work, but it didn’t on my O/S.
$ sudo cp dsdt.aml /boot/patched-dsdt.aml
$ echo acpi /boot/patched-dsdt.aml | sudo tee -a /boot/grub/custom.cfg
$ sudo update-grub
This should work on systems where the /boot/grub/custom.cfg
is included by default; on Ubuntu, this rule is encoded in /etc/grub.d/41_custom
:
$ cat /etc/grub.d/41_custom
#!/bin/sh
cat <<EOF
if [ -f \${config_directory}/custom.cfg ]; then
source \${config_directory}/custom.cfg
elif [ -z "\${config_directory}" -a -f \$prefix/custom.cfg ]; then
source \$prefix/custom.cfg
fi
EOF
In case a given distro doesn’t include /boot/grub/custom.cfg
, just add the rule file.
Result
On reboot, support for S3 sleep state will be advertised:
$ sudo dmesg | grep 'ACPI.*supports S'
[ 0.648536] ACPI: PM: (supports S0 S3 S4 S5)
# Go to sleep!
#
$ systemctl suspend
Watch out! After suspending, closing the laptop lid will wake up the system!! I don’t know what precisely causes this, but fixing this behavior is outside the scope of the article.
Debugging
If the procedure doesn’t yield the desired effect, my advice is to first rule out problems with the boot override; disable the S4 sleep support (just comment or remove the corresponding block), and if, after boot, the change has been successfully applied:
$ sudo dmesg | grep 'ACPI.*supports S'
[ 0.309933] ACPI: PM: (supports S0 S5)
then the problem is in the DSDT patch.
Methods not working
The following methods either didn’t work on my system, or they’re not robust:
- Setting
GRUB_EARLY_INITRD_LINUX_CUSTOM
won’t work on at least some operating systems (ie. Fedora); - Loading the ACPI SSDTs from EFI variables yielded a write error on my system;
- Loading ACPI SSDTs from configfs didn’t work on my system as well, due to configfs not having the required directory after mount;
- Various ways to enable advanced firmware settings on the given laptop model.
I don’t exclude that with appropriate changes, some of the methods above can work.
References
The following are some references on the topic. Watch out: the Microsoft and Anadtech references are biased and/or deceptive, and they’re present only for completeness.
- ACPI DSDT, on the OSDev wiki
- Sleeping states, on Microsoft Learn
- Modern Standby vs S3 on Microsoft Learn
- S0ix sleeping state, on Anandtech
- Another article, with context, on enabling an S3 support on a different Yoga model, on Windows
- Kernel DSDT patch frequently mentioned
Conclusion
Removal of the S3 sleep state is a terrible state of affairs.
It’s not clear who drove this change; according to some, Microsoft initiated it, in its push for the “Connected/Modern Standby”; according to others, the producers did.
I believe that Microsoft has been the drive, in particular, considering that they’ve been pushing the Modern Standby through a campaign of deceptive half-truths. Don’t forget that Microsoft ships on the near-totality of the (non-Mac) desktop PCs, and they have an enormous leverage on producers.
The future is uncertain. Some hardware producers do make available the S3 option in the firmware; vote with your wallet (and some noise 😁).