xref: /openbsd-src/sys/dev/efi/efi.c (revision f6aab3d83b51b91c24247ad2c2573574de475a82)
1 /*	$OpenBSD: efi.c,v 1.1 2023/01/14 12:11:11 kettenis Exp $	*/
2 /*
3  * Copyright (c) 2022 3mdeb <contact@3mdeb.com>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 #include <sys/param.h>
19 #include <sys/systm.h>
20 #include <sys/malloc.h>
21 
22 #include <dev/efi/efi.h>
23 #include <dev/efi/efiio.h>
24 #include <machine/efivar.h>
25 
26 struct cfdriver efi_cd = {
27 	NULL, "efi", DV_DULL
28 };
29 
30 int	efiioc_get_table(struct efi_softc *sc, void *);
31 int	efiioc_var_get(struct efi_softc *sc, void *);
32 int	efiioc_var_next(struct efi_softc *sc, void *);
33 int	efiioc_var_set(struct efi_softc *sc, void *);
34 int	efi_adapt_error(EFI_STATUS);
35 
36 int
37 efiopen(dev_t dev, int flag, int mode, struct proc *p)
38 {
39 	return (efi_cd.cd_ndevs > 0 ? 0 : ENXIO);
40 }
41 
42 int
43 eficlose(dev_t dev, int flag, int mode, struct proc *p)
44 {
45 	return 0;
46 }
47 
48 int
49 efiioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
50 {
51 	struct efi_softc *sc = efi_cd.cd_devs[0];
52 	int error;
53 
54 	switch (cmd) {
55 	case EFIIOC_GET_TABLE:
56 		error = efiioc_get_table(sc, data);
57 		break;
58 	case EFIIOC_VAR_GET:
59 		error = efiioc_var_get(sc, data);
60 		break;
61 	case EFIIOC_VAR_NEXT:
62 		error = efiioc_var_next(sc, data);
63 		break;
64 	case EFIIOC_VAR_SET:
65 		error = efiioc_var_set(sc, data);
66 		break;
67 	default:
68 		error = ENOTTY;
69 		break;
70 	}
71 
72 	return error;
73 }
74 
75 int
76 efiioc_get_table(struct efi_softc *sc, void *data)
77 {
78 	EFI_GUID esrt_guid = EFI_SYSTEM_RESOURCE_TABLE_GUID;
79 	struct efi_get_table_ioc *ioc = data;
80 	char *buf = NULL;
81 	int error;
82 
83 	/* Only ESRT is supported at the moment. */
84 	if (memcmp(&ioc->uuid, &esrt_guid, sizeof(ioc->uuid)) != 0)
85 		return EINVAL;
86 
87 	/* ESRT might not be present. */
88 	if (sc->sc_esrt == NULL)
89 		return ENXIO;
90 
91 	if (efi_enter_check(sc)) {
92 		free(buf, M_TEMP, ioc->table_len);
93 		return ENOSYS;
94 	}
95 
96 	ioc->table_len = sizeof(*sc->sc_esrt) +
97 	    sizeof(EFI_SYSTEM_RESOURCE_ENTRY) * sc->sc_esrt->FwResourceCount;
98 
99 	/* Return table length to userspace. */
100 	if (ioc->buf == NULL) {
101 		efi_leave(sc);
102 		return 0;
103 	}
104 
105 	/* Refuse to copy only part of the table. */
106 	if (ioc->buf_len < ioc->table_len) {
107 		efi_leave(sc);
108 		return EINVAL;
109 	}
110 
111 	buf = malloc(ioc->table_len, M_TEMP, M_WAITOK);
112 	memcpy(buf, sc->sc_esrt, ioc->table_len);
113 
114 	efi_leave(sc);
115 
116 	error = copyout(buf, ioc->buf, ioc->table_len);
117 	free(buf, M_TEMP, ioc->table_len);
118 
119 	return error;
120 }
121 
122 int
123 efiioc_var_get(struct efi_softc *sc, void *data)
124 {
125 	struct efi_var_ioc *ioc = data;
126 	void *value = NULL;
127 	efi_char *name = NULL;
128 	size_t valuesize = ioc->datasize;
129 	EFI_STATUS status;
130 	int error;
131 
132 	if (valuesize > 0)
133 		value = malloc(valuesize, M_TEMP, M_WAITOK);
134 	name = malloc(ioc->namesize, M_TEMP, M_WAITOK);
135 	error = copyin(ioc->name, name, ioc->namesize);
136 	if (error != 0)
137 		goto leave;
138 
139 	/* NULL-terminated name must fit into namesize bytes. */
140 	if (name[ioc->namesize / sizeof(*name) - 1] != 0) {
141 		error = EINVAL;
142 		goto leave;
143 	}
144 
145 	if (efi_enter_check(sc)) {
146 		error = ENOSYS;
147 		goto leave;
148 	}
149 	status = sc->sc_rs->GetVariable(name, (EFI_GUID *)&ioc->vendor,
150 	    &ioc->attrib, &ioc->datasize, value);
151 	efi_leave(sc);
152 
153 	if (status == EFI_BUFFER_TOO_SMALL) {
154 		/*
155 		 * Return size of the value, which was set by EFI RT,
156 		 * reporting no error to match FreeBSD's behaviour.
157 		 */
158 		ioc->data = NULL;
159 		goto leave;
160 	}
161 
162 	error = efi_adapt_error(status);
163 	if (error == 0)
164 		error = copyout(value, ioc->data, ioc->datasize);
165 
166 leave:
167 	free(value, M_TEMP, valuesize);
168 	free(name, M_TEMP, ioc->namesize);
169 	return error;
170 }
171 
172 int
173 efiioc_var_next(struct efi_softc *sc, void *data)
174 {
175 	struct efi_var_ioc *ioc = data;
176 	efi_char *name;
177 	size_t namesize = ioc->namesize;
178 	EFI_STATUS status;
179 	int error;
180 
181 	name = malloc(namesize, M_TEMP, M_WAITOK);
182 	error = copyin(ioc->name, name, namesize);
183 	if (error)
184 		goto leave;
185 
186 	if (efi_enter_check(sc)) {
187 		error = ENOSYS;
188 		goto leave;
189 	}
190 	status = sc->sc_rs->GetNextVariableName(&ioc->namesize,
191 	    name, (EFI_GUID *)&ioc->vendor);
192 	efi_leave(sc);
193 
194 	if (status == EFI_BUFFER_TOO_SMALL) {
195 		/*
196 		 * Return size of the name, which was set by EFI RT,
197 		 * reporting no error to match FreeBSD's behaviour.
198 		 */
199 		ioc->name = NULL;
200 		goto leave;
201 	}
202 
203 	error = efi_adapt_error(status);
204 	if (error == 0)
205 		error = copyout(name, ioc->name, ioc->namesize);
206 
207 leave:
208 	free(name, M_TEMP, namesize);
209 	return error;
210 }
211 
212 int
213 efiioc_var_set(struct efi_softc *sc, void *data)
214 {
215 	struct efi_var_ioc *ioc = data;
216 	void *value = NULL;
217 	efi_char *name = NULL;
218 	EFI_STATUS status;
219 	int error;
220 
221 	/* Zero datasize means variable deletion. */
222 	if (ioc->datasize > 0) {
223 		value = malloc(ioc->datasize, M_TEMP, M_WAITOK);
224 		error = copyin(ioc->data, value, ioc->datasize);
225 		if (error)
226 			goto leave;
227 	}
228 
229 	name = malloc(ioc->namesize, M_TEMP, M_WAITOK);
230 	error = copyin(ioc->name, name, ioc->namesize);
231 	if (error)
232 		goto leave;
233 
234 	/* NULL-terminated name must fit into namesize bytes. */
235 	if (name[ioc->namesize / sizeof(*name) - 1] != 0) {
236 		error = EINVAL;
237 		goto leave;
238 	}
239 
240 	if (securelevel > 0) {
241 		error = EPERM;
242 		goto leave;
243 	}
244 
245 	if (efi_enter_check(sc)) {
246 		error = ENOSYS;
247 		goto leave;
248 	}
249 	status = sc->sc_rs->SetVariable(name, (EFI_GUID *)&ioc->vendor,
250 	    ioc->attrib, ioc->datasize, value);
251 	efi_leave(sc);
252 
253 	error = efi_adapt_error(status);
254 
255 leave:
256 	free(value, M_TEMP, ioc->datasize);
257 	free(name, M_TEMP, ioc->namesize);
258 	return error;
259 }
260 
261 int
262 efi_adapt_error(EFI_STATUS status)
263 {
264 	switch (status) {
265 	case EFI_SUCCESS:
266 		return 0;
267 	case EFI_DEVICE_ERROR:
268 		return EIO;
269 	case EFI_INVALID_PARAMETER:
270 		return EINVAL;
271 	case EFI_NOT_FOUND:
272 		return ENOENT;
273 	case EFI_OUT_OF_RESOURCES:
274 		return EAGAIN;
275 	case EFI_SECURITY_VIOLATION:
276 		return EPERM;
277 	case EFI_UNSUPPORTED:
278 		return ENOSYS;
279 	case EFI_WRITE_PROTECTED:
280 		return EROFS;
281 	default:
282 		return EIO;
283 	}
284 }
285