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