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
26struct cfdriver efi_cd = {
27	NULL, "efi", DV_DULL
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
36int
37efiopen(dev_t dev, int flag, int mode, struct proc *p)
38{
39	return (efi_cd.cd_ndevs > 0 ? 0 : ENXIO);
40}
41
42int
43eficlose(dev_t dev, int flag, int mode, struct proc *p)
44{
45	return 0;
46}
47
48int
49efiioctl(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
75int
76efiioc_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
122int
123efiioc_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
166leave:
167	free(value, M_TEMP, valuesize);
168	free(name, M_TEMP, ioc->namesize);
169	return error;
170}
171
172int
173efiioc_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
207leave:
208	free(name, M_TEMP, namesize);
209	return error;
210}
211
212int
213efiioc_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
255leave:
256	free(value, M_TEMP, ioc->datasize);
257	free(name, M_TEMP, ioc->namesize);
258	return error;
259}
260
261int
262efi_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