diff options
author | Joel Sing <jsing@cvs.openbsd.org> | 2012-01-11 14:47:03 +0000 |
---|---|---|
committer | Joel Sing <jsing@cvs.openbsd.org> | 2012-01-11 14:47:03 +0000 |
commit | 11c62b32113e88e6569071d0c1e18ac8418336d0 (patch) | |
tree | 7dff992b099305b43b91b484ed8c67f7f66d4f10 /sys/arch/amd64 | |
parent | 8be10ea4f64de84f03dcc10b3e013d821b737a2d (diff) |
Teach OpenBSD/amd64 boot(8) how to access softraid(8) volumes. This
allows a kernel to be loaded from a softraid RAID 1 volume. Furthermore,
if you boot from a disk that is a member of a bootable softraid volume
then it will default to booting from the softraid volume (sr[0-9]a:/bsd).
Discussed with deraadt@
Diffstat (limited to 'sys/arch/amd64')
-rw-r--r-- | sys/arch/amd64/stand/libsa/biosdev.c | 211 | ||||
-rw-r--r-- | sys/arch/amd64/stand/libsa/dev_i386.c | 35 | ||||
-rw-r--r-- | sys/arch/amd64/stand/libsa/disk.h | 9 | ||||
-rw-r--r-- | sys/arch/amd64/stand/libsa/diskprobe.c | 190 |
4 files changed, 397 insertions, 48 deletions
diff --git a/sys/arch/amd64/stand/libsa/biosdev.c b/sys/arch/amd64/stand/libsa/biosdev.c index a8950c2375e..1b913f3b5eb 100644 --- a/sys/arch/amd64/stand/libsa/biosdev.c +++ b/sys/arch/amd64/stand/libsa/biosdev.c @@ -1,4 +1,4 @@ -/* $OpenBSD: biosdev.c,v 1.16 2011/07/19 01:08:35 krw Exp $ */ +/* $OpenBSD: biosdev.c,v 1.17 2012/01/11 14:47:02 jsing Exp $ */ /* * Copyright (c) 1996 Michael Shalayeff @@ -31,10 +31,12 @@ #include <sys/param.h> #include <sys/reboot.h> #include <sys/disklabel.h> +#include <dev/biovar.h> +#include <dev/softraidvar.h> +#include <isofs/cd9660/iso.h> +#include <lib/libsa/saerrno.h> #include <machine/tss.h> #include <machine/biosvar.h> -#include <lib/libsa/saerrno.h> -#include <isofs/cd9660/iso.h> #include "disk.h" #include "libsa.h" #include "biosdev.h" @@ -47,18 +49,14 @@ static int EDD_rw (int, int, u_int32_t, u_int32_t, void *); static u_int findopenbsd(bios_diskinfo_t *, const char **); +static const char *sr_getdisklabel(struct sr_boot_volume *, struct disklabel *); +static int sr_strategy(struct sr_boot_volume *, int, daddr32_t, size_t, + void *, size_t *); + extern int debug; int bios_bootdev; int bios_cddev = -1; /* Set by srt0 if coming from CD */ -#if 0 -struct biosdisk { - bios_diskinfo_t *bios_info; - dev_t bsddev; - struct disklabel disklabel; -}; -#endif - struct EDD_CB { u_int8_t edd_len; /* size of packet */ u_int8_t edd_res1; /* reserved */ @@ -341,7 +339,7 @@ biosd_io(int rw, bios_diskinfo_t *bd, u_int off, int nsect, void *buf) } /* - * Try to read the bsd label on the given BIOS device + * Try to read the bsd label on the given BIOS device. */ static u_int findopenbsd(bios_diskinfo_t *bd, const char **err) @@ -457,11 +455,14 @@ bios_getdisklabel(bios_diskinfo_t *bd, struct disklabel *label) int biosopen(struct open_file *f, ...) { - va_list ap; - register char *cp, **file; + struct sr_boot_volume *bv; + register char *cp, **file; dev_t maj, unit, part; struct diskinfo *dip; - int biosdev; + int biosdev, devlen; + const char *st; + va_list ap; + char *dev; va_start(ap, f); cp = *(file = va_arg(ap, char **)); @@ -473,46 +474,87 @@ biosopen(struct open_file *f, ...) #endif f->f_devdata = NULL; - /* search for device specification */ - cp += 2; - if (cp[2] != ':') { - if (cp[3] != ':') - return ENOENT; - else - cp++; - } - for (maj = 0; maj < nbdevs && strncmp(*file, bdevs[maj], cp - *file); ) - maj++; - if (maj >= nbdevs) { - printf("Unknown device: "); - for (cp = *file; *cp != ':'; cp++) - putchar(*cp); - putchar('\n'); - return EADAPT; - } + /* Search for device specification. */ + dev = cp; + if (cp[4] == ':') + devlen = 2; + else if (cp[5] == ':') + devlen = 3; + else + return ENOENT; + cp += devlen; - /* get unit */ + /* Get unit. */ if ('0' <= *cp && *cp <= '9') unit = *cp++ - '0'; else { printf("Bad unit number\n"); return EUNIT; } - /* get partition */ + + /* Get partition. */ if ('a' <= *cp && *cp <= 'p') part = *cp++ - 'a'; else { - printf("Bad partition id\n"); + printf("Bad partition\n"); return EPART; } + /* Get filename. */ cp++; /* skip ':' */ if (*cp != 0) *file = cp; else f->f_flags |= F_RAW; + /* Intercept softraid disks. */ + if (strncmp("sr", dev, 2) == 0) { + + /* Create a fake diskinfo for this softraid volume. */ + SLIST_FOREACH(bv, &sr_volumes, sbv_link) + if (bv->sbv_unit == unit) + break; + if (bv == NULL) { + printf("Unknown device: sr%d\n", unit); + return EADAPT; + } + if (bv->sbv_diskinfo == NULL) { + dip = alloc(sizeof(struct diskinfo)); + bzero(dip, sizeof(*dip)); + bv->sbv_diskinfo = dip; + dip->sr_vol = bv; + dip->bios_info.flags |= BDI_BADLABEL; + } + + dip = bv->sbv_diskinfo; + + if (dip->bios_info.flags & BDI_BADLABEL) { + /* Attempt to read disklabel. */ + bv->sbv_part = 'c'; + if (sr_getdisklabel(bv, &dip->disklabel)) + return ERDLAB; + dip->bios_info.flags &= ~BDI_BADLABEL; + } + + bv->sbv_part = part + 'a'; + + bootdev_dip = dip; + f->f_devdata = dip; + + return 0; + } + + for (maj = 0; maj < nbdevs && + strncmp(dev, bdevs[maj], devlen); maj++); + if (maj >= nbdevs) { + printf("Unknown device: "); + for (cp = *file; *cp != ':'; cp++) + putchar(*cp); + putchar('\n'); + return EADAPT; + } + biosdev = unit; switch (maj) { case 0: /* wd */ @@ -556,8 +598,7 @@ biosopen(struct open_file *f, ...) /* Try for disklabel again (might be removable media) */ if (dip->bios_info.flags & BDI_BADLABEL) { - const char *st = bios_getdisklabel(&dip->bios_info, - &dip->disklabel); + st = bios_getdisklabel(&dip->bios_info, &dip->disklabel); #ifdef BIOS_DEBUG if (debug && st) printf("%s\n", st); @@ -662,10 +703,12 @@ biosstrategy(void *devdata, int rw, daddr32_t blk, size_t size, void *buf, u_int8_t error = 0; size_t nsect; + /* Intercept strategy for softraid volumes. */ + if (dip->sr_vol) + return sr_strategy(dip->sr_vol, rw, blk, size, buf, rsize); + nsect = (size + DEV_BSIZE-1) / DEV_BSIZE; - if (rsize != NULL) - blk += dip->disklabel. - d_partitions[B_PARTITION(dip->bsddev)].p_offset; + blk += dip->disklabel.d_partitions[B_PARTITION(dip->bsddev)].p_offset; /* Read all, sub-functions handle track boundaries */ if (blk < 0) @@ -700,3 +743,91 @@ biosioctl(struct open_file *f, u_long cmd, void *data) { return 0; } + +static int +sr_strategy(struct sr_boot_volume *bv, int rw, daddr32_t blk, size_t size, + void *buf, size_t *rsize) +{ + struct diskinfo *sr_dip, *dip; + struct sr_boot_chunk *bc; + + /* We only support read-only softraid. */ + if (rw != F_READ) + return EPERM; + + /* Partition offset within softraid volume. */ + sr_dip = (struct diskinfo *)bv->sbv_diskinfo; + blk += sr_dip->disklabel.d_partitions[bv->sbv_part - 'a'].p_offset; + + if (bv->sbv_level == 0) { + return ENOTSUP; + } else if (bv->sbv_level == 1) { + + /* Select first online chunk. */ + SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link) + if (bc->sbc_state == BIOC_SDONLINE) + break; + if (bc == NULL) + return EIO; + + dip = (struct diskinfo *)bc->sbc_diskinfo; + dip->bsddev = bc->sbc_mm; + blk += bv->sbv_data_offset; + + /* XXX - If I/O failed we should try another chunk... */ + return biosstrategy(dip, rw, blk, size, buf, rsize); + + } else if (bv->sbv_level == 'C') { + printf("mmmm... crypto!\n"); + return ENOTSUP; + } else + return ENOTSUP; +} + +static const char * +sr_getdisklabel(struct sr_boot_volume *bv, struct disklabel *label) +{ + struct dos_partition *dp; + struct dos_mbr mbr; + u_int start = 0; + char *buf; + int i; + + /* Check for MBR to determine partition offset. */ + bzero(&mbr, sizeof(mbr)); + sr_strategy(bv, F_READ, DOSBBSECTOR, sizeof(struct dos_mbr), + &mbr, NULL); + if (mbr.dmbr_sign == DOSMBR_SIGNATURE) { + + /* Search for OpenBSD partition */ + for (i = 0; i < NDOSPART; i++) { + dp = &mbr.dmbr_parts[i]; + if (!dp->dp_size) + continue; + if (dp->dp_typ == DOSPTYP_OPENBSD) { + if (dp->dp_start > (dp->dp_start + DOSBBSECTOR)) + continue; + start = dp->dp_start + DOSBBSECTOR; + } + } + } + + start += LABELSECTOR; + + /* Read the disklabel. */ + buf = alloca(DEV_BSIZE); + sr_strategy(bv, F_READ, start, sizeof(struct disklabel), buf, NULL); + +#if BIOS_DEBUG + printf("sr_getdisklabel: magic %lx\n", + ((struct disklabel *)buf)->d_magic); + for (i = 0; i < MAXPARTITIONS; i++) + printf("part %c: type = %d, size = %d, offset = %d\n", 'a' + i, + (int)((struct disklabel *)buf)->d_partitions[i].p_fstype, + (int)((struct disklabel *)buf)->d_partitions[i].p_size, + (int)((struct disklabel *)buf)->d_partitions[i].p_offset); +#endif + + /* Fill in disklabel */ + return (getdisklabel(buf, label)); +} diff --git a/sys/arch/amd64/stand/libsa/dev_i386.c b/sys/arch/amd64/stand/libsa/dev_i386.c index b5c5f98a1de..4b762562a79 100644 --- a/sys/arch/amd64/stand/libsa/dev_i386.c +++ b/sys/arch/amd64/stand/libsa/dev_i386.c @@ -1,4 +1,4 @@ -/* $OpenBSD: dev_i386.c,v 1.8 2011/06/26 23:19:11 tedu Exp $ */ +/* $OpenBSD: dev_i386.c,v 1.9 2012/01/11 14:47:02 jsing Exp $ */ /* * Copyright (c) 1996-1999 Michael Shalayeff @@ -26,10 +26,15 @@ * THE POSSIBILITY OF SUCH DAMAGE. */ -#include "libsa.h" -#include "biosdev.h" #include <sys/param.h> +#include <sys/queue.h> +#include <sys/disklabel.h> #include <dev/cons.h> +#include <dev/biovar.h> +#include <dev/softraidvar.h> +#include "libsa.h" +#include "biosdev.h" +#include "disk.h" extern int debug; @@ -89,6 +94,10 @@ devopen(struct open_file *f, const char *fname, char **file) void devboot(dev_t bootdev, char *p) { + struct sr_boot_volume *bv; + struct sr_boot_chunk *bc; + int sr_boot_vol = -1; + #ifdef _TEST *p++ = '/'; *p++ = 'd'; @@ -97,7 +106,25 @@ devboot(dev_t bootdev, char *p) *p++ = '/'; *p++ = 'r'; #endif - if (bootdev & 0x100) { + + /* + * See if we booted from a disk that is a member of a bootable + * softraid volume. + */ + SLIST_FOREACH(bv, &sr_volumes, sbv_link) { + if (bv->sbv_flags & BIOC_SCBOOTABLE) + SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link) + if (bc->sbc_disk == bootdev) + sr_boot_vol = bv->sbv_unit; + if (sr_boot_vol != -1) + break; + } + + if (sr_boot_vol != -1) { + *p++ = 's'; + *p++ = 'r'; + *p++ = '0' + sr_boot_vol; + } else if (bootdev & 0x100) { *p++ = 'c'; *p++ = 'd'; *p++ = '0'; diff --git a/sys/arch/amd64/stand/libsa/disk.h b/sys/arch/amd64/stand/libsa/disk.h index e95a3642924..efb84c603d4 100644 --- a/sys/arch/amd64/stand/libsa/disk.h +++ b/sys/arch/amd64/stand/libsa/disk.h @@ -1,4 +1,4 @@ -/* $OpenBSD: disk.h,v 1.1 2004/02/03 12:09:47 mickey Exp $ */ +/* $OpenBSD: disk.h,v 1.2 2012/01/11 14:47:02 jsing Exp $ */ /* * Copyright (c) 1997 Tobias Weingartner @@ -36,6 +36,7 @@ struct diskinfo { bios_diskinfo_t bios_info; struct disklabel disklabel; + struct sr_boot_volume *sr_vol; dev_t bsddev, bootdev; @@ -43,10 +44,12 @@ struct diskinfo { }; TAILQ_HEAD(disklist_lh, diskinfo); -/* Head of this list */ +/* Head of this list. */ extern struct diskinfo *bootdev_dip; +/* List of softraid volumes. */ +extern struct sr_boot_volume_head sr_volumes; + void dump_diskinfo(void); #endif /* _DISKPROBE_H */ - diff --git a/sys/arch/amd64/stand/libsa/diskprobe.c b/sys/arch/amd64/stand/libsa/diskprobe.c index e8cdf8e7e7b..4a9f80597ed 100644 --- a/sys/arch/amd64/stand/libsa/diskprobe.c +++ b/sys/arch/amd64/stand/libsa/diskprobe.c @@ -1,7 +1,8 @@ -/* $OpenBSD: diskprobe.c,v 1.8 2010/04/23 15:25:20 jsing Exp $ */ +/* $OpenBSD: diskprobe.c,v 1.9 2012/01/11 14:47:02 jsing Exp $ */ /* * Copyright (c) 1997 Tobias Weingartner + * Copyright (c) 2012 Joel Sing <jsing@openbsd.org> * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -34,6 +35,8 @@ #include <sys/queue.h> #include <sys/reboot.h> #include <sys/disklabel.h> +#include <dev/biovar.h> +#include <dev/softraidvar.h> #include <stand/boot/bootarg.h> #include <machine/biosvar.h> #include <lib/libz/zlib.h> @@ -49,6 +52,9 @@ static int disksum(int); /* List of disk devices we found/probed */ struct disklist_lh disklist; +/* List of softraid volumes. */ +struct sr_boot_volume_head sr_volumes; + /* Pointer to boot device */ struct diskinfo *bootdev_dip; @@ -160,6 +166,186 @@ hardprobe(void) } +static void +srprobe(void) +{ + struct sr_boot_volume *bv, *bv1, *bv2; + struct sr_boot_chunk *bc, *bc1, *bc2; + struct sr_meta_chunk *mc; + struct sr_metadata *md; + struct diskinfo *dip; + struct partition *pp; + int i, error, volno; + dev_t bsd_dev; + daddr_t off; + + /* Probe for softraid volumes. */ + SLIST_INIT(&sr_volumes); + + md = alloc(SR_META_SIZE * 512); + + TAILQ_FOREACH(dip, &disklist, list) { + + /* Only check hard disks, skip those with I/O errors. */ + if ((dip->bios_info.bios_number & 0x80) == 0 || + (dip->bios_info.flags & BDI_INVALID)) + continue; + + /* Make sure disklabel has been read. */ + if ((dip->bios_info.flags & (BDI_BADLABEL|BDI_GOODLABEL)) == 0) + continue; + + for (i = 0; i < MAXPARTITIONS; i++) { + + pp = &dip->disklabel.d_partitions[i]; + if (pp->p_fstype != FS_RAID || pp->p_size == 0) + continue; + + /* Read softraid metadata. */ + bzero(md, SR_META_SIZE * 512); + off = DL_GETPOFFSET(pp) + SR_META_OFFSET; + error = biosd_io(F_READ, &dip->bios_info, off, + SR_META_SIZE, md); + if (error) + continue; + + /* Is this valid softraid metadata? */ + if (md->ssdi.ssd_magic != SR_MAGIC) + continue; + + /* XXX - validate checksum. */ + + /* Locate chunk-specific metadata for this chunk. */ + mc = (struct sr_meta_chunk *)(md + 1); + mc += md->ssdi.ssd_chunk_id; + + /* XXX - extract necessary optional metadata. */ + + bc = alloc(sizeof(struct sr_boot_chunk)); + bc->sbc_diskinfo = dip; + bc->sbc_disk = dip->bios_info.bios_number; + bc->sbc_part = 'a' + i; + + bsd_dev = dip->bios_info.bsd_dev; + bc->sbc_mm = MAKEBOOTDEV(B_TYPE(bsd_dev), + B_ADAPTOR(bsd_dev), B_CONTROLLER(bsd_dev), + B_UNIT(bsd_dev), bc->sbc_part - 'a'); + + bc->sbc_chunk_id = md->ssdi.ssd_chunk_id; + bc->sbc_ondisk = md->ssd_ondisk; + bc->sbc_state = mc->scm_status; + + /* Handle key disks separately... later. */ + if (md->ssdi.ssd_level == SR_KEYDISK_LEVEL) + continue; + + SLIST_FOREACH(bv, &sr_volumes, sbv_link) { + if (bcmp(&md->ssdi.ssd_uuid, &bv->sbv_uuid, + sizeof(md->ssdi.ssd_uuid)) == 0) + break; + } + + if (bv == NULL) { + bv = alloc(sizeof(struct sr_boot_volume)); + bv->sbv_level = md->ssdi.ssd_level; + bv->sbv_volid = md->ssdi.ssd_volid; + bv->sbv_chunk_no = md->ssdi.ssd_chunk_no; + bv->sbv_flags = md->ssdi.ssd_vol_flags; + bv->sbv_size = md->ssdi.ssd_size; + bv->sbv_data_offset = md->ssd_data_offset; + bcopy(&md->ssdi.ssd_uuid, &bv->sbv_uuid, + sizeof(md->ssdi.ssd_uuid)); + SLIST_INIT(&bv->sbv_chunks); + + /* Maintain volume order. */ + bv2 = NULL; + SLIST_FOREACH(bv1, &sr_volumes, sbv_link) { + if (bv1->sbv_volid > bv->sbv_volid) + break; + bv2 = bv1; + } + if (bv2 == NULL) + SLIST_INSERT_HEAD(&sr_volumes, bv, + sbv_link); + else + SLIST_INSERT_AFTER(bv2, bv, sbv_link); + } + + /* Maintain chunk order. */ + bc2 = NULL; + SLIST_FOREACH(bc1, &bv->sbv_chunks, sbc_link) { + if (bc1->sbc_chunk_id > bc->sbc_chunk_id) + break; + bc2 = bc1; + } + if (bc2 == NULL) + SLIST_INSERT_HEAD(&bv->sbv_chunks, + bc, sbc_link); + else + SLIST_INSERT_AFTER(bc2, bc, sbc_link); + + bv->sbv_chunks_found++; + } + } + + /* + * Assemble RAID volumes. + */ + volno = 0; + SLIST_FOREACH(bv, &sr_volumes, sbv_link) { + + /* Skip if this is a hotspare "volume". */ + if (bv->sbv_level == SR_HOTSPARE_LEVEL && + bv->sbv_chunk_no == 1) + continue; + + /* Determine current ondisk version. */ + bv->sbv_ondisk = 0; + SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link) { + if (bc->sbc_ondisk > bv->sbv_ondisk) + bv->sbv_ondisk = bc->sbc_ondisk; + } + SLIST_FOREACH(bc, &bv->sbv_chunks, sbc_link) { + if (bc->sbc_ondisk != bv->sbv_ondisk) + bc->sbc_state = BIOC_SDOFFLINE; + } + + /* XXX - Check for duplicate chunks. */ + + /* + * Validate that volume has sufficient chunks for + * read-only access. + * + * XXX - check chunk states. + */ + bv->sbv_state = BIOC_SVOFFLINE; + switch (bv->sbv_level) { + case 0: + case 'C': + case 'c': + if (bv->sbv_chunk_no == bv->sbv_chunks_found) + bv->sbv_state = BIOC_SVONLINE; + break; + + case 1: + if (bv->sbv_chunk_no == bv->sbv_chunks_found) + bv->sbv_state = BIOC_SVONLINE; + else if (bv->sbv_chunks_found > 0) + bv->sbv_state = BIOC_SVDEGRADED; + break; + } + + bv->sbv_unit = volno++; + if (bv->sbv_state != BIOC_SVOFFLINE) + printf(" sr%d%s", bv->sbv_unit, + bv->sbv_flags & BIOC_SCBOOTABLE ? "*" : ""); + } + + if (md) + free(md, 0); +} + + /* Probe for all BIOS supported disks */ u_int32_t bios_cksumlen; void @@ -182,6 +368,8 @@ diskprobe(void) #endif hardprobe(); + srprobe(); + /* Checksumming of hard disks */ for (i = 0; disksum(i++) && i < MAX_CKSUMLEN; ) ; |