/* * 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 #include #include #include #include #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 /* 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_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 { 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);