jcs's openbsd hax
openbsd
1/* $OpenBSD: efi.c,v 1.4 2025/09/16 12:18:10 hshoexer 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
26struct cfdriver efi_cd = {
27 NULL, "efi", DV_DULL, CD_COCOVM
28};
29
30int efiioc_get_table(struct efi_softc *sc, void *);
31int efiioc_var_get(struct efi_softc *sc, void *);
32int efiioc_var_next(struct efi_softc *sc, void *);
33int efiioc_var_set(struct efi_softc *sc, void *);
34int efi_adapt_error(EFI_STATUS);
35
36EFI_GET_VARIABLE efi_get_variable;
37EFI_SET_VARIABLE efi_set_variable;
38EFI_GET_NEXT_VARIABLE_NAME efi_get_next_variable_name;
39
40int
41efiopen(dev_t dev, int flag, int mode, struct proc *p)
42{
43 return (efi_cd.cd_ndevs > 0 ? 0 : ENXIO);
44}
45
46int
47eficlose(dev_t dev, int flag, int mode, struct proc *p)
48{
49 return 0;
50}
51
52int
53efiioctl(dev_t dev, u_long cmd, caddr_t data, int flag, struct proc *p)
54{
55 struct efi_softc *sc = efi_cd.cd_devs[0];
56 int error;
57
58 if (sc->sc_rs == NULL || sc->sc_pm == NULL)
59 return ENOTTY;
60
61 switch (cmd) {
62 case EFIIOC_GET_TABLE:
63 error = efiioc_get_table(sc, data);
64 break;
65 case EFIIOC_VAR_GET:
66 error = efiioc_var_get(sc, data);
67 break;
68 case EFIIOC_VAR_NEXT:
69 error = efiioc_var_next(sc, data);
70 break;
71 case EFIIOC_VAR_SET:
72 error = efiioc_var_set(sc, data);
73 break;
74 default:
75 error = ENOTTY;
76 break;
77 }
78
79 return error;
80}
81
82int
83efiioc_get_table(struct efi_softc *sc, void *data)
84{
85 EFI_GUID esrt_guid = EFI_SYSTEM_RESOURCE_TABLE_GUID;
86 struct efi_get_table_ioc *ioc = data;
87 char *buf = NULL;
88 int error;
89
90 /* Only ESRT is supported at the moment. */
91 if (memcmp(&ioc->uuid, &esrt_guid, sizeof(ioc->uuid)) != 0)
92 return EINVAL;
93
94 /* ESRT might not be present. */
95 if (sc->sc_esrt == NULL)
96 return ENXIO;
97
98 if (efi_enter_check(sc)) {
99 free(buf, M_TEMP, ioc->table_len);
100 return ENOSYS;
101 }
102
103 ioc->table_len = sizeof(*sc->sc_esrt) +
104 sizeof(EFI_SYSTEM_RESOURCE_ENTRY) * sc->sc_esrt->FwResourceCount;
105
106 /* Return table length to userspace. */
107 if (ioc->buf == NULL) {
108 efi_leave(sc);
109 return 0;
110 }
111
112 /* Refuse to copy only part of the table. */
113 if (ioc->buf_len < ioc->table_len) {
114 efi_leave(sc);
115 return EINVAL;
116 }
117
118 buf = malloc(ioc->table_len, M_TEMP, M_WAITOK);
119 memcpy(buf, sc->sc_esrt, ioc->table_len);
120
121 efi_leave(sc);
122
123 error = copyout(buf, ioc->buf, ioc->table_len);
124 free(buf, M_TEMP, ioc->table_len);
125
126 return error;
127}
128
129int
130efiioc_var_get(struct efi_softc *sc, void *data)
131{
132 struct efi_var_ioc *ioc = data;
133 void *value = NULL;
134 efi_char *name = NULL;
135 size_t valuesize = ioc->datasize;
136 EFI_STATUS status;
137 int error;
138
139 if (valuesize > 0)
140 value = malloc(valuesize, M_TEMP, M_WAITOK);
141 name = malloc(ioc->namesize, M_TEMP, M_WAITOK);
142 error = copyin(ioc->name, name, ioc->namesize);
143 if (error != 0)
144 goto leave;
145
146 /* NULL-terminated name must fit into namesize bytes. */
147 if (name[ioc->namesize / sizeof(*name) - 1] != 0) {
148 error = EINVAL;
149 goto leave;
150 }
151
152 if (efi_get_variable) {
153 status = efi_get_variable(name, (EFI_GUID *)&ioc->vendor,
154 &ioc->attrib, &ioc->datasize, value);
155 } else {
156 if (efi_enter_check(sc)) {
157 error = ENOSYS;
158 goto leave;
159 }
160 status = sc->sc_rs->GetVariable(name, (EFI_GUID *)&ioc->vendor,
161 &ioc->attrib, &ioc->datasize, value);
162 efi_leave(sc);
163 }
164
165 if (status == EFI_BUFFER_TOO_SMALL) {
166 /*
167 * Return size of the value, which was set by EFI RT,
168 * reporting no error to match FreeBSD's behaviour.
169 */
170 ioc->data = NULL;
171 goto leave;
172 }
173
174 error = efi_adapt_error(status);
175 if (error == 0)
176 error = copyout(value, ioc->data, ioc->datasize);
177
178leave:
179 free(value, M_TEMP, valuesize);
180 free(name, M_TEMP, ioc->namesize);
181 return error;
182}
183
184int
185efiioc_var_next(struct efi_softc *sc, void *data)
186{
187 struct efi_var_ioc *ioc = data;
188 efi_char *name;
189 size_t namesize = ioc->namesize;
190 EFI_STATUS status;
191 int error;
192
193 name = malloc(namesize, M_TEMP, M_WAITOK);
194 error = copyin(ioc->name, name, namesize);
195 if (error)
196 goto leave;
197
198 if (efi_get_next_variable_name) {
199 status = efi_get_next_variable_name(&ioc->namesize,
200 name, (EFI_GUID *)&ioc->vendor);
201 } else {
202 if (efi_enter_check(sc)) {
203 error = ENOSYS;
204 goto leave;
205 }
206 status = sc->sc_rs->GetNextVariableName(&ioc->namesize,
207 name, (EFI_GUID *)&ioc->vendor);
208 efi_leave(sc);
209 }
210
211 if (status == EFI_BUFFER_TOO_SMALL) {
212 /*
213 * Return size of the name, which was set by EFI RT,
214 * reporting no error to match FreeBSD's behaviour.
215 */
216 ioc->name = NULL;
217 goto leave;
218 }
219
220 error = efi_adapt_error(status);
221 if (error == 0)
222 error = copyout(name, ioc->name, ioc->namesize);
223
224leave:
225 free(name, M_TEMP, namesize);
226 return error;
227}
228
229int
230efiioc_var_set(struct efi_softc *sc, void *data)
231{
232 struct efi_var_ioc *ioc = data;
233 void *value = NULL;
234 efi_char *name = NULL;
235 EFI_STATUS status;
236 int error;
237
238 /* Zero datasize means variable deletion. */
239 if (ioc->datasize > 0) {
240 value = malloc(ioc->datasize, M_TEMP, M_WAITOK);
241 error = copyin(ioc->data, value, ioc->datasize);
242 if (error)
243 goto leave;
244 }
245
246 name = malloc(ioc->namesize, M_TEMP, M_WAITOK);
247 error = copyin(ioc->name, name, ioc->namesize);
248 if (error)
249 goto leave;
250
251 /* NULL-terminated name must fit into namesize bytes. */
252 if (name[ioc->namesize / sizeof(*name) - 1] != 0) {
253 error = EINVAL;
254 goto leave;
255 }
256
257 if (securelevel > 0) {
258 error = EPERM;
259 goto leave;
260 }
261
262 if (efi_set_variable) {
263 status = efi_set_variable(name, (EFI_GUID *)&ioc->vendor,
264 ioc->attrib, ioc->datasize, value);
265 } else {
266 if (efi_enter_check(sc)) {
267 error = ENOSYS;
268 goto leave;
269 }
270 status = sc->sc_rs->SetVariable(name, (EFI_GUID *)&ioc->vendor,
271 ioc->attrib, ioc->datasize, value);
272 efi_leave(sc);
273 }
274
275 error = efi_adapt_error(status);
276
277leave:
278 free(value, M_TEMP, ioc->datasize);
279 free(name, M_TEMP, ioc->namesize);
280 return error;
281}
282
283int
284efi_adapt_error(EFI_STATUS status)
285{
286 switch (status) {
287 case EFI_SUCCESS:
288 return 0;
289 case EFI_DEVICE_ERROR:
290 return EIO;
291 case EFI_INVALID_PARAMETER:
292 return EINVAL;
293 case EFI_NOT_FOUND:
294 return ENOENT;
295 case EFI_OUT_OF_RESOURCES:
296 return EAGAIN;
297 case EFI_SECURITY_VIOLATION:
298 return EPERM;
299 case EFI_UNSUPPORTED:
300 return ENOSYS;
301 case EFI_WRITE_PROTECTED:
302 return EROFS;
303 default:
304 return EIO;
305 }
306}