Message ID | e72ce423489dd604ae98ad3b76e07caf944cb581.1498636759.git.lukas@wunner.de (mailing list archive) |
---|---|
State | Changes Requested, archived |
Headers | show |
On Wed, Jun 28, 2017 at 8:20 PM, Lukas Wunner <lukas@wunner.de> wrote: > While the rest of the world has standardized on _DSD as the way to store > device properties in AML (introduced with ACPI 5.1 in 2014), Apple has > been using a custom _DSM to achieve the same for much longer (ever since > they switched from DeviceTree-based PowerPC to Intel in 2005, verified > with MacOS X 10.4.11). > +/** > + * acpi_retrieve_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. > + */ > +static void acpi_retrieve_apple_properties(struct acpi_device *adev) > +{ > + unsigned int i, j, newsize = 0, numprops, skipped = 0; > + union acpi_object *props, *newprops; > + void *free_space; > + > + if (!IS_ENABLED(CONFIG_X86) || !dmi_match(DMI_SYS_VENDOR, "Apple Inc.")) > + return; You are using this in few places, perhaps it makes sense to do in (maybe) header like drivers/acpi/apple.h static inline bool is_apple_system(void) { return IS_ENABLED(CONFIG_X86) && dmi_match(DMI_SYS_VENDOR, "Apple Inc."); } I know this makes more LOCs, the rationale is to keep such deterministic things in one place (basically maintenance). Rafael, Mika? > + /* newsize = key length + value length of each tuple */ > + numprops = props->package.count / 2; > + 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)) { > + key->type = ACPI_TYPE_ANY; /* mark as to be skipped */ > + skipped++; > + continue; > + } > + newsize += key->string.length + 1; > + if ( val->type == ACPI_TYPE_BUFFER) > + newsize += val->buffer.length; > + } > + > + if (skipped) > + acpi_handle_info(adev->handle, FW_INFO > + "skipped %u properties: wrong type\n", > + skipped); > + if (skipped == numprops) > + goto out_free; > + > + /* newsize += top-level package + 3 objects for each key/value tuple */ > + newsize += (1 + 3 * (numprops - skipped)) * 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 = numprops - skipped; > + newprops->package.elements = &newprops[1]; > + free_space = &newprops[1 + 3 * (numprops - skipped)]; > + > + for (i = 0, j = 0; i < numprops; i++) { Instead I would rather to create a bitmask above and do here something like for_each_bit_set() (Note, we have a lot of helpers to work with bitmaps) > + union acpi_object *key = &props->package.elements[i * 2]; > + union acpi_object *val = &props->package.elements[i * 2 + 1]; > + unsigned int k = (1 + numprops - skipped) + j * 2; > + unsigned int v = k + 1; /* index into newprops */ ...and this rather complex arithmetics will go away. > + > + if (key->type == ACPI_TYPE_ANY) > + continue; /* skipped */ ...and this. > + > + 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 { checkpatch.pl ? > + 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++; /* not incremented for skipped properties */ > + }
On Wed, Jun 28, 2017 at 07:20:19PM +0200, Lukas Wunner wrote: > While the rest of the world has standardized on _DSD as the way to store > device properties in AML (introduced with ACPI 5.1 in 2014), Apple has > been using a custom _DSM to achieve the same for much longer (ever since > they switched from DeviceTree-based PowerPC to Intel in 2005, verified > with MacOS X 10.4.11). > > The theory of operation on macOS is as follows: AppleACPIPlatform.kext > invokes mergeEFIproperties() and mergeDSMproperties() for each device to > merge properties conveyed by EFI drivers as well as properties stored in > AML into the I/O Kit registry from which they can be retrieved by > drivers. We've been supporting EFI properties since commit 58c5475aba67 > ("x86/efi: Retrieve and assign Apple device properties"). The present > commit adds support for _DSM properties, thereby completing our support > for Apple device properties. The _DSM properties are made available > under the primary fwnode, the EFI properties under the secondary fwnode. > So for devices which possess both property types, they can all be > elegantly accessed with the uniform API in <linux/property.h>. > > Until recently we had no need to support _DSM properties, they contained > only uninteresting garbage. The situation has changed with MacBooks and > MacBook Pros introduced since 2015: Their keyboard is attached with SPI > instead of USB and the _CRS data which is necessary to initialize the > spi driver only contains valid information if OSPM responds "false" to > _OSI("Darwin"). If OSPM responds "true", _CRS is empty and the spi > driver fails to initialize. The rationale is very simple, Apple only > cares about macOS and Windows: On Windows, _CRS contains valid data, > whereas on macOS it is empty. Instead, macOS gleans the necessary data > from the _DSM properties. > > Since Linux deliberately defaults to responding "true" to _OSI("Darwin"), > we need to emulate macOS' behaviour by initializing the spi driver with > data returned by the _DSM. > > An out-of-tree driver for the SPI keyboard exists which currently binds > to the ACPI device, invokes the _DSM, parses the returned package and > instantiates an SPI device with the data gleaned from the _DSM: > https://github.com/cb22/macbook12-spi-driver/commit/9a416d699ef4 > https://github.com/cb22/macbook12-spi-driver/commit/0c34936ed9a1 > > By adding support for Apple's _DSM properties in generic ACPI code, the > out-of-tree driver will be able to register as a regular SPI device, > significantly reducing its amount of code and improving its chances to > be mainlined. > > The SPI keyboard will not be the only user of this commit: E.g. on the > MacBook8,1, the UART-attached Bluetooth device likewise returns empty > _CRS data if OSPM returns "true" to _OSI("Darwin"). > > The _DSM returns a Package whose format unfortunately deviates slightly > from the _DSD spec: The properties are marshalled up in a single Package > as alternating key/value elements, unlike _DSD which stores them as a > Package of 2-element Packages. The present commit therefore converts > the Package to _DSD format and the ACPI core can then treat the data as > if Apple would follow the standard. > > Well, except for one small annoyance: The properties returned by the > _DSM only ever have one of two types, Integer or Buffer. The former is > retrievable as usual with device_property_read_u64(), but the latter is > not part of the _DSD spec and it is not possible to retrieve Buffer > properties with the device_property_read_*() functions due to the type > checking performed in drivers/acpi/property.c. It is however possible > to retrieve them with acpi_dev_get_property(). Apple is using the > Buffer type somewhat sloppily to store null-terminated strings but also > integers. The real data type is not distinguishable by the ACPI core > and the onus is on the caller to use the contents of the Buffer in an > appropriate way. > > In case Apple moves to _DSD in the future, this commit first checks for > _DSD and falls back to _DSM only if _DSD is not found. > > Cc: Rafael J. Wysocki <rafael.j.wysocki@intel.com> > Cc: Mika Westerberg <mika.westerberg@linux.intel.com> Andy had some comments which I think you should consider. Regardless of that this looks good to me. Also I have one of those new Macs with SPI connected keyboard so happy to see the support getting into mainline ;-) Acked-by: Mika Westerberg <mika.westerberg@linux.intel.com> -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Wed, Jun 28, 2017 at 09:53:49PM +0300, Andy Shevchenko wrote: > On Wed, Jun 28, 2017 at 8:20 PM, Lukas Wunner <lukas@wunner.de> wrote: > > While the rest of the world has standardized on _DSD as the way to store > > device properties in AML (introduced with ACPI 5.1 in 2014), Apple has > > been using a custom _DSM to achieve the same for much longer (ever since > > they switched from DeviceTree-based PowerPC to Intel in 2005, verified > > with MacOS X 10.4.11). [snip] > > +static void acpi_retrieve_apple_properties(struct acpi_device *adev) > > +{ > > + unsigned int i, j, newsize = 0, numprops, skipped = 0; > > + union acpi_object *props, *newprops; > > + void *free_space; > > + > > + if (!IS_ENABLED(CONFIG_X86) || !dmi_match(DMI_SYS_VENDOR, "Apple Inc.")) > > + return; > > You are using this in few places, perhaps it makes sense to do in > (maybe) header like > drivers/acpi/apple.h > > static inline bool is_apple_system(void) > { > return IS_ENABLED(CONFIG_X86) && dmi_match(DMI_SYS_VENDOR, "Apple Inc."); > } > > I know this makes more LOCs, the rationale is to keep such > deterministic things in one place (basically maintenance). You mean like is_uv_system() for SGI UltraViolet? I could add a global bool is_apple_system which is set early during boot, then the DMI check would only have to be performed once. The bool would be #defined to false on non-x86 arches via a header file which would have to live in include/linux/. I could add an x86-specific config option such as CONFIG_X86_APPLE which would allow removal of all Mac-specific quirks and behaviour by likewise #defining is_apple_system to false. I'm not sure if the x86 maintainers will approve of this. Next thing you know, someone wants to add the same thing for Dell or whatever. OTOH the amount of code to deal with Apple quirks may justify it. Adding x86 maintainers to cc. Ingo, Thomas, Peter, any opinion? My concern is that this series is bikeshedded / postponed indefinitely if it is made to depend on such bigger changes. (The discussion of which is legitimate of course.) Thanks, Lukas -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
On Sun, Jul 2, 2017 at 1:07 PM, Lukas Wunner <lukas@wunner.de> wrote: > On Wed, Jun 28, 2017 at 09:53:49PM +0300, Andy Shevchenko wrote: >> On Wed, Jun 28, 2017 at 8:20 PM, Lukas Wunner <lukas@wunner.de> wrote: >> > While the rest of the world has standardized on _DSD as the way to store >> > device properties in AML (introduced with ACPI 5.1 in 2014), Apple has >> > been using a custom _DSM to achieve the same for much longer (ever since >> > they switched from DeviceTree-based PowerPC to Intel in 2005, verified >> > with MacOS X 10.4.11). > [snip] >> > +static void acpi_retrieve_apple_properties(struct acpi_device *adev) >> > +{ >> > + unsigned int i, j, newsize = 0, numprops, skipped = 0; >> > + union acpi_object *props, *newprops; >> > + void *free_space; >> > + >> > + if (!IS_ENABLED(CONFIG_X86) || !dmi_match(DMI_SYS_VENDOR, "Apple Inc.")) >> > + return; >> >> You are using this in few places, perhaps it makes sense to do in >> (maybe) header like >> drivers/acpi/apple.h >> >> static inline bool is_apple_system(void) >> { >> return IS_ENABLED(CONFIG_X86) && dmi_match(DMI_SYS_VENDOR, "Apple Inc."); >> } >> >> I know this makes more LOCs, the rationale is to keep such >> deterministic things in one place (basically maintenance). > > You mean like is_uv_system() for SGI UltraViolet? > > I could add a global bool is_apple_system which is set early during boot, > then the DMI check would only have to be performed once. The bool would > be #defined to false on non-x86 arches via a header file which would have > to live in include/linux/. I could add an x86-specific config option > such as CONFIG_X86_APPLE which would allow removal of all Mac-specific > quirks and behaviour by likewise #defining is_apple_system to false. > > I'm not sure if the x86 maintainers will approve of this. Next thing > you know, someone wants to add the same thing for Dell or whatever. > OTOH the amount of code to deal with Apple quirks may justify it. > Adding x86 maintainers to cc. Ingo, Thomas, Peter, any opinion? Well, it doesn't have to go into arch/x86/. We have drivers/acpi/x86/ now, so you can put it in there, say into apple.c that will only be built for CONFIG_X86 set. Then, instead of using a concealed #ifdef in-line, you could do that openly in a header. Moreover, having a static bool variable in drivers/acpi/x86/apple.c would not be super-objectionable IMO. > My concern is that this series is bikeshedded / postponed indefinitely > if it is made to depend on such bigger changes. (The discussion of > which is legitimate of course.) Of course, the Apple properties check in scan.c only makes sense if the system is Apple, but then the dmi_match() adds a bit of overhead for everything x86 that isn't Apple, which I guess is the majority. So I'd like that overhead to be reduced if possible. Thanks, Rafael -- To unsubscribe from this list: send the line "unsubscribe linux-acpi" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
diff --git a/drivers/acpi/property.c b/drivers/acpi/property.c index 116bfc1937b5..6a56f3a85f18 100644 --- a/drivers/acpi/property.c +++ b/drivers/acpi/property.c @@ -15,7 +15,9 @@ #include <linux/acpi.h> #include <linux/device.h> +#include <linux/dmi.h> #include <linux/export.h> +#include <linux/uuid.h> #include "internal.h" @@ -34,6 +36,124 @@ static const u8 ads_uuid[16] = { 0xe6, 0xe3, 0xb8, 0xdb, 0x86, 0x58, 0xa6, 0x4b, 0x87, 0x95, 0x13, 0x19, 0xf5, 0x2a, 0x96, 0x6b }; +/* 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_retrieve_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. + */ +static void acpi_retrieve_apple_properties(struct acpi_device *adev) +{ + unsigned int i, j, newsize = 0, numprops, skipped = 0; + union acpi_object *props, *newprops; + void *free_space; + + if (!IS_ENABLED(CONFIG_X86) || !dmi_match(DMI_SYS_VENDOR, "Apple Inc.")) + return; + + props = acpi_evaluate_dsm_typed(adev->handle, &apple_prp_guid, 1, 0, + NULL, ACPI_TYPE_BUFFER); + if (!props) + return; + + if (!props->buffer.length || 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; + + /* newsize = key length + value length of each tuple */ + numprops = props->package.count / 2; + 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)) { + key->type = ACPI_TYPE_ANY; /* mark as to be skipped */ + skipped++; + continue; + } + newsize += key->string.length + 1; + if ( val->type == ACPI_TYPE_BUFFER) + newsize += val->buffer.length; + } + + if (skipped) + acpi_handle_info(adev->handle, FW_INFO + "skipped %u properties: wrong type\n", + skipped); + if (skipped == numprops) + goto out_free; + + /* newsize += top-level package + 3 objects for each key/value tuple */ + newsize += (1 + 3 * (numprops - skipped)) * 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 = numprops - skipped; + newprops->package.elements = &newprops[1]; + free_space = &newprops[1 + 3 * (numprops - skipped)]; + + for (i = 0, j = 0; i < numprops; i++) { + union acpi_object *key = &props->package.elements[i * 2]; + union acpi_object *val = &props->package.elements[i * 2 + 1]; + unsigned int k = (1 + numprops - skipped) + j * 2; + unsigned int v = k + 1; /* index into newprops */ + + if (key->type == ACPI_TYPE_ANY) + continue; /* skipped */ + + 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++; /* not incremented for skipped properties */ + } + WARN_ON(free_space != (void *)newprops + newsize); + + adev->data.properties = newprops; + adev->data.pointer = newprops; + +out_free: + ACPI_FREE(props); +} static bool acpi_enumerate_nondev_subnodes(acpi_handle scope, const union acpi_object *desc, @@ -376,6 +496,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_retrieve_apple_properties(adev); } static void acpi_destroy_nondev_subnodes(struct list_head *list)