Apple device properties
Subsystems (6)
v4 cover Apple device properties
Retrieve device properties from EFI on Macs before ExitBootServices is called and assign them to devices (patch [3/4]). The devices that properties pertain to are encoded as EFI Device Paths, so add a parser for these (patch [1/4]). As a first use case, amend the Thunderbolt driver to take advantage of the Device ROM supplied by EFI (patch [4/4]).
Changes since v3:
- New patch [2/4] which introduces efi_call_proto() and efi_table_attr() macros to allow bitness-agnostic EFI protocol calls and table lookups. Previously when retrieving the properties in patch [3/4], a ternary operator was used to make the code work in mixed-mode, but Matt correctly remarked that it impacted readability. The new approach seeks to maintain readability while avoiding code duplication.
- Address all other comments by Matt on patch [3/4]: In eboot.c, move comparison of fw_vendor and call to retrieve_apple_device_properties() out of efi_main() and into a separate setup_quirks() function which may accommodate future vendor- or device-specific quirks. Fix up comments and curly braces, add newlines and suffix typedefs with "_t" as requested by Matt.
- Fix bug in comparison of fw_vendor in patch [3/4] wherein the system table was assumed to be the 64-bit version when compiling on x86_64, which obviously didn't work in mixed-mode.
- Fix double free in error path in patch [4/4] spotted by Andreas.
Link to v3:
http://www.spinics.net/lists/linux-efi/msg09647.html
Browseable on GitHub:
https://github.com/l1k/linux/commits/apple_properties_v4
Thanks,
Lukas
Lukas Wunner (4):
efi: Add device path parser
efi: Allow bitness-agnostic protocol calls x86/efi: Retrieve and assign Apple device properties
thunderbolt: Use Device ROM retrieved from EFI
Documentation/kernel-parameters.txt | 5 + arch/arm/include/asm/efi.h | 3 + arch/arm64/include/asm/efi.h | 3 + arch/x86/boot/compressed/eboot.c | 65 +++++++++ arch/x86/include/asm/efi.h | 16 ++- arch/x86/include/uapi/asm/bootparam.h | 1 + drivers/firmware/efi/Kconfig | 18 +++ drivers/firmware/efi/Makefile | 2 + drivers/firmware/efi/apple-properties.c | 248 ++++++++++++++++++++++++++++++++ drivers/firmware/efi/dev-path-parser.c | 203 ++++++++++++++++++++++++++ drivers/thunderbolt/Kconfig | 1 + drivers/thunderbolt/eeprom.c | 43 ++++++ drivers/thunderbolt/switch.c | 2 +- include/linux/efi.h | 37 +++++ 14 files changed, 641 insertions(+), 6 deletions(-) create mode 100644 drivers/firmware/efi/apple-properties.c create mode 100644 drivers/firmware/efi/dev-path-parser.c
Signature · 1 line
2.10.1
A message that is not in the archive (sent off-list or not yet received): cover.1478506920.git.lukas-JFq808J9C/[email protected]
PATCH v4 1/4 efi: Add device path parser
We're about to extended the efistub to retrieve device properties from EFI on Apple Macs. The properties use EFI Device Paths to indicate the device they belong to. This commit adds a parser which, given an EFI Device Path, locates the corresponding struct device and returns a reference to it.
Initially only ACPI and PCI Device Path nodes are supported, these are the only types needed for Apple device properties (the corresponding macOS function AppleACPIPlatformExpert::matchEFIDevicePath() does not support any others). Further node types can be added with little to moderate effort.
Apple device properties is currently the only use case of this parser, but Peter Jones intends to use it to match up devices with the ConInDev/ConOutDev/ErrOutDev variables and add sysfs attributes to these devices to say the hardware supports using them as console. Thus, make this parser a separate component which can be selected with config option EFI_DEV_PATH_PARSER. It can in principle be compiled as a module if acpi_get_first_physical_node() and acpi_bus_type are exported (and efi_get_device_by_path() itself is exported).
The dependency on CONFIG_ACPI is needed for acpi_match_device_ids(). It can be removed if an empty inline stub is added for that function.
--- drivers/firmware/efi/Kconfig | 5 + drivers/firmware/efi/Makefile | 1 + drivers/firmware/efi/dev-path-parser.c | 203 +++++++++++++++++++++++++++++++++ include/linux/efi.h | 20 ++++ 4 files changed, 229 insertions(+) create mode 100644 drivers/firmware/efi/dev-path-parser.c
drivers/firmware/efi/Kconfig +5 -0
@@ -133,3 +133,8 @@ endmenu config UEFI_CPER bool + +config EFI_DEV_PATH_PARSER + bool + depends on ACPI + default n
drivers/firmware/efi/Makefile +1 -0
@@ -21,6 +21,7 @@ obj-$(CONFIG_EFI_STUB) += libstub/ obj-$(CONFIG_EFI_FAKE_MEMMAP) += fake_mem.o obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o obj-$(CONFIG_EFI_TEST) += test/ +obj-$(CONFIG_EFI_DEV_PATH_PARSER) += dev-path-parser.o arm-obj-$(CONFIG_EFI) := arm-init.o arm-runtime.o obj-$(CONFIG_ARM) += $(arm-obj-y)
drivers/firmware/efi/dev-path-parser.c new file +203 -0
203 lines not on this page · raw message
include/linux/efi.h +20 -0
@@ -1148,6 +1148,26 @@ struct efi_generic_dev_path {u16 length; } __attribute ((packed)); +struct efi_dev_path { +u8 type;/* can be replaced with unnamed */ +u8 sub_type;/* struct efi_generic_dev_path; */ +u16 length;/* once we've moved to -std=c11 */ +union { +struct { +u32 hid; +u32 uid; + } acpi; +struct { +u8 fn; +u8 dev; + } pci; + }; +} __attribute ((packed)); + +#if IS_ENABLED (CONFIG_EFI_DEV_PATH_PARSER ) +struct device *efi_get_device_by_path (struct efi_dev_path **node,size_t *len); +#endif +static inline void memrange_efi_to_native (u64 *addr,u64 *npages) { *npages =PFN_UP (*addr + (*npages<<EFI_PAGE_SHIFT )) -PFN_DOWN (*addr);
Signature · 1 line
2.10.1
PATCH v4 2/4 efi: Allow bitness-agnostic protocol calls
In reply to a message not in the archiveWe already have a macro to invoke boot services which on x86 adapts automatically to the bitness of the EFI firmware: efi_call_early().
The macro allows sharing of functions across arches and bitness variants as long as those functions only call boot services. However in practice functions in the EFI stub contain a mix of boot services calls and protocol calls.
Add an efi_call_proto() macro for bitness-agnostic protocol calls to allow sharing more code across arches as well as deduplicating 32 bit and 64 bit code paths.
On x86, implement it using a new efi_table_attr() macro for bitness- agnostic table lookups. Refactor efi_call_early() to make use of the same macro. (The resulting object code remains identical.)
--- arch/arm/include/asm/efi.h | 3 +++ arch/arm64/include/asm/efi.h | 3 +++ arch/x86/include/asm/efi.h | 16 +++++++++++----- 3 files changed, 17 insertions(+), 5 deletions(-)
arch/arm/include/asm/efi.h +3 -0
@@ -57,6 +57,9 @@ void efi_virtmap_unload(void);#define __efi_call_early (f, ...) f(__VA_ARGS__)#define efi_is_64bit () (false) +#define efi_call_proto (protocol, f, instance, ...) \ + ((protocol##_t *)instance)->f(instance, ##__VA_ARGS__) +struct screen_info *alloc_screen_info (efi_system_table_t *sys_table_arg);void free_screen_info (efi_system_table_t *sys_table,struct screen_info *si);
arch/arm64/include/asm/efi.h +3 -0
@@ -51,6 +51,9 @@ int efi_set_mapping_permissions(struct mm_struct *mm, efi_memory_desc_t *md);#define __efi_call_early (f, ...) f(__VA_ARGS__)#define efi_is_64bit () (true) +#define efi_call_proto (protocol, f, instance, ...) \ + ((protocol##_t *)instance)->f(instance, ##__VA_ARGS__) + #define alloc_screen_info(x...) &screen_info #define free_screen_info(x...)
arch/x86/include/asm/efi.h +11 -5
@@ -210,12 +210,18 @@ static inline bool efi_is_64bit(void)return __efi_early ()->is64; } +#define efi_table_attr (table, attr, instance) \ + (efi_is_64bit() ? \ + ((table##_64_t *)(unsigned long)instance)->attr : \ + ((table##_32_t *)(unsigned long)instance)->attr) + +#define efi_call_proto (protocol, f, instance, ...) \ + __efi_early()->call(efi_table_attr(protocol, f, instance), \ + instance, ##__VA_ARGS__) +#define efi_call_early (f, ...) \ - __efi_early()->call(efi_is_64bit() ? \ - ((efi_boot_services_64_t *)(unsigned long) \ - __efi_early()->boot_services)->f : \ - ((efi_boot_services_32_t *)(unsigned long) \ - __efi_early()->boot_services)->f, __VA_ARGS__) + __efi_early()->call(efi_table_attr(efi_boot_services, f, \ + __efi_early()->boot_services), __VA_ARGS__)#define __efi_call_early (f, ...) \ __efi_early()->call((unsigned long)f, __VA_ARGS__);
Signature · 1 line
2.10.1
A message that is not in the archive (sent off-list or not yet received): b6591dbe76700984afd4c881408aadb7b10587ba.1478506920.git.lukas-JFq808J9C/[email protected]
Re: [PATCH v4 2/4] efi: Allow bitness-agnostic protocol calls
Quoted 16 lines · Lukas Wunner
We already have a macro to invoke boot services which on x86 adapts automatically to the bitness of the EFI firmware:
efi_call_early().The macro allows sharing of functions across arches and bitness variants as long as those functions only call boot services. However in practice functions in the EFI stub contain a mix of boot services calls and protocol calls.
Add an
efi_call_proto()macro for bitness-agnostic protocol calls to allow sharing more code across arches as well as deduplicating 32 bit and 64 bit code paths.On x86, implement it using a new
efi_table_attr()macro for bitness- agnostic table lookups. Refactorefi_call_early()to make use of the same macro. (The resulting object code remains identical.)
Quoted 8 lines
Cc Matt Fleming <matt-mF/unelCI9GS6iBeEJttW/[email protected]>Cc Ard Biesheuvel <[email protected]>Signed-off-by Lukas Wunner <lukas-JFq808J9C/[email protected]>--- arch/arm/include/asm/efi.h | 3 +++ arch/arm64/include/asm/efi.h | 3 +++ arch/x86/include/asm/efi.h | 16 +++++++++++----- 3 files changed, 17 insertions(+), 5 deletions(-)
Neat!
PATCH v4 3/4 x86/efi: Retrieve and assign Apple device properties
In reply to a message not in the archiveApple's EFI drivers supply device properties which are needed to support Macs optimally. They contain vital information which cannot be obtained any other way (e.g. Thunderbolt Device ROM). They're also used to convey the current device state so that OS drivers can pick up where EFI drivers left (e.g. GPU mode setting).
There's an EFI driver dubbed "AAPL,PathProperties" which implements a per-device key/value store. Other EFI drivers populate it using a custom protocol. The macOS bootloader /System/Library/CoreServices/boot.efi retrieves the properties with the same protocol. The kernel extension AppleACPIPlatform.kext subsequently merges them into the I/O Kit registry (see ioreg(8)) where they can be queried by other kernel extensions and user space.
This commit extends the efistub to retrieve the device properties before ExitBootServices is called. It assigns them to devices in an fs_initcall so that they can be queried with the API in <linux/property.h>.
Note that the device properties will only be available if the kernel is booted with the efistub. Distros should adjust their installers to always use the efistub on Macs. grub with the "linux" directive will not work unless the functionality of this commit is duplicated in grub. (The "linuxefi" directive should work but is not included upstream as of this writing.)
The custom protocol has GUID 91BD12FE-F6C3-44FB-A5B7-5122AB303AE0 and looks like this:
typedef struct {
unsigned long version; /* 0x10000 */
efi_status_t (*get) (
IN struct apple_properties_protocol *this,
IN struct efi_dev_path *device,
IN efi_char16_t *property_name,
OUT void *buffer,
IN OUT u32 *buffer_len);
/* EFI_SUCCESS, EFI_NOT_FOUND, EFI_BUFFER_TOO_SMALL */
efi_status_t (*set) (
IN struct apple_properties_protocol *this,
IN struct efi_dev_path *device,
IN efi_char16_t *property_name,
IN void *property_value,
IN u32 property_value_len);
/* allocates copies of property name and value */
/* EFI_SUCCESS, EFI_OUT_OF_RESOURCES */
efi_status_t (*del) (
IN struct apple_properties_protocol *this,
IN struct efi_dev_path *device,
IN efi_char16_t *property_name);
/* EFI_SUCCESS, EFI_NOT_FOUND */
efi_status_t (*get_all) (
IN struct apple_properties_protocol *this,
OUT void *buffer,
IN OUT u32 *buffer_len);
/* EFI_SUCCESS, EFI_BUFFER_TOO_SMALL */
} apple_properties_protocol;Thanks to Pedro Vilaça for this blog post which was helpful in reverse engineering Apple's EFI drivers and bootloader:
https://reverse.put.as/2016/06/25/apple-efi-firmware-passwords-and-the-scbo-myth/
If someone at Apple is reading this, please note there's a memory leak in your implementation of the del() function as the property struct is freed but the name and value allocations are not.
Neither the macOS bootloader nor Apple's EFI drivers check the protocol version, but we do to avoid breakage if it's ever changed. It's been the same since at least OS X 10.6 (2009).
The get_all() function conveniently fills a buffer with all properties in marshalled form which can be passed to the kernel as a setup_data payload. The number of device properties is dynamic and can change between a first invocation of get_all() (to determine the buffer size) and a second invocation (to retrieve the actual buffer), hence the peculiar loop which does not finish until the buffer size settles. The macOS bootloader does the same.
The setup_data payload is later on unmarshalled in an fs_initcall. The idea is that most buses instantiate devices in "subsys" initcall level and drivers are usually bound to these devices in "device" initcall level, so we assign the properties in-between, i.e. in "fs" initcall level.
This assumes that devices to which properties pertain are instantiated from a "subsys" initcall or earlier. That should always be the case since on macOS, AppleACPIPlatformExpert::matchEFIDevicePath() only supports ACPI and PCI nodes and we've fully scanned those buses during "subsys" initcall level.
The second assumption is that properties are only needed from a "device" initcall or later. Seems reasonable to me, but should this ever not work out, an alternative approach would be to store the property sets e.g. in a btree early during boot. Then whenever device_add() is called, an EFI Device Path would have to be constructed for the newly added device, and looked up in the btree. That way, the property set could be assigned to the device immediately on instantiation. And this would also work for devices instantiated in a deferred fashion. It seems like this approach would be more complicated and require more code. That doesn't seem justified without a specific use case.
For comparison, the strategy on macOS is to assign properties to objects in the ACPI namespace (AppleACPIPlatformExpert::mergeEFIProperties()). That approach is definitely wrong as it fails for devices not present in the namespace: The NHI EFI driver supplies properties for attached Thunderbolt devices, yet on Macs with Thunderbolt 1 only one device level behind the host controller is described in the namespace. Consequently macOS cannot assign properties for chained devices. With Thunderbolt 2 they started to describe three device levels behind host controllers in the namespace but this grossly inflates the SSDT and still fails if the user daisy-chained more than three devices.
We copy the property names and values from the setup_data payload to swappable virtual memory and afterwards make the payload available to the page allocator. This is just for the sake of good housekeeping, it wouldn't occupy a meaningful amount of physical memory (4444 bytes on my machine). Only the payload is freed, not the setup_data header since otherwise we'd break the list linkage and we cannot safely update the predecessor's ->next link because there's no locking for the list.
The payload is currently not passed on to kexec'ed kernels, same for PCI ROMs retrieved by setup_efi_pci(). This can be added later if there is demand by amending setup_efi_state(). The payload can then no longer be made available to the page allocator of course.
--- Documentation/kernel-parameters.txt | 5 + arch/x86/boot/compressed/eboot.c | 65 +++++++++ arch/x86/include/uapi/asm/bootparam.h | 1 + drivers/firmware/efi/Kconfig | 13 ++ drivers/firmware/efi/Makefile | 1 + drivers/firmware/efi/apple-properties.c | 248 ++++++++++++++++++++++++++++++++ include/linux/efi.h | 17 +++ 7 files changed, 350 insertions(+) create mode 100644 drivers/firmware/efi/apple-properties.c
Documentation/kernel-parameters.txt +5 -0
@@ -1062,6 +1062,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted. dscc4.setup= [NET] + dump_apple_properties [X86] + Dump name and content of EFI device properties on + x86 Macs. Useful for driver authors to determine + what data is available or for reverse-engineering. + dyndbg[="val"] [KNL,DYNAMIC_DEBUG] module.dyndbg[="val"] Enable debug messages at boot time. See
arch/x86/boot/compressed/eboot.c +65 -0
77 lines not on this page · raw message
arch/x86/include/uapi/asm/bootparam.h +1 -0
@@ -7,6 +7,7 @@#define SETUP_DTB 2#define SETUP_PCI 3#define SETUP_EFI 4 +#define SETUP_APPLE_PROPERTIES 5/* ram_size flags */ #define RAMDISK_IMAGE_START_MASK 0x07FF
drivers/firmware/efi/Kconfig +13 -0
@@ -129,6 +129,19 @@ config EFI_TEST Say Y here to enable the runtime services support via /dev/efi_test. If unsure, say N. +config APPLE_PROPERTIES + bool "Apple Device Properties" + depends on EFI_STUB && X86 + select EFI_DEV_PATH_PARSER + select UCS2_STRING + help + Retrieve properties from EFI on Apple Macs and assign them to + devices, allowing for improved support of Apple hardware. + Properties that would otherwise be missing include the + Thunderbolt Device ROM and GPU configuration data. + + If unsure, say Y if you have a Mac. Otherwise N. + endmenu config UEFI_CPER
drivers/firmware/efi/Makefile +1 -0
@@ -22,6 +22,7 @@ obj-$(CONFIG_EFI_FAKE_MEMMAP) += fake_mem.o obj-$(CONFIG_EFI_BOOTLOADER_CONTROL) += efibc.o obj-$(CONFIG_EFI_TEST) += test/ obj-$(CONFIG_EFI_DEV_PATH_PARSER) += dev-path-parser.o +obj-$(CONFIG_APPLE_PROPERTIES) += apple-properties.o arm-obj-$(CONFIG_EFI) := arm-init.o arm-runtime.o obj-$(CONFIG_ARM) += $(arm-obj-y)
drivers/firmware/efi/apple-properties.c new file +248 -0
248 lines not on this page · raw message
include/linux/efi.h +17 -0
@@ -443,6 +443,22 @@ typedef struct {#define EFI_PCI_IO_ATTRIBUTE_VGA_PALETTE_IO_16 0x20000#define EFI_PCI_IO_ATTRIBUTE_VGA_IO_16 0x40000 +typedef struct { +u32 version; +u32 get; +u32 set; +u32 del; +u32 get_all; +}apple_properties_protocol_32_t ; + +typedef struct { +u64 version; +u64 get; +u64 set; +u64 del; +u64 get_all; +}apple_properties_protocol_64_t ; +/* * Types and defines for EFI ResetSystem */
@@ -592,6 +608,7 @@ void efi_native_runtime_setup(void);#define EFI_RNG_ALGORITHM_RAW EFI_GUID(0xe43176d7, 0xb6e8, 0x4827, 0xb7, 0x84, 0x7f, 0xfd, 0xc4, 0xb6, 0x85, 0x61)#define EFI_MEMORY_ATTRIBUTES_TABLE_GUID EFI_GUID(0xdcfa911d, 0x26eb, 0x469f, 0xa2, 0x20, 0x38, 0xb7, 0xdc, 0x46, 0x12, 0x20)#define EFI_CONSOLE_OUT_DEVICE_GUID EFI_GUID(0xd3b36f2c, 0xd551, 0x11d4, 0x9a, 0x46, 0x00, 0x90, 0x27, 0x3f, 0xc1, 0x4d) +#define APPLE_PROPERTIES_PROTOCOL_GUID EFI_GUID(0x91bd12fe, 0xf6c3, 0x44fb, 0xa5, 0xb7, 0x51, 0x22, 0xab, 0x30, 0x3a, 0xe0)/* * This GUID is used to pass to the kernel proper the struct screen_info
Signature · 1 line
2.10.1
PATCH v4 4/4 thunderbolt: Use Device ROM retrieved from EFI
In reply to a message not in the archiveMacs with Thunderbolt 1 do not have a unit-specific DROM: The DROM is empty with uid 0x1000000000000. (Apple started factory-burning a unit- specific DROM with Thunderbolt 2.)
Instead, the NHI EFI driver supplies a DROM in a device property. Use it if available. It's only available when booting with the efistub. If it's not available, silently fall back to our hardcoded DROM.
The size of the DROM is always 256 bytes. The number is hardcoded into the NHI EFI driver. This commit can deal with an arbitrary size however, just in case they ever change that.
Background information: The EFI firmware volume contains ROM files for the NHI, GMUX and several other chips as well as key material. This strategy allows Apple to deploy ROM or key updates by simply publishing an EFI firmware update on their website. Drivers do not access those files directly but rather through a file server via EFI protocol AC5E4829-A8FD-440B-AF33-9FFE013B12D8. Files are identified by GUID, the NHI DROM has 339370BD-CFC6-4454-8EF7-704653120818.
The NHI EFI driver amends that file with a unit-specific uid. The uid has 64 bit but its entropy is much lower: 24 bit represent the model, 24 bit are taken from a serial number, 16 bit are fixed. The NHI EFI driver obtains the serial number via the DataHub protocol, copies it into the DROM, calculates the CRC and submits the result as a device property.
A modification is needed in the resume code where we currently read the uid of all switches in the hierarchy to detect plug events that occurred during sleep. On Thunderbolt 1 root switches this will now lead to a mismatch between the uid of the empty DROM and the EFI DROM. Exempt the root switch from this check: It's built in, so the uid should never change. However we continue to *read* the uid of the root switch, this seems like a good way to test its reachability after resume.
--- drivers/thunderbolt/Kconfig | 1 + drivers/thunderbolt/eeprom.c | 43 +++++++++++++++++++++++++++++++++++++++++++ drivers/thunderbolt/switch.c | 2 +- 3 files changed, 45 insertions(+), 1 deletion(-)
drivers/thunderbolt/Kconfig +1 -0
@@ -1,6 +1,7 @@ menuconfig THUNDERBOLT tristate "Thunderbolt support for Apple devices" depends on PCI + select APPLE_PROPERTIES select CRC32 help Cactus Ridge Thunderbolt Controller driver
drivers/thunderbolt/eeprom.c +43 -0
67 lines not on this page · raw message
drivers/thunderbolt/switch.c +1 -1
@@ -460,7 +460,7 @@ int tb_switch_resume(struct tb_switch *sw)tb_sw_warn (sw,"uid read failed\n" );return err; } -if (sw->uid != uid) { +if (sw != sw->tb->root_switch && sw->uid != uid) {tb_sw_info (sw,"changed while suspended (uid %#llx -> %#llx)\n" , sw->uid, uid);
Signature · 1 line
2.10.1
Quoted 5 lines · Lukas Wunner
Retrieve device properties from EFI on Macs before ExitBootServices is called and assign them to devices (patch [3/4]). The devices that properties pertain to are encoded as EFI Device Paths, so add a parser for these (patch [1/4]). As a first use case, amend the Thunderbolt driver to take advantage of the Device ROM supplied by EFI (patch [4/4]).
Looks good to me Lukas. Queued for v4.10, thanks!