LWN.net Logo

Writing an ACPI driver - an introduction

December 21, 2009

This article was contributed by Matthew Garrett

The Advanced Configuration and Power Interface specification (ACPI) was introduced to replace the myriad of differing protocols for providing configuration data to the operating system. It additionally provided a new power control specification to replace APM, moving policy decisions out of the hard-coded BIOS and into the operating system. Since then it's had a number of extensions implementing all kinds of functionality, variously specified and unspecified. Hardware vendors have seized upon this to implement their own custom "value add" interfaces, taking advantage of the existing specified functionality while adding their own non-standard extensions.

In this article we'll be looking at writing a driver to manage a persistent irritation on Toshiba hardware: the case of the missing Bluetooth. Some Toshibas will boot without Bluetooth, or will manage to lose it somewhere between being suspended and resumed. Sometimes it'll be there until the owner hits the rfkill switch, at which point it's gone no matter how plaintively the user flicks the switch back and forth. In short, the Bluetooth interface is fickle, flaky and not to be relied upon. We're lucky, though. Toshiba implemented their Bluetooth control in the form of an ACPI device. At this point some of you may feel that this is some unusual meaning of the word "lucky", but it's really not as bad as it could be.

First, we'll need one of the ACPI tables. ACPI tables are sections of information provided to the operating system by the BIOS; they contain either blocks of configuration information or, alternatively, executable code in a compiled bytecode called AML. The table that we want is the "Discrete System Descriptor Table", or DSDT. This provides a set of configuration information and control methods for the system hardware. On Linux, it can be found in /sys/firmware/acpi/tables/DSDT. We need to decompile it from the AML bytecode to ASL (ACPI Source Language), which can be done with iasl - the Intel ACPI compiler. This will typically be available as a package in distributions but can also be downloaded as source from acpica.org . The -d option to iasl decompiles an executable table to something resembling the original source. For reference there's an example of a decompiled DSDT here, and it contains the devices and methods discussed in the rest of this article.

Looking at the decompiled DSDT, the first thing we realise is that there's a huge pile of junk and extraneous configuration in here, so let's try to find something useful. First of all, let's look for interesting devices. ACPI device names are limited to four characters, which is generally not helpful in finding something interesting from scratch. Thankfully there's also the _HID string, which provides a tag for identifying the type of device. These strings use the same namespace as old ISA PNP devices, so some of them may be familiar to those of you who spent too long cursing at IRQ and IO settings in the bad old days. So ignore anything with a _HID tag that starts with PNP - it's some piece of standardized system hardware that's unlikely to be doing anything interesting.

On this Toshiba, that leaves us with 5 devices - NSC1100, SMCF030, TOS6205, ACPI0003 and TOS6208. According to the ACPI specification, ACPI0003 is an AC adapter. So ignore that. Google says that NSC1100 is a TPM device. SMCF030 is an infrared port. So that leaves TOS6205 and TOS6208, which look something like this:

    Device (BT)
    {
	Name (_HID, "TOS6205")
	...
    }

    Device (VALZ)
    {
	Name (_HID, "TOS6208")
	...
    }

VALZ turns out to be the generic event and control interface for all kinds of other bits of laptop functionality. There's already a driver for this in the kernel (toshiba_acpi.c), so let's ignore that. The one called BT certainly sounds like a better bet, so TOS6205 it is.

At this point we can write a skeleton driver that does nothing other than bind to this ACPI device. It's only a few lines of code to do that, and it's consistent between all ACPI drivers. All we need to do is register an ACPI driver structure with add and remove functions. These will be called whenever the kernel finds an ACPI device with the TOS6205 ID, and we can do further setup there.

    #include <linux/kernel.h>
    #include <linux/module.h>
    #include <linux/init.h>
    #include <linux/types.h>
    #include <acpi/acpi_bus.h>
    #include <acpi/acpi_drivers.h>

    static int toshiba_bt_rfkill_add(struct acpi_device *device);
    static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type);

    static const struct acpi_device_id bt_device_ids[] = {
        { "TOS6205", 0},
        { "", 0},
    };
    MODULE_DEVICE_TABLE(acpi, bt_device_ids);

    static struct acpi_driver toshiba_bt_driver = {
        .name =         "Toshiba BT",
        .class =        "Toshiba",
        .ids =          bt_device_ids,
        .ops =          {
                                .add =          toshiba_bt_rfkill_add,
                                .remove =       toshiba_bt_rfkill_remove,
                        },
	.owner =	THIS_MODULE,
    };

    static int toshiba_bt_rfkill_add(struct acpi_device *device)
    {
	return 0;
    }

    static int __init toshiba_bt_rfkill_init(void)
    {
        int result = 0;

        result = acpi_bus_register_driver(&toshiba_bt_driver);
        if (result < 0) {
                ACPI_DEBUG_PRINT((ACPI_DB_ERROR,
                                  "Error registering driver\n"));
                return -ENODEV;
        }

        return 0;
    }

    static int toshiba_bt_rfkill_remove(struct acpi_device *device, int type)
    {
	return 0;
    }

    static void __exit toshiba_bt_rfkill_exit(void)
    {
        acpi_bus_unregister_driver(&toshiba_bt_driver);
    }

    module_init(toshiba_bt_rfkill_init);
    module_exit(toshiba_bt_rfkill_exit);

Now what? Let's look at the device's methods. These are functions associated with the device, and will typically be declared in the same scope block. For the Toshiba Bluetooth device, we see something like this:

    Device (BT)
    {
	Name (_HID "TOS6205")
	Method (_STA, 0, Notserialized)
	{
		...
	}
	Method (AUSB, 0, Notserialized)
	{
		...
	}
	Method (DUSB, 0, Notserialized)
	{
		...
	}
	Method (BTPO, 0, Notserialized)
	{
		...
	}
	Method (BTPF, 0, Notserialized)
	{
		...
	}
	Method (BTST, 0, Notserialized)
	{
		...
	}
    }

The device has an _STA method. Methods with a leading _ are supposed to be reserved for the ACPI specification. _STA is defined as returning the device status, and, in this case, will tell us whether the TOS6205 device is functional or not. If there's no Bluetooth on the machine, it'll return zero. The ACPI core handles this for us, so we can ignore it.

The other methods are BTST, AUSB, DUSB, BTPO and BTPF. Working out what these do can be awkward. The DSDT I'm looking at (and which is linked above) has methods that read and write from a set of objects which reflect the hardware state in some way. DUSB writes a zero to an object called BTDT, while AUSB writes one to it. BTPF writes a zero to an object called BTPW, while BTPO writes one to it. BTST returns a byte with the value of BTSW in the low bit, BTDT in bit six, and BTPW in bit seven. Looking at the DSDT, we can see that BTDT, BTPW and BTSW are defined in a block that looks like this:

    OperationRegion (ERAM, EmbeddedControl, 0x00, 0xFF)
    Field (ERAM, ByteAcc, Lock, Preserve) {
        BTDT, 1
	BTPW, 1
	...
	BTSW, 1
	...
    }

An OperationRegion is ACPI speak for an addressable system resource - it may refer to an area of memory, system IO space, a PCI configuration block or (in this case) the registers of the system embedded controller. Objects can be declared within that block in order to let the ACPI code read and write to them. In this case, BTDT, BTPW and BTSW all refer to sections of the embedded controller register space. The number after the comma is the number of bits the object takes up, so we now know that BTDT is the first bit of the first byte-sized register of the embedded controller, BTPW the second bit and BTSW the fifth. Writing to these values will probably cause the embedded controller (a microprocessor running its own firmware) to perform some act in return - alternatively, an external event (such as flicking a switch) may generate an event picked up by the embedded controller and causing it to update a register's contents.

(Side note: generally speaking, accessing the embedded controller registers directly from the operating system is an error. Vendors may alter the embedded controller firmware and the layout of the bits between different models or even different BIOS versions. When this happens, ACPI methods will be updated to match - so if there's an ACPI method to call to interface with the embedded controller, use that rather than trying to drive it directly. This kind of thing can also act in our favour - ACPI interfaces tend to be retained over a range of models, even if the underlying hardware has changed dramatically. One machine may make an opaque system management call in response to an ACPI method, while another with the same method may return a register directly. By comparing the tables from different machines with the same interface, it's often possible to learn more about what these methods are actually meant to do).

We can make pretty good guesses about what's going on here. BTST returns a set of register values, so is probably the BlueTooth device STatus. BTSW presumably contains the state of the kill switch on the laptop. BTPW sounds like it's the power state - so if BTPF and BTPO change that, they're presumably BlueTooth Power ofF and BlueTooth Power On. AUSB and DUSB sound like Attach USB and Detach USB.

So at this point, we have enough knowledge of the interface to implement a Bluetooth enabler driver. On module load we should check the switch status. If it's on, we should enable the Bluetooth.

    static int toshiba_bluetooth_enable(acpi_handle handle)
    {
	acpi_status result;
	acpi_integer status;

	result = acpi_evaluate_integer(handle, "BTST", NULL, &status);

	if (ACPI_FAILURE(result))
	    return -EINVAL;

	/* Check the current status */
	if (!(status & (1 << 0))) /* The switch is off */
	    return -EBUSY;

	if (!(status & (1 << 6))) /* The device is detached - attach it */
	    result = acpi_evaluate_object(handle, "AUSB", NULL, NULL);

	if (ACPI_FAILURE(result)) {
	    printk(KERN_ERR "Failed to reattach Toshiba Bluetooth device\n");
	    return -ENODEV;
	}

	if (!(status & (1 << 7))) /* The device isn't powered up */
	    result = acpi_evaluate_object(handle, "BTPO", NULL, NULL);

	if (ACPI_FAILURE(result)) {
	    printk(KERN_ERR "Failed to power on Toshiba Bluetooth device\n");
	    return -ENODEV;
	}

	return 0;
    }

    static int toshiba_bt_rfkill_add(struct acpi_device *device)
    {
        return toshiba_bluetooth_enable(device->handle);
    }

acpi_evaluate_object() asks the kernel's ACPI interpreter to find a method underneath a specific area of namespace (in this case, the namespace of the device that we've bound to - the first argument provides that), with the third and fourth arguments providing the arguments to be passed to the function and the results returned from the function respectively. acpi_evaluate_integer() is similar, but assumes that the method is going to return an integer.

(Random stylistic sidepoint: When looking at individual bits inside a returned integer, I find it neater to refer to the 0th bit as "1 << 0" rather than 1. There's no especially good reason for this).

Ok. What about when the user flicks the switch back? ACPI can catch various hardware events and then signal them to the operating system. This is done via the "Notify" keyword. Grepping for "Notify" in the DSDT reveals a pile of cases we don't care about, but also a couple of lines like:

    Notify (BT, 0x90)

The first argument to notify is the device to be notified. The kernel will check whether a driver is bound to this device, and if so will pass the notification on to the driver. 0x90 is simply the event type - numbers below 0x80 are device-independent and defined by the ACPI specification, whereas those above are device-specific and only defined by the ACPI specification if the device itself is defined by the ACPI specification. Toshiba has presumably defined what 0x90 means, but given that it's the only event sent by the device we'll just assume it means "Reattach the Bluetooth device". This machine doesn't seem to send an event when Bluetooth device is turned off, but that's less of a concern because the USB device vanishes anyway - there's nothing we need to do in response. Anyway, let's hook up a notification function.

    .ops = {
		.add =          toshiba_bt_rfkill_add,
		.remove =       toshiba_bt_rfkill_remove,
		.notify =       toshiba_bt_rfkill_notify,
            },

    static void toshiba_bt_rfkill_notify(struct acpi_device *device, u32 event)
    {
	toshiba_bluetooth_enable(device->handle);
    }

We ignore the event type in this case because some machines seem to send different numbers here, and we don't really get useful information from it. Other devices may use different event values to indicate the type of event that's been received, and in those cases you'd want to check it appropriately.

At this point we now have a driver that enables Bluetooth at boot (if it's enabled) and responds to the switch being flicked by enabling Bluetooth again. The only remaining case is the one where the user turned off Bluetooth while the system was running, suspended and then flicked the switch back to enable. Let's check again on resume.

    .ops = {
		.add =          toshiba_bt_rfkill_add,
		.remove =       toshiba_bt_rfkill_remove,
		.notify =       toshiba_bt_rfkill_notify,
		.resume =       toshiba_bt_rfkill_resume,
            },

    static int toshiba_bt_rfkill_resume(struct acpi_device *device)
    {
	toshiba_bluetooth_enable(device->handle);
	return 0;
    }

And that's it - a fully functional ACPI driver. The TOS6205 device is one of the simplest ACPI devices I've found, but the principle is the same for any other. In more complex cases you'll want to expose some kind of userspace interface to perform method calls on the device. These should use standard kernel interfaces wherever possible. Backlight control should be carried out via the backlight class, device hotkeys should be sent via an input device and more complex radio control should use the rfkill layer. More device-specific functionality may require you to add sysfs attributes directly, which is somewhat outside the scope of this article.

It would be nice to hope for these ACPI interfaces to become standardized over time, but unfortunately there seems to be little willingness on the part of the companies involved to do so. Some vendors are even moving away from using ACPI directly and are instead using WMI interfaces - a mechanism intended for exposing system management information to Windows applications, but easily subverted into a general purpose control system with even less transparency than pure ACPI. But that's a subject for a different article, and there are still many pieces of hardware with pure ACPI interfaces and no drivers written as yet.


(Log in to post comments)

Writing an ACPI driver - an introduction

Posted Dec 24, 2009 10:07 UTC (Thu) by modernjazz (guest, #4185) [Link]

At this point some of you may feel that this is some unusual meaning of the word "lucky"

Thanks for a good laugh!

Writing an ACPI driver - an introduction

Posted Dec 24, 2009 12:40 UTC (Thu) by quotemstr (subscriber, #45331) [Link]

These strings use the same namespace as old ISA PNP devices
How did that happen? It's a bit like "yeah, my car's timing pulley is actually derived from the Antikythera mechanism, so if you're a classical archaeologist, you'll be right at home fixing it."

Writing an ACPI driver - an introduction

Posted Dec 25, 2009 1:12 UTC (Fri) by BenHutchings (subscriber, #37955) [Link]

The PNPBIOS extensions didn't just cover ISA cards but most non-PCI on-board devices. ACPI also covers those non-PCI on-board devices. So it's not entirely crazy to use the same namespace.

Writing an ACPI driver - an introduction

Posted Dec 24, 2009 13:08 UTC (Thu) by hadess (subscriber, #24252) [Link]

Thanks Matthew :)

Thanks!

Posted Dec 24, 2009 17:18 UTC (Thu) by dion (subscriber, #2764) [Link]

I'm writing this from the best laptop I've ever owned, a Toshiba U400-13G, there is only one problem with it and that's exactly what's described here.

Thank you very much!

Thanks!

Posted Dec 24, 2009 18:35 UTC (Thu) by mjg59 (subscriber, #23239) [Link]

Jes Sorensen has written a driver for this device that's landed in 2.6.33. It's basically equivalent to
the driver in this article, but he got there first (and actually uses a Toshiba on a daily basis, so...)

Writing an ACPI driver - an introduction

Posted Dec 26, 2009 4:04 UTC (Sat) by lun (guest, #57167) [Link]

DSDT = "Differentiated System Description Table"

Writing an ACPI driver - an introduction

Posted Dec 28, 2009 14:41 UTC (Mon) by johnflux (guest, #58833) [Link]

I have a physical switch to turn on/off the wireless, but I get the same behaviour as mentioned here. Could it be fixed in a similar way?

Writing an ACPI driver - an introduction

Posted Dec 28, 2009 21:08 UTC (Mon) by mjg59 (subscriber, #23239) [Link]

Quite possibly. What kind of laptop is it?

Writing an ACPI driver - an introduction

Posted Dec 29, 2009 4:06 UTC (Tue) by johnflux (guest, #58833) [Link]

Toshiba Satellite L300D. Are there any details that I can give to get someone else to write the acpi driver for me? :-)

Writing an ACPI driver - an introduction

Posted Dec 29, 2009 10:31 UTC (Tue) by mjg59 (subscriber, #23239) [Link]

Does it not have the interface discussed in this article?

Writing an ACPI driver - an introduction

Posted Dec 29, 2009 11:33 UTC (Tue) by johnflux (guest, #58833) [Link]

How can I tell? Just looking at the kernel sources?

Writing an ACPI driver - an introduction

Posted Dec 29, 2009 14:00 UTC (Tue) by mjg59 (subscriber, #23239) [Link]

A driver for this hardware has been merged for 2.6.33 as the Toshiba Bluetooth Enabler driver.
Does that work for you?

Read/Write Once bits set on boot

Posted Jan 20, 2010 13:50 UTC (Wed) by kDawg (guest, #63114) [Link]

I have a question that's slightly off topic. When trying to manipulate bits that are R/WO is it possible to modify them via ACPI? Or do they need to be set in BIOS. If so then would it be possible to modify BIOS to change these bits?

More specifically would it be possible to modify ICH10's PxCMD—Port [5:0] Command Register (D31:F2) bit #18? For reference see Intel's ICH10 spec. page 553. It is set to 1 whereas I believe it's supposed to be 0. Since it's R/WO then I don't believe it's possible to modify the DSDT to enable this function.

Thanks, your response would be greatly appreciated.

Read/Write Once bits set on boot

Posted Jan 20, 2010 20:36 UTC (Wed) by mjg59 (subscriber, #23239) [Link]

ACPI has no capabilities above and beyond the operating system. Some ACPI calls may trap into
system management mode, but you could in principle do the same from the OS. So, not really...

Writing an ACPI driver - an introduction

Posted Feb 10, 2012 10:10 UTC (Fri) by kinbo (guest, #82843) [Link]

Thanks, Matthew, for your good tutorial.

I have read it several times, and got more and more,but there are still some questions bothering me.
1. When will the toshiba_bt_rfkill_notify function be called? and can userspace call it? If so, how?
2. How can userspace know the BT status?
3. How can userspace application change the BT status without flicking the switch.
4. How to call a userspace application by flicking a switch?

Thanks, your response would be great appreciated.

Writing an ACPI driver - an introduction

Posted Feb 10, 2012 13:54 UTC (Fri) by mjg59 (subscriber, #23239) [Link]

It'll be called when the firmware generates a notification, which is up to the firmware. Since all the driver does is then attach the device, you can only detect that in userspace by looking for the uevent generated when the USB device reattaches. There's no way (with this driver) for userspace to forcibly change the BT status. Doing that would require it to hook into the rfkill layer.

Writing an ACPI driver - an introduction

Posted Jun 7, 2012 4:50 UTC (Thu) by Nilssab (guest, #85002) [Link]

Hello,

Thank you for a fantastic article. I realize it's a bit old, but I'm really interested in getting to know the inner workings of drivers in linux, and this is the most hands-on information I've found as of yet, so I have some questions:

The driver you wrote, I don't understand if this is c or asl, and how I would go about compiling and installing it to make the driver get loaded and actually do something. Could you give a brief explanation? Or do you have any suggestion of where I might find useful information regarding this?

I have a programming background with knowledge of c, c++, java etc. but I have not touched drivers once.

Copyright © 2009, Eklektix, Inc.
Comments and public postings are copyrighted by their creators.
Linux is a registered trademark of Linus Torvalds