In this article I’d like to describe some of the typical BIOS design flaws of a modern netbook, and methods which can be used to locate, dissect and heal the bugs.
First of all, ACPI is a generic management interface which controls a lot of hardware functions on modern computers ranging from power and battery control to detecting external displays. It consists of a several configuration tables, one of which contains code for a virtual machine to be executed by an operating system kernel. The latter was added to make the system as flexible as possible.
Theoretically, this system should have been made hardware-specific chipset drivers unneccessary. It is quite potent (not to say overblown) and is definitely able to accomplish the task; Macs are a good example, as they use ACPI extensively and correctly.
In reality, however, PC-based hardware vendors would supply buggy and incomplete ACPI tables for their systems, and vendor lock-in appears not as the least reason to me. Therefore, such systems require numerous nontrivial workarounds, often flawed and undocumented. I’ve attempted to fix the ACPI itself for a particular computer instead.
The system I have is a Samsung N250+ netbook. It has quite good hardware (except for battery-hungry and quirky Broadcom WLAN card which I have replaced with a better one by Atheros), but the ROM BIOS quality is really poor. At the moment of release there even was no way of enabling the wireless card on a Linux system; its state could be changed via CMOS Setup, through. Now there is a kernel driver, but it uses a fundamentally flawed approach, too (and it has some usability problems).
Examining the current state
As can be seen on line 725 of the source, this driver uses SMI calls (via an interface called SABI) to set the backlight level, change performance mode (this actually changes just the fan speed) and control the wireless card power. An SMI call is a command which leads CPU into so-called System Management Mode: a special processor and chipset feature which is equally similar to a hypervisor and a rootkit.
BIOS can set up the chipset to intercept certain operations (like accessing memory or I/O ports) and launch SMM, which cannot be detected nor interrupted by OS. It then may execute arbitrary code: for example, SMM is used to trick old OSes (think of DOS) to believe that a USB mouse is actually a PS/2 one without any changes to the OS itself. Moreover, a memory area which belongs to SMM under no circumstances can be accessed by the OS, making it impossible to study its behavior directly.
Hopefully, in this case SMI calls probably just change a byte or two, and it may be possible to determine their locations without examining SMM code itself.
Next, let’s take a look at the ACPI tables. There are plenty of them, but we need one called DSDT—the biggest and most important one which contains handlers for a huge number of possible hardware events.
To dump the tables and rework the code two utilites are required:
iasl. On a Debian-based system they can be found in packages with same names.
To ease the demonstration, I’ve uploaded the table on github; initial state can be checked out here. As you can see, the table is quite big at more than 5000 lines; tables more than 25000 lines long are not uncommon.
At an attempt to compile the table back to bytecode (try
make) without any changes, the compiler will spit out a few warnings and errors. They are quite trivial to fix just by looking at error messages and ACPI specification; this Gentoo forum thread has some pointers as well. Note that while the manual is 7 years older than my notebook, the latter has roughly same quirks as described (and fixes do work, too). Fixed version can be found at this commit.
My netbook has LED backlight, which means that its brightness could be controlled simply by keeping it on for a known part of time, e.g. to dim it by 30% one could keep it on just for 70% of time. To make the flickering invisible, this switching (called PWM) is done on a frequency far above the sensitivity level of a human eye—200 kHz is good enough.
In this case, PWM duty cycle is probably controlled by an integrated graphics controller. We can see it on a PCI bus:
1 2 3 4 5
00:02.0 are an address of the device on the bus. With this address, we can inspect and modify the properties of the device, as Linux provides numerous sysfs hooks for that purpose. One of them is an ability to read and write PCI configuration space: a memory block of 256 bytes used to configure a PCI device. First 64 of them have predefined meaning; other ones can be freely used by device vendor.
Let’s check what changes in the device configuration when we alter backlight level with an SMM-based driver (note that it would be perfectly possible with a closed-source driver or even on Windows: all you need is a tool to scrap the configuration space):
1 2 3 4 5 6 7 8 9 10 11 12 13 14
So, the byte at index
0xf4 controls the brightness level. This can be verified by running
sudo setpci -s 00:02.0 f4.b=80 (replacing the
80 with a brightness value).
Now, let’s set up DSDT code to update this value (and possibly determine the cause for it to not work in first place).
According to ACPI specification (section B.6.2, page 704), a compliant graphic adapter description should implement methods
_BQC. Our DSDT has these methods defined at line 1767. Here is the annotated source code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
To make this work via PCI configuration space writing, a new field first should be defined in an ACPI structure describing that space. The adapter has an address of
00:02.0; this corresponds to a value of
0x00020000 ACPI can understand as an address of a PCI device (section 6.1.1 on page 200). A device with such an address is defined at line 1325; the PCI configuration space description follows.
As was said, first 64 (
0x40) bytes in this space are reserved for internal purposes. Because of that, ACPI does not even include them to the region: it is defined as
OperationRegion (IGDP, PCI_Config, 0x40, 0xC0), where third argument means a count of bytes skipped from the beginning. Our brightness byte with a whole-space address of
0xf4 is located at position
0xb4 in this region.
Below that, field definitions are located. The whole
Field construct represents a stream of bit fields (the field length is defined in bits, not bytes), where one can be placed after another, or at a particular
Offset (contrary to fields, offsets are given in bytes). Let’s call our brightness level field
BLVL and incorporate it to the structure:
1 2 3 4 5 6 7 8 9 10
As ACPI has hierarchical naming system, our field is now globally accessible as
\_SB.PCI0.IGD0.BLVL (the name is defined by the nesting of
Scope clauses). We can now rewrite three backlight control methods to access
BLVL field directly:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
Updated DSDT can be found in the repository.
While testing the changes, I’ve encountered a need for debugging the code. This can be done with a
Store (something, Debug) command. Don’t forget to enable ACPI debug output by adding
acpi.debug_level=0x1f parameter to kernel command line.
The changed and compiled (
iasl -tc dsdt.dsl) DSDT should now replace vendor-provided one. To achieve the goal, we could reflash the BIOS—but it is not even known where DSDT is located in it. So, a simpler approach can be used: Linux can be instructed to ignore the DSDT found in system RAM and load a provided one instead. To do that, you should place compiled file
dsdt.hex (verify that it contains a C array definition; the
-tc option instructs
iasl to emit one) in
include/ directory of Linux source tree and set option
dsdt.hex. (To be able to access the latter, you should turn off
CONFIG_STANDALONE; it is named “Select only drivers that do not need compile-time external firmware” and located in “Generic driver options”.)
Compile the modified kernel and reboot. Voilá: ACPI driver can now set backlight level. (Try
echo 7 >/sys/class/backlight/acpi_video0/brightness).
To locate other fields in the PCI configuration space which might be changed by SMM-based driver, I wrote a simple script. Note that some devices, namely PCI-Express bridges and network adapters, have a lot of spurious changes which happen in background on their own.
Sadly, not the fan speed nor wireless rfkill switch state were not linked to any changes within the configuration space. I guess that they may be done through Embedded Controller and via SMBus interface, which means that no permanent changes are accumulated in the system RAM itself, and all of the processing is buried deep inside the SMM BIOS.
Moreover, even if I could find the rfkill interface, there is no standard way to describe it in ACPI. On laptops where it actually is exported via ACPI, there is a platform-specific driver handling that (contrary to the backlight, which can be controlled in a generic way).