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 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 47 eficlose(dev_t dev, int flag, int mode, struct proc *p) 48 { 49 return 0; 50 } 51 52 int 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 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 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 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 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 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