#include <sys/cdefs.h>
#include <stand.h>
#include <sys/param.h>
#include <sys/errno.h>
#include <sys/diskmbr.h>
#include <sys/vtoc.h>
#include <sys/disk.h>
#include <sys/reboot.h>
#include <sys/queue.h>
#include <sys/multiboot.h>
#include <sys/zfs_bootenv.h>
#include <machine/bootinfo.h>
#include <machine/elf.h>
#include <machine/pc/bios.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <a.out.h>
#include "bootstrap.h"
#include "libi386.h"
#include <btxv86.h>
#include "lib.h"
#include "rbx.h"
#include "cons.h"
#include "bootargs.h"
#include "disk.h"
#include "part.h"
#include "paths.h"
#include "libzfs.h"
#define ARGS 0x900
#define NOPT 15
#define NDEV 3
#define BIOS_NUMDRIVES 0x475
#define DRV_HARD 0x80
#define DRV_MASK 0x7f
#define TYPE_AD 0
#define TYPE_DA 1
#define TYPE_MAXHARD TYPE_DA
#define TYPE_FD 2
extern uint32_t _end;
extern const struct multiboot_header mb_header;
extern uint64_t start_sector;
static const char optstr[NOPT] = "DhaCcdgmnpqrstv";
static const unsigned char flags[NOPT] = {
RBX_DUAL,
RBX_SERIAL,
RBX_ASKNAME,
RBX_CDROM,
RBX_CONFIG,
RBX_KDB,
RBX_GDB,
RBX_MUTE,
RBX_NOINTR,
RBX_PAUSE,
RBX_QUIET,
RBX_DFLTROOT,
RBX_SINGLE,
RBX_TEXT_MODE,
RBX_VERBOSE
};
uint32_t opts;
#define PATH_ZFSLOADER "/boot/zfsloader"
static const struct string {
const char *p;
size_t len;
} loadpath[] = {
{ PATH_LOADER, sizeof (PATH_LOADER) },
{ PATH_ZFSLOADER, sizeof (PATH_ZFSLOADER) }
};
static const unsigned char dev_maj[NDEV] = {30, 4, 2};
extern int sio_port;
static struct i386_devdesc *bdev;
static char cmd[512];
static char cmddup[512];
static char kname[1024];
static int comspeed = SIOSPD;
static struct bootinfo bootinfo;
static uint32_t bootdev;
static struct zfs_boot_args zfsargs;
extern vm_offset_t high_heap_base;
extern uint32_t bios_basemem, bios_extmem, high_heap_size;
static char *heap_top;
static char *heap_bottom;
static void i386_zfs_probe(void);
static void load(void);
static int parse_cmd(void);
struct arch_switch archsw;
static char boot_devname[2 * ZFS_MAXNAMELEN + 8];
struct devsw *devsw[] = {
&bioshd,
&zfs_dev,
NULL
};
struct fs_ops *file_system[] = {
&zfs_fsops,
&ufs_fsops,
&dosfs_fsops,
NULL
};
caddr_t
ptov(uintptr_t x)
{
return (PTOV(x));
}
int
main(void)
{
unsigned i;
int fd;
bool auto_boot;
bool nextboot = false;
struct disk_devdesc devdesc;
char *ptr;
bios_getmem();
if (high_heap_size > 0) {
heap_top = PTOV(high_heap_base + high_heap_size);
heap_bottom = PTOV(high_heap_base);
} else {
heap_bottom = (char *)
(roundup2(__base + (int32_t)&_end, 0x10000) - __base);
heap_top = (char *)PTOV(bios_basemem);
}
setheap(heap_bottom, heap_top);
biosacpi_detect();
ptr = getenv("console");
if (ptr != NULL && strcmp(ptr, "text") != 0) {
char mode[10];
ioctrl = IO_SERIAL;
snprintf(mode, sizeof (mode), "%s-mode", ptr);
switch (ptr[3]) {
case 'a':
sio_port = 0x3F8;
break;
case 'b':
sio_port = 0x2F8;
break;
case 'c':
sio_port = 0x3E8;
break;
case 'd':
sio_port = 0x2E8;
break;
}
ptr = getenv(mode);
if (ptr != NULL) {
comspeed = strtoul(ptr, NULL, 0);
if (sio_init(115200 / comspeed) != 0)
ioctrl |= IO_KEYBOARD;
}
}
bcache_init(32768, 512);
archsw.arch_autoload = NULL;
archsw.arch_getdev = i386_getdev;
archsw.arch_copyin = NULL;
archsw.arch_copyout = NULL;
archsw.arch_readin = NULL;
archsw.arch_isainb = NULL;
archsw.arch_isaoutb = NULL;
archsw.arch_zfs_probe = i386_zfs_probe;
bootinfo.bi_version = BOOTINFO_VERSION;
bootinfo.bi_size = sizeof (bootinfo);
bootinfo.bi_basemem = bios_basemem / 1024;
bootinfo.bi_extmem = bios_extmem / 1024;
bootinfo.bi_memsizes_valid++;
bootinfo.bi_bios_dev = *(uint8_t *)PTOV(ARGS);
if (bootinfo.bi_bios_dev < 0x80)
snprintf(boot_devname, sizeof (boot_devname), "disk%d:",
bootinfo.bi_bios_dev);
else
snprintf(boot_devname, sizeof (boot_devname), "disk%d:",
bootinfo.bi_bios_dev - 0x80);
for (i = 0; devsw[i] != NULL; i++)
if (devsw[i]->dv_init != NULL)
(devsw[i]->dv_init)();
disk_parsedev(&devdesc, boot_devname + 4, NULL);
bootdev = MAKEBOOTDEV(dev_maj[DEVT_DISK], devdesc.d_slice + 1,
devdesc.dd.d_unit,
devdesc.d_partition >= 0 ? devdesc.d_partition : 0xff);
if (bdev != NULL && bdev->dd.d_dev->dv_type == DEVT_ZFS) {
strncpy(boot_devname, zfs_fmtdev(bdev), sizeof (boot_devname));
if (zfs_get_bootonce(bdev, OS_BOOTONCE, cmd,
sizeof (cmd)) == 0) {
nvlist_t *benv;
nextboot = true;
memcpy(cmddup, cmd, sizeof (cmd));
if (parse_cmd()) {
printf("failed to parse bootonce command\n");
exit(0);
}
if (!OPT_CHECK(RBX_QUIET))
printf("zfs bootonce: %s\n", cmddup);
if (zfs_get_bootenv(bdev, &benv) == 0) {
nvlist_add_string(benv, OS_BOOTONCE_USED,
cmddup);
zfs_set_bootenv(bdev, benv);
}
*cmd = 0;
}
}
free(bdev);
i386_getdev((void **)&bdev, boot_devname, NULL);
env_setenv("currdev", EV_VOLATILE, boot_devname, i386_setcurrdev,
env_nounset);
setenv("screen-#rows", "24", 1);
auto_boot = true;
fd = open(PATH_CONFIG, O_RDONLY);
if (fd == -1)
fd = open(PATH_DOTCONFIG, O_RDONLY);
if (fd != -1) {
ssize_t cmdlen;
if ((cmdlen = read(fd, cmd, sizeof (cmd))) > 0)
cmd[cmdlen] = '\0';
else
*cmd = '\0';
close(fd);
}
if (*cmd) {
memcpy(cmddup, cmd, sizeof (cmd));
if (parse_cmd())
auto_boot = false;
if (!OPT_CHECK(RBX_QUIET))
printf("%s: %s\n", PATH_CONFIG, cmddup);
*cmd = 0;
}
if (nextboot && !auto_boot)
exit(0);
if (auto_boot && !*kname) {
auto_boot = false;
for (i = 0; i < nitems(loadpath); i++) {
memcpy(kname, loadpath[i].p, loadpath[i].len);
if (keyhit(3))
break;
load();
}
}
memcpy(kname, loadpath[0].p, loadpath[0].len);
for (;;) {
if (!auto_boot || !OPT_CHECK(RBX_QUIET)) {
printf("\nillumos/x86 boot\n");
printf("Default: %s%s\nboot: ", boot_devname, kname);
}
if (ioctrl & IO_SERIAL)
sio_flush();
if (!auto_boot || keyhit(5))
getstr(cmd, sizeof (cmd));
else if (!auto_boot || !OPT_CHECK(RBX_QUIET))
putchar('\n');
auto_boot = false;
if (parse_cmd()) {
putchar('\a');
} else {
putchar('\n');
load();
}
}
}
void
exit(int x)
{
__exit(x);
}
static void
load(void)
{
union {
struct exec ex;
Elf32_Ehdr eh;
} hdr;
static Elf32_Phdr ep[2];
static Elf32_Shdr es[2];
caddr_t p;
uint32_t addr, x;
int fd, fmt, i, j;
if ((fd = open(kname, O_RDONLY)) == -1) {
printf("\nCan't find %s\n", kname);
return;
}
if (read(fd, &hdr, sizeof (hdr)) != sizeof (hdr)) {
close(fd);
return;
}
if (N_GETMAGIC(hdr.ex) == ZMAGIC) {
fmt = 0;
} else if (IS_ELF(hdr.eh)) {
fmt = 1;
} else {
printf("Invalid %s\n", "format");
close(fd);
return;
}
if (fmt == 0) {
addr = hdr.ex.a_entry & 0xffffff;
p = PTOV(addr);
lseek(fd, PAGE_SIZE, SEEK_SET);
if (read(fd, p, hdr.ex.a_text) != hdr.ex.a_text) {
close(fd);
return;
}
p += roundup2(hdr.ex.a_text, PAGE_SIZE);
if (read(fd, p, hdr.ex.a_data) != hdr.ex.a_data) {
close(fd);
return;
}
p += hdr.ex.a_data + roundup2(hdr.ex.a_bss, PAGE_SIZE);
bootinfo.bi_symtab = VTOP(p);
memcpy(p, &hdr.ex.a_syms, sizeof (hdr.ex.a_syms));
p += sizeof (hdr.ex.a_syms);
if (hdr.ex.a_syms) {
if (read(fd, p, hdr.ex.a_syms) != hdr.ex.a_syms) {
close(fd);
return;
}
p += hdr.ex.a_syms;
if (read(fd, p, sizeof (int)) != sizeof (int)) {
close(fd);
return;
}
x = *(uint32_t *)p;
p += sizeof (int);
x -= sizeof (int);
if (read(fd, p, x) != x) {
close(fd);
return;
}
p += x;
}
} else {
lseek(fd, hdr.eh.e_phoff, SEEK_SET);
for (j = i = 0; i < hdr.eh.e_phnum && j < 2; i++) {
if (read(fd, ep + j, sizeof (ep[0])) !=
sizeof (ep[0])) {
close(fd);
return;
}
if (ep[j].p_type == PT_LOAD)
j++;
}
for (i = 0; i < 2; i++) {
p = PTOV(ep[i].p_paddr & 0xffffff);
lseek(fd, ep[i].p_offset, SEEK_SET);
if (read(fd, p, ep[i].p_filesz) != ep[i].p_filesz) {
close(fd);
return;
}
}
p += roundup2(ep[1].p_memsz, PAGE_SIZE);
bootinfo.bi_symtab = VTOP(p);
if (hdr.eh.e_shnum == hdr.eh.e_shstrndx + 3) {
lseek(fd, hdr.eh.e_shoff +
sizeof (es[0]) * (hdr.eh.e_shstrndx + 1),
SEEK_SET);
if (read(fd, &es, sizeof (es)) != sizeof (es)) {
close(fd);
return;
}
for (i = 0; i < 2; i++) {
memcpy(p, &es[i].sh_size,
sizeof (es[i].sh_size));
p += sizeof (es[i].sh_size);
lseek(fd, es[i].sh_offset, SEEK_SET);
if (read(fd, p, es[i].sh_size) !=
es[i].sh_size) {
close(fd);
return;
}
p += es[i].sh_size;
}
}
addr = hdr.eh.e_entry & 0xffffff;
}
close(fd);
bootinfo.bi_esymtab = VTOP(p);
bootinfo.bi_kernelname = VTOP(kname);
if (bdev->dd.d_dev->dv_type == DEVT_ZFS) {
zfsargs.size = sizeof (zfsargs);
zfsargs.pool = bdev->d_kind.zfs.pool_guid;
zfsargs.root = bdev->d_kind.zfs.root_guid;
__exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK),
bootdev,
KARGS_FLAGS_ZFS | KARGS_FLAGS_EXTARG,
(uint32_t)bdev->d_kind.zfs.pool_guid,
(uint32_t)(bdev->d_kind.zfs.pool_guid >> 32),
VTOP(&bootinfo),
zfsargs);
} else {
__exec((caddr_t)addr, RB_BOOTINFO | (opts & RBX_MASK),
bootdev, 0, 0, 0, VTOP(&bootinfo));
}
}
static int
mount_root(char *arg)
{
char *root;
struct i386_devdesc *ddesc;
uint8_t part;
if (asprintf(&root, "%s:", arg) < 0)
return (1);
if (i386_getdev((void **)&ddesc, root, NULL)) {
free(root);
return (1);
}
free(bdev);
bdev = ddesc;
if (bdev->dd.d_dev->dv_type == DEVT_DISK) {
if (bdev->d_kind.biosdisk.partition == -1)
part = 0xff;
else
part = bdev->d_kind.biosdisk.partition;
bootdev = MAKEBOOTDEV(dev_maj[bdev->dd.d_dev->dv_type],
bdev->d_kind.biosdisk.slice + 1,
bdev->dd.d_unit, part);
bootinfo.bi_bios_dev = bd_unit2bios(bdev);
}
strncpy(boot_devname, root, sizeof (boot_devname));
setenv("currdev", root, 1);
free(root);
return (0);
}
static void
fs_list(char *arg)
{
int fd;
struct dirent *d;
char line[80];
fd = open(arg, O_RDONLY);
if (fd < 0)
return;
pager_open();
while ((d = readdirfd(fd)) != NULL) {
sprintf(line, "%s\n", d->d_name);
if (pager_output(line))
break;
}
pager_close();
close(fd);
}
static int
parse_cmd(void)
{
char *arg = cmd;
char *ep, *p, *q;
const char *cp;
char line[80];
int c, i;
while ((c = *arg++)) {
if (isspace(c))
continue;
for (p = arg; *p != '\0' && !isspace(*p); p++)
;
ep = p;
if (*p != '\0')
*p++ = '\0';
if (c == '-') {
while ((c = *arg++)) {
if (isspace(c))
break;
if (c == 'P') {
if (*(uint8_t *)PTOV(0x496) & 0x10) {
cp = "yes";
} else {
opts |= OPT_SET(RBX_DUAL);
opts |= OPT_SET(RBX_SERIAL);
cp = "no";
}
printf("Keyboard: %s\n", cp);
continue;
} else if (c == 'S') {
char *end;
errno = 0;
i = strtol(arg, &end, 10);
if (errno == 0 &&
*arg != '\0' &&
*end == '\0' &&
i > 0 &&
i <= 115200) {
comspeed = i;
break;
} else {
printf("warning: bad value for "
"speed: %s\n", arg);
}
arg = end;
}
for (i = 0; c != optstr[i]; i++)
if (i == NOPT - 1)
return (-1);
opts ^= OPT_SET(flags[i]);
}
if (OPT_CHECK(RBX_DUAL))
ioctrl = IO_SERIAL | IO_KEYBOARD;
else if (OPT_CHECK(RBX_SERIAL))
ioctrl = IO_SERIAL;
else
ioctrl = IO_KEYBOARD;
if (ioctrl & IO_SERIAL) {
if (sio_init(115200 / comspeed) != 0)
ioctrl &= ~IO_SERIAL;
}
} else if (c == '?') {
printf("\n");
fs_list(arg);
zfs_list(arg);
return (-1);
} else {
arg--;
if (strcmp(arg, "status") == 0) {
pager_open();
for (i = 0; devsw[i] != NULL; i++) {
if (devsw[i]->dv_print != NULL) {
if (devsw[i]->dv_print(1))
break;
} else {
sprintf(line,
"%s: (unknown)\n",
devsw[i]->dv_name);
if (pager_output(line))
break;
}
}
pager_close();
return (-1);
}
if (strncmp(arg, "zfs:", 4) == 0)
q = strrchr(arg + 4, ':');
else
q = strrchr(arg, ':');
if (q != NULL) {
*q++ = '\0';
if (mount_root(arg) != 0)
return (-1);
arg = q;
}
if ((i = ep - arg)) {
if ((size_t)i >= sizeof (kname))
return (-1);
memcpy(kname, arg, i + 1);
}
}
arg = p;
}
return (0);
}
struct probe_args {
int fd;
char *devname;
uint_t secsz;
uint64_t offset;
};
static int
parttblread(void *arg, void *buf, size_t blocks, uint64_t offset)
{
struct probe_args *ppa = arg;
size_t size = ppa->secsz * blocks;
lseek(ppa->fd, offset * ppa->secsz, SEEK_SET);
if (read(ppa->fd, buf, size) == size)
return (0);
return (EIO);
}
static int
probe_partition(void *arg, const char *partname,
const struct ptable_entry *part)
{
struct probe_args pa, *ppa = arg;
struct ptable *table;
uint64_t *pool_guid_ptr = NULL;
uint64_t pool_guid = 0;
char devname[32];
int len, ret = 0;
len = strlen(ppa->devname);
if (len > sizeof (devname))
len = sizeof (devname);
strncpy(devname, ppa->devname, len - 1);
devname[len - 1] = '\0';
snprintf(devname, sizeof (devname), "%s%s:", devname, partname);
switch (part->type) {
case PART_RESERVED:
case PART_VTOC_BOOT:
case PART_VTOC_SWAP:
return (ret);
default:
break;
}
if (part->type == PART_SOLARIS2) {
pa.offset = part->start;
pa.fd = open(devname, O_RDONLY);
if (pa.fd == -1)
return (ret);
pa.devname = devname;
pa.secsz = ppa->secsz;
table = ptable_open(&pa, part->end - part->start + 1,
ppa->secsz, parttblread);
if (table != NULL) {
enum ptable_type pt = ptable_gettype(table);
if (pt == PTABLE_VTOC8 || pt == PTABLE_VTOC) {
ret = ptable_iterate(table, &pa,
probe_partition);
ptable_close(table);
close(pa.fd);
return (ret);
}
ptable_close(table);
}
close(pa.fd);
}
if (ppa->offset + part->start == start_sector) {
pool_guid_ptr = &pool_guid;
strncpy(boot_devname, devname, sizeof (boot_devname));
}
ret = zfs_probe_dev(devname, pool_guid_ptr);
if (pool_guid != 0 && bdev == NULL) {
bdev = malloc(sizeof (struct i386_devdesc));
bzero(bdev, sizeof (struct i386_devdesc));
bdev->dd.d_dev = &zfs_dev;
bdev->d_kind.zfs.pool_guid = pool_guid;
}
return (0);
}
static int
probe_disk(char *devname)
{
struct ptable *table;
struct probe_args pa;
uint64_t mediasz;
int ret;
pa.offset = 0;
pa.devname = devname;
pa.fd = open(devname, O_RDONLY);
if (pa.fd == -1) {
return (ENXIO);
}
ret = ioctl(pa.fd, DIOCGMEDIASIZE, &mediasz);
if (ret == 0)
ret = ioctl(pa.fd, DIOCGSECTORSIZE, &pa.secsz);
if (ret == 0) {
table = ptable_open(&pa, mediasz / pa.secsz, pa.secsz,
parttblread);
if (table != NULL) {
ret = ptable_iterate(table, &pa, probe_partition);
ptable_close(table);
}
}
close(pa.fd);
return (ret);
}
static void
i386_zfs_probe(void)
{
char devname[32];
int boot_unit;
struct i386_devdesc dev;
dev.dd.d_dev = &bioshd;
boot_unit = bd_bios2unit(bootinfo.bi_bios_dev);
for (dev.dd.d_unit = 0; bd_unit2bios(&dev) >= 0; dev.dd.d_unit++) {
snprintf(devname, sizeof (devname), "%s%d:", bioshd.dv_name,
dev.dd.d_unit);
if (dev.dd.d_unit != boot_unit)
zfs_probe_dev(devname, NULL);
else
probe_disk(devname);
}
}