Skip to content

Instantly share code, notes, and snippets.

@ahaggart
Last active April 21, 2023 09:31
Show Gist options
  • Save ahaggart/43e11cf0434f672200d5b239f55a2dc6 to your computer and use it in GitHub Desktop.
Save ahaggart/43e11cf0434f672200d5b239f55a2dc6 to your computer and use it in GitHub Desktop.
Ubuntu X1 Carbon 6th Gen Sleep Patching

A good night's sleep for the Lenovo X1 Carbon Gen6

This is a copy of the original blog post here, as a precaution against the link going dead. I've added a few of my own notes that I found helpful.

If you own a Lenovo X1 6th generation and want to use Linux, you're not going to be happy from the start. The entire range of hardware works splendidly and almost out of the box (let alone the fingerprint reader), but Lenovo decided to make one major change in the new UEFI firmware image. They removed traditional deep sleep (ACPI S3 sleep state) in favor of a new, Microsoft-driven sleep state called Windows Modern Standby, aka Si03. This sleep state doesn't fully turn off all components except for main memory, but puts the devices themselves into an ultra low-power state. This way, much like modern smartphones do, some devices can briefly wake up particular components of the system - most notably communication devices. The idea is, to have an always-connected feature, to e.g. download updates while sleeping or stay connected to a WiFi during sleep. This may or may not be preferable idea, but for me it is not.

On the Intel Core architecture, Linux doesn't deal with Si03 too well (as opposed to Intel Atom CPUs) and suspend energy consumption is around 4 watts on the X1, which is extremely high and won't even give you a single day of suspend time.

Luckly, together with great help from a few hackers from the Arch Linux forums (especially Ranguvar for sorting things out and fiji-flo for the working patch), we could establish a workaround. The idea is to patch the DSDT tables, a part of the ACPI firmware, written in ACPI machine language (a freakish place). Using that patch, sleep energy consumption drops from 4 to 0.15 watts on my machine.

If you follow this guide, no one is responsible for any damage to your hardware or any other kind of harming your machine. However, we didn't hear a single report of such cases as of now, and it works fine for all of us who tried it and reported to us.

The following steps should guide you to how you can do it yourself.

  1. Reboot, enter your BIOS/UEFI. Go to Config - Thunderbolt (TM) 3 - set Thunerbolt BIOS Assist Mode to Enabled. It has also been reported that Security - Secure Boot must be disabled.

  2. Install iasl (Intel's compiler/decompiler for ACPI machine language) and cpio from your distribution.

    sudo apt install iasl cpio
  3. Get a dump of your ACPI DSDT table.

    cat /sys/firmware/acpi/tables/DSDT > dsdt.aml
  4. Decompile the dump, which will generate a .dsl source based on the .aml ACPI machine language dump.

    iasl -d dsdt.aml
  5. Download the patch and apply it against dsdt.dsl:

    patch --verbose < X1C6_S3_DSDT.patch

    Note: Hunk 6 may fail due to different specified memory regions. In this case, simply edit the (almost fully patched) dsdt.dsl file, search for and entirely delete the two lines reading solely the word "One". You can look at hunk 6 in the patch file to see how the lines above and below look like if you're unsure.

    Plan B: If this does not work (patch is rejected): It has been the case, that certain UEFI settings may lead to different DSDT images. This means that it may be possible that the above patch doesn't work at all with your decompiled DSL. If that is the case, don't worry: Go through the .patch file in your editor, and change your dsdt.dsl by hand. This means locating the lines which are removed in the patch and removing them in your dsl. The patch contains only one section at the end which adds a few lines - these are important and make the sleep magic happen.

    Make sure that the hex number at the end of the first non-commented line is incremented by one (reading DefinitionBlock, should be around line 21). E.g., if it was 0x00000000 change it to 0x00000001. Otherwise, the kernel won't inject the new DSDT table.

  6. Recompile your patched version of the .dsl source.

    iasl -ve -tc dsdt.dsl

There shouldn't be any errors. When recompilation was successful, iasl will have built a new binary .aml file including the S3 patch.

  1. Now we have to create a CPIO archive with the correct structure, which GRUB can load on boot (much like initrd is loaded). We name the final image acpi_override and copy it into /boot/.

    mkdir -p kernel/firmware/acpi
    cp dsdt.aml kernel/firmware/acpi
    find kernel | cpio -H newc --create > acpi_override
    cp acpi_override /boot
  2. We yet have to tell GRUB to load our new DSDT table on boot in its configuration file, usually located in /boot/grub/grub.cfg or something similar. Look out for the GRUB menu entry you're usually booting, and simply add our new image to the initrd line. It should look somewhat like that (if your initrd line contains other elements, leave them as they are and simply add the new ACPI override):

    initrd /acpi_override /initramfs-linux.img
    

    Note: You will need to do this again when your distribution updates the kernel and re-writes the GRUB configuration. I'm looking for a more automated approach, but was too lazy to do it so far.

    Note: To persist this change across updates, copy the Ubuntu section from /boot/grub/grub.cfg and paste it into /etc/grub.d/40_custom. This will add another entry to the GRUB boot menu. I edited the title to say Ubuntu (Sleep Patch) to distinguish the two entries.

  3. GRUB needs to boot the kernel with a parameter setting the deep sleep state as default. The best place to do this is /etc/default/grub, since that file is not going to be overwritten when the GRUB config becomes regenerated. Simply add mem_sleep_default=deep to the GRUB_CMDLINE_LINUX_DEFAULT configuration option. It should look somewhat like that:

    GRUB_CMDLINE_LINUX_DEFAULT="quiet mem_sleep_default=deep"
    

    Note: If you are using 40_custom from the note above, this won't work. You'll need to manually add mem_sleep_default=deep to the entry, right above the initrd line. You should see quiet, add it right after that (before $vt_handoff). After this run sudo update-grub to add the custom menu entry to the boot menu.

  4. Reboot.

If everything worked, you shouldn't see any boot errors and the kernel will confirm that S3 is working. The output of the following commands should look the same on your machine:

dmesg | grep ACPI | grep supports
    [    0.213668] ACPI: (supports S0 S3 S4 S5)
cat /sys/power/mem_sleep
    s2idle [deep]

In most setups, simply closing the lid will probably trigger deep sleep. If you're using a systemd-based distribution (most of which are), you can also verify if it works on the command line:

systemctl suspend -i

Once again, many thanks to Ranguvar for the great collaboration on the Arch forums, and to fiji-flo for managing to hack the first fully working patch. Also, to whomever wrote the article on DSDT patching in the glorious Arch Wiki. And the entire Arch community in general, you're wonderful.

@jclaveau
Copy link

jclaveau commented Apr 21, 2023

First thank you very much for updating / sharing this!

EDIT: There is now an option in the bios to handle this without any Grub config change https://askubuntu.com/a/1384945/1026501

Efficiency

  • In my case, before S3 is enabled, my laptop used 6W only if no program was launched. With a Brave and a Firefox started, it consumed 10w in sleep mode!
  • Once S3 enabled, it consumes 2W
  • I realized afterwards that unplugging my USB-C / HDMI dongle reduced the consumed power by 1.5W. So I guess I had a ~4W consumption before enabling S3. Without the dongle, my laptop now consumes 0.5W!

A little difference in the patch

Following your process I first tried with a line like

        initrd /acpi_override /boot/initrd.img-5.19.0-38-generic

in /etc/grub.d/40_custom
But I got a kernel panic after /acpi_override was not found.
Adding /boot fixed it.

        initrd /boot/acpi_override /boot/initrd.img-5.19.0-38-generic

Btw I'm using Kubuntu 22.10

A slightly different patch

I had to manually patch my acpi table which is covered. In the patch below you can see that this part is already existing inside a condition:

-        Name (\_S3, Package (0x04)  // _S3_: S3 System State
-        {
-            0x05,
-            0x05,
-            0x00,
-            0x00
-        })
    If ((STY0 == 0x00))
    {
        Name (\_S3, Package (0x04)  // _S3_: S3 System State
        {
            0x05,
            0x05,
            0x00,
            0x00
        })
    }

With, in my case STYO defined like this at the beginning:

        STY0,   8

I have no clue what this variable is about but, at the only other place its used, its value must be 1:

    If ((STY0 == 0x01))
    {
        Scope (\_SB.PCI0.GFX0)
        {
            Method (_DEP, 0, NotSerialized)  // _DEP: Dependencies
            {
...

This is the option provided here https://askubuntu.com/a/1384945/1026501

Anyway removing the condition worked in my case!

Here is the patch corresponding to my laptop:

--- ./dsdt.1.dsl        2023-04-21 10:47:39.895795060 +0200
+++ dsdt.manually-patched.dsl   2023-04-21 10:32:04.260188987 +0200
@@ -18,9 +18,8 @@
  *     Compiler ID      "INTL"
  *     Compiler Version 0x20160527 (538314023)
  */
-DefinitionBlock ("", "DSDT", 2, "LENOVO", "SKL     ", 0x00000000)
+DefinitionBlock ("", "DSDT", 2, "LENOVO", "SKL     ", 0x00000001)
 {
-    External (_GPE.TBNF, MethodObj)    // 0 Arguments
     External (_PR_.BGIA, UnknownObj)
     External (_PR_.BGMA, UnknownObj)
     External (_PR_.BGMS, UnknownObj)
@@ -40,19 +39,15 @@
     External (_PR_.PDTS, UnknownObj)
     External (_PR_.PKGA, UnknownObj)
     External (_PR_.POWS, UnknownObj)
-    External (_PR_.PR00, DeviceObj)
     External (_PR_.PR00.LPSS, PkgObj)
     External (_PR_.PR00.TPSS, PkgObj)
     External (_PR_.TRPD, UnknownObj)
     External (_PR_.TRPF, UnknownObj)
-    External (_SB_.GGIV, MethodObj)    // 1 Arguments
-    External (_SB_.GGOV, MethodObj)    // 1 Arguments
     External (_SB_.IETM, DeviceObj)
     External (_SB_.IETM.DPTE, UnknownObj)
     External (_SB_.PCI0.B0D4.NPCC, PkgObj)
     External (_SB_.PCI0.CTCD, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.CTCN, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.GFX0, DeviceObj)
     External (_SB_.PCI0.GFX0.AINT, MethodObj)    // 2 Arguments
     External (_SB_.PCI0.GFX0.ALSI, UnknownObj)
     External (_SB_.PCI0.GFX0.CBLV, UnknownObj)
@@ -71,109 +66,81 @@
     External (_SB_.PCI0.HDAS.PS3X, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.HIDW, MethodObj)    // 4 Arguments
     External (_SB_.PCI0.HIWC, MethodObj)    // 1 Arguments
-    External (_SB_.PCI0.ISP0, DeviceObj)
-    External (_SB_.PCI0.LPCB.EC__.HKEY.DYTC, MethodObj)    // 1 Arguments
     External (_SB_.PCI0.LPCB.H_EC.XDAT, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.PAUD.PUAM, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.PEG0, DeviceObj)
-    External (_SB_.PCI0.PEG0.PEGP, DeviceObj)
     External (_SB_.PCI0.PEG0.PG00.PEGP, DeviceObj)
-    External (_SB_.PCI0.PEG1, DeviceObj)
     External (_SB_.PCI0.PEG1.PG01.PEGP, DeviceObj)
-    External (_SB_.PCI0.PEG2, DeviceObj)
     External (_SB_.PCI0.PEG2.PG02.PEGP, DeviceObj)
     External (_SB_.PCI0.PTDP, UnknownObj)
     External (_SB_.PCI0.RP01.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP01.PXSX, DeviceObj)
-    External (_SB_.PCI0.RP01.PXSX.WGST, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP01.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP02.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP02.PXSX, DeviceObj)
     External (_SB_.PCI0.RP02.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP02.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP03.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP03.PXSX, DeviceObj)
     External (_SB_.PCI0.RP03.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP03.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP04.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP04.PXSX, DeviceObj)
     External (_SB_.PCI0.RP04.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP04.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP05.PON_, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP05.PWRG, UnknownObj)
-    External (_SB_.PCI0.RP05.PXSX, DeviceObj)
     External (_SB_.PCI0.RP05.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP05.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP05.RSTG, UnknownObj)
     External (_SB_.PCI0.RP05.SCLK, UnknownObj)
     External (_SB_.PCI0.RP06.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP06.PXSX, DeviceObj)
     External (_SB_.PCI0.RP06.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP06.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP07.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP07.PXSX, DeviceObj)
     External (_SB_.PCI0.RP07.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP07.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP08.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP08.PXSX, DeviceObj)
     External (_SB_.PCI0.RP08.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP08.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP09.PEGP.NVST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP09.PON_, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP09.PWRG, UnknownObj)
-    External (_SB_.PCI0.RP09.PXSX, DeviceObj)
     External (_SB_.PCI0.RP09.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP09.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP09.RSTG, UnknownObj)
     External (_SB_.PCI0.RP09.SCLK, UnknownObj)
     External (_SB_.PCI0.RP10.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP10.PXSX, DeviceObj)
     External (_SB_.PCI0.RP10.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP10.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP11.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP11.PXSX, DeviceObj)
     External (_SB_.PCI0.RP11.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP11.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP12.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP12.PXSX, DeviceObj)
     External (_SB_.PCI0.RP12.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP12.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP13.PON_, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP13.PWRG, UnknownObj)
-    External (_SB_.PCI0.RP13.PXSX, DeviceObj)
     External (_SB_.PCI0.RP13.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP13.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP13.RSTG, UnknownObj)
     External (_SB_.PCI0.RP13.SCLK, UnknownObj)
     External (_SB_.PCI0.RP14.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP14.PXSX, DeviceObj)
     External (_SB_.PCI0.RP14.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP14.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP15.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP15.PXSX, DeviceObj)
     External (_SB_.PCI0.RP15.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP15.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP16.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP16.PXSX, DeviceObj)
     External (_SB_.PCI0.RP16.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP16.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP17.PON_, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP17.PWRG, UnknownObj)
-    External (_SB_.PCI0.RP17.PXSX, DeviceObj)
     External (_SB_.PCI0.RP17.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP17.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP17.RSTG, UnknownObj)
     External (_SB_.PCI0.RP17.SCLK, UnknownObj)
     External (_SB_.PCI0.RP18.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP18.PXSX, DeviceObj)
     External (_SB_.PCI0.RP18.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP18.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP19.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP19.PXSX, DeviceObj)
     External (_SB_.PCI0.RP19.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP19.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP20.PON_, MethodObj)    // 0 Arguments
-    External (_SB_.PCI0.RP20.PXSX, DeviceObj)
     External (_SB_.PCI0.RP20.PXSX.WGST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.RP20.PXSX.WIST, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.SAT0.NVM1.VLPM, UnknownObj)
@@ -192,20 +159,15 @@
     External (_SB_.PCI0.XHC_.RHUB.PS0X, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.XHC_.RHUB.PS2X, MethodObj)    // 0 Arguments
     External (_SB_.PCI0.XHC_.RHUB.PS3X, MethodObj)    // 0 Arguments
-    External (_SB_.SGOV, MethodObj)    // 2 Arguments
-    External (_SB_.TBFP, MethodObj)    // 1 Arguments
     External (_SB_.TPM_.PTS_, MethodObj)    // 1 Arguments
     External (_SB_.UBTC.NTFY, MethodObj)    // 0 Arguments
     External (_TZ_.ETMD, IntObj)
     External (_TZ_.TZ00, DeviceObj)
     External (_TZ_.TZ01, DeviceObj)
-    External (ADBG, MethodObj)    // 1 Arguments
     External (ALSE, UnknownObj)
-    External (BNUM, UnknownObj)
     External (BRTL, UnknownObj)
     External (CFGD, UnknownObj)
     External (DIDX, UnknownObj)
-    External (DX2H, MethodObj)    // 2 Arguments
     External (GSMI, UnknownObj)
     External (IGDS, UnknownObj)
     External (LHIH, UnknownObj)
@@ -215,29 +177,6 @@
     External (M32L, UnknownObj)
     External (M64B, UnknownObj)
     External (M64L, UnknownObj)
-    External (MBGS, MethodObj)    // 1 Arguments
-    External (MMRP, MethodObj)    // 1 Arguments
-    External (MMTB, MethodObj)    // 1 Arguments
-    External (ODV0, IntObj)
-    External (ODV1, IntObj)
-    External (ODV2, IntObj)
-    External (ODV3, IntObj)
-    External (ODV4, IntObj)
-    External (ODV5, IntObj)
-    External (ODV6, IntObj)
-    External (ODV7, IntObj)
-    External (ODV8, IntObj)
-    External (ODV9, IntObj)
-    External (ODVA, IntObj)
-    External (ODVB, IntObj)
-    External (ODVC, IntObj)
-    External (ODVD, IntObj)
-    External (ODVE, IntObj)
-    External (ODVF, IntObj)
-    External (ODVG, IntObj)
-    External (ODVH, IntObj)
-    External (ODVI, IntObj)
-    External (ODVJ, IntObj)
     External (PC00, IntObj)
     External (PC01, UnknownObj)
     External (PC02, UnknownObj)
@@ -257,9 +196,6 @@
     External (PTTB, UnknownObj)
     External (RTBT, IntObj)
     External (SGMD, UnknownObj)
-    External (STDV, IntObj)
-    External (TBTD, MethodObj)    // 1 Arguments
-    External (TBTF, MethodObj)    // 1 Arguments
     External (TBTS, IntObj)
     External (XBAS, UnknownObj)
 
@@ -418,9 +354,7 @@
     Name (SS1, 0x00)
     Name (SS2, 0x00)
     Name (SS3, One)
-    One
     Name (SS4, One)
-    One
     OperationRegion (GNVS, SystemMemory, 0x4FF4F000, 0x0792)
     Field (GNVS, AnyAcc, Lock, Preserve)
     {
@@ -13982,8 +13916,7 @@
                     If ((DerefOf (Arg0 [0x00]) == 0x01))
                     {
                         ADBG (Concatenate ("POFF GPIO=", ToHexString (DerefOf (Arg0 [0x02]))))
-                        \_SB.SGOV (DerefOf (Arg0 [0x02]), (DerefOf (Arg0 [0x03]) ^
-                            0x01))
+                        \_SB.SGOV (DerefOf (Arg0 [0x02]), (DerefOf (Arg0 [0x03]) ^ 0x01))
                     }
 
                     If ((DerefOf (Arg0 [0x00]) == 0x02))
@@ -27869,16 +27802,13 @@
         0x00,
         0x00
     })
-    If ((STY0 == 0x00))
+    Name (\_S3, Package (0x04)  // _S3_: S3 System State
     {
-        Name (\_S3, Package (0x04)  // _S3_: S3 System State
-        {
-            0x05,
-            0x05,
-            0x00,
-            0x00
-        })
-    }
+        0x05,
+        0x05,
+        0x00,
+        0x00
+    })
 
     Name (\_S4, Package (0x04)  // _S4_: S4 System State
     {

Hoping my case could help. Thanks again!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment