Re: [PATCH v2] platform: x86: Add ACPI driver for ChromeOS

From: Enric Balletbo i Serra
Date: Tue Mar 24 2020 - 12:36:25 EST


Hi Srinivas,

Thanks for your quick answer.

On 22/3/20 16:21, Srinivas Pandruvada wrote:
> On Sun, 2020-03-22 at 10:43 +0100, Enric Balletbo i Serra wrote:
>> This driver attaches to the ChromeOS ACPI device and then exports the
>> values
>> reported by the ACPI in a sysfs directory. The ACPI values are
>> presented in
>> the string form (numbers as decimal values) or binary blobs, and can
>> be
>> accessed as the contents of the appropriate read only files in the
>> sysfs
>> directory tree originating in /sys/devices/platform/chromeos_acpi.
>>
>> Signed-off-by: Enric Balletbo i Serra <enric.balletbo@xxxxxxxxxxxxx>
>> ---
>>
>
> [...]
>
>> /sys/devices/platform/chromeos_acpi/BINF.2 : 1
> Just wondering why don't you create additional attributes under ACPI
> device itself, like in your case:
> /sys/bus/acpi/devices/GGL0001/*
>

As I commented in my previous email, it was to no not break current ChromeOS
userspace. But I assume we will need to break it, so I'll investigate how hard
is to do that change on the userspace side.

Thanks,
Enric B.S.


> Additional attributes are added for ACPI device objects at such
> location in other cases also for some PNP* and INT3* objects.
>
> This will make your driver simple with one attr group.
>
> Thanks,
> Srinivas
>
>
>> /sys/devices/platform/chromeos_acpi/FMAP : -2031616
>> /sys/devices/platform/chromeos_acpi/HWID : SAMUS E25-G7R-W35
>> /sys/devices/platform/chromeos_acpi/BINF.0 : 0
>> /sys/devices/platform/chromeos_acpi/GPIO.0/GPIO.2 : -1
>> /sys/devices/platform/chromeos_acpi/GPIO.0/GPIO.0 : 1
>> /sys/devices/platform/chromeos_acpi/GPIO.0/GPIO.3 : INT3437:00
>> /sys/devices/platform/chromeos_acpi/GPIO.0/GPIO.1 : 0
>> /sys/devices/platform/chromeos_acpi/FRID : Google_Samus.6300.102.0
>> /sys/devices/platform/chromeos_acpi/VBNV.0 : 38
>> /sys/devices/platform/chromeos_acpi/BINF.3 : 2
>> /sys/devices/platform/chromeos_acpi/BINF.1 : 1
>> /sys/devices/platform/chromeos_acpi/GPIO.1/GPIO.2 : 16
>> /sys/devices/platform/chromeos_acpi/GPIO.1/GPIO.0 : 3
>> /sys/devices/platform/chromeos_acpi/GPIO.1/GPIO.3 : INT3437:00
>> /sys/devices/platform/chromeos_acpi/GPIO.1/GPIO.1 : 1
>> /sys/devices/platform/chromeos_acpi/CHSW : 0
>> /sys/devices/platform/chromeos_acpi/FWID : Google_Samus.6300.330.0
>> /sys/devices/platform/chromeos_acpi/VBNV.1 : 16
>> /sys/devices/platform/chromeos_acpi/BINF.4 : 0
>>
>> And for binary packages:
>>
>> cat /sys/devices/platform/chromeos_acpi/MECK | hexdump
>> 0000000 02fb 8e72 a025 0a73 0f13 095e 9e07 41e6
>> 0000010 f9e6 bb4e 76cc bef9 cca7 70e2 8f6d 863d
>> 0000020
>>
>> cat /sys/devices/platform/chromeos_acpi/VDAT | hexdump
>> 0000000 6256 4453 0002 0000 0448 0000 0000 0000
>> 0000010 0c00 0000 0000 0000 0850 0000 0000 0000
>> 0000020 7c54 0003 0000 0000 0420 0000 0000 0000
>> 0000030 0408 0000 0000 0000 0007 0000 0000 0000
>> 0000040 0003 0000 0000 0000 0448 0000 0000 0000
>> 0000050 0408 0000 0000 0000 9335 1f80 0000 0000
>> 0000060 69a8 21f3 0000 0000 1d02 21f9 0000 0000
>> 0000070 ba55 371b 0000 0000 0000 0000 0000 0000
>> 0000080 bcae 001d 0000 0000 0003 0001 0001 0003
>> 0000090 000c 0000 0003 0001 0003 0001 0001 0000
>> 00000a0 0001 0000 0000 0000 cc00 01da 0000 0000
>> 00000b0 0200 0000 0204 0000 0001 0000 0000 0000
>> 00000c0 0800 0000 0000 0000 0000 0001 0000 0000
>> 00000d0 0001 0001 1301 0000 0000 0000 0000 0000
>> 00000e0 0000 0000 0000 0000 0000 0000 0000 0000
>> *
>>
>> Thanks,
>> Enric
>>
>> [1] https://lkml.org/lkml/2017/7/31/378
>>
>> Changes in v2:
>> - Note that this version is a total rework, with those major changes:
>> - Use lists to track dinamically allocated attributes and groups.
>> - Use sysfs binary attributes to store the ACPI contents.
>> - Remove all the functionalities except the one that creates the
>> sysfs files.
>>
>> drivers/platform/x86/Kconfig | 12 +
>> drivers/platform/x86/Makefile | 1 +
>> drivers/platform/x86/chromeos_acpi.c | 489
>> +++++++++++++++++++++++++++
>> 3 files changed, 502 insertions(+)
>> create mode 100644 drivers/platform/x86/chromeos_acpi.c
>>
>> diff --git a/drivers/platform/x86/Kconfig
>> b/drivers/platform/x86/Kconfig
>> index 587403c44598..917a1c1a0758 100644
>> --- a/drivers/platform/x86/Kconfig
>> +++ b/drivers/platform/x86/Kconfig
>> @@ -72,6 +72,18 @@ config ACERHDF
>> If you have an Acer Aspire One netbook, say Y or M
>> here.
>>
>> +config ACPI_CHROMEOS
>> + tristate "ChromeOS specific ACPI extensions"
>> + depends on ACPI
>> + depends on CHROME_PLATFORMS
>> + help
>> + This driver provides the firmware interface for the services
>> + exported through the ChromeOS interfaces when using ChromeOS
>> + ACPI firmware.
>> +
>> + If you have an ACPI-compatible Chromebook, say Y or M
>> + here.
>> +
>> config ALIENWARE_WMI
>> tristate "Alienware Special feature control"
>> depends on ACPI
>> diff --git a/drivers/platform/x86/Makefile
>> b/drivers/platform/x86/Makefile
>> index 3747b1f07cf1..222e2e88ccb8 100644
>> --- a/drivers/platform/x86/Makefile
>> +++ b/drivers/platform/x86/Makefile
>> @@ -83,6 +83,7 @@ obj-$(CONFIG_SAMSUNG_Q10) += samsung-q10.o
>> obj-$(CONFIG_APPLE_GMUX) += apple-gmux.o
>> obj-$(CONFIG_INTEL_RST) += intel-rst.o
>> obj-$(CONFIG_INTEL_SMARTCONNECT) += intel-smartconnect.o
>> +obj-$(CONFIG_ACPI_CHROMEOS) += chromeos_acpi.o
>>
>> obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o
>> obj-$(CONFIG_INTEL_PMC_IPC) += intel_pmc_ipc.o
>> diff --git a/drivers/platform/x86/chromeos_acpi.c
>> b/drivers/platform/x86/chromeos_acpi.c
>> new file mode 100644
>> index 000000000000..4d9addee2473
>> --- /dev/null
>> +++ b/drivers/platform/x86/chromeos_acpi.c
>> @@ -0,0 +1,489 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * ChromeOS specific ACPI extensions
>> + *
>> + * Copyright 2011 Google, Inc.
>> + * Copyright 2020 Google LLC
>> + *
>> + * This file is a rework and part of the code is ported from
>> + * drivers/platform/x86/chromeos_acpi.c of the chromeos-3.18 kernel
>> and
>> + * was originally written by Vadim Bendebury <vbendeb@xxxxxxxxxxxx>.
>> + *
>> + * This driver attaches to the ChromeOS ACPI device and then exports
>> the
>> + * values reported by the ACPI in a sysfs directory. All values are
>> + * presented in the string form (numbers as decimal values) and can
>> be
>> + * accessed as the contents of the appropriate read only files in
>> the
>> + * sysfs directory tree originating in
>> /sys/devices/platform/chromeos_acpi.
>> + */
>> +
>> +#include <linux/acpi.h>
>> +#include <linux/kernel.h>
>> +#include <linux/list.h>
>> +#include <linux/module.h>
>> +#include <linux/platform_device.h>
>> +
>> +/*
>> + * ACPI method name for MLST; the response for this method is a
>> package of
>> + * strings listing the methods which should be reflected in sysfs.
>> + */
>> +#define MLST "MLST"
>> +
>> +/*
>> + * The default list of methods the ChromeOS ACPI device is supposed
>> to export,
>> + * if the MLST method is not present or is poorly formed. The MLST
>> method
>> + * itself is included, to aid in debugging.
>> + */
>> +static char *chromeos_acpi_default_methods[] = {
>> + "CHSW", "HWID", "BINF", "GPIO", "CHNV", "FWID", "FRID", MLST
>> +};
>> +
>> +/*
>> + * Representation of a single sysfs attribute. In addition to the
>> standard
>> + * bin_attribute structure has a list of these structures (to keep
>> track for
>> + * de-allocation when removing the driver) and a pointer to the
>> actual
>> + * attribute name and value, reported when accessing the appropriate
>> sysfs
>> + * file.
>> + */
>> +struct chromeos_acpi_attribute {
>> + struct bin_attribute bin_attr;
>> + struct list_head list;
>> + char *name;
>> + char *data;
>> +};
>> +
>> +/*
>> + * Representation of a sysfs attribute group (a sub directory in the
>> device's
>> + * sysfs directory). In addition to the standard structure has lists
>> to allow
>> + * to keep track of the allocated structures.
>> + */
>> +struct chromeos_acpi_attribute_group {
>> + struct list_head attribs;
>> + struct list_head list;
>> + struct kobject *kobj; /* chromeos_acpi/name directory */
>> + char *name;
>> +};
>> +
>> +/*
>> + * This is the main structure, we use it to store data and adds
>> links pointing
>> + * at lists of allocated attributes and attribute groups.
>> + */
>> +struct chromeos_acpi_dev {
>> + struct platform_device *pdev;
>> +
>> + struct chromeos_acpi_attribute_group root;
>> + struct list_head groups;
>> +};
>> +
>> +static struct chromeos_acpi_dev chromeos_acpi;
>> +
>> +static ssize_t chromeos_acpi_read_bin_attribute(struct file *filp,
>> + struct kobject *kobj,
>> + struct bin_attribute
>> *bin_attr,
>> + char *buffer, loff_t
>> pos,
>> + size_t count)
>> +{
>> + struct chromeos_acpi_attribute *info = bin_attr->private;
>> +
>> + return memory_read_from_buffer(buffer, count, &pos, info->data,
>> + info->bin_attr.size);
>> +}
>> +
>> +static char *chromeos_acpi_alloc_name(char *name, int count, int
>> index)
>> +{
>> + char *str;
>> +
>> + if (count == 1)
>> + str = kstrdup(name, GFP_KERNEL);
>> + else
>> + str = kasprintf(GFP_KERNEL, "%s.%d", name, index);
>> +
>> + return str;
>> +}
>> +
>> +static int
>> +chromeos_acpi_add_attr(struct chromeos_acpi_attribute_group *aag,
>> + union acpi_object *element, char *name,
>> + int count, int index)
>> +{
>> + struct chromeos_acpi_attribute *info;
>> + char buffer[24]; /* enough to store a u64 and "\n\0" */
>> + int length;
>> + int ret;
>> +
>> + info = kzalloc(sizeof(*info), GFP_KERNEL);
>> + if (!info)
>> + return -ENOMEM;
>> +
>> + info->name = chromeos_acpi_alloc_name(name, count, index);
>> + if (!info->name) {
>> + ret = -ENOMEM;
>> + goto free_attribute;
>> + }
>> +
>> + sysfs_bin_attr_init(&info->bin_attr);
>> + info->bin_attr.attr.name = info->name;
>> + info->bin_attr.attr.mode = 0444;
>> +
>> + switch (element->type) {
>> + case ACPI_TYPE_BUFFER:
>> + length = element->buffer.length;
>> + info->data = kmemdup(element->buffer.pointer,
>> + length, GFP_KERNEL);
>> + break;
>> + case ACPI_TYPE_INTEGER:
>> + length = snprintf(buffer, sizeof(buffer), "%d",
>> + (int)element->integer.value);
>> + info->data = kmemdup(buffer, length, GFP_KERNEL);
>> + break;
>> + case ACPI_TYPE_STRING:
>> + length = element->string.length + 1;
>> + info->data = kstrdup(element->string.pointer,
>> GFP_KERNEL);
>> + break;
>> + default:
>> + ret = -EINVAL;
>> + goto free_attr_name;
>> + }
>> +
>> + if (!info->data) {
>> + ret = -ENOMEM;
>> + goto free_attr_name;
>> + }
>> +
>> + info->bin_attr.size = length;
>> + info->bin_attr.read = chromeos_acpi_read_bin_attribute;
>> + info->bin_attr.private = info;
>> +
>> + INIT_LIST_HEAD(&info->list);
>> +
>> + ret = sysfs_create_bin_file(aag->kobj, &info->bin_attr);
>> + if (ret)
>> + goto free_attr_data;
>> +
>> + list_add(&info->list, &aag->attribs);
>> +
>> + return 0;
>> +
>> +free_attr_data:
>> + kfree(info->data);
>> +free_attr_name:
>> + kfree(info->name);
>> +free_attribute:
>> + kfree(info);
>> + return ret;
>> +}
>> +
>> +static void
>> +chromeos_acpi_remove_attribs(struct chromeos_acpi_attribute_group
>> *aag)
>> +{
>> + struct chromeos_acpi_attribute *attr, *tmp_attr;
>> +
>> + list_for_each_entry_safe(attr, tmp_attr, &aag->attribs, list) {
>> + sysfs_remove_bin_file(aag->kobj, &attr->bin_attr);
>> + kfree(attr->name);
>> + kfree(attr->data);
>> + kfree(attr);
>> + }
>> +}
>> +
>> +/**
>> + * chromeos_acpi_add_group() - Create a sysfs group including
>> attributes
>> + * representing a nested ACPI package.
>> + *
>> + * @obj: Package contents as returned by ACPI.
>> + * @name: Name of the group.
>> + * @num_attrs: Number of attributes of this package.
>> + * @index: Index number of this particular group.
>> + *
>> + * The created group is called @name in case there is a single
>> instance, or
>> + * @name.@index otherwise.
>> + *
>> + * All group and attribute storage allocations are included in the
>> lists for
>> + * tracking of allocated memory.
>> + *
>> + * Return: 0 on success, negative errno on failure.
>> + */
>> +static int chromeos_acpi_add_group(union acpi_object *obj, char
>> *name,
>> + int num_attrs, int index)
>> +{
>> + struct device *dev = &chromeos_acpi.pdev->dev;
>> + struct chromeos_acpi_attribute_group *aag;
>> + union acpi_object *element;
>> + int i, count, ret;
>> +
>> + aag = kzalloc(sizeof(*aag), GFP_KERNEL);
>> + if (!aag)
>> + return -ENOMEM;
>> +
>> + aag->name = chromeos_acpi_alloc_name(name, num_attrs, index);
>> + if (!aag->name) {
>> + ret = -ENOMEM;
>> + goto free_group;
>> + }
>> +
>> + aag->kobj = kobject_create_and_add(aag->name, &dev->kobj);
>> + if (!aag->kobj) {
>> + ret = -EINVAL;
>> + goto free_group_name;
>> + }
>> +
>> + INIT_LIST_HEAD(&aag->attribs);
>> + INIT_LIST_HEAD(&aag->list);
>> +
>> + count = obj->package.count;
>> + element = obj->package.elements;
>> + for (i = 0; i < count; i++, element++) {
>> + ret = chromeos_acpi_add_attr(aag, element, name, count,
>> i);
>> + if (ret)
>> + goto free_group_attr;
>> + }
>> +
>> + list_add(&aag->list, &chromeos_acpi.groups);
>> +
>> + return 0;
>> +
>> +free_group_attr:
>> + chromeos_acpi_remove_attribs(aag);
>> + kobject_put(aag->kobj);
>> +free_group_name:
>> + kfree(aag->name);
>> +free_group:
>> + kfree(aag);
>> + return ret;
>> +}
>> +
>> +static void chromeos_acpi_remove_groups(void)
>> +{
>> + struct chromeos_acpi_attribute_group *aag, *tmp_aag;
>> +
>> + list_for_each_entry_safe(aag, tmp_aag, &chromeos_acpi.groups,
>> list) {
>> + chromeos_acpi_remove_attribs(aag);
>> + kfree(aag->name);
>> + kobject_put(aag->kobj);
>> + kfree(aag);
>> + }
>> +}
>> +
>> +/**
>> + * chromeos_acpi_handle_package() - Create sysfs group including
>> attributes
>> + * representing an ACPI package.
>> + *
>> + * @obj: Package contents as returned by ACPI.
>> + * @name: Name of the group.
>> + *
>> + * Scalar objects included in the package get sysfs attributes
>> created for
>> + * them. Nested packages are passed to a function creating a sysfs
>> group per
>> + * package.
>> + *
>> + * Return: 0 on success, negative errno on failure.
>> + */
>> +static int chromeos_acpi_handle_package(union acpi_object *obj, char
>> *name)
>> +{
>> + struct device *dev = &chromeos_acpi.pdev->dev;
>> + int count = obj->package.count;
>> + union acpi_object *element;
>> + int i, ret = 0;
>> +
>> + element = obj->package.elements;
>> + for (i = 0; i < count; i++, element++) {
>> + if (element->type == ACPI_TYPE_BUFFER ||
>> + element->type == ACPI_TYPE_STRING ||
>> + element->type == ACPI_TYPE_INTEGER)
>> + /* Create a single attribute in the root
>> directory */
>> + ret =
>> chromeos_acpi_add_attr(&chromeos_acpi.root,
>> + element, name,
>> + count, i);
>> + else if (element->type == ACPI_TYPE_PACKAGE)
>> + /* Create a group of attributes */
>> + ret = chromeos_acpi_add_group(element, name,
>> + count, i);
>> + else
>> + ret = -EINVAL;
>> + if (ret)
>> + dev_err(dev,
>> + "failed to create group attributes
>> (%d)\n",
>> + ret);
>> + }
>> +
>> + return ret;
>> +}
>> +
>> +/**
>> + * chromeos_acpi_add_method() - Evaluate an ACPI method and create
>> sysfs
>> + * attributes.
>> + *
>> + * @adev: ACPI device
>> + * @name: Name of the method to evaluate
>> + *
>> + * Return: 0 on success, non-zero on failure
>> + */
>> +static int chromeos_acpi_add_method(struct acpi_device *adev, char
>> *name)
>> +{
>> + struct device *dev = &chromeos_acpi.pdev->dev;
>> + struct acpi_buffer output;
>> + union acpi_object *obj;
>> + acpi_status status;
>> + int ret = 0;
>> +
>> + output.length = ACPI_ALLOCATE_BUFFER;
>> +
>> + status = acpi_evaluate_object(adev->handle, name, NULL,
>> &output);
>> + if (ACPI_FAILURE(status)) {
>> + dev_err(dev, "failed to retrieve %s (%d)\n", name,
>> status);
>> + return status;
>> + }
>> +
>> + obj = output.pointer;
>> + if (obj->type == ACPI_TYPE_PACKAGE)
>> + ret = chromeos_acpi_handle_package(obj, name);
>> +
>> + kfree(output.pointer);
>> +
>> + return ret;
>> +}
>> +
>> +/**
>> + * chromeos_acpi_process_mlst() - Evaluate the MLST method and add
>> methods
>> + * listed in the response.
>> + *
>> + * @adev: ACPI device
>> + *
>> + * Returns: 0 if successful, non-zero if error.
>> + */
>> +static int chromeos_acpi_process_mlst(struct acpi_device *adev)
>> +{
>> + char name[ACPI_NAMESEG_SIZE + 1];
>> + union acpi_object *element, *obj;
>> + struct acpi_buffer output;
>> + acpi_status status;
>> + int ret = 0;
>> + int size;
>> + int i;
>> +
>> + output.length = ACPI_ALLOCATE_BUFFER;
>> + status = acpi_evaluate_object(adev->handle, MLST, NULL,
>> + &output);
>> + if (ACPI_FAILURE(status))
>> + return status;
>> +
>> + obj = output.pointer;
>> + if (obj->type != ACPI_TYPE_PACKAGE) {
>> + ret = -EINVAL;
>> + goto free_acpi_buffer;
>> + }
>> +
>> + element = obj->package.elements;
>> + for (i = 0; i < obj->package.count; i++, element++) {
>> + if (element->type == ACPI_TYPE_STRING) {
>> + size = min(element->string.length + 1,
>> + (u32)ACPI_NAMESEG_SIZE + 1);
>> + strlcpy(name, element->string.pointer, size);
>> + ret = chromeos_acpi_add_method(adev, name);
>> + if (ret) {
>> + chromeos_acpi_remove_groups();
>> + break;
>> + }
>> + }
>> + }
>> +
>> +free_acpi_buffer:
>> + kfree(output.pointer);
>> +
>> + return ret;
>> +}
>> +
>> +static int chromeos_acpi_device_add(struct acpi_device *adev)
>> +{
>> + struct chromeos_acpi_attribute_group *aag =
>> &chromeos_acpi.root;
>> + struct device *dev = &chromeos_acpi.pdev->dev;
>> + int i, ret;
>> +
>> + INIT_LIST_HEAD(&aag->attribs);
>> + INIT_LIST_HEAD(&aag->list);
>> +
>> + aag->kobj = &dev->kobj;
>> +
>> + /*
>> + * Attempt to add methods by querying the device's MLST method
>> + * for the list of methods.
>> + */
>> + if (!chromeos_acpi_process_mlst(adev))
>> + return 0;
>> +
>> + dev_info(dev, "falling back to default list of methods\n");
>> +
>> + for (i = 0; i < ARRAY_SIZE(chromeos_acpi_default_methods); i++)
>> {
>> + ret = chromeos_acpi_add_method(adev,
>> + chromeos_acpi_default_meth
>> ods[i]);
>> + if (ret) {
>> + dev_err(dev, "failed to add default methods
>> (%d)\n",
>> + ret);
>> + return ret;
>> + }
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static int chromeos_acpi_device_remove(struct acpi_device *adev)
>> +{
>> + /* Remove dinamically allocated sysfs groups and attributes */
>> + chromeos_acpi_remove_groups();
>> + /* Remove attributes from the root group */
>> + chromeos_acpi_remove_attribs(&chromeos_acpi.root);
>> +
>> + return 0;
>> +}
>> +
>> +static const struct acpi_device_id chromeos_device_ids[] = {
>> + { "GGL0001", 0 },
>> + { }
>> +};
>> +MODULE_DEVICE_TABLE(acpi, chromeos_device_ids);
>> +
>> +static struct acpi_driver chromeos_acpi_driver = {
>> + .name = "ChromeOS ACPI driver",
>> + .class = "chromeos-acpi",
>> + .ids = chromeos_device_ids,
>> + .ops = {
>> + .add = chromeos_acpi_device_add,
>> + .remove = chromeos_acpi_device_remove,
>> + },
>> + .owner = THIS_MODULE,
>> +};
>> +
>> +static int __init chromeos_acpi_init(void)
>> +{
>> + int ret;
>> +
>> + chromeos_acpi.pdev =
>> platform_device_register_simple("chromeos_acpi",
>> + PLATFORM_DEVID_NONE,
>> NULL, 0);
>> + if (IS_ERR(chromeos_acpi.pdev)) {
>> + pr_err("unable to register chromeos_acpi platform
>> device\n");
>> + return PTR_ERR(chromeos_acpi.pdev);
>> + }
>> +
>> + INIT_LIST_HEAD(&chromeos_acpi.groups);
>> +
>> + ret = acpi_bus_register_driver(&chromeos_acpi_driver);
>> + if (ret < 0) {
>> + pr_err("failed to register chromeos_acpi driver
>> (%d)\n", ret);
>> + platform_device_unregister(chromeos_acpi.pdev);
>> + chromeos_acpi.pdev = NULL;
>> + return ret;
>> + }
>> +
>> + return 0;
>> +}
>> +
>> +static void __exit chromeos_acpi_exit(void)
>> +{
>> + acpi_bus_unregister_driver(&chromeos_acpi_driver);
>> + platform_device_unregister(chromeos_acpi.pdev);
>> +}
>> +
>> +module_init(chromeos_acpi_init);
>> +module_exit(chromeos_acpi_exit);
>> +
>> +MODULE_AUTHOR("Enric Balletbo i Serra <enric.balletbo@xxxxxxxxxxxxx>
>> ");
>> +MODULE_LICENSE("GPL");
>> +MODULE_DESCRIPTION("ChromeOS specific ACPI extensions");
>
>