1 /* $NetBSD: efi.c,v 1.2 2021/10/10 14:52:30 jmcneill 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.2 2021/10/10 14:52:30 jmcneill 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 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 if (efi_ops == NULL) { 106 return ENXIO; 107 } 108 if (atomic_cas_uint(&efi_isopen, 0, 1) == 1) { 109 return EBUSY; 110 } 111 return 0; 112 } 113 114 static int 115 efi_close(dev_t dev, int flags, int type, struct lwp *l) 116 { 117 KASSERT(efi_isopen); 118 atomic_swap_uint(&efi_isopen, 0); 119 return 0; 120 } 121 122 static int 123 efi_status_to_error(efi_status status) 124 { 125 switch (status) { 126 case EFI_SUCCESS: 127 return 0; 128 case EFI_INVALID_PARAMETER: 129 return EINVAL; 130 case EFI_UNSUPPORTED: 131 return EOPNOTSUPP; 132 case EFI_BUFFER_TOO_SMALL: 133 return ERANGE; 134 case EFI_DEVICE_ERROR: 135 return EIO; 136 case EFI_WRITE_PROTECTED: 137 return EROFS; 138 case EFI_OUT_OF_RESOURCES: 139 return ENOMEM; 140 case EFI_NOT_FOUND: 141 return ENOENT; 142 case EFI_SECURITY_VIOLATION: 143 return EACCES; 144 default: 145 return EIO; 146 } 147 } 148 149 static int 150 efi_ioctl_var_get(struct efi_var_ioc *var) 151 { 152 uint16_t *namebuf; 153 void *databuf = NULL; 154 size_t datasize; 155 efi_status status; 156 int error; 157 158 if (var->name == NULL || var->namesize == 0 || 159 (var->data != NULL && var->datasize == 0)) { 160 return EINVAL; 161 } 162 if (var->namesize > EFI_VARNAME_MAXLENGTH) { 163 return ENOMEM; 164 } 165 166 namebuf = kmem_alloc(var->namesize, KM_SLEEP); 167 error = copyin(var->name, namebuf, var->namesize); 168 if (error != 0) { 169 goto done; 170 } 171 if (namebuf[var->namesize / 2 - 1] != '\0') { 172 error = EINVAL; 173 goto done; 174 } 175 datasize = var->datasize; 176 if (datasize != 0) { 177 databuf = kmem_alloc(datasize, KM_SLEEP); 178 error = copyin(var->data, databuf, datasize); 179 if (error != 0) { 180 goto done; 181 } 182 } 183 184 status = efi_ops->efi_getvar(namebuf, &var->vendor, &var->attrib, 185 &var->datasize, databuf); 186 if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) { 187 error = efi_status_to_error(status); 188 goto done; 189 } 190 if (status == EFI_SUCCESS && databuf != NULL) { 191 error = copyout(databuf, var->data, var->datasize); 192 } else { 193 var->data = NULL; 194 } 195 196 done: 197 kmem_free(namebuf, var->namesize); 198 if (databuf != NULL) { 199 kmem_free(databuf, datasize); 200 } 201 return error; 202 } 203 204 static int 205 efi_ioctl_var_next(struct efi_var_ioc *var) 206 { 207 efi_status status; 208 uint16_t *namebuf; 209 size_t namesize; 210 int error; 211 212 if (var->name == NULL || var->namesize == 0) { 213 return EINVAL; 214 } 215 if (var->namesize > EFI_VARNAME_MAXLENGTH) { 216 return ENOMEM; 217 } 218 219 namesize = var->namesize; 220 namebuf = kmem_alloc(namesize, KM_SLEEP); 221 error = copyin(var->name, namebuf, namesize); 222 if (error != 0) { 223 goto done; 224 } 225 226 status = efi_ops->efi_nextvar(&var->namesize, namebuf, &var->vendor); 227 if (status != EFI_SUCCESS && status != EFI_BUFFER_TOO_SMALL) { 228 error = efi_status_to_error(status); 229 goto done; 230 } 231 if (status == EFI_SUCCESS) { 232 error = copyout(namebuf, var->name, var->namesize); 233 } else { 234 var->name = NULL; 235 } 236 237 done: 238 kmem_free(namebuf, namesize); 239 return error; 240 } 241 242 static int 243 efi_ioctl_var_set(struct efi_var_ioc *var) 244 { 245 efi_status status; 246 uint16_t *namebuf; 247 uint16_t *databuf = NULL; 248 int error; 249 250 if (var->name == NULL || var->namesize == 0) { 251 return EINVAL; 252 } 253 254 namebuf = kmem_alloc(var->namesize, KM_SLEEP); 255 error = copyin(var->name, namebuf, var->namesize); 256 if (error != 0) { 257 goto done; 258 } 259 if (namebuf[var->namesize / 2 - 1] != '\0') { 260 error = EINVAL; 261 goto done; 262 } 263 if (var->datasize != 0) { 264 databuf = kmem_alloc(var->datasize, KM_SLEEP); 265 error = copyin(var->data, databuf, var->datasize); 266 if (error != 0) { 267 goto done; 268 } 269 } 270 271 status = efi_ops->efi_setvar(namebuf, &var->vendor, var->attrib, 272 var->datasize, databuf); 273 error = efi_status_to_error(status); 274 275 done: 276 kmem_free(namebuf, var->namesize); 277 if (databuf != NULL) { 278 kmem_free(databuf, var->datasize); 279 } 280 return error; 281 } 282 283 static int 284 efi_ioctl(dev_t dev, u_long cmd, void *data, int flags, struct lwp *l) 285 { 286 KASSERT(efi_ops != NULL); 287 288 switch (cmd) { 289 case EFIIOC_VAR_GET: 290 return efi_ioctl_var_get(data); 291 case EFIIOC_VAR_NEXT: 292 return efi_ioctl_var_next(data); 293 case EFIIOC_VAR_SET: 294 return efi_ioctl_var_set(data); 295 } 296 297 return ENOTTY; 298 } 299 300 void 301 efi_register_ops(const struct efi_ops *ops) 302 { 303 KASSERT(efi_ops == NULL); 304 efi_ops = ops; 305 } 306 307 void 308 efiattach(int count) 309 { 310 } 311