root/usr/src/psm/stand/cpr/sparcv9/sun4u/cprboot.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*
 * cprboot - prom client that restores kadb/kernel pages
 *
 * simple cprboot overview:
 *      reset boot-file/boot-device to their original values
 *      open cpr statefile, usually "/.CPR"
 *      read in statefile
 *      close statefile
 *      restore kernel pages
 *      jump back into kernel text
 *
 *
 * cprboot supports a restartable statefile for FAA/STARS,
 * Federal Aviation Administration
 * Standard Terminal Automation Replacement System
 */

#include <sys/types.h>
#include <sys/cpr.h>
#include <sys/promimpl.h>
#include <sys/ddi.h>
#include "cprboot.h"

char *volname = NULL;

/*
 * local defs
 */
#define CB_MAXPROP      256
#define CB_MAXARGS      8


/*
 * globals
 */
struct statefile sfile;

char cpr_statefile[OBP_MAXPATHLEN];
char cpr_filesystem[OBP_MAXPATHLEN];

int cpr_debug;                          /* cpr debug, set with uadmin 3 10x */
uint_t cb_msec;                         /* cprboot start runtime */
uint_t cb_dents;                        /* number of dtlb entries */

int do_halt = 0;                        /* halt (enter mon) after load */
int verbose = 0;                        /* verbose, traces cprboot ops */

char rsvp[] = "please reboot";
char prog[] = "cprboot";
char entry[] = "ENTRY";
char ent_fmt[] = "\n%s %s\n";


/*
 * file scope
 */
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);

        /*
         * build/debug aid; this condition should not occur
         */
        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);
        }

        /* clear console */
        prom_printf(cstr);

        prom_printf("Restoring the System. Please Wait... ");
        return (0);
}


/*
 * read bootargs and convert to arg vector
 *
 * sets globals:
 *      cb_argbuf
 *      cb_args
 */
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);

        /*
         * scan to the trailing NULL so the last arg
         * will be found without any special-case code
         */
        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();
}


/*
 * bootargs should start with "-F cprboot"
 *
 * may set globals:
 *      specialstate
 *      reusable
 *      do_halt
 *      verbose
 */
static void
check_bootargs(void)
{
        char **argv, *str, *cp;

        argv = cb_args;

        /* expect "-F" */
        str = "-F";
        if (*argv == NULL || prom_strcmp(*argv, str))
                usage(str, *argv);
        argv++;

        /* expect "cprboot*" */
        if (*argv == NULL || prom_strncmp(*argv, prog, sizeof (prog) - 1))
                usage(prog, *argv);

        /*
         * optional args
         */
        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;
                }
        }
}


/*
 * reset prom props and get statefile info
 *
 * sets globals:
 *      cpr_filesystem
 *      cpr_statefile
 */
static int
cb_startup(void)
{
        CB_VENTRY(cb_startup);

        if (!reusable) {
                /*
                 * Restore the original values of the nvram properties modified
                 * during suspend.  Note: if we can't get this info from the
                 * defaults file, the state file may be obsolete or bad, so we
                 * abort.  However, failure to restore one or more properties
                 * is NOT fatal (better to continue the resume).
                 */
                if (cpr_reset_properties() == -1) {
                        prom_printf("\n%s: cannot read saved "
                            "nvram info, %s\n", prog, rsvp);
                        return (ERR);
                }
        }

        /*
         * simple copy if using specialstate,
         * otherwise read in fs and statefile from a config file
         */
        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);
        }

        /*
         * for block devices, seek past the disk label and bootblock
         */
        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);

        /*
         * close the device so the prom will free up 20+ pages
         */
        (void) cpr_statefile_close(sfile.fd);
        return (0);
}


/*
 * to restore kernel pages, we have to open a prom device to read-in
 * the statefile contents; a prom "open" request triggers the driver
 * and various packages to allocate 20+ pages; unfortunately, some or
 * all of those pages always clash with kernel pages, and we cant write
 * to them without corrupting the prom.
 *
 * to solve that problem, the only real solution is to close the device
 * to free up those pages; this means we need to open, read-in the entire
 * statefile, and close; and to store the statefile, we need to allocate
 * plenty of space, usually around 2 to 60 MB.
 *
 * the simplest alloc means is prom_alloc(), which will "claim" both
 * virt and phys pages, and creates mappings with a "map" request;
 * "map" also causes the prom to alloc pages, and again these clash
 * with kernel pages...
 *
 * to solve the "map" problem, we just reserve virt and phys pages and
 * manage the translations by creating our own tlb entries instead of
 * relying on the prom.
 *
 * sets globals:
 *      cpr_test_mode
 *      sfile.kpages
 *      sfile.size
 *      sfile.buf
 *      sfile.low_ppn
 *      sfile.high_ppn
 */
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));

        /*
         * read-in and check cpr dump header
         */
        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);

        /*
         * alloc virt and phys space with 512K alignment;
         * alloc size should be (n * tte size);
         */
        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);
        }

        /*
         * record low and high phys page numbers for sfile.buf
         */
        sfile.low_ppn = ADDR_TO_PN(phys);
        sfile.high_ppn = sfile.low_ppn + mmu_btop(sfile.size) - 1;

        /*
         * setup destination virt and phys addrs for reads;
         * mapin-mask tells when to create a new tlb entry for the
         * next set of reads;  NB: the read and tlb method needs
         * ((big-pagesize % read-size) == 0)
         */
        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) {
                /*
                 * do a full spin (4 spin chars)
                 * for every MB read (8 reads = 256K)
                 */
                if ((cnt & 0x7) == 0)
                        cb_spin();

                /*
                 * map-in statefile buf pages in 512K blocks;
                 * see MMU_PAGESIZE512K above
                 */
                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");

        /*
         * free up any unused phys pages trailing the statefile buffer;
         * these pages will later appear on the physavail list
         */
        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)));
        }

        /*
         * start the statefile buffer offset at the base of
         * the statefile buffer and skip past the dump header
         */
        sfile.buf_offset = 0;
        SF_ADV(sizeof (cdump));

        /*
         * finish with the first block mapped-in to provide easy virt access
         * to machdep structs and the bitmap; for 2.8, the combined size of
         * (cdd_t + cmd_t + csu_md_t + prom_words + cbd_t) is about 1K,
         * leaving room for a bitmap representing nearly 32GB
         */
        cb_mapin(sfile.buf, sfile.low_ppn,
            TTE512K, TTE_HWWR_INT, dtlb_index);

        return (0);
}


/*
 * cprboot first stage worklist
 */
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
};

/*
 * cprboot second stage worklist
 */
static int (*second_worklist[])(void) = {
        cb_relocate,
        cb_tracking_setup,
        cb_restore_kpages,
        cb_terminator,
        cb_ksetup,
        cb_mpsetup,
        NULL
};


/*
 * simple loop driving major cprboot operations;
 * exits to prom if any error is returned
 */
static void
cb_drive(int (**worklist)(void))
{
        int i;

        for (i = 0; worklist[i] != NULL; i++) {
                if (worklist[i]())
                        cb_exit_to_mon();
        }
}


/*
 * debugging support: drop to prom if do_halt is set
 */
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();
        }
}


/*
 * main is called twice from "cb_srt0.s", args are:
 *      cookie    ieee1275 cif handle
 *      first     (true): first stage, (false): second stage
 *
 * first stage summary:
 *      various setup
 *      allocate a big statefile buffer
 *      read in the statefile
 *      setup the bitmap
 *      create a new stack
 *
 * return to "cb_srt0.s", switch to new stack
 *
 * second stage summary:
 *      relocate cprboot phys pages
 *      setup tracking for statefile buffer pages
 *      restore kernel pages
 *      various cleanup
 *      install tlb entries for the nucleus and cpr module
 *      restore registers and jump into cpr module
 */
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);
        }
}