blob: 19111d8beca8a1420e82176b5cecce95d1715ba4 [file] [log] [blame]
/*
* 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"
#undef PAGE0
#define PAGE0 ((struct zeropage *)0x00000000)
char commandline[CMDLINELEN];
int Debug = 0;
int interactive = 0;
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;
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);
printf("\nEntry %08x first %08x n %d\n",
loadable.entry, loadable.first, loadable.n);
for (i = 0; i < loadable.n; i++)
{
loadable.segment[i].mem = PHYS(loadable.segment[i].mem);
printf("Segment %d load %08x size %d mediaptr 0x%lx\n",
i, loadable.segment[i].mem, loadable.segment[i].length,
loadable.segment[i].offset);
}
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);
puts("\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;
parse_pfname(path, &part, kern_dir);
if ((p = strrchr(kern_dir, '/')) != NULL)
{
*++p = '.';
*++p = '\0';
}
else
{
strcpy(kern_dir, "/.");
}
printf("Directory listing of %s\n\n", kern_dir);
partition_transform(&part, NULL);
/* partition table starts from zero */
part_fd = offset_open(bootdev, 512 * partition[part - 1].start,
512 * partition[part - 1].length);
if(ext2_mount(part_fd, 0, 0) == -1) {
printf("Failed to mount partition %d\n", part);
return;
}
if((fd = ext2_open(kern_dir)) == -1) {
printf("Failed to open directory %s\n", kern_dir);
return;
}
while((dir = ext2_readdir(fd, 0)) != NULL)
if(dir[0] != '.') /* skip hidden files and . and .. */
printf(" %s\n", dir);
printf("\n");
ext2_close(fd);
/* There's no umount ... since the next mount reuses
* the current one's data structures ... */
}
static void
interact(int *ok)
{
#define MAX_ARGV 40
char *argv[MAX_ARGV], *p;
char orig[CMDLINELEN];
const char sep[] = " \t";
char numbuf[4];
char fieldbuf[200];
int i, argc, editfield;
strcpy(orig, commandline);
while (1)
{
puts("Current command line:\n");
puts(commandline);
puts("\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]);
}
puts("\n"
"<#> edit the numbered field\n"
"'b' boot with this command line\n"
"'r' restore command line\n"
"'l' list dir\n"
"'x' reset and reboot machine\n"
"? ");
numbuf[0] = '0';
numbuf[1] = '\0';
enter_text(numbuf, sizeof numbuf - 1);
puts("\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);
ls(argv[0]);
continue;
}
if (numbuf[0] == 'x')
pdc_do_reset();
editfield = parse_number(numbuf, &p);
if (editfield >= MAX_ARGV)
{
puts("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);
puts("\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 is due
* how the pci code in Linux kernel scans the PCI bus. Check the relevant
* models and return ttyS1 for such machines.
* Beware: pdc_model_sysmodel() may return a machine name which has trailing
* spaces.
*/
static char *
get_default_serial_console()
{
char sys_model_name[81];
char *ttyS1_models[] = {
"9000/800/rp3410",
"9000/800/rp3420",
"9000/800/rp3440",
NULL
};
if (pdc_model_sysmodel(sys_model_name) == PDC_OK) {
char **check = ttyS1_models;
while (*check) {
if (strncmp(*check, sys_model_name, strlen(*check)) == 0)
return "ttyS1";
check++;
}
}
return "ttyS0";
}
unsigned
iplmain(int is_interactive, char *initialstackptr, int started_wide)
{
extern char _end, _edata;
int partitioned;
unsigned entry;
struct firstblock f;
int blocked_bootdev;
int wide;
int kern_part, rd_part;
char kern_name[128], rd_name[128];
char kern_fullname[128];
int ok = 1;
/* 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 */
puts("alo ipl " PALOVERSION " ");
puts(bld_info);
puts("\n");
interactive = is_interactive;
if (Debug) printf("iplmain(%d, started %s)\n", interactive,
started_wide ? "wide" : "narrow");
if (Debug) printf("initial-sp %p\n", initialstackptr);
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]);
}
printf("\n%s contains:\n",
partitioned ? "PALO(F0) partition" : "Boot image");
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;
strcpy(commandline,
parse(commandline, &kern_part, kern_name, &rd_part, rd_name));
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() & (OS_32|OS_64)) == (OS_32|OS_64))
{
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() & OS_64))
{
printf("Choosing 64-bit kernel\n");
kernfd = offset_open(bootdev, f.kern64_offset, f.kern64_sz);
}
else if (kernfd == -1 && (pdc_os_bits() & OS_32))
{
printf("Choosing 32-bit kernel\n");
kernfd = offset_open(bootdev, f.kern32_offset, f.kern32_sz);
}
}
/* FIXME!!! This *could* overwrite us -- probably should check */
if (!load_kernel(kernfd, &entry, &wide))
{
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");
}
}
}
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, 512 * pp->start, 512 * 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);
goto restart;
}
bkern_fd = byteio_open(kern_fd);
if (!load_kernel(bkern_fd, &entry, &wide))
{
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\n"
"message you see, you may need to switch your console. This is\n"
"a common symptom -- search the FAQ and mailing list at parisc-linux.org\n\n",
entry);
return entry;
}