18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * dev-path-parser.c - EFI Device Path parser 48c2ecf20Sopenharmony_ci * Copyright (C) 2016 Lukas Wunner <lukas@wunner.de> 58c2ecf20Sopenharmony_ci * 68c2ecf20Sopenharmony_ci * This program is free software; you can redistribute it and/or modify 78c2ecf20Sopenharmony_ci * it under the terms of the GNU General Public License (version 2) as 88c2ecf20Sopenharmony_ci * published by the Free Software Foundation. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include <linux/acpi.h> 128c2ecf20Sopenharmony_ci#include <linux/efi.h> 138c2ecf20Sopenharmony_ci#include <linux/pci.h> 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cistruct acpi_hid_uid { 168c2ecf20Sopenharmony_ci struct acpi_device_id hid[2]; 178c2ecf20Sopenharmony_ci char uid[11]; /* UINT_MAX + null byte */ 188c2ecf20Sopenharmony_ci}; 198c2ecf20Sopenharmony_ci 208c2ecf20Sopenharmony_cistatic int __init match_acpi_dev(struct device *dev, const void *data) 218c2ecf20Sopenharmony_ci{ 228c2ecf20Sopenharmony_ci struct acpi_hid_uid hid_uid = *(const struct acpi_hid_uid *)data; 238c2ecf20Sopenharmony_ci struct acpi_device *adev = to_acpi_device(dev); 248c2ecf20Sopenharmony_ci 258c2ecf20Sopenharmony_ci if (acpi_match_device_ids(adev, hid_uid.hid)) 268c2ecf20Sopenharmony_ci return 0; 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_ci if (adev->pnp.unique_id) 298c2ecf20Sopenharmony_ci return !strcmp(adev->pnp.unique_id, hid_uid.uid); 308c2ecf20Sopenharmony_ci else 318c2ecf20Sopenharmony_ci return !strcmp("0", hid_uid.uid); 328c2ecf20Sopenharmony_ci} 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic long __init parse_acpi_path(const struct efi_dev_path *node, 358c2ecf20Sopenharmony_ci struct device *parent, struct device **child) 368c2ecf20Sopenharmony_ci{ 378c2ecf20Sopenharmony_ci struct acpi_hid_uid hid_uid = {}; 388c2ecf20Sopenharmony_ci struct device *phys_dev; 398c2ecf20Sopenharmony_ci 408c2ecf20Sopenharmony_ci if (node->header.length != 12) 418c2ecf20Sopenharmony_ci return -EINVAL; 428c2ecf20Sopenharmony_ci 438c2ecf20Sopenharmony_ci sprintf(hid_uid.hid[0].id, "%c%c%c%04X", 448c2ecf20Sopenharmony_ci 'A' + ((node->acpi.hid >> 10) & 0x1f) - 1, 458c2ecf20Sopenharmony_ci 'A' + ((node->acpi.hid >> 5) & 0x1f) - 1, 468c2ecf20Sopenharmony_ci 'A' + ((node->acpi.hid >> 0) & 0x1f) - 1, 478c2ecf20Sopenharmony_ci node->acpi.hid >> 16); 488c2ecf20Sopenharmony_ci sprintf(hid_uid.uid, "%u", node->acpi.uid); 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci *child = bus_find_device(&acpi_bus_type, NULL, &hid_uid, 518c2ecf20Sopenharmony_ci match_acpi_dev); 528c2ecf20Sopenharmony_ci if (!*child) 538c2ecf20Sopenharmony_ci return -ENODEV; 548c2ecf20Sopenharmony_ci 558c2ecf20Sopenharmony_ci phys_dev = acpi_get_first_physical_node(to_acpi_device(*child)); 568c2ecf20Sopenharmony_ci if (phys_dev) { 578c2ecf20Sopenharmony_ci get_device(phys_dev); 588c2ecf20Sopenharmony_ci put_device(*child); 598c2ecf20Sopenharmony_ci *child = phys_dev; 608c2ecf20Sopenharmony_ci } 618c2ecf20Sopenharmony_ci 628c2ecf20Sopenharmony_ci return 0; 638c2ecf20Sopenharmony_ci} 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_cistatic int __init match_pci_dev(struct device *dev, void *data) 668c2ecf20Sopenharmony_ci{ 678c2ecf20Sopenharmony_ci unsigned int devfn = *(unsigned int *)data; 688c2ecf20Sopenharmony_ci 698c2ecf20Sopenharmony_ci return dev_is_pci(dev) && to_pci_dev(dev)->devfn == devfn; 708c2ecf20Sopenharmony_ci} 718c2ecf20Sopenharmony_ci 728c2ecf20Sopenharmony_cistatic long __init parse_pci_path(const struct efi_dev_path *node, 738c2ecf20Sopenharmony_ci struct device *parent, struct device **child) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci unsigned int devfn; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci if (node->header.length != 6) 788c2ecf20Sopenharmony_ci return -EINVAL; 798c2ecf20Sopenharmony_ci if (!parent) 808c2ecf20Sopenharmony_ci return -EINVAL; 818c2ecf20Sopenharmony_ci 828c2ecf20Sopenharmony_ci devfn = PCI_DEVFN(node->pci.dev, node->pci.fn); 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_ci *child = device_find_child(parent, &devfn, match_pci_dev); 858c2ecf20Sopenharmony_ci if (!*child) 868c2ecf20Sopenharmony_ci return -ENODEV; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci return 0; 898c2ecf20Sopenharmony_ci} 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci/* 928c2ecf20Sopenharmony_ci * Insert parsers for further node types here. 938c2ecf20Sopenharmony_ci * 948c2ecf20Sopenharmony_ci * Each parser takes a pointer to the @node and to the @parent (will be NULL 958c2ecf20Sopenharmony_ci * for the first device path node). If a device corresponding to @node was 968c2ecf20Sopenharmony_ci * found below @parent, its reference count should be incremented and the 978c2ecf20Sopenharmony_ci * device returned in @child. 988c2ecf20Sopenharmony_ci * 998c2ecf20Sopenharmony_ci * The return value should be 0 on success or a negative int on failure. 1008c2ecf20Sopenharmony_ci * The special return values 0x01 (EFI_DEV_END_INSTANCE) and 0xFF 1018c2ecf20Sopenharmony_ci * (EFI_DEV_END_ENTIRE) signal the end of the device path, only 1028c2ecf20Sopenharmony_ci * parse_end_path() is supposed to return this. 1038c2ecf20Sopenharmony_ci * 1048c2ecf20Sopenharmony_ci * Be sure to validate the node length and contents before commencing the 1058c2ecf20Sopenharmony_ci * search for a device. 1068c2ecf20Sopenharmony_ci */ 1078c2ecf20Sopenharmony_ci 1088c2ecf20Sopenharmony_cistatic long __init parse_end_path(const struct efi_dev_path *node, 1098c2ecf20Sopenharmony_ci struct device *parent, struct device **child) 1108c2ecf20Sopenharmony_ci{ 1118c2ecf20Sopenharmony_ci if (node->header.length != 4) 1128c2ecf20Sopenharmony_ci return -EINVAL; 1138c2ecf20Sopenharmony_ci if (node->header.sub_type != EFI_DEV_END_INSTANCE && 1148c2ecf20Sopenharmony_ci node->header.sub_type != EFI_DEV_END_ENTIRE) 1158c2ecf20Sopenharmony_ci return -EINVAL; 1168c2ecf20Sopenharmony_ci if (!parent) 1178c2ecf20Sopenharmony_ci return -ENODEV; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci *child = get_device(parent); 1208c2ecf20Sopenharmony_ci return node->header.sub_type; 1218c2ecf20Sopenharmony_ci} 1228c2ecf20Sopenharmony_ci 1238c2ecf20Sopenharmony_ci/** 1248c2ecf20Sopenharmony_ci * efi_get_device_by_path - find device by EFI Device Path 1258c2ecf20Sopenharmony_ci * @node: EFI Device Path 1268c2ecf20Sopenharmony_ci * @len: maximum length of EFI Device Path in bytes 1278c2ecf20Sopenharmony_ci * 1288c2ecf20Sopenharmony_ci * Parse a series of EFI Device Path nodes at @node and find the corresponding 1298c2ecf20Sopenharmony_ci * device. If the device was found, its reference count is incremented and a 1308c2ecf20Sopenharmony_ci * pointer to it is returned. The caller needs to drop the reference with 1318c2ecf20Sopenharmony_ci * put_device() after use. The @node pointer is updated to point to the 1328c2ecf20Sopenharmony_ci * location immediately after the "End of Hardware Device Path" node. 1338c2ecf20Sopenharmony_ci * 1348c2ecf20Sopenharmony_ci * If another Device Path instance follows, @len is decremented by the number 1358c2ecf20Sopenharmony_ci * of bytes consumed. Otherwise @len is set to %0. 1368c2ecf20Sopenharmony_ci * 1378c2ecf20Sopenharmony_ci * If a Device Path node is malformed or its corresponding device is not found, 1388c2ecf20Sopenharmony_ci * @node is updated to point to this offending node and an ERR_PTR is returned. 1398c2ecf20Sopenharmony_ci * 1408c2ecf20Sopenharmony_ci * If @len is initially %0, the function returns %NULL. Thus, to iterate over 1418c2ecf20Sopenharmony_ci * all instances in a path, the following idiom may be used: 1428c2ecf20Sopenharmony_ci * 1438c2ecf20Sopenharmony_ci * while (!IS_ERR_OR_NULL(dev = efi_get_device_by_path(&node, &len))) { 1448c2ecf20Sopenharmony_ci * // do something with dev 1458c2ecf20Sopenharmony_ci * put_device(dev); 1468c2ecf20Sopenharmony_ci * } 1478c2ecf20Sopenharmony_ci * if (IS_ERR(dev)) 1488c2ecf20Sopenharmony_ci * // report error 1498c2ecf20Sopenharmony_ci * 1508c2ecf20Sopenharmony_ci * Devices can only be found if they're already instantiated. Most buses 1518c2ecf20Sopenharmony_ci * instantiate devices in the "subsys" initcall level, hence the earliest 1528c2ecf20Sopenharmony_ci * initcall level in which this function should be called is "fs". 1538c2ecf20Sopenharmony_ci * 1548c2ecf20Sopenharmony_ci * Returns the device on success or 1558c2ecf20Sopenharmony_ci * %ERR_PTR(-ENODEV) if no device was found, 1568c2ecf20Sopenharmony_ci * %ERR_PTR(-EINVAL) if a node is malformed or exceeds @len, 1578c2ecf20Sopenharmony_ci * %ERR_PTR(-ENOTSUPP) if support for a node type is not yet implemented. 1588c2ecf20Sopenharmony_ci */ 1598c2ecf20Sopenharmony_cistruct device * __init efi_get_device_by_path(const struct efi_dev_path **node, 1608c2ecf20Sopenharmony_ci size_t *len) 1618c2ecf20Sopenharmony_ci{ 1628c2ecf20Sopenharmony_ci struct device *parent = NULL, *child; 1638c2ecf20Sopenharmony_ci long ret = 0; 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci if (!*len) 1668c2ecf20Sopenharmony_ci return NULL; 1678c2ecf20Sopenharmony_ci 1688c2ecf20Sopenharmony_ci while (!ret) { 1698c2ecf20Sopenharmony_ci if (*len < 4 || *len < (*node)->header.length) 1708c2ecf20Sopenharmony_ci ret = -EINVAL; 1718c2ecf20Sopenharmony_ci else if ((*node)->header.type == EFI_DEV_ACPI && 1728c2ecf20Sopenharmony_ci (*node)->header.sub_type == EFI_DEV_BASIC_ACPI) 1738c2ecf20Sopenharmony_ci ret = parse_acpi_path(*node, parent, &child); 1748c2ecf20Sopenharmony_ci else if ((*node)->header.type == EFI_DEV_HW && 1758c2ecf20Sopenharmony_ci (*node)->header.sub_type == EFI_DEV_PCI) 1768c2ecf20Sopenharmony_ci ret = parse_pci_path(*node, parent, &child); 1778c2ecf20Sopenharmony_ci else if (((*node)->header.type == EFI_DEV_END_PATH || 1788c2ecf20Sopenharmony_ci (*node)->header.type == EFI_DEV_END_PATH2)) 1798c2ecf20Sopenharmony_ci ret = parse_end_path(*node, parent, &child); 1808c2ecf20Sopenharmony_ci else 1818c2ecf20Sopenharmony_ci ret = -ENOTSUPP; 1828c2ecf20Sopenharmony_ci 1838c2ecf20Sopenharmony_ci put_device(parent); 1848c2ecf20Sopenharmony_ci if (ret < 0) 1858c2ecf20Sopenharmony_ci return ERR_PTR(ret); 1868c2ecf20Sopenharmony_ci 1878c2ecf20Sopenharmony_ci parent = child; 1888c2ecf20Sopenharmony_ci *node = (void *)*node + (*node)->header.length; 1898c2ecf20Sopenharmony_ci *len -= (*node)->header.length; 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci if (ret == EFI_DEV_END_ENTIRE) 1938c2ecf20Sopenharmony_ci *len = 0; 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_ci return child; 1968c2ecf20Sopenharmony_ci} 197