summaryrefslogtreecommitdiffstats
path: root/sys/boot/i386/gptboot/gptboot.c
diff options
context:
space:
mode:
authorjhb <jhb@FreeBSD.org>2007-10-24 21:33:00 +0000
committerjhb <jhb@FreeBSD.org>2007-10-24 21:33:00 +0000
commit2f8a906c36edd256f39a8d2ef209fef1a548d79e (patch)
tree2e0ad7e3920a2c896b216cf32db1db922c1f0e29 /sys/boot/i386/gptboot/gptboot.c
parent81c7dc737f4505dc976b462a40fdd5832e63a765 (diff)
downloadFreeBSD-src-2f8a906c36edd256f39a8d2ef209fef1a548d79e.zip
FreeBSD-src-2f8a906c36edd256f39a8d2ef209fef1a548d79e.tar.gz
First cut at support for booting a GPT labeled disk via the BIOS bootstrap
on i386 and amd64 machines. The overall process is that /boot/pmbr lives in the PMBR (similar to /boot/mbr for MBR disks) and is responsible for locating and loading /boot/gptboot. /boot/gptboot is similar to /boot/boot except that it groks GPT rather than MBR + bsdlabel. Unlike /boot/boot, /boot/gptboot lives in its own dedicated GPT partition with a new "FreeBSD boot" type. This partition does not have a fixed size in that /boot/pmbr will load the entire partition into the lower 640k. However, it is limited in that it can only be 545k. That's still a lot better than the current 7.5k limit for boot2 on MBR. gptboot mostly acts just like boot2 in that it reads /boot.config and loads up /boot/loader. Some more details: - Include uuid_equal() and uuid_is_nil() in libstand. - Add a new 'boot' command to gpt(8) which makes a GPT disk bootable using /boot/pmbr and /boot/gptboot. Note that the disk must have some free space for the boot partition. - This required exposing the backend of the 'add' function as a gpt_add_part() function to the rest of gpt(8). 'boot' uses this to create a boot partition if needed. - Don't cripple cgbase() in the UFS boot code for /boot/gptboot so that it can handle a filesystem > 1.5 TB. - /boot/gptboot has a simple loader (gptldr) that doesn't do any I/O unlike boot1 since /boot/pmbr loads all of gptboot up front. The C portion of gptboot (gptboot.c) has been repocopied from boot2.c. The primary changes are to parse the GPT to find a root filesystem and to use 64-bit disk addresses. Currently gptboot assumes that the first UFS partition on the disk is the / filesystem, but this algorithm will likely be improved in the future. - Teach the biosdisk driver in /boot/loader to understand GPT tables. GPT partitions are identified as 'disk0pX:' (e.g. disk0p2:) which is similar to the /dev names the kernel uses (e.g. /dev/ad0p2). - Add a new "freebsd-boot" alias to g_part() for the new boot UUID. MFC after: 1 month Discussed with: marcel (some things might still change, but am committing what I have so far)
Diffstat (limited to 'sys/boot/i386/gptboot/gptboot.c')
-rw-r--r--sys/boot/i386/gptboot/gptboot.c188
1 files changed, 117 insertions, 71 deletions
diff --git a/sys/boot/i386/gptboot/gptboot.c b/sys/boot/i386/gptboot/gptboot.c
index f227233..401328b 100644
--- a/sys/boot/i386/gptboot/gptboot.c
+++ b/sys/boot/i386/gptboot/gptboot.c
@@ -17,8 +17,7 @@
__FBSDID("$FreeBSD$");
#include <sys/param.h>
-#include <sys/disklabel.h>
-#include <sys/diskmbr.h>
+#include <sys/gpt.h>
#include <sys/dirent.h>
#include <sys/reboot.h>
@@ -31,7 +30,6 @@ __FBSDID("$FreeBSD$");
#include <btxv86.h>
-#include "boot2.h"
#include "lib.h"
#define IO_KEYBOARD 1
@@ -99,6 +97,7 @@ __FBSDID("$FreeBSD$");
extern uint32_t _end;
+static const uuid_t freebsd_ufs_uuid = GPT_ENT_TYPE_FREEBSD_UFS;
static const char optstr[NOPT] = "DhaCcdgmnpqrsv"; /* Also 'P', 'S' */
static const unsigned char flags[NOPT] = {
RBX_DUAL,
@@ -124,9 +123,8 @@ static struct dsk {
unsigned drive;
unsigned type;
unsigned unit;
- unsigned slice;
- unsigned part;
- unsigned start;
+ int part;
+ daddr_t start;
int init;
} dsk;
static char cmd[512];
@@ -137,20 +135,21 @@ static struct bootinfo bootinfo;
static uint8_t ioctrl = IO_KEYBOARD;
void exit(int);
+static int bcmp(const void *, const void *, size_t);
static void load(void);
static int parse(void);
static int xfsread(ino_t, void *, size_t);
-static int dskread(void *, unsigned, unsigned);
+static int dskread(void *, daddr_t, unsigned);
static void printf(const char *,...);
static void putchar(int);
+static void memcpy(void *, const void *, int);
static uint32_t memsize(void);
-static int drvread(void *, unsigned, unsigned);
+static int drvread(void *, daddr_t, unsigned);
static int keyhit(unsigned);
static int xputc(int);
static int xgetc(int);
static int getc(int);
-static void memcpy(void *, const void *, int);
static void
memcpy(void *dst, const void *src, int len)
{
@@ -168,6 +167,7 @@ strcmp(const char *s1, const char *s2)
return (unsigned char)*s1 - (unsigned char)*s2;
}
+#define GPTBOOT
#include "ufsread.c"
static inline int
@@ -239,7 +239,7 @@ main(void)
dsk.drive = *(uint8_t *)PTOV(ARGS);
dsk.type = dsk.drive & DRV_HARD ? TYPE_AD : TYPE_FD;
dsk.unit = dsk.drive & DRV_MASK;
- dsk.slice = *(uint8_t *)PTOV(ARGS + 1) + 1;
+ dsk.part = -1;
bootinfo.bi_version = BOOTINFO_VERSION;
bootinfo.bi_size = sizeof(bootinfo);
bootinfo.bi_basemem = 0; /* XXX will be filled by loader or kernel */
@@ -280,10 +280,10 @@ main(void)
for (;;) {
if (!autoboot || !OPT_CHECK(RBX_QUIET))
printf("\nFreeBSD/i386 boot\n"
- "Default: %u:%s(%u,%c)%s\n"
+ "Default: %u:%s(%up%u)%s\n"
"boot: ",
dsk.drive & DRV_MASK, dev_nm[dsk.type], dsk.unit,
- 'a' + dsk.part, kname);
+ dsk.part, kname);
if (ioctrl & IO_SERIAL)
sio_flush();
if (!autoboot || keyhit(5*SECOND))
@@ -395,12 +395,12 @@ load(void)
bootinfo.bi_kernelname = VTOP(kname);
bootinfo.bi_bios_dev = dsk.drive;
__exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK),
- MAKEBOOTDEV(dev_maj[dsk.type], dsk.slice, dsk.unit, dsk.part),
+ MAKEBOOTDEV(dev_maj[dsk.type], dsk.part + 1, dsk.unit, 0xff),
0, 0, 0, VTOP(&bootinfo));
}
static int
-parse()
+parse(void)
{
char *arg = cmd;
char *ep, *p, *q;
@@ -467,19 +467,16 @@ parse()
if (arg[1] != ',' || dsk.unit > 9)
return -1;
arg += 2;
- dsk.slice = WHOLE_DISK_SLICE;
+ dsk.part = -1;
if (arg[1] == ',') {
- dsk.slice = *arg - '0' + 1;
- if (dsk.slice > NDOSPART)
+ dsk.part = *arg - '0';
+ if (dsk.part < 1 || dsk.part > 9)
return -1;
arg += 2;
}
- if (arg[1] != ')')
+ if (arg[0] != ')')
return -1;
- dsk.part = *arg - 'a';
- if (dsk.part > 7)
- return (-1);
- arg += 2;
+ arg++;
if (drv == -1)
drv = dsk.unit;
dsk.drive = (dsk.type <= TYPE_MAXHARD
@@ -498,63 +495,87 @@ parse()
}
static int
-dskread(void *buf, unsigned lba, unsigned nblk)
+dskread(void *buf, daddr_t lba, unsigned nblk)
{
- struct dos_partition *dp;
- struct disklabel *d;
+ struct gpt_hdr hdr;
+ struct gpt_ent *ent;
char *sec;
- unsigned sl, i;
+ daddr_t slba, elba;
+ int part, entries_per_sec;
if (!dsk_meta) {
+ /* Read and verify GPT. */
sec = dmadat->secbuf;
dsk.start = 0;
- if (drvread(sec, DOSBBSECTOR, 1))
+ if (drvread(sec, 1, 1))
return -1;
- dp = (void *)(sec + DOSPARTOFF);
- sl = dsk.slice;
- if (sl < BASE_SLICE) {
- for (i = 0; i < NDOSPART; i++)
- if (dp[i].dp_typ == DOSPTYP_386BSD &&
- (dp[i].dp_flag & 0x80 || sl < BASE_SLICE)) {
- sl = BASE_SLICE + i;
- if (dp[i].dp_flag & 0x80 ||
- dsk.slice == COMPATIBILITY_SLICE)
+ memcpy(&hdr, sec, sizeof(hdr));
+ if (bcmp(hdr.hdr_sig, GPT_HDR_SIG, sizeof(hdr.hdr_sig)) != 0 ||
+ hdr.hdr_lba_self != 1 || hdr.hdr_revision < 0x00010000 ||
+ hdr.hdr_entsz < sizeof(*ent) || DEV_BSIZE % hdr.hdr_entsz != 0) {
+ printf("Invalid GPT header\n");
+ return -1;
+ }
+
+ /* XXX: CRC check? */
+
+ /*
+ * If the partition isn't specified, then search for the first UFS
+ * partition and hope it is /. Perhaps we should be using an OS
+ * flag in the GPT entry to mark / partitions.
+ *
+ * If the partition is specified, then figure out the LBA for the
+ * sector containing that partition index and load it.
+ */
+ entries_per_sec = DEV_BSIZE / hdr.hdr_entsz;
+ if (dsk.part == -1) {
+ slba = hdr.hdr_lba_table;
+ elba = slba + hdr.hdr_entries / entries_per_sec;
+ while (slba < elba && dsk.part == -1) {
+ if (drvread(sec, slba, 1))
+ return -1;
+ for (part = 0; part < entries_per_sec; part++) {
+ ent = (struct gpt_ent *)(sec + part * hdr.hdr_entsz);
+ if (bcmp(&ent->ent_type, &freebsd_ufs_uuid,
+ sizeof(uuid_t)) == 0) {
+ dsk.part = (slba - hdr.hdr_lba_table) *
+ entries_per_sec + part + 1;
+ dsk.start = ent->ent_lba_start;
break;
+ }
}
- if (dsk.slice == WHOLE_DISK_SLICE)
- dsk.slice = sl;
- }
- if (sl != WHOLE_DISK_SLICE) {
- if (sl != COMPATIBILITY_SLICE)
- dp += sl - BASE_SLICE;
- if (dp->dp_typ != DOSPTYP_386BSD) {
- printf("Invalid %s\n", "slice");
- return -1;
+ slba++;
}
- dsk.start = dp->dp_start;
- }
- if (drvread(sec, dsk.start + LABELSECTOR, 1))
- return -1;
- d = (void *)(sec + LABELOFFSET);
- if (d->d_magic != DISKMAGIC || d->d_magic2 != DISKMAGIC) {
- if (dsk.part != RAW_PART) {
- printf("Invalid %s\n", "label");
+ if (dsk.part == -1) {
+ printf("No UFS partition was found\n");
return -1;
}
} else {
- if (!dsk.init) {
- if (d->d_type == DTYPE_SCSI)
- dsk.type = TYPE_DA;
- dsk.init++;
+ if (dsk.part > hdr.hdr_entries) {
+ printf("Invalid partition index\n");
+ return -1;
}
- if (dsk.part >= d->d_npartitions ||
- !d->d_partitions[dsk.part].p_size) {
- printf("Invalid %s\n", "partition");
+ slba = hdr.hdr_lba_table + (dsk.part - 1) / entries_per_sec;
+ if (drvread(sec, slba, 1))
+ return -1;
+ part = (dsk.part - 1) % entries_per_sec;
+ ent = (struct gpt_ent *)(sec + part * hdr.hdr_entsz);
+ if (bcmp(&ent->ent_type, &freebsd_ufs_uuid, sizeof(uuid_t)) != 0) {
+ printf("Specified partition is not UFS\n");
return -1;
}
- dsk.start += d->d_partitions[dsk.part].p_offset;
- dsk.start -= d->d_partitions[RAW_PART].p_offset;
+ dsk.start = ent->ent_lba_start;
+ }
+ /*
+ * XXX: No way to detect SCSI vs. ATA currently.
+ */
+#if 0
+ if (!dsk.init) {
+ if (d->d_type == DTYPE_SCSI)
+ dsk.type = TYPE_DA;
+ dsk.init++;
}
+#endif
}
return drvread(buf, dsk.start + lba, nblk);
}
@@ -606,21 +627,46 @@ putchar(int c)
}
static int
-drvread(void *buf, unsigned lba, unsigned nblk)
+bcmp(const void *b1, const void *b2, size_t length)
+{
+ const char *p1 = b1, *p2 = b2;
+
+ if (length == 0)
+ return (0);
+ do {
+ if (*p1++ != *p2++)
+ break;
+ } while (--length);
+ return (length);
+}
+
+static struct {
+ uint16_t len;
+ uint16_t count;
+ uint16_t seg;
+ uint16_t off;
+ uint64_t lba;
+} packet;
+
+static int
+drvread(void *buf, daddr_t lba, unsigned nblk)
{
static unsigned c = 0x2d5c7c2f;
if (!OPT_CHECK(RBX_QUIET))
printf("%c\b", c = c << 8 | c >> 24);
- v86.ctl = V86_ADDR | V86_CALLF | V86_FLAGS;
- v86.addr = XREADORG; /* call to xread in boot1 */
- v86.es = VTOPSEG(buf);
- v86.eax = lba;
- v86.ebx = VTOPOFF(buf);
- v86.ecx = lba >> 16;
- v86.edx = nblk << 8 | dsk.drive;
- v86int();
+ packet.len = 0x10;
+ packet.count = nblk;
+ packet.seg = VTOPOFF(buf);
+ packet.off = VTOPSEG(buf);
+ packet.lba = lba;
v86.ctl = V86_FLAGS;
+ v86.addr = 0x13;
+ v86.eax = 0x4200;
+ v86.edx = dsk.drive;
+ v86.ds = VTOPSEG(&packet);
+ v86.esi = VTOPOFF(&packet);
+ v86int();
if (V86_CY(v86.efl)) {
printf("error %u lba %u\n", v86.eax >> 8 & 0xff, lba);
return -1;
OpenPOWER on IntegriCloud