diff options
Diffstat (limited to 'drivers/acpi')
-rw-r--r-- | drivers/acpi/Makefile | 1 | ||||
-rw-r--r-- | drivers/acpi/internal.h | 6 | ||||
-rw-r--r-- | drivers/acpi/property.c | 3 | ||||
-rw-r--r-- | drivers/acpi/x86/apple.c | 141 |
4 files changed, 151 insertions, 0 deletions
diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index b1aacfc62b1f..90265ab4437a 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -50,6 +50,7 @@ acpi-$(CONFIG_ACPI_REDUCED_HARDWARE_ONLY) += evged.o acpi-y += sysfs.o acpi-y += property.o acpi-$(CONFIG_X86) += acpi_cmos_rtc.o +acpi-$(CONFIG_X86) += x86/apple.o acpi-$(CONFIG_X86) += x86/utils.o acpi-$(CONFIG_DEBUG_FS) += debugfs.o acpi-$(CONFIG_ACPI_NUMA) += numa.o diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 58dd7ab3c653..ee066d74f5bf 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -233,6 +233,12 @@ static inline void suspend_nvs_restore(void) {} void acpi_init_properties(struct acpi_device *adev); void acpi_free_properties(struct acpi_device *adev); +#ifdef CONFIG_X86 +void acpi_extract_apple_properties(struct acpi_device *adev); +#else +static inline void acpi_extract_apple_properties(struct acpi_device *adev) {} +#endif + /*-------------------------------------------------------------------------- Watchdog -------------------------------------------------------------------------- */ diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index 116bfc1937b5..fc1bb083f5f0 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -376,6 +376,9 @@ void acpi_init_properties(struct acpi_device *adev) if (acpi_of && !adev->flags.of_compatible_ok) acpi_handle_info(adev->handle, ACPI_DT_NAMESPACE_HID " requires 'compatible' property\n"); + + if (!adev->data.pointer) + acpi_extract_apple_properties(adev); } static void acpi_destroy_nondev_subnodes(struct list_head *list) diff --git a/drivers/acpi/x86/apple.c b/drivers/acpi/x86/apple.c new file mode 100644 index 000000000000..51b4cf9f25da --- /dev/null +++ b/drivers/acpi/x86/apple.c @@ -0,0 +1,141 @@ +/* + * apple.c - Apple ACPI quirks + * Copyright (C) 2017 Lukas Wunner <lukas@wunner.de> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License (version 2) as + * published by the Free Software Foundation. + */ + +#include <linux/acpi.h> +#include <linux/bitmap.h> +#include <linux/platform_data/x86/apple.h> +#include <linux/uuid.h> + +/* Apple _DSM device properties GUID */ +static const guid_t apple_prp_guid = + GUID_INIT(0xa0b5b7c6, 0x1318, 0x441c, + 0xb0, 0xc9, 0xfe, 0x69, 0x5e, 0xaf, 0x94, 0x9b); + +/** + * acpi_extract_apple_properties - retrieve and convert Apple _DSM properties + * @adev: ACPI device for which to retrieve the properties + * + * Invoke Apple's custom _DSM once to check the protocol version and once more + * to retrieve the properties. They are marshalled up in a single package as + * alternating key/value elements, unlike _DSD which stores them as a package + * of 2-element packages. Convert to _DSD format and make them available under + * the primary fwnode. + */ +void acpi_extract_apple_properties(struct acpi_device *adev) +{ + unsigned int i, j = 0, newsize = 0, numprops, numvalid; + union acpi_object *props, *newprops; + unsigned long *valid = NULL; + void *free_space; + + if (!x86_apple_machine) + return; + + props = acpi_evaluate_dsm_typed(adev->handle, &apple_prp_guid, 1, 0, + NULL, ACPI_TYPE_BUFFER); + if (!props) + return; + + if (!props->buffer.length) + goto out_free; + + if (props->buffer.pointer[0] != 3) { + acpi_handle_info(adev->handle, FW_INFO + "unsupported properties version %*ph\n", + props->buffer.length, props->buffer.pointer); + goto out_free; + } + + ACPI_FREE(props); + props = acpi_evaluate_dsm_typed(adev->handle, &apple_prp_guid, 1, 1, + NULL, ACPI_TYPE_PACKAGE); + if (!props) + return; + + numprops = props->package.count / 2; + if (!numprops) + goto out_free; + + valid = kcalloc(BITS_TO_LONGS(numprops), sizeof(long), GFP_KERNEL); + if (!valid) + goto out_free; + + /* newsize = key length + value length of each tuple */ + for (i = 0; i < numprops; i++) { + union acpi_object *key = &props->package.elements[i * 2]; + union acpi_object *val = &props->package.elements[i * 2 + 1]; + + if ( key->type != ACPI_TYPE_STRING || + (val->type != ACPI_TYPE_INTEGER && + val->type != ACPI_TYPE_BUFFER)) + continue; /* skip invalid properties */ + + __set_bit(i, valid); + newsize += key->string.length + 1; + if ( val->type == ACPI_TYPE_BUFFER) + newsize += val->buffer.length; + } + + numvalid = bitmap_weight(valid, numprops); + if (numprops > numvalid) + acpi_handle_info(adev->handle, FW_INFO + "skipped %u properties: wrong type\n", + numprops - numvalid); + if (numvalid == 0) + goto out_free; + + /* newsize += top-level package + 3 objects for each key/value tuple */ + newsize += (1 + 3 * numvalid) * sizeof(union acpi_object); + newprops = ACPI_ALLOCATE_ZEROED(newsize); + if (!newprops) + goto out_free; + + /* layout: top-level package | packages | key/value tuples | strings */ + newprops->type = ACPI_TYPE_PACKAGE; + newprops->package.count = numvalid; + newprops->package.elements = &newprops[1]; + free_space = &newprops[1 + 3 * numvalid]; + + for_each_set_bit(i, valid, numprops) { + union acpi_object *key = &props->package.elements[i * 2]; + union acpi_object *val = &props->package.elements[i * 2 + 1]; + unsigned int k = 1 + numvalid + j * 2; /* index into newprops */ + unsigned int v = k + 1; + + newprops[1 + j].type = ACPI_TYPE_PACKAGE; + newprops[1 + j].package.count = 2; + newprops[1 + j].package.elements = &newprops[k]; + + newprops[k].type = ACPI_TYPE_STRING; + newprops[k].string.length = key->string.length; + newprops[k].string.pointer = free_space; + memcpy(free_space, key->string.pointer, key->string.length); + free_space += key->string.length + 1; + + newprops[v].type = val->type; + if (val->type == ACPI_TYPE_INTEGER) { + newprops[v].integer.value = val->integer.value; + } else { + newprops[v].buffer.length = val->buffer.length; + newprops[v].buffer.pointer = free_space; + memcpy(free_space, val->buffer.pointer, + val->buffer.length); + free_space += val->buffer.length; + } + j++; /* count valid properties */ + } + WARN_ON(free_space != (void *)newprops + newsize); + + adev->data.properties = newprops; + adev->data.pointer = newprops; + +out_free: + ACPI_FREE(props); + kfree(valid); +} |