Skip to contentSkip to conversation

Apple device properties

PATCH In review Lukas Wunner 2 participants 6 messages Sep 7, 2016 – Sep 13, 2016

Subsystems (5)
Documentation x86 ACPI EFI +1 more subsystems
lore
Share & tools
lore
#0 lore raw

v2 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 [2/4]). As a first use case, amend the Thunderbolt driver to take advantage of the Device ROM supplied by EFI (patch [4/4]).

Patch [1/4] is already queued in Rafael J. Wysocki's tree for 4.9 and is included here only because patch [2/4] wouldn't compile without it.

The series also depends on these two patches which are already queued in Matt's tree for 4.9:

- x86/efi: Optimize away setup_gop32/64 if unused
https://patchwork.kernel.org/patch/9315763/

- x86/efi: Allow invocation of arbitrary boot services
https://patchwork.kernel.org/patch/9293371/

Changes since v1:

- Previously there were two separate patches for retrieving properties and assigning them to devices. These are now squashed together in patch [3/4]. (Requested by Matt Fleming.)

- The version of the EFI properties protocol as well as the properties payload is now checked.

- Applied a bit of polish all over.

Link to v1:
https://lkml.org/lkml/2016/7/27/218

Browseable on GitHub:
https://github.com/l1k/linux/commits/apple_properties_v2

Thanks,

Lukas

Lukas Wunner (4):
ACPI / bus: Make acpi_get_first_physical_node() public
efi: Add device path parser
x86/efi: Retrieve and assign Apple device properties
thunderbolt: Use Device ROM retrieved from EFI

 Documentation/kernel-parameters.txt     |   5 +
 arch/x86/boot/compressed/eboot.c        |  63 +++++++++
 arch/x86/include/uapi/asm/bootparam.h   |   1 +
 drivers/acpi/internal.h                 |   1 -
 drivers/firmware/efi/Kconfig            |  18 +++
 drivers/firmware/efi/Makefile           |   2 +
 drivers/firmware/efi/apple-properties.c | 230 ++++++++++++++++++++++++++++++++
 drivers/firmware/efi/dev-path-parser.c  | 186 ++++++++++++++++++++++++++
 drivers/thunderbolt/Kconfig             |   1 +
 drivers/thunderbolt/eeprom.c            |  42 ++++++
 drivers/thunderbolt/switch.c            |   2 +-
 include/linux/acpi.h                    |   7 +
 include/linux/efi.h                     |  38 ++++++
 13 files changed, 594 insertions(+), 2 deletions(-)
 create mode 100644 drivers/firmware/efi/apple-properties.c
 create mode 100644 drivers/firmware/efi/dev-path-parser.c
Signature · 1 line
2.9.3
#1 lore raw

PATCH v2 1/4 ACPI / bus: Make acpi_get_first_physical_node() public

Following the fwnode of a device is currently a one-way road: We provide ACPI_COMPANION() to obtain the fwnode but there's no (public) method to do the reverse. Granted, there may be multiple physical_nodes, but often the first one in the list is sufficient.

A handy function to obtain it was introduced with commit 3b95bd160547 ("ACPI: introduce a function to find the first physical device"), but currently it's only available internally.

We're about to add an EFI Device Path parser which needs this function. Consider the following device path: ACPI(PNP0A03,0)/PCI(28,2)/PCI(0,0) The PCI root is encoded as an ACPI device in the path, so the parser has to find the corresponding ACPI device, then find its physical node, find the PCI bridge in slot 1c (decimal 28), function 2 below it and finally find the PCI device in slot 0, function 0.

To this end, make acpi_get_first_physical_node() public.

---
 drivers/acpi/internal.h | 1 -
 include/linux/acpi.h    | 7 +++++++
 2 files changed, 7 insertions(+), 1 deletion(-)
drivers/acpi/internal.h +0 -1
index 27cc7fe..48e718e 100644
@@ -113,7 +113,6 @@ bool acpi_device_is_present(struct acpi_device *adev);
 bool acpi_device_is_battery(struct acpi_device *adev);
 bool acpi_device_is_first_physical_node(struct acpi_device *adev,
 					const struct device *dev);
-struct device *acpi_get_first_physical_node(struct acpi_device *adev);
 
 /* --------------------------------------------------------------------------
                      Device Matching and Notification
include/linux/acpi.h +7 -0
index 288fac5..8ab6a95 100644
@@ -85,6 +85,8 @@ static inline const char *acpi_dev_name(struct acpi_device *adev)
 	return dev_name(&adev->dev);
 }
 
+struct device *acpi_get_first_physical_node(struct acpi_device *adev);
+
 enum acpi_irq_model_id {
 	ACPI_IRQ_MODEL_PIC = 0,
 	ACPI_IRQ_MODEL_IOAPIC,
@@ -588,6 +590,11 @@ static inline const char *acpi_dev_name(struct acpi_device *adev)
 	return NULL;
 }
 
+static inline struct device *acpi_get_first_physical_node(struct acpi_device *adev)
+{
+	return NULL;
+}
+
 static inline void early_acpi_table_init(void *data, size_t size) { }
 static inline void acpi_early_init(void) { }
 static inline void acpi_subsystem_init(void) { }
Signature · 1 line
2.9.3
#2 lore raw

PATCH v2 2/4 efi: Add device path parser

In reply to Lukas Wunner

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 moderate effort.

Apple device properties is currently the only use case of this parser, but since this may be useful to others, make it 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 get_device_by_efi_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.

Cc Matt Fleming <matt-mF/unelCI9GS6iBeEJttW/[email protected]>
---
 drivers/firmware/efi/Kconfig           |   5 +
 drivers/firmware/efi/Makefile          |   1 +
 drivers/firmware/efi/dev-path-parser.c | 186 +++++++++++++++++++++++++++++++++
 include/linux/efi.h                    |  21 ++++
 4 files changed, 213 insertions(+)
 create mode 100644 drivers/firmware/efi/dev-path-parser.c
drivers/firmware/efi/Kconfig +5 -0
index c981be1..893fda4 100644
@@ -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
index c8a439f..3e91ae3 100644
@@ -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 +186 -0

186 lines not on this page · raw message

include/linux/efi.h +21 -0
index 4ae1a1a..29ba93c 100644
@@ -1136,6 +1136,27 @@ 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)
+int get_device_by_efi_path(struct efi_dev_path **node, size_t len,
+			   struct device **dev);
+#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.9.3
#3 lore raw

PATCH v2 3/4 x86/efi: Retrieve and assign Apple device properties

In reply to Lukas Wunner

Apple'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 /usr/standalone/i386/boot.efi retrieves the properties with the same protocol. The module 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 @osxreverser 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        |  63 +++++++++
 arch/x86/include/uapi/asm/bootparam.h   |   1 +
 drivers/firmware/efi/Kconfig            |  13 ++
 drivers/firmware/efi/Makefile           |   1 +
 drivers/firmware/efi/apple-properties.c | 230 ++++++++++++++++++++++++++++++++
 include/linux/efi.h                     |  17 +++
 7 files changed, 330 insertions(+)
 create mode 100644 drivers/firmware/efi/apple-properties.c
Documentation/kernel-parameters.txt +5 -0
index 82b42c9..d693fc3d 100644
@@ -993,6 +993,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 +63 -0

81 lines not on this page · raw message

arch/x86/include/uapi/asm/bootparam.h +1 -0
index c18ce67..b10bf31 100644
@@ -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
index 893fda4..2e78b0b 100644
@@ -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
index 3e91ae3..ad67342 100644
@@ -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 +230 -0

230 lines not on this page · raw message

include/linux/efi.h +17 -0
index 29ba93c..32f2f1f 100644
@@ -434,6 +434,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;
+
+typedef struct {
+	u64 version;
+	u64 get;
+	u64 set;
+	u64 del;
+	u64 get_all;
+} apple_properties_protocol_64;
+
 /*
  * Types and defines for EFI ResetSystem
  */
@@ -582,6 +598,7 @@ void efi_native_runtime_setup(void);
 #define EFI_RNG_PROTOCOL_GUID			EFI_GUID(0x3152bca5, 0xeade, 0x433d,  0x86, 0x2e, 0xc0, 0x1c, 0xdc, 0x29, 0x1f, 0x44)
 #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.9.3
#4 lore raw

PATCH v2 4/4 thunderbolt: Use Device ROM retrieved from EFI

In reply to Lukas Wunner

Macs 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.

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.

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.

---
 drivers/thunderbolt/Kconfig  |  1 +
 drivers/thunderbolt/eeprom.c | 42 ++++++++++++++++++++++++++++++++++++++++++
 drivers/thunderbolt/switch.c |  2 +-
 3 files changed, 44 insertions(+), 1 deletion(-)
drivers/thunderbolt/Kconfig +1 -0
index c121acc..0056df7 100644
@@ -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 +42 -0

66 lines not on this page · raw message

drivers/thunderbolt/switch.c +1 -1
index 1e116f5..d8a6cf1 100644
@@ -458,7 +458,7 @@ int tb_switch_resume(struct tb_switch *sw)
 		tb_sw_warn(sw, "uid read failed\n");
 		return err;
 	}
-	if (sw->uid != uid) {
+	if (tb_route(sw) && sw->uid != uid) {
 		tb_sw_info(sw,
 			"changed while suspended (uid %#llx -> %#llx)\n",
 			sw->uid, uid);
Signature · 1 line
2.9.3
#5 lore raw
In reply to Lukas Wunner
Quoted 9 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 [2/4]). As a first use case, amend the Thunderbolt driver to take advantage of the Device ROM supplied by EFI (patch [4/4]).

Patch [1/4] is already queued in Rafael J. Wysocki's tree for 4.9 and is included here only because patch [2/4] wouldn't compile without it.

Quoted 8 lines

The series also depends on these two patches which are already queued in Matt's tree for 4.9:

- x86/efi: Optimize away setup_gop32/64 if unused
https://patchwork.kernel.org/patch/9315763/

- x86/efi: Allow invocation of arbitrary boot services
https://patchwork.kernel.org/patch/9293371/

I had a quick look over these series and nothing looks too crazy, but I still need to do an in-depth review.

Given the patch dependencies you outlined above, could you resubmit this after the v4.9 merge window closes? That way it won't be forgotten about.