-/*
+ /*
* chromeos_acpi.c - ChromeOS specific ACPI support
*
*
- * Copyright (C) 2010 ChromeOS contributors
+ * 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
#include <linux/kernel.h>
#include <linux/module.h>
+#include <linux/nvram.h>
#include <linux/platform_device.h>
#include <linux/acpi.h>
-MODULE_AUTHOR("Google Inc.");
-MODULE_DESCRIPTION("Chrome OS Extras Driver");
-MODULE_LICENSE("GPL");
+#include "../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
-#define CHROMEOS_ACPI_VERSION "0.02"
+
+/* 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 */
- {"PNP6666", 0}, /* dummy name to get us going */
+ {"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, int type);
+static struct chromeos_acpi_if chromeos_acpi_if_data;
static struct acpi_driver chromeos_acpi_driver = {
.name = "ChromeOS Device",
.class = "ChromeOS",
.owner = THIS_MODULE,
};
-/* The methods the chromeos ACPI device is supposed to export */
-static char *chromeos_methods[] = {
- "CHSW", "HWID", "BINF", "GPIO", "CHNV", "FWID", "FRID"
+/* 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
};
/*
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.
int total_size, room_left;
int value_len = strlen(value);
- if (!value_len) {
+ if (!value_len)
return NULL;
- }
value_len++; /* include the terminating zero */
if (count != 1) {
if (count >= 1000) {
printk(MY_ERR "%s: too many (%d) instances of %s\n",
- __FUNCTION__, count, name);
+ __func__, count, name);
return NULL;
}
/* allow up to three digits and the dot */
paa = kzalloc(total_size, GFP_KERNEL);
if (!paa) {
- printk(MY_ERR "out of memory in %s!\n", __FUNCTION__);
+ printk(MY_ERR "out of memory in %s!\n", __func__);
return NULL;
}
- paa->dev_attr.attr.owner = THIS_MODULE;
+ 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);
struct acpi_attribute *paa =
create_sysfs_attribute(value, name, count, instance);
- if (!paa) {
+ if (!paa)
return;
- }
- if (device_create_file(&chromeos_acpi.p_dev->dev, &paa->dev_attr)) {
+ if (device_create_file(&chromeos_acpi.p_dev->dev, &paa->dev_attr))
printk(MY_ERR "failed to create attribute for %s\n", name);
- }
}
/*
static void handle_nested_acpi_package(union acpi_object *po, char *pm,
int total, int instance)
{
- int ii, size, count, jj;
+ int i, size, count, j;
struct acpi_attribute_group *aag;
count = po->package.count;
if (total != 1) {
if (total >= 1000) {
printk(MY_ERR "%s: too many (%d) instances of %s\n",
- __FUNCTION__, total, pm);
+ __func__, total, pm);
return;
}
/* allow up to three digits and the dot */
aag = kzalloc(size, GFP_KERNEL);
if (!aag) {
- printk(MY_ERR "out of memory in %s!\n", __FUNCTION__);
+ printk(MY_ERR "out of memory in %s!\n", __func__);
return;
}
/* room left in the buffer */
size = size - (aag->ag.name - (char *)aag);
- if (total != 1) {
+ if (total != 1)
snprintf((char *)aag->ag.name, size, "%s.%d", pm, instance);
- } else {
+ else
snprintf((char *)aag->ag.name, size, "%s", pm);
- }
- jj = 0; /* attribute index */
- for (ii = 0; ii < count; ii++) {
- union acpi_object *element = po->package.elements + ii;
+ 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;
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, ii);
+ paa = create_sysfs_attribute(attr_value, pm, count, i);
break;
case ACPI_TYPE_STRING:
copy_size = min(element->string.length,
- sizeof(attr_value) - 1);
+ (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, ii);
+ paa = create_sysfs_attribute(attr_value, pm, count, i);
break;
default:
element->type);
continue;
}
- aag->ag.attrs[jj++] = &paa->dev_attr.attr;
+ aag->ag.attrs[j++] = &paa->dev_attr.attr;
}
- if (sysfs_create_group(&chromeos_acpi.p_dev->dev.kobj, &aag->ag)) {
+ 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;
}
/*
*/
static void handle_acpi_package(union acpi_object *po, char *pm)
{
- int jj;
+ int j;
int count = po->package.count;
- for (jj = 0; jj < count; jj++) {
- union acpi_object *element = po->package.elements + jj;
+ for (j = 0; j < count; j++) {
+ union acpi_object *element = po->package.elements + j;
int copy_size = 0;
- char attr_value[40]; /* 40 chars be enough for names */
+ 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, jj);
+ 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,
- sizeof(attr_value) - 1);
+ (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, jj);
+ 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, jj);
+ handle_nested_acpi_package(element, pm, count, j);
break;
default:
- printk(MY_ERR "ignoring type %d\n", element->type);
+ printk(MY_ERR "ignoring type %d (%s)\n",
+ element->type, pm);
break;
}
}
}
-static int chromeos_device_add(struct acpi_device *device)
+
+/*
+ * 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)
{
- int ii;
+ acpi_status status;
+ struct acpi_buffer output;
+ union acpi_object *po;
- for (ii = 0; ii < ARRAY_SIZE(chromeos_methods); ii++) {
- union acpi_object *po;
- acpi_status status;
- struct acpi_buffer output;
- char *pm = chromeos_methods[ii];
+ output.length = ACPI_ALLOCATE_BUFFER;
+ output.pointer = NULL;
- output.length = ACPI_ALLOCATE_BUFFER;
- output.pointer = NULL;
+ status = acpi_evaluate_object(device->handle, pm, NULL, &output);
- 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;
+ }
- if (!ACPI_SUCCESS(status)) {
- printk(MY_ERR "failed to retrieve %s (%d)\n", pm,
- status);
- continue;
- }
+ 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);
+}
- po = 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;
+ }
- if (po->type != ACPI_TYPE_PACKAGE) {
- printk(MY_ERR "%s is not a package, ignored\n", pm);
+ 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(element->string.length,
+ (u32)ACPI_NAME_SIZE);
+ memcpy(method, element->string.pointer, copy_size);
+ method[copy_size] = '\0';
+ add_acpi_method(device, method);
} else {
- handle_acpi_package(po, pm);
+ pr_debug(MY_LOGPREFIX "ignoring type %d\n",
+ element->type);
}
- kfree(po);
}
+
+ kfree(output.pointer);
return 0;
}
-static int chromeos_device_remove(struct acpi_device *device, int type)
+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 void chromeos_acpi_exit(void)
+static int chromeos_device_remove(struct acpi_device *device, int type)
{
- acpi_bus_unregister_driver(&chromeos_acpi_driver);
-
- while (chromeos_acpi.groups) {
- struct acpi_attribute_group *aag;
- aag = chromeos_acpi.groups;
- chromeos_acpi.groups = aag->next_acpi_attr_group;
- sysfs_remove_group(&chromeos_acpi.p_dev->dev.kobj, &aag->ag);
- kfree(aag);
- }
-
- while (chromeos_acpi.attributes) {
- struct acpi_attribute *aa = chromeos_acpi.attributes;
- chromeos_acpi.attributes = aa->next_acpi_attr;
- device_remove_file(&chromeos_acpi.p_dev->dev, &aa->dev_attr);
- kfree(aa);
- }
-
- platform_device_unregister(chromeos_acpi.p_dev);
- printk(MY_INFO "removed\n");
+ 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;
- printk(MY_INFO "ChromeOS ACPI Extras version %s built on %s@%s\n",
- CHROMEOS_ACPI_VERSION, __DATE__, __TIME__);
+ ret = chromeos_vbc_register(&chromeos_vbc_nvram);
+ if (ret)
+ return ret;
chromeos_acpi.p_dev = platform_device_register_simple("chromeos_acpi",
-1, NULL, 0);
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");
- printk(MY_INFO "installed\n");
return 0;
}
-module_init(chromeos_acpi_init);
-module_exit(chromeos_acpi_exit);
+subsys_initcall(chromeos_acpi_init);