-/* $OpenBSD: efidev.c,v 1.1 2016/12/17 23:38:33 patrick Exp $ */
+/* $OpenBSD: efidev.c,v 1.2 2018/08/25 20:43:39 kettenis Exp $ */
/*
* Copyright (c) 2015 YASUOKA Masahiko <yasuoka@yasuoka.net>
#include <sys/reboot.h>
#include <sys/disklabel.h>
#include <lib/libz/zlib.h>
+#include <isofs/cd9660/iso.h>
#include "libsa.h"
static EFI_STATUS
efid_io(int, efi_diskinfo_t, u_int, int, void *);
static int efid_diskio(int, struct diskinfo *, u_int, int, void *);
+static int efi_getdisklabel_cd9660(efi_diskinfo_t, struct disklabel *);
+static u_int findopenbsd(efi_diskinfo_t, const char **);
+static u_int findopenbsd_gpt(efi_diskinfo_t, const char **);
+static int gpt_chk_mbr(struct dos_partition *, u_int64_t);
static EFI_STATUS
efid_io(int rw, efi_diskinfo_t ed, u_int off, int nsect, void *buf)
}
/*
- * Read disk label from the device.
+ * Returns 0 if the MBR with the provided partition array is a GPT protective
+ * MBR, and returns 1 otherwise. A GPT protective MBR would have one and only
+ * one MBR partition, an EFI partition that either covers the whole disk or as
+ * much of it as is possible with a 32bit size field.
+ *
+ * Taken from kern/subr_disk.c.
+ *
+ * NOTE: MS always uses a size of UINT32_MAX for the EFI partition!**
*/
-int
-efi_getdisklabel(struct diskinfo *dip)
+static int
+gpt_chk_mbr(struct dos_partition *dp, u_int64_t dsize)
{
- char *msg;
- int sector;
- size_t rsize;
- struct disklabel *lp;
- char buf[DEV_BSIZE];
+ struct dos_partition *dp2;
+ int efi, found, i;
+ u_int32_t psize;
+
+ found = efi = 0;
+ for (dp2=dp, i=0; i < NDOSPART; i++, dp2++) {
+ if (dp2->dp_typ == DOSPTYP_UNUSED)
+ continue;
+ found++;
+ if (dp2->dp_typ != DOSPTYP_EFI)
+ continue;
+ psize = letoh32(dp2->dp_size);
+ if (psize == (dsize - 1) ||
+ psize == UINT32_MAX) {
+ if (letoh32(dp2->dp_start) == 1)
+ efi++;
+ }
+ }
+ if (found == 1 && efi == 1)
+ return (0);
- /*
- * Find OpenBSD Partition in DOS partition table.
- */
- sector = 0;
- if (efistrategy(dip, F_READ, DOSBBSECTOR, DEV_BSIZE, buf, &rsize))
- return EOFFSET;
+ return (1);
+}
- if (*(u_int16_t *)&buf[DOSMBR_SIGNATURE_OFF] == DOSMBR_SIGNATURE) {
- int i;
- struct dos_partition *dp = (struct dos_partition *)buf;
+/*
+ * Try to find the disk address of the first MBR OpenBSD partition.
+ *
+ * N.B.: must boot from a partition within first 2^32-1 sectors!
+ *
+ * Called only if the MBR on sector 0 is *not* a protective MBR
+ * and *does* have a valid signature.
+ *
+ * We don't check the signatures of EBR's, and they cannot be
+ * protective MBR's so there is no need to check for that.
+ */
+static u_int
+findopenbsd(efi_diskinfo_t ed, const char **err)
+{
+ EFI_STATUS status;
+ struct dos_mbr mbr;
+ struct dos_partition *dp;
+ u_int mbroff = DOSBBSECTOR;
+ u_int mbr_eoff = DOSBBSECTOR; /* Offset of MBR extended partition. */
+ int i, maxebr = DOS_MAXEBR, nextebr;
+
+again:
+ if (!maxebr--) {
+ *err = "too many extended partitions";
+ return (-1);
+ }
+
+ /* Read MBR */
+ bzero(&mbr, sizeof(mbr));
+ status = efid_io(F_READ, ed, mbroff, 1, &mbr);
+ if (EFI_ERROR(status)) {
+ *err = "Disk I/O Error";
+ return (-1);
+ }
+
+ /* Search for OpenBSD partition */
+ nextebr = 0;
+ for (i = 0; i < NDOSPART; i++) {
+ dp = &mbr.dmbr_parts[i];
+ if (!dp->dp_size)
+ continue;
+#ifdef BIOS_DEBUG
+ if (debug)
+ printf("found partition %u: "
+ "type %u (0x%x) offset %u (0x%x)\n",
+ (int)(dp - mbr.dmbr_parts),
+ dp->dp_typ, dp->dp_typ,
+ dp->dp_start, dp->dp_start);
+#endif
+ if (dp->dp_typ == DOSPTYP_OPENBSD) {
+ if (dp->dp_start > (dp->dp_start + mbroff))
+ continue;
+ return (dp->dp_start + mbroff);
+ }
/*
- * Lookup OpenBSD slice. If there is none, go ahead
- * and try to read the disklabel off sector #0.
+ * Record location of next ebr if and only if this is the first
+ * extended partition in this boot record!
*/
+ if (!nextebr && (dp->dp_typ == DOSPTYP_EXTEND ||
+ dp->dp_typ == DOSPTYP_EXTENDL)) {
+ nextebr = dp->dp_start + mbr_eoff;
+ if (nextebr < dp->dp_start)
+ nextebr = (u_int)-1;
+ if (mbr_eoff == DOSBBSECTOR)
+ mbr_eoff = dp->dp_start;
+ }
+ }
+
+ if (nextebr && nextebr != (u_int)-1) {
+ mbroff = nextebr;
+ goto again;
+ }
+
+ return (-1);
+}
+
+/*
+ * Try to find the disk address of the first GPT OpenBSD partition.
+ *
+ * N.B.: must boot from a partition within first 2^32-1 sectors!
+ *
+ * Called only if the MBR on sector 0 *is* a protective MBR
+ * with a valid signature and sector 1 is a valid GPT header.
+ */
+static u_int
+findopenbsd_gpt(efi_diskinfo_t ed, const char **err)
+{
+ EFI_STATUS status;
+ struct gpt_header gh;
+ int i, part, found;
+ uint64_t lba;
+ uint32_t orig_csum, new_csum;
+ uint32_t ghsize, ghpartsize, ghpartnum, ghpartspersec;
+ uint32_t gpsectors;
+ const char openbsd_uuid_code[] = GPT_UUID_OPENBSD;
+ struct gpt_partition gp;
+ static struct uuid *openbsd_uuid = NULL, openbsd_uuid_space;
+ static u_char buf[4096];
+
+ /* Prepare OpenBSD UUID */
+ if (openbsd_uuid == NULL) {
+ /* XXX: should be replaced by uuid_dec_be() */
+ memcpy(&openbsd_uuid_space, openbsd_uuid_code,
+ sizeof(openbsd_uuid_space));
+ openbsd_uuid_space.time_low =
+ betoh32(openbsd_uuid_space.time_low);
+ openbsd_uuid_space.time_mid =
+ betoh16(openbsd_uuid_space.time_mid);
+ openbsd_uuid_space.time_hi_and_version =
+ betoh16(openbsd_uuid_space.time_hi_and_version);
+
+ openbsd_uuid = &openbsd_uuid_space;
+ }
+
+ if (EFI_BLKSPERSEC(ed) > 8) {
+ *err = "disk sector > 4096 bytes\n";
+ return (-1);
+ }
+
+ /* LBA1: GPT Header */
+ lba = 1;
+ status = efid_io(F_READ, ed, EFI_SECTOBLK(ed, lba), EFI_BLKSPERSEC(ed),
+ buf);
+ if (EFI_ERROR(status)) {
+ *err = "Disk I/O Error";
+ return (-1);
+ }
+ memcpy(&gh, buf, sizeof(gh));
+
+ /* Check signature */
+ if (letoh64(gh.gh_sig) != GPTSIGNATURE) {
+ *err = "bad GPT signature\n";
+ return (-1);
+ }
+
+ if (letoh32(gh.gh_rev) != GPTREVISION) {
+ *err = "bad GPT revision\n";
+ return (-1);
+ }
- memcpy(dp, &buf[DOSPARTOFF], NDOSPART * sizeof(*dp));
- for (i = 0; i < NDOSPART; i++) {
- if (dp[i].dp_typ == DOSPTYP_OPENBSD) {
- sector = letoh32(dp[i].dp_start);
+ ghsize = letoh32(gh.gh_size);
+ if (ghsize < GPTMINHDRSIZE || ghsize > sizeof(struct gpt_header)) {
+ *err = "bad GPT header size\n";
+ return (-1);
+ }
+
+ /* Check checksum */
+ orig_csum = gh.gh_csum;
+ gh.gh_csum = 0;
+ new_csum = crc32(0, (unsigned char *)&gh, ghsize);
+ gh.gh_csum = orig_csum;
+ if (letoh32(orig_csum) != new_csum) {
+ *err = "bad GPT header checksum\n";
+ return (-1);
+ }
+
+ lba = letoh64(gh.gh_part_lba);
+ ghpartsize = letoh32(gh.gh_part_size);
+ ghpartspersec = ed->blkio->Media->BlockSize / ghpartsize;
+ ghpartnum = letoh32(gh.gh_part_num);
+ gpsectors = (ghpartnum + ghpartspersec - 1) / ghpartspersec;
+ new_csum = crc32(0L, Z_NULL, 0);
+ found = 0;
+ for (i = 0; i < gpsectors; i++, lba++) {
+ status = efid_io(F_READ, ed, EFI_SECTOBLK(ed, lba),
+ EFI_BLKSPERSEC(ed), buf);
+ if (EFI_ERROR(status)) {
+ *err = "Disk I/O Error";
+ return (-1);
+ }
+ for (part = 0; part < ghpartspersec; part++) {
+ if (ghpartnum == 0)
break;
- }
+ new_csum = crc32(new_csum, buf + part * sizeof(gp),
+ sizeof(gp));
+ ghpartnum--;
+ if (found)
+ continue;
+ memcpy(&gp, buf + part * sizeof(gp), sizeof(gp));
+ if (memcmp(&gp.gp_type, openbsd_uuid,
+ sizeof(struct uuid)) == 0)
+ found = 1;
+ }
+ }
+ if (new_csum != letoh32(gh.gh_part_csum)) {
+ *err = "bad GPT entries checksum\n";
+ return (-1);
+ }
+ if (found) {
+ lba = letoh64(gp.gp_lba_start);
+ /* Bootloaders do not current handle addresses > UINT_MAX! */
+ if (lba > UINT_MAX || EFI_SECTOBLK(ed, lba) > UINT_MAX) {
+ *err = "OpenBSD Partition LBA > 2**32 - 1";
+ return (-1);
+ }
+ return (u_int)lba;
+ }
+
+ return (-1);
+}
+
+const char *
+efi_getdisklabel(efi_diskinfo_t ed, struct disklabel *label)
+{
+ u_int start = 0;
+ uint8_t buf[DEV_BSIZE];
+ struct dos_partition dosparts[NDOSPART];
+ EFI_STATUS status;
+ const char *err = NULL;
+ int error;
+
+ /*
+ * Read sector 0. Ensure it has a valid MBR signature.
+ *
+ * If it's a protective MBR then try to find the disklabel via
+ * GPT. If it's not a protective MBR, try to find the disklabel
+ * via MBR.
+ */
+ memset(buf, 0, sizeof(buf));
+ status = efid_io(F_READ, ed, DOSBBSECTOR, 1, buf);
+ if (EFI_ERROR(status))
+ return ("Disk I/O Error");
+
+ /* Check MBR signature. */
+ if (buf[510] != 0x55 || buf[511] != 0xaa) {
+ if (efi_getdisklabel_cd9660(ed, label) == 0)
+ return (NULL);
+ return ("invalid MBR signature");
+ }
+
+ memcpy(dosparts, buf+DOSPARTOFF, sizeof(dosparts));
+
+ /* check for GPT protective MBR. */
+ if (gpt_chk_mbr(dosparts, ed->blkio->Media->LastBlock + 1) == 0) {
+ start = findopenbsd_gpt(ed, &err);
+ if (start == (u_int)-1) {
+ if (err != NULL)
+ return (err);
+ return ("no OpenBSD GPT partition");
+ }
+ } else {
+ start = findopenbsd(ed, &err);
+ if (start == (u_int)-1) {
+ if (err != NULL)
+ return (err);
+ return "no OpenBSD MBR partition\n";
}
}
- if (efistrategy(dip, F_READ, sector + DOS_LABELSECTOR, DEV_BSIZE,
- buf, &rsize))
- return EOFFSET;
+ /* Load BSD disklabel */
+#ifdef BIOS_DEBUG
+ if (debug)
+ printf("loading disklabel @ %u\n", start + DOS_LABELSECTOR);
+#endif
+ /* read disklabel */
+ error = efid_io(F_READ, ed, EFI_SECTOBLK(ed, start) + DOS_LABELSECTOR,
+ 1, buf);
- if ((msg = getdisklabel(buf + LABELOFFSET, &dip->disklabel)))
- printf("sd%d: getdisklabel: %s\n", 0, msg);
+ if (error)
+ return "failed to read disklabel";
- lp = &dip->disklabel;
+ /* Fill in disklabel */
+ return (getdisklabel(buf, label));
+}
- /* check partition */
- if ((dip->sc_part >= lp->d_npartitions) ||
- (lp->d_partitions[dip->sc_part].p_fstype == FS_UNUSED)) {
- DPRINTF(("illegal partition\n"));
- return (EPART);
+static int
+efi_getdisklabel_cd9660(efi_diskinfo_t ed, struct disklabel *label)
+{
+ int off;
+ uint8_t buf[DEV_BSIZE];
+ EFI_STATUS status;
+
+ for (off = 0; off < 100; off++) {
+ status = efid_io(F_READ, ed,
+ EFI_BLKSPERSEC(ed) * (16 + off), 1, buf);
+ if (EFI_ERROR(status))
+ return (-1);
+ if (bcmp(buf + 1, ISO_STANDARD_ID, 5) != 0 ||
+ buf[0] == ISO_VD_END)
+ return (-1);
+ if (buf[0] == ISO_VD_PRIMARY)
+ break;
}
+ if (off >= 100)
+ return (-1);
+
+ /* Create an imaginary disk label */
+ label->d_secsize = 2048;
+ label->d_ntracks = 1;
+ label->d_nsectors = 100;
+ label->d_ncylinders = 1;
+ label->d_secpercyl = label->d_ntracks * label->d_nsectors;
+
+ strncpy(label->d_typename, "ATAPI CD-ROM", sizeof(label->d_typename));
+ label->d_type = DTYPE_ATAPI;
+
+ strncpy(label->d_packname, "fictitious", sizeof(label->d_packname));
+ DL_SETDSIZE(label, 100);
+
+ label->d_bbsize = 2048;
+ label->d_sbsize = 2048;
+
+ /* 'a' partition covering the "whole" disk */
+ DL_SETPOFFSET(&label->d_partitions[0], 0);
+ DL_SETPSIZE(&label->d_partitions[0], 100);
+ label->d_partitions[0].p_fstype = FS_UNUSED;
+
+ /* The raw partition is special */
+ DL_SETPOFFSET(&label->d_partitions[RAW_PART], 0);
+ DL_SETPSIZE(&label->d_partitions[RAW_PART], 100);
+ label->d_partitions[RAW_PART].p_fstype = FS_UNUSED;
+
+ label->d_npartitions = MAXPARTITIONS;
+
+ label->d_magic = DISKMAGIC;
+ label->d_magic2 = DISKMAGIC;
+ label->d_checksum = dkcksum(label);
return (0);
}
struct diskinfo *dip = &diskinfo;
va_list ap;
u_int unit, part;
- int error;
+ const char *err;
if (disk == NULL)
return (ENXIO);
diskinfo.ed.mediaid = disk->Media->MediaId;
diskinfo.sc_part = part;
- error = efi_getdisklabel(&diskinfo);
- if (error)
- return (error);
+ err = efi_getdisklabel(&dip->ed, &dip->disklabel);
+ if (err) {
+ printf("%s\n", err);
+ return EINVAL;
+ }
f->f_devdata = dip;
size_t nsect;
nsect = (size + DEV_BSIZE - 1) / DEV_BSIZE;
- blk += dip->disklabel.d_partitions[B_PARTITION(dip->sc_part)].p_offset;
+ blk += DL_SECTOBLK(&dip->disklabel,
+ dip->disklabel.d_partitions[B_PARTITION(dip->sc_part)].p_offset);
if (blk < 0)
error = EINVAL;