1/*-
2 * Copyright (c) 2016 John Baldwin <jhb@FreeBSD.org>
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 *    notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 *    notice, this list of conditions and the following disclaimer in the
12 *    documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24 * SUCH DAMAGE.
25 */
26
27#include <sys/cdefs.h>
28
29#include <efi.h>
30#include <efilib.h>
31
32static EFI_GUID ImageDevicePathGUID =
33    EFI_LOADED_IMAGE_DEVICE_PATH_PROTOCOL_GUID;
34static EFI_GUID DevicePathGUID = DEVICE_PATH_PROTOCOL;
35static EFI_GUID DevicePathToTextGUID = EFI_DEVICE_PATH_TO_TEXT_PROTOCOL_GUID;
36static EFI_DEVICE_PATH_TO_TEXT_PROTOCOL *textProtocol;
37
38EFI_DEVICE_PATH *
39efi_lookup_image_devpath(EFI_HANDLE handle)
40{
41	EFI_DEVICE_PATH *devpath;
42	EFI_STATUS status;
43
44	status = OpenProtocolByHandle(handle, &ImageDevicePathGUID,
45	    (void **)&devpath);
46	if (EFI_ERROR(status))
47		devpath = NULL;
48	return (devpath);
49}
50
51EFI_DEVICE_PATH *
52efi_lookup_devpath(EFI_HANDLE handle)
53{
54	EFI_DEVICE_PATH *devpath;
55	EFI_STATUS status;
56
57	status = OpenProtocolByHandle(handle, &DevicePathGUID,
58	    (void **)&devpath);
59	if (EFI_ERROR(status))
60		devpath = NULL;
61	return (devpath);
62}
63
64void
65efi_close_devpath(EFI_HANDLE handle)
66{
67	EFI_STATUS status;
68
69	status = BS->CloseProtocol(handle, &DevicePathGUID, IH, NULL);
70	if (EFI_ERROR(status))
71		printf("CloseProtocol error: %lu\n", EFI_ERROR_CODE(status));
72}
73
74CHAR16 *
75efi_devpath_name(EFI_DEVICE_PATH *devpath)
76{
77	static int once = 1;
78	EFI_STATUS status;
79
80	if (devpath == NULL)
81		return (NULL);
82	if (once) {
83		status = BS->LocateProtocol(&DevicePathToTextGUID, NULL,
84		    (VOID **)&textProtocol);
85		if (EFI_ERROR(status))
86			textProtocol = NULL;
87		once = 0;
88	}
89	if (textProtocol == NULL)
90		return (NULL);
91
92	return (textProtocol->ConvertDevicePathToText(devpath, TRUE, TRUE));
93}
94
95void
96efi_free_devpath_name(CHAR16 *text)
97{
98
99	BS->FreePool(text);
100}
101
102EFI_DEVICE_PATH *
103efi_devpath_last_node(EFI_DEVICE_PATH *devpath)
104{
105
106	if (IsDevicePathEnd(devpath))
107		return (NULL);
108	while (!IsDevicePathEnd(NextDevicePathNode(devpath)))
109		devpath = NextDevicePathNode(devpath);
110	return (devpath);
111}
112
113EFI_DEVICE_PATH *
114efi_devpath_trim(EFI_DEVICE_PATH *devpath)
115{
116	EFI_DEVICE_PATH *node, *copy;
117	size_t prefix, len;
118
119	if ((node = efi_devpath_last_node(devpath)) == NULL)
120		return (NULL);
121	prefix = (UINT8 *)node - (UINT8 *)devpath;
122	if (prefix == 0)
123		return (NULL);
124	len = prefix + DevicePathNodeLength(NextDevicePathNode(node));
125	copy = malloc(len);
126	if (copy != NULL) {
127		memcpy(copy, devpath, prefix);
128		node = (EFI_DEVICE_PATH *)((UINT8 *)copy + prefix);
129		SetDevicePathEndNode(node);
130	}
131	return (copy);
132}
133
134EFI_HANDLE
135efi_devpath_handle(EFI_DEVICE_PATH *devpath)
136{
137	EFI_STATUS status;
138	EFI_HANDLE h;
139
140	/*
141	 * There isn't a standard way to locate a handle for a given
142	 * device path.  However, querying the EFI_DEVICE_PATH protocol
143	 * for a given device path should give us a handle for the
144	 * closest node in the path to the end that is valid.
145	 */
146	status = BS->LocateDevicePath(&DevicePathGUID, &devpath, &h);
147	if (EFI_ERROR(status))
148		return (NULL);
149	return (h);
150}
151
152bool
153efi_devpath_match(EFI_DEVICE_PATH *devpath1, EFI_DEVICE_PATH *devpath2)
154{
155	size_t len;
156
157	if (devpath1 == NULL || devpath2 == NULL)
158		return (false);
159
160	while (true) {
161		if (DevicePathType(devpath1) != DevicePathType(devpath2) ||
162		    DevicePathSubType(devpath1) != DevicePathSubType(devpath2))
163			return (false);
164
165		len = DevicePathNodeLength(devpath1);
166		if (len != DevicePathNodeLength(devpath2))
167			return (false);
168
169		if (memcmp(devpath1, devpath2, len) != 0)
170			return (false);
171
172		if (IsDevicePathEnd(devpath1))
173			break;
174		devpath1 = NextDevicePathNode(devpath1);
175		devpath2 = NextDevicePathNode(devpath2);
176	}
177	return (true);
178}
179
180bool
181efi_devpath_is_prefix(EFI_DEVICE_PATH *prefix, EFI_DEVICE_PATH *path)
182{
183	size_t len;
184
185	if (prefix == NULL || path == NULL)
186		return (false);
187
188	while (1) {
189		if (IsDevicePathEnd(prefix))
190			break;
191
192		if (DevicePathType(prefix) != DevicePathType(path) ||
193		    DevicePathSubType(prefix) != DevicePathSubType(path))
194			return (false);
195
196		len = DevicePathNodeLength(prefix);
197		if (len != DevicePathNodeLength(path))
198			return (false);
199
200		if (memcmp(prefix, path, len) != 0)
201			return (false);
202
203		prefix = NextDevicePathNode(prefix);
204		path = NextDevicePathNode(path);
205	}
206	return (true);
207}
208