xref: /openbsd-src/sys/dev/efi/efi.c (revision f7910aa7534d27bc146c2addca49ce5c8b5f8c36)
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