1 /* $NetBSD: efi.c,v 1.4 2022/09/24 11:06:03 riastradh Exp $ */ 2 3 /*- 4 * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca> 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED 23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 /* 30 * This pseudo-driver implements a /dev/efi character device that provides 31 * ioctls for using UEFI runtime time and variable services. 32 */ 33 34 #include <sys/cdefs.h> 35 __KERNEL_RCSID(0, "$NetBSD: efi.c,v 1.4 2022/09/24 11:06:03 riastradh Exp $"); 36 37 #include <sys/param.h> 38 #include <sys/conf.h> 39 #include <sys/kmem.h> 40 #include <sys/atomic.h> 41 #include <sys/efiio.h> 42 43 #include <dev/efivar.h> 44 45 #ifdef _LP64 46 #define EFIERR(x) (0x8000000000000000 | x) 47 #else 48 #define EFIERR(x) (0x80000000 | x) 49 #endif 50 51 #define EFI_SUCCESS 0 52 #define EFI_INVALID_PARAMETER EFIERR(2) 53 #define EFI_UNSUPPORTED EFIERR(3) 54 #define EFI_BUFFER_TOO_SMALL EFIERR(5) 55 #define EFI_DEVICE_ERROR EFIERR(7) 56 #define EFI_WRITE_PROTECTED EFIERR(8) 57 #define EFI_OUT_OF_RESOURCES EFIERR(9) 58 #define EFI_NOT_FOUND EFIERR(14) 59 #define EFI_SECURITY_VIOLATION EFIERR(26) 60 61 #include "ioconf.h" 62 63 /* 64 * Maximum length of an EFI variable name. The UEFI spec doesn't specify a 65 * constraint, but we want to limit the size to act as a guard rail against 66 * allocating too much kernel memory. 67 */ 68 #define EFI_VARNAME_MAXLENGTH EFI_PAGE_SIZE 69 70 /* 71 * Pointer to arch specific EFI backend. 72 */ 73 static const struct efi_ops *efi_ops = NULL; 74 75 /* 76 * Only allow one user of /dev/efi at a time. Even though the MD EFI backends 77 * should serialize individual UEFI RT calls, the UEFI specification says 78 * that a SetVariable() call between calls to GetNextVariableName() may 79 * produce unpredictable results, and we want to avoid this. 80 */ 81 static volatile u_int efi_isopen = 0; 82 83 static dev_type_open(efi_open); 84 static dev_type_close(efi_close); 85 static dev_type_ioctl(efi_ioctl); 86 87 const struct cdevsw efi_cdevsw = { 88 .d_open = efi_open, 89 .d_close = efi_close, 90 .d_ioctl = efi_ioctl, 91 .d_read = noread, 92 .d_write = nowrite, 93 .d_stop = nostop, 94 .d_tty = notty, 95 .d_poll = nopoll, 96 .d_mmap = nommap, 97 .d_kqfilter = nokqfilter, 98 .d_discard = nodiscard, 99 .d_flag = D_OTHER | D_MPSAFE, 100 }; 101 102 static int 103 efi_open(dev_t dev, int flags, int type, struct lwp *l) 104 { 105 106 if (efi_ops == NULL) { 107 return ENXIO; 108 } 109 if (atomic_swap_uint(&efi_isopen, 1) == 1) { 110 return EBUSY; 111 } 112 membar_acquire(); 113 return 0; 114 } 115 116 static int 117 efi_close(dev_t dev, int flags, int type, struct lwp *l) 118 { 119 120 KASSERT(efi_isopen); 121 atomic_store_release(&efi_isopen, 0); 122 return 0; 123 } 124 125 static int 126 efi_status_to_error(efi_status status) 127 { 128 switch (status) { 129 case EFI_SUCCESS: 130 return 0; 131 case EFI_INVALID_PARAMETER: 132 return EINVAL; 133 case EFI_UNSUPPORTED: 134 return EOPNOTSUPP; 135 case EFI_BUFFER_TOO_SMALL: 136 return ERANGE; 137 case EFI_DEVICE_ERROR: 138 return EIO; 139 case EFI_WRITE_PROTECTED: 140 return EROFS; 141 case EFI_OUT_OF_RESOURCES: 142 return ENOMEM; 143 case EFI_NOT_FOUND: 144 return ENOENT; 145 case EFI_SECURITY_VIOLATION: 146 return EACCES; 147 default: 148 return EIO; 149 } 150 } 151 152 static int 153 efi_ioctl_var_get(struct efi_var_ioc *var) 154 { 155 uint16_t *namebuf; 156 void *databuf = NULL; 157 size_t datasize; 158 efi_status status; 159 int error; 160 161 if (var->name == NULL || var->namesize == 0 || 162 (var->data != NULL && var->datasize == 0)) { 163 return EINVAL; 164 } 165 if (var->namesize > EFI_VARNAME_MAXLENGTH) { 166 return ENOMEM; 167 } 168 169 namebuf = kmem_alloc(var->namesize, KM_SLEEP); 170 error = copyin(var->name, namebuf, var->namesize); 171 if (error != 0) { 172 goto done; 173 } 174 if (namebuf[var->namesize / 2 - 1] != '\0') { 175 error = EINVAL; 176 goto done; 177 } 178 datasize = var->datasize; 179 if (datasize != 0) { 180 databuf = kmem_alloc(datasize, KM_SLEEP); 181 error = copyin(var->data, databuf, datasize); 182 if (error != 0) { 183 goto done; 184 } 185 } 186 187 status = efi_ops->efi_getvar(namebuf, &var->vendor, &var->attrib, 188 &var->datasize, databuf); 189 if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) { 190 error = efi_status_to_error(status); 191 goto done; 192 } 193 if (status == EFI_SUCCESS && databuf != NULL) { 194 error = copyout(databuf, var->data, var->datasize); 195 } else { 196 var->data = NULL; 197 } 198 199 done: 200 kmem_free(namebuf, var->namesize); 201 if (databuf != NULL) { 202 kmem_free(databuf, datasize); 203 } 204 return error; 205 } 206 207 static int 208 efi_ioctl_var_next(struct efi_var_ioc *var) 209 { 210 efi_status status; 211 uint16_t *namebuf; 212 size_t namesize; 213 int error; 214 215 if (var->name == NULL || var->namesize == 0) { 216 return EINVAL; 217 } 218 if (var->namesize > EFI_VARNAME_MAXLENGTH) { 219 return ENOMEM; 220 } 221 222 namesize = var->namesize; 223 namebuf = kmem_alloc(namesize, KM_SLEEP); 224 error = copyin(var->name, namebuf, namesize); 225 if (error != 0) { 226 goto done; 227 } 228 229 status = efi_ops->efi_nextvar(&var->namesize, namebuf, &var->vendor); 230 if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) { 231 error = efi_status_to_error(status); 232 goto done; 233 } 234 if (status == EFI_SUCCESS) { 235 error = copyout(namebuf, var->name, var->namesize); 236 } else { 237 var->name = NULL; 238 } 239 240 done: 241 kmem_free(namebuf, namesize); 242 return error; 243 } 244 245 static int 246 efi_ioctl_var_set(struct efi_var_ioc *var) 247 { 248 efi_status status; 249 uint16_t *namebuf; 250 uint16_t *databuf = NULL; 251 int error; 252 253 if (var->name == NULL || var->namesize == 0) { 254 return EINVAL; 255 } 256 257 namebuf = kmem_alloc(var->namesize, KM_SLEEP); 258 error = copyin(var->name, namebuf, var->namesize); 259 if (error != 0) { 260 goto done; 261 } 262 if (namebuf[var->namesize / 2 - 1] != '\0') { 263 error = EINVAL; 264 goto done; 265 } 266 if (var->datasize != 0) { 267 databuf = kmem_alloc(var->datasize, KM_SLEEP); 268 error = copyin(var->data, databuf, var->datasize); 269 if (error != 0) { 270 goto done; 271 } 272 } 273 274 status = efi_ops->efi_setvar(namebuf, &var->vendor, var->attrib, 275 var->datasize, databuf); 276 error = efi_status_to_error(status); 277 278 done: 279 kmem_free(namebuf, var->namesize); 280 if (databuf != NULL) { 281 kmem_free(databuf, var->datasize); 282 } 283 return error; 284 } 285 286 static int 287 efi_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l) 288 { 289 KASSERT(efi_ops != NULL); 290 291 switch (cmd) { 292 case EFIIOC_VAR_GET: 293 return efi_ioctl_var_get(data); 294 case EFIIOC_VAR_NEXT: 295 return efi_ioctl_var_next(data); 296 case EFIIOC_VAR_SET: 297 return efi_ioctl_var_set(data); 298 } 299 300 return ENOTTY; 301 } 302 303 void 304 efi_register_ops(const struct efi_ops *ops) 305 { 306 KASSERT(efi_ops == NULL); 307 efi_ops = ops; 308 } 309 310 void 311 efiattach(int count) 312 { 313 } 314