#include <sys/types.h>
#include <sys/cpr.h>
#include <sys/promimpl.h>
#include <sys/ddi.h>
#include "cprboot.h"
char *volname = NULL;
#define CB_MAXPROP 256
#define CB_MAXARGS 8
struct statefile sfile;
char cpr_statefile[OBP_MAXPATHLEN];
char cpr_filesystem[OBP_MAXPATHLEN];
int cpr_debug;
uint_t cb_msec;
uint_t cb_dents;
int do_halt = 0;
int verbose = 0;
char rsvp[] = "please reboot";
char prog[] = "cprboot";
char entry[] = "ENTRY";
char ent_fmt[] = "\n%s %s\n";
static char cb_argbuf[CB_MAXPROP];
static char *cb_args[CB_MAXARGS];
static int reusable;
char *specialstate;
static int
cb_intro(void)
{
static char cstr[] = "\014" "\033[1P" "\033[18;21H";
CB_VENTRY(cb_intro);
if ((uintptr_t)_end > CB_SRC_VIRT) {
prom_printf("\ndata collision:\n"
"(_end=0x%p > CB_LOW_VIRT=0x%x), recompile...\n",
(void *)_end, CB_SRC_VIRT);
return (ERR);
}
prom_printf(cstr);
prom_printf("Restoring the System. Please Wait... ");
return (0);
}
static void
get_bootargs(void)
{
char *cp, *tail, *argp, **argv;
CB_VENTRY(get_bootargs);
(void) prom_strcpy(cb_argbuf, prom_bootargs());
tail = cb_argbuf + prom_strlen(cb_argbuf);
argv = cb_args;
for (cp = argp = cb_argbuf; cp <= tail; cp++) {
if (prom_strchr(" \t\n\r", *cp) == NULL)
continue;
*cp = '\0';
if (cp - argp) {
*argv++ = argp;
if ((argv - cb_args) == (CB_MAXARGS - 1))
break;
}
argp = cp + 1;
}
*argv = NULLP;
if (verbose) {
for (argv = cb_args; *argv; argv++) {
prom_printf(" %d: \"%s\"\n",
(int)(argv - cb_args), *argv);
}
}
}
static void
usage(char *expect, char *got)
{
if (got == NULL)
got = "(NULL)";
prom_printf("\nbad OBP boot args: expect %s, got %s\n"
"Usage: boot -F %s [-R] [-S <diskpath>]\n%s\n\n",
expect, got, prog, rsvp);
prom_exit_to_mon();
}
static void
check_bootargs(void)
{
char **argv, *str, *cp;
argv = cb_args;
str = "-F";
if (*argv == NULL || prom_strcmp(*argv, str))
usage(str, *argv);
argv++;
if (*argv == NULL || prom_strncmp(*argv, prog, sizeof (prog) - 1))
usage(prog, *argv);
str = "-[SR]";
for (argv++; *argv; argv++) {
cp = *argv;
if (*cp != '-')
usage(str, *argv);
switch (*++cp) {
case 'R':
case 'r':
reusable = 1;
break;
case 'S':
case 's':
if (*++argv)
specialstate = *argv;
else
usage("statefile-path", *argv);
break;
case 'h':
do_halt = 1;
break;
case 'v':
verbose = 1;
break;
default:
usage(str, *argv);
break;
}
}
}
static int
cb_startup(void)
{
CB_VENTRY(cb_startup);
if (!reusable) {
if (cpr_reset_properties() == -1) {
prom_printf("\n%s: cannot read saved "
"nvram info, %s\n", prog, rsvp);
return (ERR);
}
}
if (specialstate)
(void) prom_strcpy(cpr_statefile, specialstate);
else if (cpr_locate_statefile(cpr_statefile, cpr_filesystem) == -1) {
prom_printf("\n%s: cannot find cpr statefile, %s\n",
prog, rsvp);
return (ERR);
}
return (0);
}
static int
cb_open_sf(void)
{
CB_VENTRY(cb_open_sf);
sfile.fd = cpr_statefile_open(cpr_statefile, cpr_filesystem);
if (sfile.fd == -1) {
prom_printf("\n%s: can't open %s", prog, cpr_statefile);
if (specialstate)
prom_printf(" on %s", cpr_filesystem);
prom_printf("\n%s\n", rsvp);
return (ERR);
}
if (volname)
(void) cpr_fs_seek(sfile.fd, CPR_SPEC_OFFSET);
else if (specialstate)
(void) prom_seek(sfile.fd, CPR_SPEC_OFFSET);
return (0);
}
static int
cb_close_sf(void)
{
CB_VENTRY(cb_close_sf);
(void) cpr_statefile_close(sfile.fd);
return (0);
}
static int
cb_read_statefile(void)
{
size_t alsize, len, resid;
physaddr_t phys, dst_phys;
char *str, *dst_virt;
int err, cnt, mmask;
uint_t dtlb_index;
ssize_t nread;
cdd_t cdump;
str = "cb_read_statefile";
CB_VPRINTF((ent_fmt, str, entry));
if (cpr_read_cdump(sfile.fd, &cdump, CPR_MACHTYPE_4U) == -1)
return (ERR);
if (cpr_debug)
prom_printf("\n");
cb_nbitmaps = cdump.cdd_bitmaprec;
cpr_test_mode = cdump.cdd_test_mode;
sfile.kpages = cdump.cdd_dumppgsize;
CPR_DEBUG(CPR_DEBUG4, "%s: total kpages %d\n", prog, sfile.kpages);
sfile.size = PAGE_ROUNDUP(cdump.cdd_filesize);
alsize = (cdump.cdd_filesize + MMU_PAGEOFFSET512K) &
MMU_PAGEMASK512K;
phys = 0;
err = cb_alloc(alsize, MMU_PAGESIZE512K, &sfile.buf, &phys);
CB_VPRINTF(("%s:\n alloc size 0x%lx, buf size 0x%lx\n"
" virt 0x%p, phys 0x%llx\n",
str, alsize, sfile.size, (void *)sfile.buf, phys));
if (err) {
prom_printf("%s: cant alloc statefile buf, size 0x%lx\n%s\n",
str, sfile.size, rsvp);
return (ERR);
}
sfile.low_ppn = ADDR_TO_PN(phys);
sfile.high_ppn = sfile.low_ppn + mmu_btop(sfile.size) - 1;
dst_phys = phys;
mmask = (MMU_PAGESIZE512K / PROM_MAX_READ) - 1;
cnt = 0;
dtlb_index = cb_dents - 1;
if (volname)
(void) cpr_fs_seek(sfile.fd, CPR_SPEC_OFFSET);
else if (specialstate)
(void) prom_seek(sfile.fd, CPR_SPEC_OFFSET);
else
(void) cpr_fs_seek(sfile.fd, 0);
CPR_DEBUG(CPR_DEBUG1, "%s: reading statefile... ", prog);
for (resid = cdump.cdd_filesize; resid; resid -= len) {
if ((cnt & 0x7) == 0)
cb_spin();
if ((cnt & mmask) == 0) {
dst_virt = sfile.buf;
cb_mapin(dst_virt, ADDR_TO_PN(dst_phys),
TTE512K, TTE_HWWR_INT, dtlb_index);
}
cnt++;
len = min(PROM_MAX_READ, resid);
nread = cpr_read(sfile.fd, dst_virt, len);
if (nread != (ssize_t)len) {
prom_printf("\n%s: prom read error, "
"expect %ld, got %ld\n", str, len, nread);
return (ERR);
}
dst_virt += len;
dst_phys += len;
}
CPR_DEBUG(CPR_DEBUG1, " \b\n");
if (alsize > sfile.size) {
len = alsize - sfile.size;
prom_free_phys(len, phys + sfile.size);
CB_VPRINTF(("%s: freed %ld phys pages (0x%llx - 0x%llx)\n",
str, mmu_btop(len),
(unsigned long long)(phys + sfile.size),
(unsigned long long)(phys + alsize)));
}
sfile.buf_offset = 0;
SF_ADV(sizeof (cdump));
cb_mapin(sfile.buf, sfile.low_ppn,
TTE512K, TTE_HWWR_INT, dtlb_index);
return (0);
}
static int (*first_worklist[])(void) = {
cb_intro,
cb_mountroot,
cb_startup,
cb_get_props,
cb_usb_setup,
cb_unmountroot,
cb_open_sf,
cb_read_statefile,
cb_close_sf,
cb_check_machdep,
cb_interpret,
cb_get_physavail,
cb_set_bitmap,
cb_get_newstack,
NULL
};
static int (*second_worklist[])(void) = {
cb_relocate,
cb_tracking_setup,
cb_restore_kpages,
cb_terminator,
cb_ksetup,
cb_mpsetup,
NULL
};
static void
cb_drive(int (**worklist)(void))
{
int i;
for (i = 0; worklist[i] != NULL; i++) {
if (worklist[i]())
cb_exit_to_mon();
}
}
static void
check_halt(char *str)
{
if (do_halt) {
prom_printf("\n%s halted by -h flag\n==> before %s\n\n",
prog, str);
cb_enter_mon();
}
}
int
main(void *cookie, int first)
{
if (first) {
prom_init(prog, cookie);
cb_msec = prom_gettime();
get_bootargs();
check_bootargs();
check_halt("first_worklist");
cb_drive(first_worklist);
return (0);
} else {
cb_drive(second_worklist);
if (verbose || CPR_DBG(1)) {
prom_printf("%s: milliseconds %d\n",
prog, prom_gettime() - cb_msec);
prom_printf("%s: resume pc 0x%lx\n",
prog, mdinfo.func);
prom_printf("%s: exit_to_kernel(0x%p, 0x%p)\n\n",
prog, cookie, (void *)&mdinfo);
}
check_halt("exit_to_kernel");
exit_to_kernel(cookie, &mdinfo);
return (ERR);
}
}