xref: /freebsd/stand/efi/libefi/devpath.c (revision 110d56cb)
1ca987d46SWarner Losh /*-
2ca987d46SWarner Losh  * Copyright (c) 2016 John Baldwin <jhb@FreeBSD.org>
3ca987d46SWarner Losh  *
4ca987d46SWarner Losh  * Redistribution and use in source and binary forms, with or without
5ca987d46SWarner Losh  * modification, are permitted provided that the following conditions
6ca987d46SWarner Losh  * are met:
7ca987d46SWarner Losh  * 1. Redistributions of source code must retain the above copyright
8ca987d46SWarner Losh  *    notice, this list of conditions and the following disclaimer.
9ca987d46SWarner Losh  * 2. Redistributions in binary form must reproduce the above copyright
10ca987d46SWarner Losh  *    notice, this list of conditions and the following disclaimer in the
11ca987d46SWarner Losh  *    documentation and/or other materials provided with the distribution.
12ca987d46SWarner Losh  *
13ca987d46SWarner Losh  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14ca987d46SWarner Losh  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15ca987d46SWarner Losh  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16ca987d46SWarner Losh  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17ca987d46SWarner Losh  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18ca987d46SWarner Losh  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19ca987d46SWarner Losh  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20ca987d46SWarner Losh  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21ca987d46SWarner Losh  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22ca987d46SWarner Losh  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23ca987d46SWarner Losh  * SUCH DAMAGE.
24ca987d46SWarner Losh  */
25ca987d46SWarner Losh 
26ca987d46SWarner Losh #include <sys/cdefs.h>
27ca987d46SWarner Losh __FBSDID("$FreeBSD$");
28ca987d46SWarner Losh 
29ca987d46SWarner Losh #include <efi.h>
30ca987d46SWarner Losh #include <efilib.h>
3116b07b25SWarner Losh #include <efichar.h>
32ca987d46SWarner Losh 
33ca987d46SWarner Losh static EFI_GUID ImageDevicePathGUID =
34ca987d46SWarner Losh     EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID;
35ca987d46SWarner Losh static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL;
36ca987d46SWarner Losh static EFI_GUID DevicePathToTextGUID = EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID;
3716b07b25SWarner Losh static EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *toTextProtocol;
3816b07b25SWarner Losh static EFI_GUID DevicePathFromTextGUID = EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL_GUID;
3916b07b25SWarner Losh static EFI_DEVICE_PATH_FROM_TEXT_PROTOCOL *fromTextProtocol;
40ca987d46SWarner Losh 
41ca987d46SWarner Losh EFI_DEVICE_PATH *
42ca987d46SWarner Losh efi_lookup_image_devpath(EFI_HANDLE handle)
43ca987d46SWarner Losh {
44ca987d46SWarner Losh 	EFI_DEVICE_PATH *devpath;
45ca987d46SWarner Losh 	EFI_STATUS status;
46ca987d46SWarner Losh 
47110d56cbSToomas Soome 	status = OpenProtocolByHandle(handle, &ImageDevicePathGUID,
48110d56cbSToomas Soome 	    (void **)&devpath);
49ca987d46SWarner Losh 	if (EFI_ERROR(status))
50ca987d46SWarner Losh 		devpath = NULL;
51ca987d46SWarner Losh 	return (devpath);
52ca987d46SWarner Losh }
53ca987d46SWarner Losh 
54ca987d46SWarner Losh EFI_DEVICE_PATH *
55ca987d46SWarner Losh efi_lookup_devpath(EFI_HANDLE handle)
56ca987d46SWarner Losh {
57ca987d46SWarner Losh 	EFI_DEVICE_PATH *devpath;
58ca987d46SWarner Losh 	EFI_STATUS status;
59ca987d46SWarner Losh 
60110d56cbSToomas Soome 	status = OpenProtocolByHandle(handle, &DevicePathGUID,
61110d56cbSToomas Soome 	    (void **)&devpath);
62ca987d46SWarner Losh 	if (EFI_ERROR(status))
63ca987d46SWarner Losh 		devpath = NULL;
64ca987d46SWarner Losh 	return (devpath);
65ca987d46SWarner Losh }
66ca987d46SWarner Losh 
67ca987d46SWarner Losh CHAR16 *
68ca987d46SWarner Losh efi_devpath_name(EFI_DEVICE_PATH *devpath)
69ca987d46SWarner Losh {
70ca987d46SWarner Losh 	EFI_STATUS status;
71ca987d46SWarner Losh 
72ca987d46SWarner Losh 	if (devpath == NULL)
73ca987d46SWarner Losh 		return (NULL);
7416b07b25SWarner Losh 	if (toTextProtocol == NULL) {
75ca987d46SWarner Losh 		status = BS->LocateProtocol(&DevicePathToTextGUID, NULL,
7616b07b25SWarner Losh 		    (VOID **)&toTextProtocol);
77ca987d46SWarner Losh 		if (EFI_ERROR(status))
7816b07b25SWarner Losh 			toTextProtocol = NULL;
79ca987d46SWarner Losh 	}
8016b07b25SWarner Losh 	if (toTextProtocol == NULL)
81ca987d46SWarner Losh 		return (NULL);
82ca987d46SWarner Losh 
8316b07b25SWarner Losh 	return (toTextProtocol->ConvertDevicePathToText(devpath, TRUE, TRUE));
84ca987d46SWarner Losh }
85ca987d46SWarner Losh 
86ca987d46SWarner Losh void
87ca987d46SWarner Losh efi_free_devpath_name(CHAR16 *text)
88ca987d46SWarner Losh {
89ca987d46SWarner Losh 
90ca987d46SWarner Losh 	BS->FreePool(text);
91ca987d46SWarner Losh }
92ca987d46SWarner Losh 
93ca987d46SWarner Losh EFI_DEVICE_PATH *
9416b07b25SWarner Losh efi_name_to_devpath(const char *path)
9516b07b25SWarner Losh {
9616b07b25SWarner Losh 	EFI_DEVICE_PATH *devpath;
9716b07b25SWarner Losh 	CHAR16 *uv;
9816b07b25SWarner Losh 	size_t ul;
9916b07b25SWarner Losh 
10016b07b25SWarner Losh 	uv = NULL;
10116b07b25SWarner Losh 	if (utf8_to_ucs2(path, &uv, &ul) != 0)
10216b07b25SWarner Losh 		return (NULL);
10316b07b25SWarner Losh 	devpath = efi_name_to_devpath16(uv);
10416b07b25SWarner Losh 	free(uv);
10516b07b25SWarner Losh 	return (devpath);
10616b07b25SWarner Losh }
10716b07b25SWarner Losh 
10816b07b25SWarner Losh EFI_DEVICE_PATH *
10916b07b25SWarner Losh efi_name_to_devpath16(CHAR16 *path)
11016b07b25SWarner Losh {
11116b07b25SWarner Losh 	EFI_STATUS status;
11216b07b25SWarner Losh 
11316b07b25SWarner Losh 	if (path == NULL)
11416b07b25SWarner Losh 		return (NULL);
11516b07b25SWarner Losh 	if (fromTextProtocol == NULL) {
11616b07b25SWarner Losh 		status = BS->LocateProtocol(&DevicePathFromTextGUID, NULL,
11716b07b25SWarner Losh 		    (VOID **)&fromTextProtocol);
11816b07b25SWarner Losh 		if (EFI_ERROR(status))
11916b07b25SWarner Losh 			fromTextProtocol = NULL;
12016b07b25SWarner Losh 	}
12116b07b25SWarner Losh 	if (fromTextProtocol == NULL)
12216b07b25SWarner Losh 		return (NULL);
12316b07b25SWarner Losh 
12416b07b25SWarner Losh 	return (fromTextProtocol->ConvertTextToDevicePath(path));
12516b07b25SWarner Losh }
12616b07b25SWarner Losh 
12716b07b25SWarner Losh void efi_devpath_free(EFI_DEVICE_PATH *devpath)
12816b07b25SWarner Losh {
12916b07b25SWarner Losh 
13016b07b25SWarner Losh 	BS->FreePool(devpath);
13116b07b25SWarner Losh }
13216b07b25SWarner Losh 
13316b07b25SWarner Losh EFI_DEVICE_PATH *
134ca987d46SWarner Losh efi_devpath_last_node(EFI_DEVICE_PATH *devpath)
135ca987d46SWarner Losh {
136ca987d46SWarner Losh 
137ca987d46SWarner Losh 	if (IsDevicePathEnd(devpath))
138ca987d46SWarner Losh 		return (NULL);
139ca987d46SWarner Losh 	while (!IsDevicePathEnd(NextDevicePathNode(devpath)))
140ca987d46SWarner Losh 		devpath = NextDevicePathNode(devpath);
141ca987d46SWarner Losh 	return (devpath);
142ca987d46SWarner Losh }
143ca987d46SWarner Losh 
144ca987d46SWarner Losh EFI_DEVICE_PATH *
145ca987d46SWarner Losh efi_devpath_trim(EFI_DEVICE_PATH *devpath)
146ca987d46SWarner Losh {
147ca987d46SWarner Losh 	EFI_DEVICE_PATH *node, *copy;
148ca987d46SWarner Losh 	size_t prefix, len;
149ca987d46SWarner Losh 
150ca987d46SWarner Losh 	if ((node = efi_devpath_last_node(devpath)) == NULL)
151ca987d46SWarner Losh 		return (NULL);
152ca987d46SWarner Losh 	prefix = (UINT8 *)node - (UINT8 *)devpath;
153ca987d46SWarner Losh 	if (prefix == 0)
154ca987d46SWarner Losh 		return (NULL);
155ca987d46SWarner Losh 	len = prefix + DevicePathNodeLength(NextDevicePathNode(node));
156ca987d46SWarner Losh 	copy = malloc(len);
157ca987d46SWarner Losh 	if (copy != NULL) {
158ca987d46SWarner Losh 		memcpy(copy, devpath, prefix);
159ca987d46SWarner Losh 		node = (EFI_DEVICE_PATH *)((UINT8 *)copy + prefix);
160ca987d46SWarner Losh 		SetDevicePathEndNode(node);
161ca987d46SWarner Losh 	}
162ca987d46SWarner Losh 	return (copy);
163ca987d46SWarner Losh }
164ca987d46SWarner Losh 
165ca987d46SWarner Losh EFI_HANDLE
166ca987d46SWarner Losh efi_devpath_handle(EFI_DEVICE_PATH *devpath)
167ca987d46SWarner Losh {
168ca987d46SWarner Losh 	EFI_STATUS status;
169ca987d46SWarner Losh 	EFI_HANDLE h;
170ca987d46SWarner Losh 
171ca987d46SWarner Losh 	/*
172ca987d46SWarner Losh 	 * There isn't a standard way to locate a handle for a given
173ca987d46SWarner Losh 	 * device path.  However, querying the EFI_DEVICE_PATH protocol
174ca987d46SWarner Losh 	 * for a given device path should give us a handle for the
175ca987d46SWarner Losh 	 * closest node in the path to the end that is valid.
176ca987d46SWarner Losh 	 */
177ca987d46SWarner Losh 	status = BS->LocateDevicePath(&DevicePathGUID, &devpath, &h);
178ca987d46SWarner Losh 	if (EFI_ERROR(status))
179ca987d46SWarner Losh 		return (NULL);
180ca987d46SWarner Losh 	return (h);
181ca987d46SWarner Losh }
182ca987d46SWarner Losh 
183ca987d46SWarner Losh bool
18413850b36SWarner Losh efi_devpath_match_node(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2)
185ca987d46SWarner Losh {
186ca987d46SWarner Losh 	size_t len;
187ca987d46SWarner Losh 
188ca987d46SWarner Losh 	if (devpath1 == NULL || devpath2 == NULL)
189ca987d46SWarner Losh 		return (false);
190ca987d46SWarner Losh 	if (DevicePathType(devpath1) != DevicePathType(devpath2) ||
191ca987d46SWarner Losh 	    DevicePathSubType(devpath1) != DevicePathSubType(devpath2))
192ca987d46SWarner Losh 		return (false);
193ca987d46SWarner Losh 	len = DevicePathNodeLength(devpath1);
194ca987d46SWarner Losh 	if (len != DevicePathNodeLength(devpath2))
195ca987d46SWarner Losh 		return (false);
196ca987d46SWarner Losh 	if (memcmp(devpath1, devpath2, len) != 0)
197ca987d46SWarner Losh 		return (false);
19813850b36SWarner Losh 	return (true);
19913850b36SWarner Losh }
200ca987d46SWarner Losh 
20183ffeb8bSWarner Losh static bool
20283ffeb8bSWarner Losh _efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2,
20383ffeb8bSWarner Losh     bool ignore_media)
20413850b36SWarner Losh {
20513850b36SWarner Losh 
20613850b36SWarner Losh 	if (devpath1 == NULL || devpath2 == NULL)
20713850b36SWarner Losh 		return (false);
20813850b36SWarner Losh 
20913850b36SWarner Losh 	while (true) {
21083ffeb8bSWarner Losh 		if (ignore_media &&
21183ffeb8bSWarner Losh 		    IsDevicePathType(devpath1, MEDIA_DEVICE_PATH) &&
21283ffeb8bSWarner Losh 		    IsDevicePathType(devpath2, MEDIA_DEVICE_PATH))
21383ffeb8bSWarner Losh 			return (true);
21413850b36SWarner Losh 		if (!efi_devpath_match_node(devpath1, devpath2))
21513850b36SWarner Losh 			return false;
216ca987d46SWarner Losh 		if (IsDevicePathEnd(devpath1))
217ca987d46SWarner Losh 			break;
218ca987d46SWarner Losh 		devpath1 = NextDevicePathNode(devpath1);
219ca987d46SWarner Losh 		devpath2 = NextDevicePathNode(devpath2);
220ca987d46SWarner Losh 	}
221ca987d46SWarner Losh 	return (true);
222ca987d46SWarner Losh }
22383ffeb8bSWarner Losh /*
22483ffeb8bSWarner Losh  * Are two devpaths identical?
22583ffeb8bSWarner Losh  */
22683ffeb8bSWarner Losh bool
22783ffeb8bSWarner Losh efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2)
22883ffeb8bSWarner Losh {
22983ffeb8bSWarner Losh 	return _efi_devpath_match(devpath1, devpath2, false);
23083ffeb8bSWarner Losh }
23183ffeb8bSWarner Losh 
23283ffeb8bSWarner Losh /*
23383ffeb8bSWarner Losh  * Like efi_devpath_match, but stops at when we hit the media device
23483ffeb8bSWarner Losh  * path node that specifies the partition information. If we match
23583ffeb8bSWarner Losh  * up to that point, then we're on the same disk.
23683ffeb8bSWarner Losh  */
23783ffeb8bSWarner Losh bool
23883ffeb8bSWarner Losh efi_devpath_same_disk(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2)
23983ffeb8bSWarner Losh {
24083ffeb8bSWarner Losh 	return _efi_devpath_match(devpath1, devpath2, true);
24183ffeb8bSWarner Losh }
242ca987d46SWarner Losh 
243ca987d46SWarner Losh bool
244ca987d46SWarner Losh efi_devpath_is_prefix(EFI_DEVICE_PATH *prefix, EFI_DEVICE_PATH *path)
245ca987d46SWarner Losh {
246ca987d46SWarner Losh 	size_t len;
247ca987d46SWarner Losh 
248ca987d46SWarner Losh 	if (prefix == NULL || path == NULL)
249ca987d46SWarner Losh 		return (false);
250ca987d46SWarner Losh 
251ca987d46SWarner Losh 	while (1) {
252ca987d46SWarner Losh 		if (IsDevicePathEnd(prefix))
253ca987d46SWarner Losh 			break;
254ca987d46SWarner Losh 
255ca987d46SWarner Losh 		if (DevicePathType(prefix) != DevicePathType(path) ||
256ca987d46SWarner Losh 		    DevicePathSubType(prefix) != DevicePathSubType(path))
257ca987d46SWarner Losh 			return (false);
258ca987d46SWarner Losh 
259ca987d46SWarner Losh 		len = DevicePathNodeLength(prefix);
260ca987d46SWarner Losh 		if (len != DevicePathNodeLength(path))
261ca987d46SWarner Losh 			return (false);
262ca987d46SWarner Losh 
263ca987d46SWarner Losh 		if (memcmp(prefix, path, len) != 0)
264ca987d46SWarner Losh 			return (false);
265ca987d46SWarner Losh 
266ca987d46SWarner Losh 		prefix = NextDevicePathNode(prefix);
267ca987d46SWarner Losh 		path = NextDevicePathNode(path);
268ca987d46SWarner Losh 	}
269ca987d46SWarner Losh 	return (true);
270ca987d46SWarner Losh }
271ee4e1d58SWarner Losh 
272ee4e1d58SWarner Losh /*
273ee4e1d58SWarner Losh  * Skip over the 'prefix' part of path and return the part of the path
274ee4e1d58SWarner Losh  * that starts with the first node that's a MEDIA_DEVICE_PATH.
275ee4e1d58SWarner Losh  */
276ee4e1d58SWarner Losh EFI_DEVICE_PATH *
277ee4e1d58SWarner Losh efi_devpath_to_media_path(EFI_DEVICE_PATH *path)
278ee4e1d58SWarner Losh {
279ee4e1d58SWarner Losh 
280ee4e1d58SWarner Losh 	while (!IsDevicePathEnd(path)) {
281ee4e1d58SWarner Losh 		if (DevicePathType(path) == MEDIA_DEVICE_PATH)
282ee4e1d58SWarner Losh 			return (path);
283ee4e1d58SWarner Losh 		path = NextDevicePathNode(path);
284ee4e1d58SWarner Losh 	}
285ee4e1d58SWarner Losh 	return (NULL);
286ee4e1d58SWarner Losh }
287c6c2a73cSWarner Losh 
288c6c2a73cSWarner Losh UINTN
289c6c2a73cSWarner Losh efi_devpath_length(EFI_DEVICE_PATH  *path)
290c6c2a73cSWarner Losh {
291c6c2a73cSWarner Losh 	EFI_DEVICE_PATH *start = path;
292c6c2a73cSWarner Losh 
293c6c2a73cSWarner Losh 	while (!IsDevicePathEnd(path))
294c6c2a73cSWarner Losh 		path = NextDevicePathNode(path);
295c6c2a73cSWarner Losh 	return ((UINTN)path - (UINTN)start) + DevicePathNodeLength(path);
296c6c2a73cSWarner Losh }
297b9e19b07SWarner Losh 
298b9e19b07SWarner Losh EFI_HANDLE
299b9e19b07SWarner Losh efi_devpath_to_handle(EFI_DEVICE_PATH *path, EFI_HANDLE *handles, unsigned nhandles)
300b9e19b07SWarner Losh {
301b9e19b07SWarner Losh 	unsigned i;
302b9e19b07SWarner Losh 	EFI_DEVICE_PATH *media, *devpath;
303b9e19b07SWarner Losh 	EFI_HANDLE h;
304b9e19b07SWarner Losh 
305b9e19b07SWarner Losh 	media = efi_devpath_to_media_path(path);
306b9e19b07SWarner Losh 	if (media == NULL)
307b9e19b07SWarner Losh 		return (NULL);
308b9e19b07SWarner Losh 	for (i = 0; i < nhandles; i++) {
309b9e19b07SWarner Losh 		h = handles[i];
310b9e19b07SWarner Losh 		devpath = efi_lookup_devpath(h);
311b9e19b07SWarner Losh 		if (devpath == NULL)
312b9e19b07SWarner Losh 			continue;
313b9e19b07SWarner Losh 		if (!efi_devpath_match_node(media, efi_devpath_to_media_path(devpath)))
314b9e19b07SWarner Losh 			continue;
315b9e19b07SWarner Losh 		return (h);
316b9e19b07SWarner Losh 	}
317b9e19b07SWarner Losh 	return (NULL);
318b9e19b07SWarner Losh }
319