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)