xref: /openbsd/sys/arch/riscv64/stand/efiboot/efidev.c (revision 73471bf0)
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