1 /* $OpenBSD: efidev.c,v 1.3 2021/06/25 17:49:49 krw Exp $ */ 2 3 /* 4 * Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net> 5 * Copyright (c) 2016 Mark Kettenis 6 * All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 1. Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * 2. Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in the 15 * documentation and/or other materials provided with the distribution. 16 * 17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 20 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 21 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 23 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 24 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 25 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 26 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 * 29 */ 30 #include <sys/param.h> 31 #include <sys/reboot.h> 32 #include <sys/disklabel.h> 33 #include <lib/libz/zlib.h> 34 #include <isofs/cd9660/iso.h> 35 36 #include "libsa.h" 37 38 #include <efi.h> 39 40 extern EFI_BOOT_SERVICES *BS; 41 42 extern int debug; 43 44 #include "disk.h" 45 #include "efidev.h" 46 47 #define EFI_BLKSPERSEC(_ed) ((_ed)->blkio->Media->BlockSize / DEV_BSIZE) 48 #define EFI_SECTOBLK(_ed, _n) ((_n) * EFI_BLKSPERSEC(_ed)) 49 50 static EFI_STATUS 51 efid_io(int, efi_diskinfo_t, u_int, int, void *); 52 static int efid_diskio(int, struct diskinfo *, u_int, int, void *); 53 const char * efi_getdisklabel(efi_diskinfo_t, struct disklabel *); 54 static int efi_getdisklabel_cd9660(efi_diskinfo_t, struct disklabel *); 55 static u_int findopenbsd(efi_diskinfo_t, const char **); 56 static u_int findopenbsd_gpt(efi_diskinfo_t, const char **); 57 static int gpt_chk_mbr(struct dos_partition *, u_int64_t); 58 59 void 60 efid_init(struct diskinfo *dip, void *handle) 61 { 62 EFI_BLOCK_IO *blkio = handle; 63 64 memset(dip, 0, sizeof(struct diskinfo)); 65 dip->ed.blkio = blkio; 66 dip->ed.mediaid = blkio->Media->MediaId; 67 dip->diskio = efid_diskio; 68 dip->strategy = efistrategy; 69 70 if (efi_getdisklabel(&dip->ed, &dip->disklabel) == NULL) 71 dip->flags |= DISKINFO_FLAG_GOODLABEL; 72 } 73 74 static EFI_STATUS 75 efid_io(int rw, efi_diskinfo_t ed, u_int off, int nsect, void *buf) 76 { 77 u_int blks, start, end; 78 EFI_PHYSICAL_ADDRESS addr; 79 EFI_STATUS status; 80 caddr_t data; 81 size_t size; 82 83 /* block count of the intrinsic block size in DEV_BSIZE */ 84 blks = EFI_BLKSPERSEC(ed); 85 if (blks == 0) 86 /* block size < 512. HP Stream 13 actually has such a disk. */ 87 return (EFI_UNSUPPORTED); 88 89 start = off / blks; 90 end = (off + nsect + blks - 1) / blks; 91 size = (end - start) * ed->blkio->Media->BlockSize; 92 93 status = BS->AllocatePages(AllocateAnyPages, EfiLoaderData, 94 EFI_SIZE_TO_PAGES(size), &addr); 95 if (EFI_ERROR(status)) 96 goto on_eio; 97 data = (caddr_t)(uintptr_t)addr; 98 99 switch (rw) { 100 case F_READ: 101 status = ed->blkio->ReadBlocks(ed->blkio, ed->mediaid, start, 102 size, data); 103 if (EFI_ERROR(status)) 104 goto on_eio; 105 memcpy(buf, data + DEV_BSIZE * (off - start * blks), 106 DEV_BSIZE * nsect); 107 break; 108 case F_WRITE: 109 if (ed->blkio->Media->ReadOnly) 110 goto on_eio; 111 if (off % blks != 0 || nsect % blks != 0) { 112 status = ed->blkio->ReadBlocks(ed->blkio, ed->mediaid, 113 start, size, data); 114 if (EFI_ERROR(status)) 115 goto on_eio; 116 } 117 memcpy(data + DEV_BSIZE * (off - start * blks), buf, 118 DEV_BSIZE * nsect); 119 status = ed->blkio->WriteBlocks(ed->blkio, ed->mediaid, start, 120 size, data); 121 if (EFI_ERROR(status)) 122 goto on_eio; 123 break; 124 } 125 126 on_eio: 127 BS->FreePages(addr, EFI_SIZE_TO_PAGES(size)); 128 129 return (status); 130 } 131 132 static int 133 efid_diskio(int rw, struct diskinfo *dip, u_int off, int nsect, void *buf) 134 { 135 EFI_STATUS status; 136 137 status = efid_io(rw, &dip->ed, off, nsect, buf); 138 139 return ((EFI_ERROR(status))? -1 : 0); 140 } 141 142 /* 143 * Returns 0 if the MBR with the provided partition array is a GPT protective 144 * MBR, and returns 1 otherwise. A GPT protective MBR would have one and only 145 * one MBR partition, an EFI partition that either covers the whole disk or as 146 * much of it as is possible with a 32bit size field. 147 * 148 * Taken from kern/subr_disk.c. 149 * 150 * NOTE: MS always uses a size of UINT32_MAX for the EFI partition!** 151 */ 152 static int 153 gpt_chk_mbr(struct dos_partition *dp, u_int64_t dsize) 154 { 155 struct dos_partition *dp2; 156 int efi, found, i; 157 u_int32_t psize; 158 159 found = efi = 0; 160 for (dp2=dp, i=0; i < NDOSPART; i++, dp2++) { 161 if (dp2->dp_typ == DOSPTYP_UNUSED) 162 continue; 163 found++; 164 if (dp2->dp_typ != DOSPTYP_EFI) 165 continue; 166 if (letoh32(dp2->dp_start) != GPTSECTOR) 167 continue; 168 psize = letoh32(dp2->dp_size); 169 if (psize <= (dsize - GPTSECTOR) || psize == UINT32_MAX) 170 efi++; 171 } 172 if (found == 1 && efi == 1) 173 return (0); 174 175 return (1); 176 } 177 178 /* 179 * Try to find the disk address of the first MBR OpenBSD partition. 180 * 181 * N.B.: must boot from a partition within first 2^32-1 sectors! 182 * 183 * Called only if the MBR on sector 0 is *not* a protective MBR 184 * and *does* have a valid signature. 185 * 186 * We don't check the signatures of EBR's, and they cannot be 187 * protective MBR's so there is no need to check for that. 188 */ 189 static u_int 190 findopenbsd(efi_diskinfo_t ed, const char **err) 191 { 192 EFI_STATUS status; 193 struct dos_mbr mbr; 194 struct dos_partition *dp; 195 u_int mbroff = DOSBBSECTOR; 196 u_int mbr_eoff = DOSBBSECTOR; /* Offset of MBR extended partition. */ 197 int i, maxebr = DOS_MAXEBR, nextebr; 198 199 again: 200 if (!maxebr--) { 201 *err = "too many extended partitions"; 202 return (-1); 203 } 204 205 /* Read MBR */ 206 bzero(&mbr, sizeof(mbr)); 207 status = efid_io(F_READ, ed, mbroff, 1, &mbr); 208 if (EFI_ERROR(status)) { 209 *err = "Disk I/O Error"; 210 return (-1); 211 } 212 213 /* Search for OpenBSD partition */ 214 nextebr = 0; 215 for (i = 0; i < NDOSPART; i++) { 216 dp = &mbr.dmbr_parts[i]; 217 if (!dp->dp_size) 218 continue; 219 #ifdef BIOS_DEBUG 220 if (debug) 221 printf("found partition %u: " 222 "type %u (0x%x) offset %u (0x%x)\n", 223 (int)(dp - mbr.dmbr_parts), 224 dp->dp_typ, dp->dp_typ, 225 dp->dp_start, dp->dp_start); 226 #endif 227 if (dp->dp_typ == DOSPTYP_OPENBSD) { 228 if (dp->dp_start > (dp->dp_start + mbroff)) 229 continue; 230 return (dp->dp_start + mbroff); 231 } 232 233 /* 234 * Record location of next ebr if and only if this is the first 235 * extended partition in this boot record! 236 */ 237 if (!nextebr && (dp->dp_typ == DOSPTYP_EXTEND || 238 dp->dp_typ == DOSPTYP_EXTENDL)) { 239 nextebr = dp->dp_start + mbr_eoff; 240 if (nextebr < dp->dp_start) 241 nextebr = (u_int)-1; 242 if (mbr_eoff == DOSBBSECTOR) 243 mbr_eoff = dp->dp_start; 244 } 245 } 246 247 if (nextebr && nextebr != (u_int)-1) { 248 mbroff = nextebr; 249 goto again; 250 } 251 252 return (-1); 253 } 254 255 /* 256 * Try to find the disk address of the first GPT OpenBSD partition. 257 * 258 * N.B.: must boot from a partition within first 2^32-1 sectors! 259 * 260 * Called only if the MBR on sector 0 *is* a protective MBR 261 * with a valid signature and sector 1 is a valid GPT header. 262 */ 263 static u_int 264 findopenbsd_gpt(efi_diskinfo_t ed, const char **err) 265 { 266 EFI_STATUS status; 267 struct gpt_header gh; 268 int i, part, found; 269 uint64_t lba; 270 uint32_t orig_csum, new_csum; 271 uint32_t ghsize, ghpartsize, ghpartnum, ghpartspersec; 272 uint32_t gpsectors; 273 const char openbsd_uuid_code[] = GPT_UUID_OPENBSD; 274 struct gpt_partition gp; 275 static struct uuid *openbsd_uuid = NULL, openbsd_uuid_space; 276 static u_char buf[4096]; 277 278 /* Prepare OpenBSD UUID */ 279 if (openbsd_uuid == NULL) { 280 /* XXX: should be replaced by uuid_dec_be() */ 281 memcpy(&openbsd_uuid_space, openbsd_uuid_code, 282 sizeof(openbsd_uuid_space)); 283 openbsd_uuid_space.time_low = 284 betoh32(openbsd_uuid_space.time_low); 285 openbsd_uuid_space.time_mid = 286 betoh16(openbsd_uuid_space.time_mid); 287 openbsd_uuid_space.time_hi_and_version = 288 betoh16(openbsd_uuid_space.time_hi_and_version); 289 290 openbsd_uuid = &openbsd_uuid_space; 291 } 292 293 if (EFI_BLKSPERSEC(ed) > 8) { 294 *err = "disk sector > 4096 bytes\n"; 295 return (-1); 296 } 297 298 /* GPT Header */ 299 lba = GPTSECTOR; 300 status = efid_io(F_READ, ed, EFI_SECTOBLK(ed, lba), EFI_BLKSPERSEC(ed), 301 buf); 302 if (EFI_ERROR(status)) { 303 *err = "Disk I/O Error"; 304 return (-1); 305 } 306 memcpy(&gh, buf, sizeof(gh)); 307 308 /* Check signature */ 309 if (letoh64(gh.gh_sig) != GPTSIGNATURE) { 310 *err = "bad GPT signature\n"; 311 return (-1); 312 } 313 314 if (letoh32(gh.gh_rev) != GPTREVISION) { 315 *err = "bad GPT revision\n"; 316 return (-1); 317 } 318 319 ghsize = letoh32(gh.gh_size); 320 if (ghsize < GPTMINHDRSIZE || ghsize > sizeof(struct gpt_header)) { 321 *err = "bad GPT header size\n"; 322 return (-1); 323 } 324 325 /* Check checksum */ 326 orig_csum = gh.gh_csum; 327 gh.gh_csum = 0; 328 new_csum = crc32(0, (unsigned char *)&gh, ghsize); 329 gh.gh_csum = orig_csum; 330 if (letoh32(orig_csum) != new_csum) { 331 *err = "bad GPT header checksum\n"; 332 return (-1); 333 } 334 335 lba = letoh64(gh.gh_part_lba); 336 ghpartsize = letoh32(gh.gh_part_size); 337 ghpartspersec = ed->blkio->Media->BlockSize / ghpartsize; 338 ghpartnum = letoh32(gh.gh_part_num); 339 gpsectors = (ghpartnum + ghpartspersec - 1) / ghpartspersec; 340 new_csum = crc32(0L, Z_NULL, 0); 341 found = 0; 342 for (i = 0; i < gpsectors; i++, lba++) { 343 status = efid_io(F_READ, ed, EFI_SECTOBLK(ed, lba), 344 EFI_BLKSPERSEC(ed), buf); 345 if (EFI_ERROR(status)) { 346 *err = "Disk I/O Error"; 347 return (-1); 348 } 349 for (part = 0; part < ghpartspersec; part++) { 350 if (ghpartnum == 0) 351 break; 352 new_csum = crc32(new_csum, buf + part * sizeof(gp), 353 sizeof(gp)); 354 ghpartnum--; 355 if (found) 356 continue; 357 memcpy(&gp, buf + part * sizeof(gp), sizeof(gp)); 358 if (memcmp(&gp.gp_type, openbsd_uuid, 359 sizeof(struct uuid)) == 0) 360 found = 1; 361 } 362 } 363 if (new_csum != letoh32(gh.gh_part_csum)) { 364 *err = "bad GPT entries checksum\n"; 365 return (-1); 366 } 367 if (found) { 368 lba = letoh64(gp.gp_lba_start); 369 /* Bootloaders do not current handle addresses > UINT_MAX! */ 370 if (lba > UINT_MAX || EFI_SECTOBLK(ed, lba) > UINT_MAX) { 371 *err = "OpenBSD Partition LBA > 2**32 - 1"; 372 return (-1); 373 } 374 return (u_int)lba; 375 } 376 377 return (-1); 378 } 379 380 const char * 381 efi_getdisklabel(efi_diskinfo_t ed, struct disklabel *label) 382 { 383 u_int start = 0; 384 uint8_t buf[DEV_BSIZE]; 385 struct dos_partition dosparts[NDOSPART]; 386 EFI_STATUS status; 387 const char *err = NULL; 388 int error; 389 390 /* 391 * Read sector 0. Ensure it has a valid MBR signature. 392 * 393 * If it's a protective MBR then try to find the disklabel via 394 * GPT. If it's not a protective MBR, try to find the disklabel 395 * via MBR. 396 */ 397 memset(buf, 0, sizeof(buf)); 398 status = efid_io(F_READ, ed, DOSBBSECTOR, 1, buf); 399 if (EFI_ERROR(status)) 400 return ("Disk I/O Error"); 401 402 /* Check MBR signature. */ 403 if (buf[510] != 0x55 || buf[511] != 0xaa) { 404 if (efi_getdisklabel_cd9660(ed, label) == 0) 405 return (NULL); 406 return ("invalid MBR signature"); 407 } 408 409 memcpy(dosparts, buf+DOSPARTOFF, sizeof(dosparts)); 410 411 /* check for GPT protective MBR. */ 412 if (gpt_chk_mbr(dosparts, ed->blkio->Media->LastBlock + 1) == 0) { 413 start = findopenbsd_gpt(ed, &err); 414 if (start == (u_int)-1) { 415 if (err != NULL) 416 return (err); 417 return ("no OpenBSD GPT partition"); 418 } 419 } else { 420 start = findopenbsd(ed, &err); 421 if (start == (u_int)-1) { 422 if (err != NULL) 423 return (err); 424 return "no OpenBSD MBR partition\n"; 425 } 426 } 427 428 /* Load BSD disklabel */ 429 #ifdef BIOS_DEBUG 430 if (debug) 431 printf("loading disklabel @ %u\n", start + DOS_LABELSECTOR); 432 #endif 433 /* read disklabel */ 434 error = efid_io(F_READ, ed, EFI_SECTOBLK(ed, start) + DOS_LABELSECTOR, 435 1, buf); 436 437 if (error) 438 return "failed to read disklabel"; 439 440 /* Fill in disklabel */ 441 return (getdisklabel(buf, label)); 442 } 443 444 static int 445 efi_getdisklabel_cd9660(efi_diskinfo_t ed, struct disklabel *label) 446 { 447 int off; 448 uint8_t buf[DEV_BSIZE]; 449 EFI_STATUS status; 450 451 for (off = 0; off < 100; off++) { 452 status = efid_io(F_READ, ed, 453 EFI_BLKSPERSEC(ed) * (16 + off), 1, buf); 454 if (EFI_ERROR(status)) 455 return (-1); 456 if (bcmp(buf + 1, ISO_STANDARD_ID, 5) != 0 || 457 buf[0] == ISO_VD_END) 458 return (-1); 459 if (buf[0] == ISO_VD_PRIMARY) 460 break; 461 } 462 if (off >= 100) 463 return (-1); 464 465 /* Create an imaginary disk label */ 466 label->d_secsize = 2048; 467 label->d_ntracks = 1; 468 label->d_nsectors = 100; 469 label->d_ncylinders = 1; 470 label->d_secpercyl = label->d_ntracks * label->d_nsectors; 471 472 strncpy(label->d_typename, "ATAPI CD-ROM", sizeof(label->d_typename)); 473 label->d_type = DTYPE_ATAPI; 474 475 strncpy(label->d_packname, "fictitious", sizeof(label->d_packname)); 476 DL_SETDSIZE(label, 100); 477 478 label->d_bbsize = 2048; 479 label->d_sbsize = 2048; 480 481 /* 'a' partition covering the "whole" disk */ 482 DL_SETPOFFSET(&label->d_partitions[0], 0); 483 DL_SETPSIZE(&label->d_partitions[0], 100); 484 label->d_partitions[0].p_fstype = FS_UNUSED; 485 486 /* The raw partition is special */ 487 DL_SETPOFFSET(&label->d_partitions[RAW_PART], 0); 488 DL_SETPSIZE(&label->d_partitions[RAW_PART], 100); 489 label->d_partitions[RAW_PART].p_fstype = FS_UNUSED; 490 491 label->d_npartitions = MAXPARTITIONS; 492 493 label->d_magic = DISKMAGIC; 494 label->d_magic2 = DISKMAGIC; 495 label->d_checksum = dkcksum(label); 496 497 return (0); 498 } 499 500 int 501 efiopen(struct open_file *f, ...) 502 { 503 struct diskinfo *dip = NULL; 504 va_list ap; 505 u_int unit, part; 506 int i = 0; 507 508 va_start(ap, f); 509 unit = va_arg(ap, u_int); 510 part = va_arg(ap, u_int); 511 va_end(ap); 512 513 if (part >= MAXPARTITIONS) 514 return (ENXIO); 515 516 TAILQ_FOREACH(dip, &disklist, list) { 517 if (i == unit) 518 break; 519 i++; 520 } 521 522 if (dip == NULL) 523 return (ENXIO); 524 525 if ((dip->flags & DISKINFO_FLAG_GOODLABEL) == 0) 526 return (ENXIO); 527 528 dip->part = part; 529 bootdev_dip = dip; 530 f->f_devdata = dip; 531 532 return 0; 533 } 534 535 int 536 efistrategy(void *devdata, int rw, daddr_t blk, size_t size, void *buf, 537 size_t *rsize) 538 { 539 struct diskinfo *dip = (struct diskinfo *)devdata; 540 int error = 0; 541 size_t nsect; 542 543 nsect = (size + DEV_BSIZE - 1) / DEV_BSIZE; 544 blk += DL_SECTOBLK(&dip->disklabel, 545 dip->disklabel.d_partitions[dip->part].p_offset); 546 547 if (blk < 0) 548 error = EINVAL; 549 else 550 error = efid_diskio(rw, dip, blk, nsect, buf); 551 552 if (rsize != NULL) 553 *rsize = nsect * DEV_BSIZE; 554 555 return (error); 556 } 557 558 int 559 eficlose(struct open_file *f) 560 { 561 f->f_devdata = NULL; 562 563 return 0; 564 } 565 566 int 567 efiioctl(struct open_file *f, u_long cmd, void *data) 568 { 569 return 0; 570 } 571