/* 
 * This file is subject to the terms and conditions of the GNU General Public
 * License.  See the file "COPYING" in the main directory of this archive
 * for more details.
 *
 * Copyright (C) Hewlett-Packard (Paul Bame) paul_bame@hp.com
 */
#include <stddef.h>
#include "bootloader.h"
#include <asm/pdc.h>
#include <asm/byteorder.h>
#include "load.h"
#include <stdint.h>

#undef PAGE0
#define PAGE0   ((struct zeropage *)0x00000000)

char commandline[CMDLINELEN];
extern char _end, _start, _edata;

int Debug = 0;
int interactive = 0;
int disk_2gb_limit;

/*
 * Beware: pdc_model_sysmodel() may return a machine name which has trailing
 * spaces.
 */
static char sys_model_name[81];

static const char *get_machine_model()
{
    if ((sys_model_name[0] == 0) &&
	pdc_model_sysmodel(sys_model_name) != PDC_OK)
	strcpy(sys_model_name, "unknown");

    return sys_model_name;
}

void flush_data_cache(char *start, size_t length)
{
    char *end = start + length;

    do
    {
	asm volatile("fdc 0(%0)" : : "r" (start));
	asm volatile("fic 0(%%sr0,%0)" : : "r" (start));
	start += 16;
    } while (start < end);
    asm volatile("fdc 0(%0)" : : "r" (end));

    asm ("sync");
}

static int
parse_number(char *s, char **next)
{
    int n = 0;

    while (is_digit(*s))
    {
	n *= 10;
	n += *s++ - '0';
    }

    *next = s;

    return n;
}

static char *
parse_pfname(char *s, int *partition, char *name)
{
    char *p1, *p2 = NULL;

    if (s != NULL || *s != '\0')
    {
	/* parse the kernel partition number */
	*partition = parse_number(s, &p1);

	/* now the kernel name */
	p2 = strpbrk(p1, " \t");
	if (p2 != NULL)
	{
	    *p2 = '\0';
	    strcpy(name, p1);
	    *p2 = ' ';
	}
	else
	{
	    strcpy(name, p1);
	    p2 = p1 + strlen(p1);
	}
    }

    return p2;
}

/*
 * ext2_open_dirstrip()
 * Get filehandle for given filename.
 * If user gave 2/boot/vmlinux, don't fail and try if he maybe actually meant 2/vmlinux.
 */
static int
ext2_open_dirstrip(char *filename)
{
    while (filename && *filename) {
	char *last;
	int fh = ext2_open(filename);
	if (fh >= 0)
		return fh;

	last = filename;
	filename = strpbrk(&filename[1], "/");
	if (filename)
		printf("Warning: %s not found. Try %s instead...\n", last, filename);
    }

    return -1;
}

static int
chk_strcat(char *out, char *in, int len, int *ok)
{
    if (*ok)
    {
	int need = strlen(out) + strlen(in) + 1;
	if (need > len)
	{
	    printf("Adding '%s' exceeds length (%d)\n", in, len);
	    *ok = 0;
	}
	else
	{
	    strcat(out, in);
	}
    }
    return *ok;
}

/* return pointer to the commandline minus palo stuff */
static char *
parse(const char *cmdline, int *kpart, char *kname, int *rdpart, char *rdname)
{
    char buf[CMDLINELEN];
    static char lcmd[CMDLINELEN];
    char *suffix1, *suffix2;
    int ok = 1;

    /* need a copy to work on */
    strcpy(buf, cmdline);

    *kpart = -1;
    *rdpart = -1;

    suffix1 = parse_pfname(buf, kpart, kname);

    if (*suffix1 != '\0')
	suffix1++;
    strcpy(lcmd, suffix1);

    /* see if we have a ramdisk */
    suffix2 = suffix1;
    if ((strncmp(suffix1, "initrd=", 7) == 0) ||
             (suffix2 = strstr(suffix1, " initrd=")) != NULL)
    {
	char *suffix3;
	lcmd[suffix2 - suffix1] = '\0';
	if (*suffix2 == ' ')
	    suffix2++;
	suffix3 = parse_pfname(suffix2 + 7, rdpart, rdname);
	chk_strcat(lcmd, suffix3, sizeof lcmd, &ok);
    }

    return lcmd;
}

int
load_kernel(int fd, unsigned *entryp, int *wide)
{
    struct loadable loadable;
    void *malloc_start;
    int i;

    if (!prepare_loadable(fd, &loadable, wide))
    {
	printf("Couldn't grok your kernel executable format\n");
	return 0;
    }

    /* need to physicalize those huge addresses */
    loadable.entry = PHYS(loadable.entry);
    loadable.first = PHYS(loadable.first);

    if (Debug)
	printf("\nEntry %08x first %08x n %d\n",
		loadable.entry, loadable.first, loadable.n);

    malloc_start = malloc_aligned(0, 1);
    malloc_start -= 5*1024*1024; /* 5 MB */

    for (i = 0; i < loadable.n; i++)
    {
	loadable.segment[i].mem =  PHYS(loadable.segment[i].mem);
	printf("Segment %d load 0x%08x size %d mediaptr 0x%lx\n",
		i, loadable.segment[i].mem, loadable.segment[i].length,
		loadable.segment[i].offset);

	/* check not to overwrite us */
	if (loadable.segment[i].mem >= (unsigned long)&_end &&
	    (loadable.segment[i].mem + loadable.segment[i].length)
			< (unsigned long)malloc_start)
		continue;

	printf("Abort: Would overwrite palo %p-%p or data %p areas.\n",
		&_start, &_end, malloc_start);
	return 0;

    }

    if (!load_loadable((char *)loadable.first, fd, &loadable))
    {
	printf("Fatal error loading kernel executable\n");
	return 0;
    }

    flush_data_cache((char *)loadable.first, loadable.size);

    *entryp = loadable.entry;

    return 1;
}

static int
load_rd(int fd, int size)
{
    extern char *rd_start, *rd_end;
    char *rd;

    if (size <= 0)
	return 0;

    /* no idea if initrd space must be aligned, but once it was before... */
    rd = malloc_aligned(size, 4096);
    printf("Loading ramdisk %d bytes @ %p...", size, rd);
    if (seekread(fd, rd, size, 0) == size)
    {
	rd_start = rd;
	rd_end = rd + size;
    }
    flush_data_cache((char *)rd, size);
    printf("\n");

    return (rd_start != 0);
}

static void
join(char *out, int argc, char *argv[], int *ok)
{
    char tmpbuf[CMDLINELEN];
    int i;

    tmpbuf[0] = '\0';
    for (i = 0; i < argc; i++)
    {
	if (i > 0)
	    chk_strcat(tmpbuf, " ", sizeof tmpbuf, ok);
	chk_strcat(tmpbuf, argv[i], sizeof tmpbuf, ok);
    }
    strcpy(out, tmpbuf);
}

static struct diskpartition partition[MAXPARTS];
static int ext2 = 0;
static int bootdev;

static void
partition_transform(int *kern_part, int *rd_part)
{
    int i, palo_part = -1;

    /* if the F0 partition is the same as the requested kernel partition,
     * for now change it to zero to re-use the existing logic.  Should do
     * the reverse in the future probably.
     */
    for (i = 0; i < sizeof partition / sizeof partition[0]; i++)
    {
	if (partition[i].id == PALO_PARTITION)
	{
	    palo_part = i + 1;
	    if (*kern_part == palo_part)
		*kern_part = 0;

	    if (rd_part && *rd_part == palo_part)
		*rd_part = 0;

	    break;
	}
    }

    if (ext2 && *kern_part == 0) {
	*kern_part = palo_part;
	if(rd_part && *rd_part == 0)
	    *rd_part = palo_part;
    }

}

static void
ls(char *path)
{
    char  *p, kern_dir[256];
    const char *dir;
    int fd, part, part_fd;
    unsigned char file_type;

    parse_pfname(path, &part, kern_dir);
    if ((p = strrchr(kern_dir, '/')) != NULL)
    {
	*++p = '.';
	*++p = '\0';
    }
    else
    {
	strcpy(kern_dir, "/.");
    }

    printf("Directory listing of %d%s\n\n", part, kern_dir);

    partition_transform(&part, NULL);

    /* partition table starts from zero */
    part_fd = offset_open(bootdev, 512ULL * partition[part - 1].start,
			      512ULL * partition[part - 1].length);
    if(ext2_mount(part_fd, 0, 0) == -1) {
	printf("Failed to mount partition %d\n", part);
	fileio_close(part_fd);
	return;
    }

    if((fd = ext2_open(kern_dir)) == -1) {
	printf("Failed to open directory %s\n", kern_dir);
	fileio_close(part_fd);
	return;
    }

    while((dir = ext2_readdir(fd, 0, &file_type)) != NULL)
	if(dir[0] != '.') /* skip hidden files and . and .. */
	    printf("  %s%s\n", dir,
		file_type == EXT2_FT_SYMLINK ? "@" :
		file_type == EXT2_FT_DIR ? "/" : "");
    
    printf("\n");
    ext2_close(fd);
    fileio_close(part_fd);
    /* There's no umount ... since the next mount reuses
     * the current one's data structures ... */
}

#define ARRAY_SIZE(_x) (sizeof(_x)/sizeof(_x[0]))
struct nvram_table_map {
	const char *model;
	uint64_t tables[2];
} nvram_table_mapping[] = {
	{ "9000/785/C8000", { 0xfffffff0f0416848, 0xfffffff0f0416860 } }, // PDC 2.13
	{ "9000/800/rp34",  { 0xfffffff0f04151f8, 0xfffffff0f0415210 } }, // PDC 45.44, 46.34 and 50.40
	{ "9000/800/rp44",  { 0xfffffff0f0420498, 0xfffffff0f04204b0 } }, // PDC 46.34
};

struct pdc_table {
	uint16_t id;
	uint16_t unknown2;
	uint32_t unknown4;
	uint64_t nvram;
	uint64_t defaults;
};

static uint64_t get_stable_rp34xx(struct nvram_table_map *map, uint16_t id)
{
	struct pdc_table *table;

	table = (void *) (uintptr_t) map->tables[0];
	if (table->id == id) {
		if (Debug)
			printf("%04x %016llx %016llx\n", table->id, table->nvram, table->defaults);
		return (uint64_t)table->nvram;
	}
	table = (void *) (uintptr_t) map->tables[1];
	while (table->id) {
		if (Debug)
			printf("%04x %016llx %016llx\n", table->id, table->nvram, table->defaults);
		if (table->id == id)
			return (uint64_t)table->nvram;
		table++;
	}
	return 0;
}

static void enable_mfg_mode(void)
{
	const char *model = get_machine_model();
	struct nvram_table_map *map;
	unsigned long pdc_vers;
	int i;

	printf("Machine: %s\n", model);
	pdc_version(&pdc_vers);
	printf("Firmware (PDC) version: %d.%d\n", pdc_vers >> 6, pdc_vers & 0x3f);

	for (i = 0; i < ARRAY_SIZE(nvram_table_mapping); i++) {
		map = nvram_table_mapping + i;
		if (!strncmp(map->model, model, strlen(map->model)))
			break;
	}

	if (i == ARRAY_SIZE(nvram_table_mapping)) {
		notfound:
		printf("Don't know how to enable manufacturing mode.\n\n");
		return;
	}

	uint64_t addr = get_stable_rp34xx(map, 0x100);
	if (Debug)
		printf("got nvram addr %016llx\n", addr);
	if (!addr)
		goto notfound;
	printf("Enabling manufacturing mode and reboot to activate...\n\n", model);
	*(uint8_t *)(uintptr_t)(addr + 0x20) = 'M';
	pdc_do_reset();
}

static void
interact(int *ok)
{
    #define MAX_ARGV 40
    char *argv[MAX_ARGV], *p;
    char orig[CMDLINELEN];
    const char sep[] = " \t";
    char numbuf[32];
    char fieldbuf[200];
    int i, argc, editfield;

    strcpy(orig, commandline);

    while (1)
    {
	printf("Current command line:\n");
	printf(commandline);
	printf("\n");

	p = commandline;
	argc = 0;
	*ok = 1;
	do
	{
	    if (argc >= MAX_ARGV)
	    {
		argc = MAX_ARGV;
		break;
	    }
	    if ((argv[argc++] = strtok(p, sep)) == NULL)
	    {
		argc--;
		break;
	    }
	    p = NULL;
	} while (1);
	for (i = 0; i < argc; i++)
	{
	    printf("%2d: %s\n", i, argv[i]);
	}
	printf("\n"
	    "<#>           edit the numbered field\n"
	    "'b'           boot with this command line\n"
	    "'r'           restore command line\n"
	    "'l <#/path>   list dir from numbered partition with path\n"
	    "'x'           reset and reboot machine\n"
#ifdef HIDDEN_COMMAND
	    "'d'           toggle debug mode\n"
	    "'L'           toggle LARGE READ MODE disk_2gb_limit\n"
#endif
	    "? ");
	numbuf[0] = '0';
	numbuf[1] = '\0';
	enter_text(numbuf, sizeof numbuf - 1);
	printf("\n");

	if (numbuf[0] == 'b')
	{
	    join(commandline, argc, argv, ok);
	    break;
	}

	if (numbuf[0] == 'r')
	{
	    strcpy(commandline, orig);
	    continue;
	}

	if (numbuf[0] == 'l')
	{
	    join(commandline, argc, argv, ok);
	    if (numbuf[1] == ' ') {
		/* add trailing slash */
		p = strrchr(numbuf, '/');
		if (p && p[1] != 0)
			strcat(numbuf, "/");
		ls(&numbuf[2]);
	    } else
		ls(argv[0]);
	    continue;
	}

	if (numbuf[0] == 'x')
	    pdc_do_reset();

	/* turn on firmware manufacturing mode if possible */
	if (numbuf[0] == 'm') { /* hidden option! */
		join(commandline, argc, argv, ok);
		enable_mfg_mode();
		continue;
	}

	if (numbuf[0] == 'd') /* hidden option! */
	{
	    join(commandline, argc, argv, ok);
	    Debug = !Debug;
	    printf("Debug mode is now %s\n", Debug ? "on":"off");
	    if (Debug)
		printf("palo loaded at %p-%p.\n", &_start, &_end);
	    continue;
	}

	if (numbuf[0] == 'L') /* hidden option! */
	{
	    join(commandline, argc, argv, ok);
	    disk_2gb_limit = !disk_2gb_limit;
	    printf("disk_2gb_limit is now %s\n", disk_2gb_limit ? "on":"off");
	    continue;
	}


	editfield = parse_number(numbuf, &p);

	if (editfield >= MAX_ARGV)
	{
	    printf("Too many input fields.\n");
	}
	else if (editfield < argc)
	{
	    strncpy(fieldbuf, argv[editfield], sizeof(fieldbuf));
	    fieldbuf[sizeof(fieldbuf)-1] = '\0';
	    enter_text(fieldbuf, sizeof fieldbuf - 1);
	    printf("\n");
	    argv[editfield] = fieldbuf;
	}

	join(commandline, argc, argv, ok);
    }
}

/*
 * On some server models the serial port of the GSP/Management card which
 * mirrors the console port shows up as ttyS1 instead of ttyS0.  This happens
 * on older Linux kernels which set up a serial port for the Diva AUX port.
 * The Linux kernel commit bcf3f1752a62 ("parisc: Hide Diva-built-in serial aux
 * and graphics card") avoids this by hiding the Diva AUX port:
 * https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=bcf3f1752a622f1372d3252d0fea8855d89812e7
 *
 */
static char *
get_default_serial_console()
{
	char *ttyS1_models[] = {
		"9000/800/rp3410",
		"9000/800/rp3440",
		NULL
	};
	const char *model;
	char **check = ttyS1_models;

	model = get_machine_model();
	while (*check) {
		if (strncmp(*check, model, strlen(*check)) == 0)
			printf("WARNING: Found rp34x0 machine. "
				"Older Linux kernel may need console=ttyS1.\n");
		check++;
	}

	return "ttyS0";
}

unsigned
iplmain(int is_interactive, char *initialstackptr, int started_wide)
{
    int partitioned;
    unsigned entry;
    struct firstblock f;
    int blocked_bootdev;
    int wide;
    int ccr_functional;
    int kern_part, rd_part;
    char kern_name[128], rd_name[128];
    char kern_fullname[128+10];
    int ok = 1;
    const char *str;

    /* BSS clear */
    bzero(&_edata, &_end - &_edata);

    /* heap grows down from initial stack pointer */
    malloc_init(initialstackptr);

    firmware_init(started_wide);
    putchar('p');	/* if you get this p and no more, string storage */
			/* in $GLOBAL$ is wrong or %dp is wrong */
    printf("alo ipl " PALOVERSION " %s\n", bld_info);
    interactive = is_interactive;
    if (Debug) {
	printf("iplmain(%d, started %s)\n", interactive,
		started_wide ? "wide" : "narrow");
	printf("initial-sp %p\n", initialstackptr);
	printf("palo loaded at %p-%p.\n", &_start, &_end);
    }

    ccr_functional = pdc_coproc_cfg();
    if (ccr_functional)
	asm("mtctl %0,%1" : : "r" (ccr_functional), "i" (10));
    else {
	printf("No functional FPUs found - stopping.\n");
	while (1) ;
    }

    disk_2gb_limit = pdc_bootdisk_2GB_limit();
    printf("Booting from media >2GB possible: %s\n", disk_2gb_limit ? "NO":"YES");

 restart:

    blocked_bootdev = pdc_bootdev_open();
    bootdev = byteio_open(blocked_bootdev);

    STRUCTREAD(bootdev, f, 0);
    if (strncmp(f.palomagic, PALOMAGIC, 4) != 0)
    {
	printf("ERROR: bad palo magic on boot device\n");
	while(1);
    }

    memset(&partition, 0, sizeof partition);
    partitioned = load_partitions(bootdev,
		partition, sizeof partition / sizeof partition[0]);

    if (partitioned)
    {
	printf("\n");
	print_ptab_pretty(partition, sizeof partition / sizeof partition[0]);
    }

    if (partitioned && f.ipl_addr < partition[0].start * 512)
	str = "PALO within boot label";
    else if (partitioned)
	str = "PALO(F0) partition";
    else
	str = "Boot image";

    printf("\n%s contains:\n", str);

    if(partitioned && f.version >= 4 && (f.flags & PFLAG_EXT2)) {
	printf("PALO is formatted EXT2/3\n");
	ext2 = 1;
    }

    if (f.version < 3 && f.kern32_sz > 0)
    {
	printf("    0/vmlinux %d bytes @ 0x%x\n", f.kern32_sz, f.kern32_offset);
    }
    else
    {
        if (f.kern32_sz > 0)
	    printf("    0/vmlinux32 %d(%d) bytes @ 0x%x\n", f.kern32_sz, f.kern32_native_sz, f.kern32_offset);
        if (f.kern64_sz > 0)
	    printf("    0/vmlinux64 %d(%d) bytes @ 0x%x\n", f.kern64_sz, f.kern64_native_sz, f.kern64_offset);
    }

    if (f.rd_sz > 0)
	printf("    0/ramdisk %d bytes @ 0x%x\n", f.rd_sz, f.rd_offset);

    if (f.cmdline[0] == '\0' && f.cmdline_old[0]) /* old style command line ? */
    {
	strcpy(f.cmdline, f.cmdline_old);
	f.cmdline_old[0] = 0;
    }

    if (f.cmdline[0] == '\0')	/* no command line specified */
    {
	die("ERROR: No command line on boot media -- faking one\n");
	strcpy(f.cmdline, "0/vmlinux root=???");
	interactive = 1;
    }

    if (strlen(f.cmdline) >= sizeof f.cmdline)
	printf("WARNING: stored command line is longer than allowed\n");
    strcpy(commandline, f.cmdline);

    /* add the right console= if there isn't one yet */
    if (strstr(commandline, " console=") == 0)
    {
	printf("\nInformation: No console specified on kernel command line."
		" This is normal.\nPALO will choose the console currently"
		" used by firmware ");

        chk_strcat(commandline, " console=", CMDLINELEN, &ok);
	if (pdc_cons_duplex())
	{
            int is_mux;

	    printf("(serial).\n");

            if(pdc_cons_mux(&is_mux) != PDC_OK)
                printf("Information: The PDC calls to query the console device failed. Assuming console=ttyS0\n");

            if(is_mux)
	        chk_strcat(commandline, "ttyB0", CMDLINELEN, &ok);
            else
	        chk_strcat(commandline, get_default_serial_console(), CMDLINELEN, &ok);

	    if (strstr(commandline, " TERM=") == 0)
	        chk_strcat(commandline, " TERM=vt102", CMDLINELEN, &ok);
	}
	else
	{
	    printf("(graphics).\n");
	    chk_strcat(commandline, "tty0", CMDLINELEN, &ok);
	    if (strstr(commandline, " sti=") == 0)
	    {
		struct {
		    unsigned char flags;
		    unsigned char bc[6];
		    unsigned char mod;
		} cons;
		int i;
		
		chk_strcat(commandline, " sti=", CMDLINELEN, &ok);
		if (pdc_read_conspath((unsigned char *)&cons) > 0)
		{
		    char pathcomp[4];
		    
		    for (i = 0; i < 6; i++)
		    {
			if (cons.bc[i] < 64)
			{
			    sprintf(pathcomp, "%d/", cons.bc[i]);
			    chk_strcat(commandline, pathcomp, CMDLINELEN, &ok);
			}
		    }
		    sprintf(pathcomp, "%d", cons.mod);
		    chk_strcat(commandline, pathcomp, CMDLINELEN, &ok);
		}
		else
		    chk_strcat (commandline, "0", CMDLINELEN, &ok);
	    }
	    if (strstr(commandline, " sti_font=") == 0)
	        chk_strcat(commandline, " sti_font=VGA8x16", CMDLINELEN, &ok);
	    if (strstr(commandline, " TERM=") == 0)
	        chk_strcat(commandline, " TERM=linux", CMDLINELEN, &ok);
	}
    }

    if (interactive)
	interact(&ok);

    /* If we have any failures after this, be sure we're interactive
     * for the re-start */
    interactive = 1;
    ok = 1;

    if (Debug) printf("commandline %s\n", commandline);
    strcpy(commandline,
	    parse(commandline, &kern_part, kern_name, &rd_part, rd_name));
    if (Debug) printf("commandline %s\n", commandline);
    if (Debug) printf("kernel %s  part %d\n", kern_name, kern_part);
    if (Debug) printf("rd     %s  part %d\n", rd_name, rd_part);

    sprintf(kern_fullname, "%d%s", kern_part, kern_name);
    chk_strcat(commandline, " palo_kernel=", CMDLINELEN, &ok);
    chk_strcat(commandline, kern_fullname, CMDLINELEN, &ok);

    printf("\nCommand line for kernel: '%s'\n", commandline);

    printf("Selected kernel: %s from partition %d\n", kern_name, kern_part);

    if (rd_part != -1)
	printf("Selected ramdisk: %s from partition %d\n", rd_name, rd_part);

    partition_transform(&kern_part, &rd_part);

    if (kern_part > 0 && !partitioned)
    {
	printf("ERROR: Requesting kernel from partition %d "
		    "on unpartitioned media!\n", kern_part);
	goto restart;
    }

    if (rd_part != -1 && rd_part != kern_part)
    {
	die("ERROR:: palo does not support ramdisk on different"
		" partition than kernel\n");
	goto restart;
    }

    if (kern_part == 0)
    {
	int kernfd;
	const char *wname;
	int rdfd;

	wname = kern_name + strlen(kern_name) - 2;
	if (wname >= kern_name && streq(wname, "32"))
	{
	    if (f.kern32_sz == 0)
	    {
	        die("Error: can't find a 32-bit kernel here");
		goto restart;
	    }
	    kernfd = offset_open(bootdev, f.kern32_offset, f.kern32_sz);
	}
	else if (wname >= kern_name && streq(wname, "64"))
	{
	    if (f.kern64_sz == 0)
	    {
	        die("Error: can't find a 64-bit kernel here");
		goto restart;
	    }
	    kernfd = offset_open(bootdev, f.kern64_offset, f.kern64_sz);
	}
	else
	{
	    if (f.version > 2)
		printf("Warning: kernel name doesn't end with 32 or 64 -- Guessing... ");

	    kernfd = -1;

	    if ((pdc_os_bits() & (PDC_MODEL_OS32|PDC_MODEL_OS64))
			== (PDC_MODEL_OS32|PDC_MODEL_OS64))
	    {
		printf("\nThis box can boot either 32 or 64-bit kernels...\n");
		if (f.kern32_offset == 0 && f.kern64_offset != 0)
		{
		    printf("Only see a 64-bit kernel, using that\n");
		    kernfd = offset_open(bootdev, f.kern64_offset, f.kern64_sz);
		}
		else if (f.kern32_offset != 0 && f.kern64_offset == 0)
		{
		    printf("Only see a 32-bit kernel, using that\n");
		    kernfd = offset_open(bootdev, f.kern32_offset, f.kern32_sz);
		}
		else if (f.kern32_offset != 0 && f.kern64_offset != 0)
		{
		    printf("Both kernels available, choosing 64-bit kernel\n");
		    kernfd = offset_open(bootdev, f.kern64_offset, f.kern64_sz);
		}
		else
		{
		    die("No kernels found.");
		    goto restart;
		}
	    }

	    if (kernfd == -1 && (pdc_os_bits() & PDC_MODEL_OS64))
	    {
		printf("Choosing 64-bit kernel\n");
		kernfd = offset_open(bootdev, f.kern64_offset, f.kern64_sz);
	    }
	    else if (kernfd == -1 && (pdc_os_bits() & PDC_MODEL_OS32))
	    {
		printf("Choosing 32-bit kernel\n");
		kernfd = offset_open(bootdev, f.kern32_offset, f.kern32_sz);
	    }
	}

	if (!load_kernel(kernfd, &entry, &wide))
	{
	    fileio_close(kernfd);
	    die("ERROR: failed to load kernel\n");
	    goto restart;
	}

	if (rd_part != -1)
	{
	    rdfd = offset_open(bootdev, f.rd_offset, f.rd_sz);
	    if (!load_rd(rdfd, f.rd_sz))
	    {
		printf("ERROR: failed to load ramdisk - proceeding anyway\n");
	    }
	    fileio_close(rdfd);
	}
    }
    else /* kern_part > 0 && we're partitioned */
    {
	int kern_fd;
	int bkern_fd;
	int rd_fd, brd_fd;
	int part_fd;
	int mount_fd;
	struct diskpartition *pp;

	if (kern_part >= MAXPARTS || 
	    (partition[kern_part - 1].id != LINUX_EXT2_PARTITION &&
	     partition[kern_part - 1].id != LINUX_RAID_PARTITION &&
	     partition[kern_part - 1].id != PALO_PARTITION) )
	{
	    printf("ERROR: Partition %d must be ext2\n", kern_part);
	    goto restart;
	}

	pp = &partition[kern_part - 1];
    
	part_fd = offset_open(bootdev, 512ULL * pp->start, 512ULL * pp->length);

	mount_fd = ext2_mount(part_fd, 0, 0);
	if (0) printf("ext2_mount(partition %d) returns %d\n",
	    kern_part, mount_fd);

	kern_fd = ext2_open_dirstrip(kern_name);
	if (0) printf("ext2_open_dirstrip(%s) = %d\n", kern_name, kern_fd);
	if (kern_fd < 0)
	{
	    printf("ERROR: open %s from partition %d failed\n",
		kern_name, kern_part);
	    fileio_close(mount_fd);
	    fileio_close(part_fd);
	    goto restart;
	}

	bkern_fd = byteio_open(kern_fd);
	if (!load_kernel(bkern_fd, &entry, &wide))
	{
	    fileio_close(bkern_fd);
	    fileio_close(mount_fd);
	    fileio_close(part_fd);
	    die("ERROR: failed to load kernel\n");
	    goto restart;
	}

	if (rd_part != -1)
	{
	    rd_fd = ext2_open_dirstrip(rd_name);
	    if(rd_fd >= 0) {
		brd_fd = byteio_open(rd_fd);

		if (!load_rd(brd_fd, ext2_filesize(rd_fd)))
		{
		    printf("ERROR: failed to load ramdisk - proceeding anyway\n");
		}
	    } else {
		printf("ERROR: failed to open ramdisk %s\n", rd_name);
	    }
	}
    }

    /* FIXME!!! need to pass command line to kernel */
    /* could theoretically use a function pointer, but they're ugly on PA */
    if(pdc_default_width(wide))
	    goto restart;
    printf("Branching to kernel entry point 0x%08x. If this is the last message you\n"
	   "see, you may need to switch your console. This is a common symptom -- search\n"
	   "the FAQ and mailing list at https://parisc.docs.kernel.org\n\n",
	   entry);
    return entry;
}
