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