flak rss random

fan service

ASUS laptops generally have a feature that lets the user toggle the fan speed. Fn-F5 on some models, Fn-F on others. The direct effect is to limit the fan speed, from whisper mode to megablast, and indirectly control performance. But it doesn’t work in OpenBSD, so I needed to write an ASUS ACPI WMI driver.

acpi

ACPI is a hardware abstraction layer that lets the computer tell the operating system how to do things. It provides functions in byte code which the OS interprets to perform certain tasks. So instead of having to write a driver for every CPU, you call the ACPI method _PSS and that tells you what to do.

Of course, vendors want to include functionality that isn’t defined in the standard, so they add some extra methods, and then you end up writing some custom drivers anyway, for the little buttons that turn the microphone on and off, etc. OpenBSD (and other OSs) have lots of little drivers for ThinkPads and whatnot.

ACPI method names are only four letters long, so what do we do if two vendors include a method called WMNB and how do we determine what it does? (Technically, the ACPI nodes are identified by seven letter names, and the methods live under them, so it’s not really a problem, but somebody got paid to make a solution anyway.)

wmi

The solution is an extension to ACPI called WMI, which stands for Windows Multiplies Irritations or something. You ask ACPI for a buffer called _WDG, and this contains a table mapping GUIDs (globally unique, says so right there in the name) to local method names. Looking through this table, we come across DCBA-FE-10-23-456789, which we recognize as a GUID of interest, and this tells us the method to call is WMNB, but now we know it’s the right one.

OpenBSD does not have a WMI driver, which is part of what I need to set the fan profile. There is a WMI driver in Linux, and it includes an ASUS support driver for a variety of functions I’m interested in.

first steps

First, I wrote a little acpi driver that attaches to PNP0C14. Read the _WDG buffer and print out all the GUIDs. Didn’t recognize any of the values, then realized I got the byte order wrong. Fixed that, saw something very close, realized I still didn’t get the byte order quite right. GUIDs are not big endian, they are not little endian, they are Goldilocks endian.

With that sorted out, I was able to enable events and get a printf every time I pushed a hotkey.

events

After receiving an interrupt and landing in our callback, we need to identify which event just happened by calling _WED. I did this, but was only getting 1 in response every time, regardless of key. I was a little stuck here. Very close to getting what I wanted, but without much clue as to what went wrong.

Turns out you can just look at your system’s ACPI code. OpenBSD runs acpidump at boot and saves all the files. Copy them to a work directory, run iasl -d, and have a look. A quick grep for _WED reveals the source.

            Method (_WED, 1, NotSerialized)
            {   
                If ((Arg0 == 0xFF))
                {
                    Return (GANQ ())
                }
     
                Return (One)
            }
            Method (GANQ, 0, Serialized)
            {
                P8XH (Zero, 0xF2)
                If (AQNO)
                {
                    AQNO--
                    Local0 = DerefOf (ATKQ [AQHI])
                    AQHI++ 
                    AQHI &= 0x0F
                    Return (Local0)
                }

                Return (Ones)
            }

Even without knowing anything at all about ASL, this looks like a function that dequeues an event, but only if called with an argument of 255. That was the bug! Once I called it with the correct argument, I started receiving different event codes for backlight toggle, fan toggle, AC attach, etc.

devices

Wrote some more code to handle getting and setting device state. There’s a magic incantation to call an ACPI method and pass it a device id, which could be the keyboard backlight, or the wifi led, or the fan profile, etc. Started with the keyboard backlight, since this was pretty easy to observe.

After fixing a few simple bugs like reversing the device ids for fnlock and backlight, I was able to blink the keyboard light by pressing F7. It’s all coming together.

Toggling the fan profile didn’t seem to accomplish anything, though. Under Windows, there is an audible difference switching to whisper mode, but I didn’t observe any change.

The Linux driver includes code for at least three different fan and performance profile settings. The same driver is used for a wide range of laptops, from thin and lights to gaming fraggers. According to the Linux driver, you can detect which devices are present by reading their state, then checking for a high bit, and then getting the value by masking off some low bits. This seemed to indicate I had a fan boost device, but it wasn’t responding to my changes.

After printing out the raw values, I noticed they were all -2. That doesn’t seem very likely. But it definitely has a high bit set, and masking off the low bits made it look reasonable. I think I missed something in the Linux driver? There’s lots of shifts and masks in lots of places.

Back to the ACPI dump, the WMNB method is a giant switch statement that ends with Return (0xFFFFFFFE). So sure enough, there’s the -2 for a missing device, but I still don’t know which device id I should be using.

Back to the Linux driver, there’s an epic norse define for ASUS_WMI_DEVID_THROTTLE_THERMAL_POLICY_VIVO which I hadn’t yet integrated because I was testing on a Zenbook, not a Vivobook. Added a check anyway, rebooted, and that’s a bingo! The Linux define name is a lie. Confirmed by inspecting the ASL code.

            If ((IIA0 == 0x00110019))
            {
                Return (FANL (IIA1))
            }

Now I had all the pieces. After a bit of cleanup and refactoring, my driver is complete. Kinda. There’s still a bunch of sensors that would be interesting to read, but my immediate concern has been addressed.

results

Pressing Fn-F after boot drops the fan speed noticeably, from a medium whir to barely a whisper. This has only a moderate effect on ordinary performance. The processor’s power budget is reduced, but not its (single core) clock speeds, so everything still runs smoothly. Lengthy compiles are slower, but I can also switch back to megablast turbo mode with the tap of a button.

Battery life is also much better. The fan itself is obviously eating less power. I think the CPU also enters a somewhat lower power state in whisper mode, or it’s willing to sleep a little deeper. It’s not clear everything that changes, and it varies by machine, but that’s the beauty of it. I wrote the driver on an AMD Zenbook, but it works without changes on an Intel Vivobook. Very convenient.

misc

I also looked at the FreeBSD driver. FreeBSD directly uses the ACPICA code interfaces, calling function like AcpiEvaluateObject which personally look kinda out of place in BSD driver code. Linux does too, but all the functions have been renamed to acpi_evaluate_object, so they resemble the local style. I’m not entirely sure of the history here, but I think the generic ACPICA was at some pointer extracted and generified from the Linux code. OpenBSD uses a custom AML interpreter, so we have functions like aml_evalname.

In OpenBSD, nearly all header files are kept with their C files. So both dsdt.c and dsdt.h live in dev/acpi. (Notable exception is core C files in kern and headers in sys.) As a first time acpi driver writer, it was very convenient that all the OpenBSD code I needed to reference, from sample drivers to data structures to helper functions, was all in one spot.

Linux separates things such that I was looking at C files in drivers/platform/x86 and header files in include/linux/platform_data/x86. And the ACPI code lives other places as well. It’s all very orderly, but at times it felt like navigating a grocery store that arranges products in alphabetical order. Logical, but not exactly cozy.

Posted 11 May 2025 01:57 by tedu Updated: 11 May 2025 01:57
Tagged: computers openbsd programming