In this article, I would like to describe the typical design errors found in the BIOS of a modern netbook, and methods for detecting, studying and correcting them.
Introduction
So,
ACPI is a universal interface to some of the hardware functions of modern computers, from power management and battery monitoring to requesting the capabilities of connected external displays.
It consists of several configuration
tables , one of which contains the code for a virtual machine running in the operating system kernel. A virtual machine that significantly complicates the implementation of ACPI has been added to make the system as flexible as possible.
')
In theory, such a system should have made drivers for the chipset unnecessary. She is very powerful (if not to say “bloated”), and can quite cope with such a task; for example, on Macs, ACPI is used quite widely and competently.
In reality, however, suppliers of PC-compatible systems often include erroneous or incomplete ACPI tables, and not the last reason for this is the vendor lock-in. Thus, these systems require the use of non-trivial workarounds, often undocumented and also not always correctly working. Instead, I tried to fix ACPI for one such system.
The computer that I have is a Samsung N250 + netbook. He has a pretty good “hardware” (with the exception of wanting a battery and generally a Broadcom WiFi card curve, which I immediately replaced with a similar Atheros), but the quality of the BIOS is very sad. At the time of release, it was not even possible to turn on (or turn off) WiFi from a Linux system: its state could only be changed through the CMOS Setup Utility. At the moment, the driver is there, but it uses a fundamentally flawed approach, and suffers from some
problems .
Examine current status
Support for the capabilities of the netbook, for which the code in the ACPI was missing, was originally implemented in the kernel module of the
easy slow down manager , which was eventually adopted into the kernel as
samsung-laptop.c .
As seen on
line 725 of the source code , this driver uses SMI calls (and a Samsung interface called SABI) to set the backlight level, change the “performance mode” (which actually only changes the fan speed) and turn on the wireless module.
An SMI call is a command that causes the CPU to activate the so-called system management mode (
SMM ), a special feature of the chipset and processor that is equally similar to the hypervisor and rootkit.
The BIOS can configure the chipset so that it intercepts certain operations (for example, access to selected regions of memory or I / O ports) and activates SMM, the OS cannot detect the fact of entering SMM (except by indirect methods), nor interrupt it, or to prevent. After that, the BIOS can execute arbitrary code: for example, SMM is used to emulate support for a PS / 2 mouse for old OS (for example, DOS) when a USB mouse is connected. Moreover, the memory area allocated for the SMM processor is not accessible under any circumstances by the OS, making a direct analysis of the logic of its operation impossible.
Fortunately, in this case, SMI calls are likely to change only a couple of bytes, and, if lucky, you can determine their location without studying the SMM mode code.
Let's take a closer look at the ACPI table. There are many types of them, but in this case only one is important,
DSDT is a table with bytecode handlers for many system events.
To extract the table from the system and modify its code, we need two utilities: “acpidump” and “iasl”. On a Debian-like OS, they are in packages with the same name.
$ sudo acpidump -o dsdt.aml -b
$ iasl -d dsdt.aml # decompiles dsdt.aml to dsdt.dsl
For clarity, I designed a table with a history of my changes as
a github repository ; the initial state is contained in
this commit . As you can see, the table is very long: more than 5000 lines. Tables longer than 25,000 rows are quite regular.
If you try to compile the table back into bytecode (type
make
) without any changes, the compiler will display several errors and notes. They are fairly easy to fix by looking only at the messages and
the ACPI specification ; There are some pretty good tips on this
Gentoo forum thread . By the way, it is seven years older than my netbook, but the tips are still relevant. The corrected table can be seen
here .
We fix the backlight
My netbook has LED backlighting, and therefore its brightness can be changed by simply turning it on for a certain proportion of identical time intervals, for example, to dim by 30%, you can keep it on 70% of the time. So that the flicker is not noticeable, this switching (
PWM ) occurs at a frequency that is deliberately higher than the sensitivity of the human eye - say, 200 kHz is enough.
In this case, the
duty cycle of the PWM is likely to be changed by the integrated graphics controller. Here it is on the PCI bus:
$ lspci
00: 00.0 Host bridge: Intel Corporation N10 Family DMI Bridge
00: 02.0 VGA compatible controller: Intel Corporation N10 Family Integrated Graphics Controller
00: 02.1 Display controller: Intel Corporation N10 Family Integrated Graphics Controller
<...>
The numbers “00: 02.0” is the address of the device on the bus. Knowing this address, you can query or change device parameters, since Linux provides many control points via
sysfs . One of them allows you to read and write
PCI configuration space : a block of 256 bytes in which device settings are stored. The first 64 bytes in this block have a specification-specific meaning, and the rest can be freely used by the manufacturer for their needs.
Let's check what happens with the configuration when the backlight level changes (although here is an example for Linux with an open driver, all this can be done for a closed driver or even on Windows; you can also read configuration space in it):
# echo 7> / sys / class / backlight / samsung / brightness
# hexdump -C /sys/bus/pci/devices/0000\:00\:02.0/config> config-1
# echo 5> / sys / class / backlight / samsung / brightness
# hexdump -C /sys/bus/pci/devices/0000\:00\:02.0/config> config-2
# diff -u config-1 config-2
--- config-1 2011-09-05 01: 06: 13.326930250 +0400
+++ config-2 2011-09-05 01: 06: 21.503828025 +0400
@@ -13.5 +13.5 @@
000000c0 00 00 00 00 01 00 00 00 00 00 00 00 a7 00 00 00 | ................ |
000000d0 01 00 22 00 00 00 00 00 00 00 00 00 00 00 00 00 | | .. "............. |
000000e0 00 00 00 00 00 00 00 00 00 80 00 00 00 00 00 00 | ................ |
-000000f0 79 00 00 00 ff 00 00 00 ad 0f 00 00 7c 0e 5c 7f | y ........... |. \. |
+ 000000f0 79 00 00 00 73 00 00 00 ad 0f 00 00 7c 0e 5c 7f | y ... s ....... |. \. | |
00000100
Thus, the byte at address 0xf4 controls the backlight level. You can verify this by running the command
sudo setpci -s 00:02.0 f4.b=80
(replacing 80 with the desired level of illumination).
Now let's rewrite DSDT so that this value is updated (and, perhaps, in the process, it will be possible to find out why the backlight control via ACPI does not work at all):
According to
the ACPI specification (Appendix B, Section 6.2, p. 704), a compatible graphic adapter description must implement the
_BCL
,
_BCM
and
_BQC
. In our DSDT, these methods are defined on
line 1767 . Here is their commented source code:
Method (_BCL, 0, NotSerialized) { Or (VDRV, 0x01, VDRV) Return (Package (0x08) { 0x64, 0x05, 0x0F, 0x18, 0x1E, 0x2D, 0x3C, 0x50 }) } Method (_BCM, 1, NotSerialized) { Divide (Arg0, 0x0A, Local0, Local1) If (LEqual (Local0, 0x00)) { BRTW (Arg0) } } Method (_BQC, 0, NotSerialized) { Divide (BRTL, 0x0A, Local0, Local1) If (LEqual (Local0, 0x00)) { Return (BRTL) } }
To change this code to work through the PCI configuration space, you need to add a new field to the structure that describes this space. Adapter address 00: 02.0 corresponds to the value 0x0002000 in ACPI (Section 6.1.1, p. 200). The device with this address is defined on
line 1325 ; the definition is followed by a description of the PCI configuration space.
As mentioned, the first 64 (0x40) bytes in this space are reserved for internal use. Because of this, ACPI does not even include them in the region; it is defined as an
OperationRegion(IGDP, PCI_Config, 0x40, 0xC0)
, where the third argument is the offset from the beginning of the PCI_Config area. The brightness control field is located at 0xf4 in the whole space, and 0xb4 in this region.
The region definition is followed by field definitions. The entire Field construct is a stream of bit fields (the length is defined in bits, not bytes), one after the other, interleaved by the indication of offsets (Offset), specified, in contrast, in bytes. Let's call our field BLVL and include it in the structure:
@@ -1347,7 +1347,8 @@ Device (IGD0) Offset (0xB0), Offset (0xB1), CDVL, 5, - Offset (0xB2), + Offset (0xB4), + BLVL, 8, Offset (0xBC), ASLS, 32 }
Since the ACPI name system is hierarchical, this field is now available globally under the name
_SB.PCI0.IGD0.BLVL
(the name is composed of the nested constructs Device and Scope), and the methods for controlling the brightness can now be rewritten so that they can access the BLVL field directly:
Method (_BCL, 0, NotSerialized) { Or (VDRV, 0x01, VDRV) Return (Package (0x12) { 0xEE, 0x22, 0x01, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF }) } Method (_BCM, 1, NotSerialized) { Store (Arg0, \_SB.PCI0.IGD0.BLVL) } Method (_BQC, 0, NotSerialized) { Return (\_SB.PCI0.IGD0.BLVL) }
The updated DSDT also lies in the
repository .
While testing my changes, I needed to debug the code in DSDT. The debug output works with the help of the Store view command (something, Debug). In order for Linux to send a message to the log, you need to add the kernel parameter
acpi.debug_level=0x1f
.
Changed and compiled (
make
or
iasl -tc dsdt.dsl
) DSDT now needs to be sent to the place that the supplier provided. To do this, it would be possible to reflash the BIOS - but I don’t even know the internal structure of the BIOS (and, if we are already talking about this, the method of flashing it). It's easier and safer to instruct Linux to use our DSDT instead of the system one. To do this, you need to compile dsdt.hex (the -tc option instructs iasl to generate the C array, as required), put it in the kernel's include / source directory and set the CONFIG_ACPI_CUSTOM_DSDT_FILE option to “dsdt.hex”. (It is not available if the CONFIG_STANDALONE option, “Select drivers for external firmware” in the “Generic driver options” is enabled.)
You can build a kernel, install it, and reboot. Voila: now changing the backlight brightness works with the standard ACPI driver. (For example,
echo 7 >/sys/class/backlight/acpi_video0/brightness
).
Other features
In order to find other similar fields that are modified by the code in SMM, I wrote a
simple script . It should be noted that some devices, namely PCI Express bridges and network adapters, generate many spontaneous changes.
Unfortunately, neither the fan speed, nor the switch of the wireless module appeared to be associated with any changes in the configuration space. Probably, they are made through the
Embedded Controller or the
SMBus interface, which means there are no permanent changes in the system memory.
Moreover, even if I found the interface for turning off the wireless module, I could not use the standard way of presenting it to the system - due to the lack of such a method in nature. On laptops, where this interface is really specified in ACPI, there is a platform-specific driver for its processing (as opposed to backlighting, for which there is a common standard).