blob: ebf65ef9736b1a57eadf0302515614df3e046ad5 [file] [log] [blame]
From 5bd52e638382186ae62a097a0c8bcbb8f6989999 Mon Sep 17 00:00:00 2001
From: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Date: Mon, 19 Jun 2017 11:23:12 +0200
Subject: [PATCH] CHROMIUM: platform: x86: add ACPI driver for ChromeOS.
This driver attaches to the Chromeos ACPI device and the exports the values
reported by the ACPI in a sysfs directory. All ACPI 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.
[rebase54(gwendal): Use ACPI_NAMESEG_SIZE instead of ACPI_NAME_SIZE]
[rebase510(gwendal): Fix x86/Makefile, ---help in Kconfig]
Signed-off-by: Olof Johansson <olof@lixom.net>
(Squashed related commits bc14076e8807 .. 6addb52fb680)
Signed-off-by: Enric Balletbo i Serra <enric.balletbo@collabora.com>
Change-Id: If201cae15f7005e3c2c440bc04f2622bf6b1daa9
---
drivers/platform/x86/Kconfig | 10 +
drivers/platform/x86/Makefile | 1 +
drivers/platform/x86/chromeos_acpi.c | 800 +++++++++++++++++++++++++++
3 files changed, 811 insertions(+)
create mode 100644 drivers/platform/x86/chromeos_acpi.c
diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig
index 7d385c3b2239..4777241a7ca9 100644
--- a/drivers/platform/x86/Kconfig
+++ b/drivers/platform/x86/Kconfig
@@ -1069,6 +1069,16 @@ config TOUCHSCREEN_DMI
config FW_ATTR_CLASS
tristate
+config ACPI_CHROMEOS
+ bool "ChromeOS specific ACPI extensions"
+ depends on ACPI
+ depends on CHROME_PLATFORMS
+ select NVRAM
+ select CHROMEOS
+ help
+ This driver provides the firmware interface for the services exported
+ through the CHROMEOS interfaces when using ChromeOS ACPI firmware.
+
config INTEL_IMR
bool "Intel Isolated Memory Region support"
depends on X86_INTEL_QUARK && IOSF_MBI
diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile
index 7ee369aab10d..6d07d56d1332 100644
--- a/drivers/platform/x86/Makefile
+++ b/drivers/platform/x86/Makefile
@@ -101,6 +101,7 @@ obj-$(CONFIG_TOSHIBA_WMI) += toshiba-wmi.o
obj-$(CONFIG_ACPI_TOSHIBA) += toshiba_acpi.o
# Laptop drivers
+obj-$(CONFIG_ACPI_CHROMEOS) += chromeos_acpi.o
obj-$(CONFIG_ACPI_CMPC) += classmate-laptop.o
obj-$(CONFIG_COMPAL_LAPTOP) += compal-laptop.o
obj-$(CONFIG_LG_LAPTOP) += lg-laptop.o
diff --git a/drivers/platform/x86/chromeos_acpi.c b/drivers/platform/x86/chromeos_acpi.c
new file mode 100644
index 000000000000..2486c5c1817b
--- /dev/null
+++ b/drivers/platform/x86/chromeos_acpi.c
@@ -0,0 +1,800 @@
+ /*
+ * chromeos_acpi.c - ChromeOS specific ACPI support
+ *
+ *
+ * Copyright (C) 2011 The Chromium OS Authors
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * This driver attaches to the ChromeOS ACPI device and the exports the values
+ * reported by the ACPI in a sysfs directory
+ * (/sys/devices/platform/chromeos_acpi).
+ *
+ * The first version of the driver provides only static information; the
+ * values reported by the driver are the snapshot reported by the ACPI at
+ * driver installation time.
+ *
+ * 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/kernel.h>
+#include <linux/module.h>
+#include <linux/nvram.h>
+#include <linux/platform_device.h>
+#include <linux/acpi.h>
+
+#include "../chrome/chromeos.h"
+
+#define CHNV_DEBUG_RESET_FLAG 0x40 /* flag for S3 reboot */
+#define CHNV_RECOVERY_FLAG 0x80 /* flag for recovery reboot */
+
+#define CHSW_RECOVERY_FW 0x00000002 /* recovery button depressed */
+#define CHSW_RECOVERY_EC 0x00000004 /* recovery button depressed */
+#define CHSW_DEVELOPER_MODE 0x00000020 /* developer switch set */
+#define CHSW_WP 0x00000200 /* write-protect (optional) */
+
+/*
+ * Structure containing one ACPI exported integer along with the validity
+ * flag.
+ */
+struct chromeos_acpi_datum {
+ unsigned cad_value;
+ bool cad_is_set;
+};
+
+/*
+ * Structure containing the set of ACPI exported integers required by chromeos
+ * wrapper.
+ */
+struct chromeos_acpi_if {
+ struct chromeos_acpi_datum switch_state;
+
+ /* chnv is a single byte offset in nvram. exported by older firmware */
+ struct chromeos_acpi_datum chnv;
+
+ /* vbnv is an address range in nvram, exported by newer firmware */
+ struct chromeos_acpi_datum nv_base;
+ struct chromeos_acpi_datum nv_size;
+};
+
+#define MY_LOGPREFIX "chromeos_acpi: "
+#define MY_ERR KERN_ERR MY_LOGPREFIX
+#define MY_NOTICE KERN_NOTICE MY_LOGPREFIX
+#define MY_INFO KERN_INFO MY_LOGPREFIX
+
+/* 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_METHOD "MLST"
+
+static const struct acpi_device_id chromeos_device_ids[] = {
+ {"GGL0001", 0}, /* Google's own */
+ {"", 0},
+};
+
+MODULE_DEVICE_TABLE(acpi, chromeos_device_ids);
+
+static int chromeos_device_add(struct acpi_device *device);
+static int chromeos_device_remove(struct acpi_device *device);
+
+static struct chromeos_acpi_if chromeos_acpi_if_data;
+static struct acpi_driver chromeos_acpi_driver = {
+ .name = "ChromeOS Device",
+ .class = "ChromeOS",
+ .ids = chromeos_device_ids,
+ .ops = {
+ .add = chromeos_device_add,
+ .remove = chromeos_device_remove,
+ },
+ .owner = THIS_MODULE,
+};
+
+/* 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 *default_methods[] = {
+ "CHSW", "HWID", "BINF", "GPIO", "CHNV", "FWID", "FRID", MLST_METHOD
+};
+
+/*
+ * Representation of a single sys fs attribute. In addition to the standard
+ * device_attribute structure has a link field, allowing to create a list of
+ * these structures (to keep track for de-allocation when removing the driver)
+ * and a pointer to the actual attribute value, reported when accessing the
+ * appropriate sys fs file
+ */
+struct acpi_attribute {
+ struct device_attribute dev_attr;
+ struct acpi_attribute *next_acpi_attr;
+ char *value;
+};
+
+/*
+ * Representation of a sys fs attribute group (a sub directory in the device's
+ * sys fs directory). In addition to the standard structure has a link to
+ * allow to keep track of the allocated structures.
+ */
+struct acpi_attribute_group {
+ struct attribute_group ag;
+ struct acpi_attribute_group *next_acpi_attr_group;
+};
+
+/*
+ * ChromeOS ACPI device wrapper adds links pointing at lists of allocated
+ * attributes and attribute groups.
+ */
+struct chromeos_acpi_dev {
+ struct platform_device *p_dev;
+ struct acpi_attribute *attributes;
+ struct acpi_attribute_group *groups;
+};
+
+static struct chromeos_acpi_dev chromeos_acpi = { };
+
+static bool chromeos_on_legacy_firmware(void)
+{
+ /*
+ * Presense of the CHNV ACPI element implies running on a legacy
+ * firmware
+ */
+ return chromeos_acpi_if_data.chnv.cad_is_set;
+}
+
+/*
+ * This function operates on legacy BIOSes which do not export VBNV element
+ * through ACPI. These BIOSes use a fixed location in NVRAM to contain a
+ * bitmask of known flags.
+ *
+ * @flag - the bitmask to set, it is the responsibility of the caller to set
+ * the proper bits.
+ *
+ * returns 0 on success (is running in legacy mode and chnv is initialized) or
+ * -1 otherwise.
+ */
+static int chromeos_set_nvram_flag(u8 flag)
+{
+ u8 cur;
+ unsigned index = chromeos_acpi_if_data.chnv.cad_value;
+
+ if (!chromeos_on_legacy_firmware())
+ return -ENODEV;
+
+ cur = nvram_read_byte(index);
+
+ if ((cur & flag) != flag)
+ nvram_write_byte(cur | flag, index);
+ return 0;
+}
+
+int chromeos_legacy_set_need_recovery(void)
+{
+ return chromeos_set_nvram_flag(CHNV_RECOVERY_FLAG);
+}
+
+/*
+ * Read the nvram buffer contents into the user provided space.
+ *
+ * retrun number of bytes copied, or -1 on any error.
+ */
+static ssize_t chromeos_vbc_nvram_read(void *buf, size_t count)
+{
+
+ int base, size, i;
+
+ if (!chromeos_acpi_if_data.nv_base.cad_is_set ||
+ !chromeos_acpi_if_data.nv_size.cad_is_set) {
+ printk(MY_ERR "%s: NVRAM not configured!\n", __func__);
+ return -ENODEV;
+ }
+
+ base = chromeos_acpi_if_data.nv_base.cad_value;
+ size = chromeos_acpi_if_data.nv_size.cad_value;
+
+ if (count < size) {
+ pr_err("%s: not enough room to read nvram (%zd < %d)\n",
+ __func__, count, size);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < size; i++)
+ ((u8 *)buf)[i] = nvram_read_byte(base++);
+
+ return size;
+}
+
+static ssize_t chromeos_vbc_nvram_write(const void *buf, size_t count)
+{
+ unsigned base, size, i;
+
+ if (!chromeos_acpi_if_data.nv_base.cad_is_set ||
+ !chromeos_acpi_if_data.nv_size.cad_is_set) {
+ printk(MY_ERR "%s: NVRAM not configured!\n", __func__);
+ return -ENODEV;
+ }
+
+ size = chromeos_acpi_if_data.nv_size.cad_value;
+ base = chromeos_acpi_if_data.nv_base.cad_value;
+
+ if (count != size) {
+ printk(MY_ERR "%s: wrong buffer size (%zd != %d)!\n", __func__,
+ count, size);
+ return -EINVAL;
+ }
+
+ for (i = 0; i < size; i++) {
+ u8 c;
+
+ c = nvram_read_byte(base + i);
+ if (c == ((u8 *)buf)[i])
+ continue;
+ nvram_write_byte(((u8 *)buf)[i], base + i);
+ }
+ return size;
+}
+
+/*
+ * To show attribute value just access the container structure's `value'
+ * field.
+ */
+static ssize_t show_acpi_attribute(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct acpi_attribute *paa;
+
+ paa = container_of(attr, struct acpi_attribute, dev_attr);
+ return snprintf(buf, PAGE_SIZE, paa->value);
+}
+
+/*
+ * create_sysfs_attribute() create and initialize an ACPI sys fs attribute
+ * structure.
+ * @value: attribute value
+ * @name: base attribute name
+ * @count: total number of instances of this attribute
+ * @instance: instance number of this particular attribute
+ *
+ * This function allocates and initializes the structure containing all
+ * information necessary to add a sys fs attribute. In case the attribute has
+ * just a single instance, the attribute file name is equal to the @name
+ * parameter . In case the attribute has several instances, the attribute
+ * file name is @name.@instance.
+ *
+ * Returns: a pointer to the allocated and initialized structure, or null if
+ * allocation failed.
+ *
+ * As a side effect, the allocated structure is added to the list in the
+ * chromeos_acpi structure. Note that the actual attribute creation is not
+ * attempted yet, in case of creation error the structure would not have an
+ * actual attribute associated with it, so when de-installing the driver this
+ * structure would be used to try to remove an attribute which does not exist.
+ * This is considered acceptable, as there is no reason for sys fs attribute
+ * creation failure.
+ */
+static struct acpi_attribute *create_sysfs_attribute(char *value, char *name,
+ int count, int instance)
+{
+ struct acpi_attribute *paa;
+ int total_size, room_left;
+ int value_len = strlen(value);
+
+ if (!value_len)
+ return NULL;
+
+ value_len++; /* include the terminating zero */
+
+ /*
+ * total allocation size includes (all strings with including
+ * terminating zeros):
+ *
+ * - value string
+ * - attribute structure size
+ * - name string
+ * - suffix string (in case there are multiple instances)
+ * - dot separating the instance suffix
+ */
+
+ total_size = value_len + sizeof(struct acpi_attribute) +
+ strlen(name) + 1;
+
+ if (count != 1) {
+ if (count >= 1000) {
+ printk(MY_ERR "%s: too many (%d) instances of %s\n",
+ __func__, count, name);
+ return NULL;
+ }
+ /* allow up to three digits and the dot */
+ total_size += 4;
+ }
+
+ paa = kzalloc(total_size, GFP_KERNEL);
+ if (!paa) {
+ printk(MY_ERR "out of memory in %s!\n", __func__);
+ return NULL;
+ }
+
+ sysfs_attr_init(&paa->dev_attr.attr);
+ paa->dev_attr.attr.mode = 0444; /* read only */
+ paa->dev_attr.show = show_acpi_attribute;
+ paa->value = (char *)(paa + 1);
+ strcpy(paa->value, value);
+ paa->dev_attr.attr.name = paa->value + value_len;
+
+ room_left = total_size - value_len -
+ offsetof(struct acpi_attribute, value);
+
+ if (count == 1) {
+ snprintf((char *)paa->dev_attr.attr.name, room_left, name);
+ } else {
+ snprintf((char *)paa->dev_attr.attr.name, room_left,
+ "%s.%d", name, instance);
+ }
+
+ paa->next_acpi_attr = chromeos_acpi.attributes;
+ chromeos_acpi.attributes = paa;
+
+ return paa;
+}
+
+/*
+ * add_sysfs_attribute() create and initialize an ACPI sys fs attribute
+ * structure and create the attribute.
+ * @value: attribute value
+ * @name: base attribute name
+ * @count: total number of instances of this attribute
+ * @instance: instance number of this particular attribute
+ */
+
+static void add_sysfs_attribute(char *value, char *name,
+ int count, int instance)
+{
+ struct acpi_attribute *paa =
+ create_sysfs_attribute(value, name, count, instance);
+
+ if (!paa)
+ return;
+
+ if (device_create_file(&chromeos_acpi.p_dev->dev, &paa->dev_attr))
+ printk(MY_ERR "failed to create attribute for %s\n", name);
+}
+
+/*
+ * handle_nested_acpi_package() create sysfs group including attributes
+ * representing a nested ACPI package.
+ *
+ * @po: package contents as returned by ACPI
+ * @pm: name of the group
+ * @total: number of instances of this package
+ * @instance: instance number of this particular group
+ *
+ * The created group is called @pm in case there is a single instance, or
+ * @pm.@instance otherwise.
+ *
+ * All group and attribute storage allocations are included in the lists for
+ * tracking of allocated memory.
+ */
+static void handle_nested_acpi_package(union acpi_object *po, char *pm,
+ int total, int instance)
+{
+ int i, size, count, j;
+ struct acpi_attribute_group *aag;
+
+ count = po->package.count;
+
+ size = strlen(pm) + 1 + sizeof(struct acpi_attribute_group) +
+ sizeof(struct attribute *) * (count + 1);
+
+ if (total != 1) {
+ if (total >= 1000) {
+ printk(MY_ERR "%s: too many (%d) instances of %s\n",
+ __func__, total, pm);
+ return;
+ }
+ /* allow up to three digits and the dot */
+ size += 4;
+ }
+
+ aag = kzalloc(size, GFP_KERNEL);
+ if (!aag) {
+ printk(MY_ERR "out of memory in %s!\n", __func__);
+ return;
+ }
+
+ aag->next_acpi_attr_group = chromeos_acpi.groups;
+ chromeos_acpi.groups = aag->next_acpi_attr_group;
+ aag->ag.attrs = (struct attribute **)(aag + 1);
+ aag->ag.name = (const char *)&aag->ag.attrs[count + 1];
+
+ /* room left in the buffer */
+ size = size - (aag->ag.name - (char *)aag);
+
+ if (total != 1)
+ snprintf((char *)aag->ag.name, size, "%s.%d", pm, instance);
+ else
+ snprintf((char *)aag->ag.name, size, "%s", pm);
+
+ j = 0; /* attribute index */
+ for (i = 0; i < count; i++) {
+ union acpi_object *element = po->package.elements + i;
+ int copy_size = 0;
+ char attr_value[40]; /* 40 chars be enough for names */
+ struct acpi_attribute *paa;
+
+ switch (element->type) {
+ case ACPI_TYPE_INTEGER:
+ copy_size = snprintf(attr_value, sizeof(attr_value),
+ "%d", (int)element->integer.value);
+ paa = create_sysfs_attribute(attr_value, pm, count, i);
+ break;
+
+ case ACPI_TYPE_STRING:
+ copy_size = min(element->string.length,
+ (u32)(sizeof(attr_value)) - 1);
+ memcpy(attr_value, element->string.pointer, copy_size);
+ attr_value[copy_size] = '\0';
+ paa = create_sysfs_attribute(attr_value, pm, count, i);
+ break;
+
+ default:
+ printk(MY_ERR "ignoring nested type %d\n",
+ element->type);
+ continue;
+ }
+ aag->ag.attrs[j++] = &paa->dev_attr.attr;
+ }
+
+ if (sysfs_create_group(&chromeos_acpi.p_dev->dev.kobj, &aag->ag))
+ printk(MY_ERR "failed to create group %s.%d\n", pm, instance);
+}
+
+/*
+ * maybe_export_acpi_int() export a single int value when required
+ *
+ * @pm: name of the package
+ * @index: index of the element of the package
+ * @value: value of the element
+ */
+static void maybe_export_acpi_int(const char *pm, int index, unsigned value)
+{
+ int i;
+ struct chromeos_acpi_exported_ints {
+ const char *acpi_name;
+ int acpi_index;
+ struct chromeos_acpi_datum *cad;
+ } exported_ints[] = {
+ { "VBNV", 0, &chromeos_acpi_if_data.nv_base },
+ { "VBNV", 1, &chromeos_acpi_if_data.nv_size },
+ { "CHSW", 0, &chromeos_acpi_if_data.switch_state },
+ { "CHNV", 0, &chromeos_acpi_if_data.chnv }
+ };
+
+ for (i = 0; i < ARRAY_SIZE(exported_ints); i++) {
+ struct chromeos_acpi_exported_ints *exported_int;
+
+ exported_int = exported_ints + i;
+
+ if (!strncmp(pm, exported_int->acpi_name, 4) &&
+ (exported_int->acpi_index == index)) {
+ printk(MY_NOTICE "registering %s %d\n", pm, index);
+ exported_int->cad->cad_value = value;
+ exported_int->cad->cad_is_set = true;
+ return;
+ }
+ }
+}
+
+/*
+ * acpi_buffer_to_string() convert contents of an ACPI buffer element into a
+ * hex string truncating it if necessary to fit into one page.
+ *
+ * @element: an acpi element known to contain an ACPI buffer.
+ *
+ * Returns: pointer to an ASCII string containing the buffer representation
+ * (whatever fit into PAGE_SIZE). The caller is responsible for
+ * freeing the memory.
+ */
+static char *acpi_buffer_to_string(union acpi_object *element)
+{
+ char *base, *p;
+ int i;
+ unsigned room_left;
+ /* Include this many characters per line */
+ unsigned char_per_line = 16;
+ unsigned blob_size;
+ unsigned string_buffer_size;
+
+ /*
+ * As of now the VDAT structure can supply as much as 3700 bytes. When
+ * expressed as a hex dump it becomes 3700 * 3 + 3700/16 + .. which
+ * clearly exceeds the maximum allowed sys fs buffer size of one page
+ * (4k).
+ *
+ * What this means is that we can't keep the entire blob in one sysfs
+ * file. Currently verified boot (the consumer of the VDAT contents)
+ * does not care about the most of the data, so as a quick fix we will
+ * truncate it here. Once the blob data beyond the 4K boundary is
+ * required this approach will have to be reworked.
+ *
+ * TODO(vbendeb): Split the data into multiple VDAT instances, each
+ * not exceeding 4K or consider exporting as a binary using
+ * sysfs_create_bin_file().
+ */
+
+ /*
+ * X, the maximum number of bytes which will fit into a sysfs file
+ * (one memory page) can be derived from the following equation (where
+ * N is number of bytes included in every hex string):
+ *
+ * 3X + X/N + 4 <= PAGE_SIZE.
+ *
+ * Solving this for X gives the following
+ */
+ blob_size = ((PAGE_SIZE - 4) * char_per_line) / (char_per_line * 3 + 1);
+
+ if (element->buffer.length > blob_size)
+ printk(MY_INFO "truncating buffer from %d to %d\n",
+ element->buffer.length, blob_size);
+ else
+ blob_size = element->buffer.length;
+
+ string_buffer_size =
+ /* three characters to display one byte */
+ blob_size * 3 +
+ /* one newline per line, all rounded up, plus
+ * extra newline in the end, plus terminating
+ * zero, hence + 4
+ */
+ blob_size/char_per_line + 4;
+
+ p = kzalloc(string_buffer_size, GFP_KERNEL);
+ if (!p) {
+ printk(MY_ERR "out of memory in %s!\n", __func__);
+ return NULL;
+ }
+
+ base = p;
+ room_left = string_buffer_size;
+ for (i = 0; i < blob_size; i++) {
+ int printed;
+ printed = snprintf(p, room_left, " %2.2x",
+ element->buffer.pointer[i]);
+ room_left -= printed;
+ p += printed;
+ if (((i + 1) % char_per_line) == 0) {
+ if (!room_left)
+ break;
+ room_left--;
+ *p++ = '\n';
+ }
+ }
+ if (room_left < 2) {
+ printk(MY_ERR "%s: no room in the buffer!\n", __func__);
+ *p = '\0';
+ } else {
+ *p++ = '\n';
+ *p++ = '\0';
+ }
+ return base;
+}
+
+/*
+ * handle_acpi_package() create sysfs group including attributes
+ * representing an ACPI package.
+ *
+ * @po: package contents as returned by ACPI
+ * @pm: name of the group
+ *
+ * Scalar objects included in the package get sys fs attributes created for
+ * them. Nested packages are passed to a function creating a sys fs group per
+ * package.
+ */
+static void handle_acpi_package(union acpi_object *po, char *pm)
+{
+ int j;
+ int count = po->package.count;
+ for (j = 0; j < count; j++) {
+ union acpi_object *element = po->package.elements + j;
+ int copy_size = 0;
+ char attr_value[256]; /* strings could be this long */
+
+ switch (element->type) {
+ case ACPI_TYPE_INTEGER:
+ copy_size = snprintf(attr_value, sizeof(attr_value),
+ "%d", (int)element->integer.value);
+ add_sysfs_attribute(attr_value, pm, count, j);
+ maybe_export_acpi_int(pm, j, (unsigned)
+ element->integer.value);
+ break;
+
+ case ACPI_TYPE_STRING:
+ copy_size = min(element->string.length,
+ (u32)(sizeof(attr_value)) - 1);
+ memcpy(attr_value, element->string.pointer, copy_size);
+ attr_value[copy_size] = '\0';
+ add_sysfs_attribute(attr_value, pm, count, j);
+ break;
+
+ case ACPI_TYPE_BUFFER: {
+ char *buf_str;
+ buf_str = acpi_buffer_to_string(element);
+ if (buf_str) {
+ add_sysfs_attribute(buf_str, pm, count, j);
+ kfree(buf_str);
+ }
+ break;
+ }
+ case ACPI_TYPE_PACKAGE:
+ handle_nested_acpi_package(element, pm, count, j);
+ break;
+
+ default:
+ printk(MY_ERR "ignoring type %d (%s)\n",
+ element->type, pm);
+ break;
+ }
+ }
+}
+
+
+/*
+ * add_acpi_method() evaluate an ACPI method and create sysfs attributes.
+ *
+ * @device: ACPI device
+ * @pm: name of the method to evaluate
+ */
+static void add_acpi_method(struct acpi_device *device, char *pm)
+{
+ acpi_status status;
+ struct acpi_buffer output;
+ union acpi_object *po;
+
+ output.length = ACPI_ALLOCATE_BUFFER;
+ output.pointer = NULL;
+
+ status = acpi_evaluate_object(device->handle, pm, NULL, &output);
+
+ if (!ACPI_SUCCESS(status)) {
+ printk(MY_ERR "failed to retrieve %s (%d)\n", pm, status);
+ return;
+ }
+
+ po = output.pointer;
+
+ if (po->type != ACPI_TYPE_PACKAGE)
+ printk(MY_ERR "%s is not a package, ignored\n", pm);
+ else
+ handle_acpi_package(po, pm);
+ kfree(output.pointer);
+}
+
+/*
+ * chromeos_process_mlst() Evaluate the MLST method and add methods listed
+ * in the response.
+ *
+ * @device: ACPI device
+ *
+ * Returns: 0 if successful, non-zero if error.
+ */
+static int chromeos_process_mlst(struct acpi_device *device)
+{
+ acpi_status status;
+ struct acpi_buffer output;
+ union acpi_object *po;
+ int j;
+
+ output.length = ACPI_ALLOCATE_BUFFER;
+ output.pointer = NULL;
+
+ status = acpi_evaluate_object(device->handle, MLST_METHOD, NULL,
+ &output);
+ if (!ACPI_SUCCESS(status)) {
+ pr_debug(MY_LOGPREFIX "failed to retrieve MLST (%d)\n",
+ status);
+ return 1;
+ }
+
+ po = output.pointer;
+ if (po->type != ACPI_TYPE_PACKAGE) {
+ printk(MY_ERR MLST_METHOD "is not a package, ignored\n");
+ kfree(output.pointer);
+ return -EINVAL;
+ }
+
+ for (j = 0; j < po->package.count; j++) {
+ union acpi_object *element = po->package.elements + j;
+ int copy_size = 0;
+ char method[ACPI_NAMESEG_SIZE + 1];
+
+ if (element->type == ACPI_TYPE_STRING) {
+ copy_size = min(element->string.length,
+ (u32)ACPI_NAMESEG_SIZE);
+ memcpy(method, element->string.pointer, copy_size);
+ method[copy_size] = '\0';
+ add_acpi_method(device, method);
+ } else {
+ pr_debug(MY_LOGPREFIX "ignoring type %d\n",
+ element->type);
+ }
+ }
+
+ kfree(output.pointer);
+ return 0;
+}
+
+static int chromeos_device_add(struct acpi_device *device)
+{
+ int i;
+
+ /* Attempt to add methods by querying the device's MLST method
+ * for the list of methods. */
+ if (!chromeos_process_mlst(device))
+ return 0;
+
+ printk(MY_INFO "falling back to default list of methods\n");
+ for (i = 0; i < ARRAY_SIZE(default_methods); i++)
+ add_acpi_method(device, default_methods[i]);
+ return 0;
+}
+
+static int chromeos_device_remove(struct acpi_device *device)
+{
+ return 0;
+}
+
+static struct chromeos_vbc chromeos_vbc_nvram = {
+ .name = "chromeos_vbc_nvram",
+ .read = chromeos_vbc_nvram_read,
+ .write = chromeos_vbc_nvram_write,
+};
+
+static int __init chromeos_acpi_init(void)
+{
+ int ret = 0;
+ acpi_status status;
+
+ if (acpi_disabled)
+ return -ENODEV;
+
+ ret = chromeos_vbc_register(&chromeos_vbc_nvram);
+ if (ret)
+ return ret;
+
+ chromeos_acpi.p_dev = platform_device_register_simple("chromeos_acpi",
+ -1, NULL, 0);
+ if (IS_ERR(chromeos_acpi.p_dev)) {
+ printk(MY_ERR "unable to register platform device\n");
+ return PTR_ERR(chromeos_acpi.p_dev);
+ }
+
+ ret = acpi_bus_register_driver(&chromeos_acpi_driver);
+ if (ret < 0) {
+ printk(MY_ERR "failed to register driver (%d)\n", ret);
+ platform_device_unregister(chromeos_acpi.p_dev);
+ chromeos_acpi.p_dev = NULL;
+ return ret;
+ }
+ printk(MY_INFO "installed%s\n",
+ chromeos_on_legacy_firmware() ? " (legacy mode)" : "");
+
+ printk(MY_INFO "chromeos_acpi: enabling S3 USB wake\n");
+ status = acpi_evaluate_object(NULL, "\\S3UE", NULL, NULL);
+ if (!ACPI_SUCCESS(status))
+ printk(MY_INFO "chromeos_acpi: failed to enable S3 USB wake\n");
+
+ return 0;
+}
+
+subsys_initcall(chromeos_acpi_init);
--
2.17.1