aboutsummaryrefslogtreecommitdiffstats
path: root/drivers/firmware/efi/dev-path-parser.c
diff options
context:
space:
mode:
authorLukas Wunner <lukas@wunner.de>2016-11-12 16:32:34 -0500
committerIngo Molnar <mingo@kernel.org>2016-11-13 02:23:15 -0500
commit46cd4b75cd0edee76e0096225c2d31f8d90e92a2 (patch)
tree78a5df320e6ced9a378495c7ce7ef26e8532e4b9 /drivers/firmware/efi/dev-path-parser.c
parent568bc4e87033d232c5fd00d5b0cd22a2ccc04944 (diff)
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. Signed-off-by: Lukas Wunner <lukas@wunner.de> Signed-off-by: Matt Fleming <matt@codeblueprint.co.uk> Cc: Andreas Noever <andreas.noever@gmail.com> Cc: Ard Biesheuvel <ard.biesheuvel@linaro.org> Cc: Linus Torvalds <torvalds@linux-foundation.org> Cc: Peter Jones <pjones@redhat.com> Cc: Peter Zijlstra <peterz@infradead.org> Cc: Thomas Gleixner <tglx@linutronix.de> Cc: linux-efi@vger.kernel.org Link: http://lkml.kernel.org/r/20161112213237.8804-7-matt@codeblueprint.co.uk Signed-off-by: Ingo Molnar <mingo@kernel.org>
Diffstat (limited to 'drivers/firmware/efi/dev-path-parser.c')
-rw-r--r--drivers/firmware/efi/dev-path-parser.c203
1 files changed, 203 insertions, 0 deletions
diff --git a/drivers/firmware/efi/dev-path-parser.c b/drivers/firmware/efi/dev-path-parser.c
new file mode 100644
index 000000000000..85d1834ee9b7
--- /dev/null
+++ b/drivers/firmware/efi/dev-path-parser.c
@@ -0,0 +1,203 @@
1/*
2 * dev-path-parser.c - EFI Device Path parser
3 * Copyright (C) 2016 Lukas Wunner <lukas@wunner.de>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License (version 2) as
7 * published by the Free Software Foundation.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, see <http://www.gnu.org/licenses/>.
16 */
17
18#include <linux/acpi.h>
19#include <linux/efi.h>
20#include <linux/pci.h>
21
22struct acpi_hid_uid {
23 struct acpi_device_id hid[2];
24 char uid[11]; /* UINT_MAX + null byte */
25};
26
27static int __init match_acpi_dev(struct device *dev, void *data)
28{
29 struct acpi_hid_uid hid_uid = *(struct acpi_hid_uid *)data;
30 struct acpi_device *adev = to_acpi_device(dev);
31
32 if (acpi_match_device_ids(adev, hid_uid.hid))
33 return 0;
34
35 if (adev->pnp.unique_id)
36 return !strcmp(adev->pnp.unique_id, hid_uid.uid);
37 else
38 return !strcmp("0", hid_uid.uid);
39}
40
41static long __init parse_acpi_path(struct efi_dev_path *node,
42 struct device *parent, struct device **child)
43{
44 struct acpi_hid_uid hid_uid = {};
45 struct device *phys_dev;
46
47 if (node->length != 12)
48 return -EINVAL;
49
50 sprintf(hid_uid.hid[0].id, "%c%c%c%04X",
51 'A' + ((node->acpi.hid >> 10) & 0x1f) - 1,
52 'A' + ((node->acpi.hid >> 5) & 0x1f) - 1,
53 'A' + ((node->acpi.hid >> 0) & 0x1f) - 1,
54 node->acpi.hid >> 16);
55 sprintf(hid_uid.uid, "%u", node->acpi.uid);
56
57 *child = bus_find_device(&acpi_bus_type, NULL, &hid_uid,
58 match_acpi_dev);
59 if (!*child)
60 return -ENODEV;
61
62 phys_dev = acpi_get_first_physical_node(to_acpi_device(*child));
63 if (phys_dev) {
64 get_device(phys_dev);
65 put_device(*child);
66 *child = phys_dev;
67 }
68
69 return 0;
70}
71
72static int __init match_pci_dev(struct device *dev, void *data)
73{
74 unsigned int devfn = *(unsigned int *)data;
75
76 return dev_is_pci(dev) && to_pci_dev(dev)->devfn == devfn;
77}
78
79static long __init parse_pci_path(struct efi_dev_path *node,
80 struct device *parent, struct device **child)
81{
82 unsigned int devfn;
83
84 if (node->length != 6)
85 return -EINVAL;
86 if (!parent)
87 return -EINVAL;
88
89 devfn = PCI_DEVFN(node->pci.dev, node->pci.fn);
90
91 *child = device_find_child(parent, &devfn, match_pci_dev);
92 if (!*child)
93 return -ENODEV;
94
95 return 0;
96}
97
98/*
99 * Insert parsers for further node types here.
100 *
101 * Each parser takes a pointer to the @node and to the @parent (will be NULL
102 * for the first device path node). If a device corresponding to @node was
103 * found below @parent, its reference count should be incremented and the
104 * device returned in @child.
105 *
106 * The return value should be 0 on success or a negative int on failure.
107 * The special return values 0x01 (EFI_DEV_END_INSTANCE) and 0xFF
108 * (EFI_DEV_END_ENTIRE) signal the end of the device path, only
109 * parse_end_path() is supposed to return this.
110 *
111 * Be sure to validate the node length and contents before commencing the
112 * search for a device.
113 */
114
115static long __init parse_end_path(struct efi_dev_path *node,
116 struct device *parent, struct device **child)
117{
118 if (node->length != 4)
119 return -EINVAL;
120 if (node->sub_type != EFI_DEV_END_INSTANCE &&
121 node->sub_type != EFI_DEV_END_ENTIRE)
122 return -EINVAL;
123 if (!parent)
124 return -ENODEV;
125
126 *child = get_device(parent);
127 return node->sub_type;
128}
129
130/**
131 * efi_get_device_by_path - find device by EFI Device Path
132 * @node: EFI Device Path
133 * @len: maximum length of EFI Device Path in bytes
134 *
135 * Parse a series of EFI Device Path nodes at @node and find the corresponding
136 * device. If the device was found, its reference count is incremented and a
137 * pointer to it is returned. The caller needs to drop the reference with
138 * put_device() after use. The @node pointer is updated to point to the
139 * location immediately after the "End of Hardware Device Path" node.
140 *
141 * If another Device Path instance follows, @len is decremented by the number
142 * of bytes consumed. Otherwise @len is set to %0.
143 *
144 * If a Device Path node is malformed or its corresponding device is not found,
145 * @node is updated to point to this offending node and an ERR_PTR is returned.
146 *
147 * If @len is initially %0, the function returns %NULL. Thus, to iterate over
148 * all instances in a path, the following idiom may be used:
149 *
150 * while (!IS_ERR_OR_NULL(dev = efi_get_device_by_path(&node, &len))) {
151 * // do something with dev
152 * put_device(dev);
153 * }
154 * if (IS_ERR(dev))
155 * // report error
156 *
157 * Devices can only be found if they're already instantiated. Most buses
158 * instantiate devices in the "subsys" initcall level, hence the earliest
159 * initcall level in which this function should be called is "fs".
160 *
161 * Returns the device on success or
162 * %ERR_PTR(-ENODEV) if no device was found,
163 * %ERR_PTR(-EINVAL) if a node is malformed or exceeds @len,
164 * %ERR_PTR(-ENOTSUPP) if support for a node type is not yet implemented.
165 */
166struct device * __init efi_get_device_by_path(struct efi_dev_path **node,
167 size_t *len)
168{
169 struct device *parent = NULL, *child;
170 long ret = 0;
171
172 if (!*len)
173 return NULL;
174
175 while (!ret) {
176 if (*len < 4 || *len < (*node)->length)
177 ret = -EINVAL;
178 else if ((*node)->type == EFI_DEV_ACPI &&
179 (*node)->sub_type == EFI_DEV_BASIC_ACPI)
180 ret = parse_acpi_path(*node, parent, &child);
181 else if ((*node)->type == EFI_DEV_HW &&
182 (*node)->sub_type == EFI_DEV_PCI)
183 ret = parse_pci_path(*node, parent, &child);
184 else if (((*node)->type == EFI_DEV_END_PATH ||
185 (*node)->type == EFI_DEV_END_PATH2))
186 ret = parse_end_path(*node, parent, &child);
187 else
188 ret = -ENOTSUPP;
189
190 put_device(parent);
191 if (ret < 0)
192 return ERR_PTR(ret);
193
194 parent = child;
195 *node = (void *)*node + (*node)->length;
196 *len -= (*node)->length;
197 }
198
199 if (ret == EFI_DEV_END_ENTIRE)
200 *len = 0;
201
202 return child;
203}