From patchwork Mon Jul 31 13:03:18 2017 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Enric Balletbo i Serra X-Patchwork-Id: 9871813 X-Patchwork-Delegate: andy.shevchenko@gmail.com Return-Path: Received: from mail.wl.linuxfoundation.org (pdx-wl-mail.web.codeaurora.org [172.30.200.125]) by pdx-korg-patchwork.web.codeaurora.org (Postfix) with ESMTP id 98E8760375 for ; Mon, 31 Jul 2017 13:03:52 +0000 (UTC) Received: from mail.wl.linuxfoundation.org (localhost [127.0.0.1]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 8003026E97 for ; Mon, 31 Jul 2017 13:03:52 +0000 (UTC) Received: by mail.wl.linuxfoundation.org (Postfix, from userid 486) id 7328127FB6; Mon, 31 Jul 2017 13:03:52 +0000 (UTC) X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on pdx-wl-mail.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-6.9 required=2.0 tests=BAYES_00, RCVD_IN_DNSWL_HI, UNPARSEABLE_RELAY autolearn=unavailable version=3.3.1 Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.wl.linuxfoundation.org (Postfix) with ESMTP id 1B2AD26E97 for ; Mon, 31 Jul 2017 13:03:51 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1752311AbdGaNDe (ORCPT ); Mon, 31 Jul 2017 09:03:34 -0400 Received: from bhuna.collabora.co.uk ([46.235.227.227]:49833 "EHLO bhuna.collabora.co.uk" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752261AbdGaNDb (ORCPT ); Mon, 31 Jul 2017 09:03:31 -0400 Received: from [127.0.0.1] (localhost [127.0.0.1]) (Authenticated sender: eballetbo) with ESMTPSA id BDFCF264462 From: Enric Balletbo i Serra To: Benson Leung , Olof Johansson , Darren Hart , Andy Shevchenko Cc: linux-kernel@vger.kernel.org, platform-driver-x86@vger.kernel.org, Olof Johansson Subject: [PATCH 2/2] platform: x86: add ACPI driver for ChromeOS. Date: Mon, 31 Jul 2017 15:03:18 +0200 Message-Id: <20170731130318.22976-3-enric.balletbo@collabora.com> X-Mailer: git-send-email 2.9.3 In-Reply-To: <20170731130318.22976-1-enric.balletbo@collabora.com> References: <20170731130318.22976-1-enric.balletbo@collabora.com> Sender: platform-driver-x86-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: platform-driver-x86@vger.kernel.org X-Virus-Scanned: ClamAV using ClamSMTP From: Olof Johansson 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. Signed-off-by: Olof Johansson Signed-off-by: Enric Balletbo i Serra --- drivers/platform/x86/Kconfig | 9 + drivers/platform/x86/Makefile | 1 + drivers/platform/x86/chromeos_acpi.c | 792 +++++++++++++++++++++++++++++++++++ 3 files changed, 802 insertions(+) create mode 100644 drivers/platform/x86/chromeos_acpi.c diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index b048607..09ea96f 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -1138,6 +1138,15 @@ config SILEAD_DMI with the OS-image for the device. This option supplies the missing information. Enable this for x86 tablets with Silead touchscreens. +config ACPI_CHROMEOS + bool "ChromeOS specific ACPI extensions" + depends on ACPI && 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. + endif # X86_PLATFORM_DEVICES config PMC_ATOM diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 91cec17..d1d7eac 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -66,6 +66,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_PVPANIC) += pvpanic.o obj-$(CONFIG_ALIENWARE_WMI) += alienware-wmi.o diff --git a/drivers/platform/x86/chromeos_acpi.c b/drivers/platform/x86/chromeos_acpi.c new file mode 100644 index 0000000..6c2a5e0 --- /dev/null +++ b/drivers/platform/x86/chromeos_acpi.c @@ -0,0 +1,792 @@ +/* + * chromeos_acpi.c - ChromeOS specific ACPI support + * + * Copyright (C) 2017 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. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include + +#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 int 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; +}; + +static struct chromeos_acpi_if chromeos_acpi_if_data; + +/* + * 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" + +/* + * 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 int 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. + * + * returns the 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) { + pr_err("NVRAM not configured\n"); + 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("not enough room to read nvram (%zd < %d)\n", + 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 int base, size, i; + + if (!chromeos_acpi_if_data.nv_base.cad_is_set || + !chromeos_acpi_if_data.nv_size.cad_is_set) { + pr_err("NVRAM not configured\n"); + return -ENODEV; + } + + size = chromeos_acpi_if_data.nv_size.cad_value; + base = chromeos_acpi_if_data.nv_base.cad_value; + + if (count != size) { + pr_err("wrong buffer size (%zd != %d)\n", 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) { + pr_err("too many (%d) instances of %s.\n", count, name); + return NULL; + } + /* allow up to three digits and the dot */ + total_size += 4; + } + + paa = kzalloc(total_size, GFP_KERNEL); + if (!paa) + 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)) + pr_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) { + pr_err("too many (%d) instances of %s\n", total, pm); + return; + } + /* allow up to three digits and the dot */ + size += 4; + } + + aag = kzalloc(size, GFP_KERNEL); + if (!aag) + 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: + pr_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)) + pr_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 int 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)) { + pr_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 int room_left; + /* Include this many characters per line */ + unsigned int char_per_line = 16; + unsigned int blob_size; + unsigned int 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: 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) + pr_info("truncating buffer from %d to %d\n", + element->buffer.length, blob_size); + else + blob_size = element->buffer.length; + + /* + * Three characters to display one byte, one newline per line, all + * rounded up, plus extra newline in the end, plus terminating + * zero, hence + 4 + */ + string_buffer_size = blob_size * 3 + blob_size / char_per_line + 4; + + p = kzalloc(string_buffer_size, GFP_KERNEL); + if (!p) + 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) { + pr_err("no room in the buffer\n"); + *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 int) + 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: + pr_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)) { + pr_err("failed to retrieve %s (%d)\n", pm, status); + return; + } + + po = output.pointer; + + if (po->type != ACPI_TYPE_PACKAGE) + pr_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("failed to retrieve MLST (%d)\n", status); + return 1; + } + + po = output.pointer; + if (po->type != ACPI_TYPE_PACKAGE) { + pr_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_NAME_SIZE + 1]; + + if (element->type == ACPI_TYPE_STRING) { + copy_size = min_t(u32, element->string.length, + ACPI_NAME_SIZE); + memcpy(method, element->string.pointer, copy_size); + method[copy_size] = '\0'; + add_acpi_method(device, method); + } + } + + kfree(output.pointer); + + 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 chromeos_acpi_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; + + pr_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_acpi_remove(struct acpi_device *device) +{ + return 0; +} + +static const struct acpi_device_id chromeos_device_ids[] = { + {"GGL0001", 0}, /* Google's own */ + {"", 0}, +}; +MODULE_DEVICE_TABLE(acpi, chromeos_device_ids); + +static struct acpi_driver chromeos_acpi_driver = { + .name = "ChromeOS ACPI driver", + .owner = THIS_MODULE, + .ids = chromeos_device_ids, + .ops = { + .add = chromeos_acpi_add, + .remove = chromeos_acpi_remove, + }, +}; + +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)) { + pr_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) { + pr_err("failed to register driver (%d)\n", ret); + + platform_device_unregister(chromeos_acpi.p_dev); + chromeos_acpi.p_dev = NULL; + return ret; + } + + pr_info("installed%s\n", chromeos_on_legacy_firmware() ? + " (legacy mode)" : ""); + + pr_info("enabling S3 USB wake\n"); + status = acpi_evaluate_object(NULL, "\\S3UE", NULL, NULL); + if (!ACPI_SUCCESS(status)) + pr_info("failed to enable S3 USB wake\n"); + + return 0; +} + +static void __exit chromeos_acpi_exit(void) +{ + acpi_bus_unregister_driver(&chromeos_acpi_driver); +} + +module_init(chromeos_acpi_init); +module_exit(chromeos_acpi_exit);