(Cc'ing Peter since he's played with device path parsing before) On Mon, 17 Oct, at 12:57:23PM, Lukas Wunner wrote: > 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@xxxxxxxxxxxxxxxxxxx> > Cc: Ard Biesheuvel <ard.biesheuvel@xxxxxxxxxx> > Signed-off-by: Lukas Wunner <lukas@xxxxxxxxx> > --- > 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 > > diff --git a/drivers/firmware/efi/Kconfig b/drivers/firmware/efi/Kconfig > index c981be1..893fda4 100644 > --- a/drivers/firmware/efi/Kconfig > +++ b/drivers/firmware/efi/Kconfig > @@ -133,3 +133,8 @@ endmenu > > config UEFI_CPER > bool > + > +config EFI_DEV_PATH_PARSER > + bool > + depends on ACPI > + default n > diff --git a/drivers/firmware/efi/Makefile b/drivers/firmware/efi/Makefile > index c8a439f..3e91ae3 100644 > --- a/drivers/firmware/efi/Makefile > +++ b/drivers/firmware/efi/Makefile > @@ -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) > diff --git a/drivers/firmware/efi/dev-path-parser.c b/drivers/firmware/efi/dev-path-parser.c > new file mode 100644 > index 0000000..549f80b > --- /dev/null > +++ b/drivers/firmware/efi/dev-path-parser.c > @@ -0,0 +1,186 @@ > +/* > + * dev-path-parser.c - EFI Device Path parser > + * Copyright (C) 2016 Lukas Wunner <lukas@xxxxxxxxx> > + * > + * 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. > + * > + * 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, see <http://www.gnu.org/licenses/>. > + */ > + > +#include <linux/acpi.h> > +#include <linux/efi.h> > +#include <linux/pci.h> > + > +struct acpi_hid_uid { > + struct acpi_device_id hid[2]; > + char uid[11]; /* UINT_MAX + null byte */ > +}; > + > +static int __init acpi_match(struct device *dev, void *data) > +{ > + struct acpi_hid_uid hid_uid = *(struct acpi_hid_uid *)data; > + struct acpi_device *adev = to_acpi_device(dev); > + > + if (acpi_match_device_ids(adev, hid_uid.hid)) > + return 0; > + > + if (adev->pnp.unique_id) > + return !strcmp(adev->pnp.unique_id, hid_uid.uid); > + else > + return !strcmp("0", hid_uid.uid); > +} > + > +static int __init get_device_by_acpi_path(struct efi_dev_path *node, > + struct device *parent, > + struct device **child) > +{ > + struct acpi_hid_uid hid_uid = {}; > + struct device *phys_dev; > + > + if (node->length != 12) > + return -EINVAL; > + > + sprintf(hid_uid.hid[0].id, "%c%c%c%04X", > + 'A' + ((node->acpi.hid >> 10) & 0x1f) - 1, > + 'A' + ((node->acpi.hid >> 5) & 0x1f) - 1, > + 'A' + ((node->acpi.hid >> 0) & 0x1f) - 1, > + node->acpi.hid >> 16); > + sprintf(hid_uid.uid, "%u", node->acpi.uid); > + > + *child = bus_find_device(&acpi_bus_type, NULL, &hid_uid, acpi_match); > + if (!*child) > + return -ENODEV; > + > + phys_dev = acpi_get_first_physical_node(to_acpi_device(*child)); > + if (phys_dev) { > + get_device(phys_dev); > + put_device(*child); > + *child = phys_dev; > + } > + > + return 0; > +} > + > +static int __init pci_match(struct device *dev, void *data) > +{ > + unsigned int devfn = *(unsigned int *)data; > + > + return dev_is_pci(dev) && to_pci_dev(dev)->devfn == devfn; > +} > + > +static int __init get_device_by_pci_path(struct efi_dev_path *node, > + struct device *parent, > + struct device **child) > +{ > + unsigned int devfn; > + > + if (node->length != 6) > + return -EINVAL; > + if (!parent) > + return -EINVAL; > + > + devfn = PCI_DEVFN(node->pci.dev, node->pci.fn); > + > + *child = device_find_child(parent, &devfn, pci_match); > + if (!*child) > + return -ENODEV; > + > + return 0; > +} > + > +/* > + * Insert parsers for further node types here. > + * > + * Each parser takes a pointer to the @node and to the @parent (will be NULL > + * for the first device path node). If a device corresponding to @node was > + * found below @parent, its reference count should be incremented and the > + * device returned in @child. > + * > + * The return value should be 0 on success or a negative int on failure. > + * The special return value 1 signals the end of the device path, only > + * get_device_by_end_path() is supposed to return this. > + * > + * Be sure to validate the node length and contents before commencing the > + * search for a device. > + */ > + > +static int __init get_device_by_end_path(struct efi_dev_path *node, > + struct device *parent, > + struct device **child) > +{ > + if (node->length != 4) > + return -EINVAL; > + if (!parent) > + return -ENODEV; > + > + *child = get_device(parent); > + return 1; > +} > + > +/** > + * get_device_by_efi_path - find device by EFI Device Path > + * @node: EFI Device Path > + * @len: maximum length of EFI Device Path in bytes > + * @dev: device found > + * > + * Parse a series of EFI Device Path nodes at @node and find the corresponding > + * device. If the device was found, its reference count is incremented and a > + * pointer to it is returned in @dev. The caller needs to drop the reference > + * with put_device() after use. The @node pointer is updated to point to the > + * location immediately after the "End Entire Device Path" node. > + * > + * If a Device Path node is malformed or its corresponding device is not found, > + * @node is updated to point to this offending node and @dev will be %NULL. > + * > + * Most buses instantiate devices in "subsys" initcall level, hence the > + * earliest initcall level in which this function should be called is "fs". > + * > + * Returns 0 on success, > + * %-ENODEV if no device was found, > + * %-EINVAL if a node is malformed or exceeds @len, > + * %-ENOTSUPP if support for a node type is not yet implemented. > + */ > +int __init get_device_by_efi_path(struct efi_dev_path **node, size_t len, > + struct device **dev) > +{ > + struct device *parent = NULL, *child; > + int ret = 0; > + > + *dev = NULL; > + > + while (ret != 1) { > + if (len < 4 || len < (*node)->length) > + ret = -EINVAL; > + else if ((*node)->type == EFI_DEV_ACPI && > + (*node)->sub_type == EFI_DEV_BASIC_ACPI) > + ret = get_device_by_acpi_path(*node, parent, &child); > + else if ((*node)->type == EFI_DEV_HW && > + (*node)->sub_type == EFI_DEV_PCI) > + ret = get_device_by_pci_path(*node, parent, &child); > + else if (((*node)->type == EFI_DEV_END_PATH || > + (*node)->type == EFI_DEV_END_PATH2) && > + (*node)->sub_type == EFI_DEV_END_ENTIRE) > + ret = get_device_by_end_path(*node, parent, &child); > + else > + ret = -ENOTSUPP; > + > + put_device(parent); > + if (ret < 0) > + return ret; > + > + parent = child; > + *node = (void *)*node + (*node)->length; > + len -= (*node)->length; > + } > + > + *dev = child; > + return 0; > +} Where in your patch series is this function called? Am I missing something? Also, unless there's some existing namespace with "get_device_by_*" I'd prefer for this function name to have "efi_" as the prefix. > diff --git a/include/linux/efi.h b/include/linux/efi.h > index 2d08948..4faf339 100644 > --- a/include/linux/efi.h > +++ b/include/linux/efi.h > @@ -1145,6 +1145,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); > -- > 2.9.3 > -- To unsubscribe from this list: send the line "unsubscribe linux-efi" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html