Linux: ACPI: Fix problems with Suspend, Resume, and Missing devices using acpi_osi=

A workaround that allows Linux (the kernel) to describe itself as a Windows version in order to enable core ACPI functions that are otherwise disabled and often cause problems for suspend, resume, and embedded Wifi, Bluetooth, and other devices.


Background

Since the mid-2000s most manufacturers of (especially) laptop, notebook and tablet PCs have been customising the Advanced Configuration and Power Interface (ACPI) in the firmware (BIOS or UEFI) for versions of Microsoft Windows operating system (OS). In particular, the Differentiated System Description Table (DSDT), which contains byte-code that the active operating system executes, is written such that unless the running OS reports itself as Windows many functions are disabled or severely restricted.

Since 2007 I've had cause to exhaustively disassemble and understand the ACPI DSDT code of many PCs. Fortunately the byte-code stored in the DSDT is easily reversed into source-code that can be understood using ACPI tooling in most Linux distributions.

As a result I recognised several common patterns in the code. One such pattern is that the DSDT enables many functions based on the name/version of the operating system that is running (known as the Operating System Interface (OSI) name). Depressingly, the OSI code that tests which operating system version is running almost always only checks for versions of Microsoft Windows.

If the code doesn't recognise the running operating system it only enables a bare minimum of functionality and leaves many functions and embedded devices (such as built-in WiFi, Bluetooth, Memory-Card, Fingerprint, Multimedia buttons, extended Function keys, external monitor ports) disabled or not completely configured.

This lack of configuration means that when Linux is the running OS it does not have all the functionality of the system hardware available to it. As a result critical functionality such as Suspend and Resume, Shutdown, and some devices, may not work or will behave eratically.

Workaround

Fortunately this issue was recognised by the Linux developers early on and they provided an option, acpi_osi=, that allows Linux to report itself as some other operating system in order to have the DSDT code enable the same functionality as it does for Microsoft Windows.

The technique requires identifying which versions of Microsoft Windows the ACPI DSDT recognises, selecting what appears to be the most recent version (on the basis that the latest version is likely to have all features enabled), and then add that version string to the Linux boot-time command-line.

Solution

I've used this technique so many times in helping Ubuntu and Linux users via the IRC Freenode network #ubuntu and ##linux channels that I've distilled it into a few reliable shell commands. These should work in any sh/ash/dash/bash shell.

It assumes the boot-loader is GRUB2 and that the system is running a version of Debian, Ubuntu, or one of their many derivatives. For the update-grub step on other distros it may be replaced with a call to grub-mkconfig ... - please check the documentation or support for the distribution in use.

These commands can also be downloaded as the ready-to-run shell script acpi_osi.sh. After downloading execute the script. It will ask you to confirm the proposed change before it goes ahead.

VERSION="$(sudo strings /sys/firmware/acpi/tables/DSDT | grep -i 'windows ' | sort | tail -1)"
echo 'Linux kernel command-line parameters required: acpi_osi=! "acpi_osi='$VERSION'"'
config() { sed -n '/.*linux[[:space:]].*root=\(.*\)/{s//BOOT_IMAGE=\1/ p;q;}' /boot/grub/grub.cfg; }
echo "Existing Command Line: ` config `"
sudo sed -i "s/^\(GRUB_CMDLINE_LINUX=.*\)\"$/\1 acpi_osi=! \\\\\"acpi_osi=$VERSION\\\\\"\"/" /etc/default/grub
echo "Modified Command Line: ` config `"
sudo update-grub

Don't be put off by all the \\ characters. Those are required in order to allow sed to insert literal double-quotation marks into the GRUB_CMDLINE_LINUX string.

/etc/default/grub will have added the acpi_osi parameters to the GRUB_CMDLINE_LINUX so it will look something like this (there may be other kernel options that were added on some previous occassion):

GRUB_CMDLINE_LINUX="acpi_osi=! \"acpi_osi=Windows 2015\""

/boot/grub/grub.cfg will have these parameters added to every linux command-line, e.g.

linux   /vmlinuz-4.13.0-16-lowlatency root=/dev/mapper/VG_OS-ubuntu_16.04_rootfs ro acpi_osi=! "acpi_osi=Windows 2015" splash $vt_handoff

Once rebooted the running kernel command-line will show something like:

$ cat /proc/cmdline
BOOT_IMAGE=/vmlinuz-4.13.0-16-lowlatency root=/dev/mapper/VG_OS-ubuntu_16.04_rootfs ro acp_osi=! "acpi_osi=Windows 2015" splash

Testing

Reboot and test the problem functionality; hopefully this will have cured or at least improved the system behaviour.

If there is no discernable improvement it may require using another of the Windows OSI strings the DSDT recognises. Therefore list them all manually rather than automatically using the last one, with:

sudo strings /sys/firmware/acpi/tables/DSDT | grep -i windows | sort

Determine which alternative OSI name you want to try and then replace any existing "acpi_osi=Windows..." in the GRUB_CMDLINE_LINUX string with this alternate in the file /etc/default/grub, execute update-grub again, reboot and test once again.

If this doesn't solve (all) the problems I'd still recommend keeping the acpi_osi=Windows... entry using the latest Windows version because it will still ensure all ACPI functionality is fully functional (non-obvious functions like battery power-saving, CPU throttling, etc.)