mirror of
https://gitlab.com/qemu-project/openbios.git
synced 2024-02-13 08:34:06 +08:00
CHRP boot method. It finds the CHRP bootscript using the file attribute 'tbxi' and the blessed directory. This method allows to boot easily Fedora (the bitness detection is done by the script and yaboot is called with the according yaboot.conf) but openSUSE is broken (you have to use "boot cd:,\suseboot\yaboot") Debian works well (as usual...). The openSUSE needs the missing forth words: "load" and "go". The MacOS bootloader (BootX) is also loaded automatically but forth script is too complex to be executed correctly. Signed-off-by: Laurent Vivier <Laurent@vivier.eu> git-svn-id: svn://coreboot.org/openbios/trunk/openbios-devel@571 f158a5a8-5612-0410-a976-696ce0be7e32
482 lines
13 KiB
C
482 lines
13 KiB
C
/*
|
|
* Creation Date: <2002/10/02 22:24:24 samuel>
|
|
* Time-stamp: <2004/03/27 01:57:55 samuel>
|
|
*
|
|
* <main.c>
|
|
*
|
|
*
|
|
*
|
|
* Copyright (C) 2002, 2003, 2004 Samuel Rydh (samuel@ibrium.se)
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation
|
|
*
|
|
*/
|
|
|
|
#include "openbios/config.h"
|
|
#include "openbios/bindings.h"
|
|
#include "openbios/elfload.h"
|
|
#include "openbios/nvram.h"
|
|
#include "libc/diskio.h"
|
|
#include "libc/vsprintf.h"
|
|
#include "kernel.h"
|
|
#include "ofmem.h"
|
|
#define NO_QEMU_PROTOS
|
|
#include "openbios/fw_cfg.h"
|
|
|
|
//#define DEBUG_QEMU
|
|
|
|
#ifdef DEBUG_QEMU
|
|
#define SUBSYS_DPRINTF(subsys, fmt, args...) \
|
|
do { printk("%s - %s: " fmt, subsys, __func__ , ##args); } while (0)
|
|
#else
|
|
#define SUBSYS_DPRINTF(subsys, fmt, args...) \
|
|
do { } while (0)
|
|
#endif
|
|
#define CHRP_DPRINTF(fmt, args...) SUBSYS_DPRINTF("CHRP", fmt, ##args)
|
|
#define ELF_DPRINTF(fmt, args...) SUBSYS_DPRINTF("ELF", fmt, ##args)
|
|
#define NEWWORLD_DPRINTF(fmt, args...) SUBSYS_DPRINTF("NEWWORLD", fmt, ##args)
|
|
|
|
static void
|
|
transfer_control_to_elf( ulong elf_entry )
|
|
{
|
|
ELF_DPRINTF("Starting ELF boot loader\n");
|
|
call_elf( 0, 0, elf_entry );
|
|
|
|
fatal_error("call_elf returned unexpectedly\n");
|
|
}
|
|
|
|
static int
|
|
load_elf_rom( ulong *elf_entry, int fd )
|
|
{
|
|
int i, lszz_offs, elf_offs;
|
|
char *addr;
|
|
Elf_ehdr ehdr;
|
|
Elf_phdr *phdr;
|
|
size_t s;
|
|
|
|
ELF_DPRINTF("Loading '%s' from '%s'\n", get_file_path(fd),
|
|
get_volume_name(fd) );
|
|
|
|
/* the ELF-image (usually) starts at offset 0x4000 */
|
|
if( (elf_offs=find_elf(fd)) < 0 ) {
|
|
ELF_DPRINTF("----> %s is not an ELF image\n", get_file_path(fd));
|
|
return -1;
|
|
}
|
|
if( !(phdr=elf_readhdrs(fd, elf_offs, &ehdr)) ) {
|
|
ELF_DPRINTF("elf_readhdrs failed\n");
|
|
return -1;
|
|
}
|
|
|
|
*elf_entry = ehdr.e_entry;
|
|
|
|
/* load segments. Compressed ROM-image assumed to be located immediately
|
|
* after the last segment */
|
|
lszz_offs = elf_offs;
|
|
for( i=0; i<ehdr.e_phnum; i++ ) {
|
|
/* p_memsz, p_flags */
|
|
s = MIN( phdr[i].p_filesz, phdr[i].p_memsz );
|
|
seek_io( fd, elf_offs + phdr[i].p_offset );
|
|
|
|
ELF_DPRINTF("filesz: %08lX memsz: %08lX p_offset: %08lX p_vaddr %08lX\n",
|
|
(ulong)phdr[i].p_filesz, (ulong)phdr[i].p_memsz, (ulong)phdr[i].p_offset,
|
|
(ulong)phdr[i].p_vaddr );
|
|
|
|
if( phdr[i].p_vaddr != phdr[i].p_paddr )
|
|
ELF_DPRINTF("WARNING: ELF segment virtual addr != physical addr\n");
|
|
lszz_offs = MAX( lszz_offs, elf_offs + phdr[i].p_offset + phdr[i].p_filesz );
|
|
if( !s )
|
|
continue;
|
|
if( ofmem_claim( phdr[i].p_vaddr, phdr[i].p_memsz, 0 ) == -1 )
|
|
fatal_error("Claim failed!\n");
|
|
|
|
addr = (char*)phdr[i].p_vaddr;
|
|
if( read_io(fd, addr, s) != s ) {
|
|
ELF_DPRINTF("read failed\n");
|
|
return -1;
|
|
}
|
|
|
|
flush_icache_range( addr, addr+s );
|
|
|
|
ELF_DPRINTF("ELF ROM-section loaded at %08lX (size %08lX)\n",
|
|
(ulong)phdr[i].p_vaddr, (ulong)phdr[i].p_memsz );
|
|
}
|
|
free( phdr );
|
|
return lszz_offs;
|
|
}
|
|
|
|
static char *
|
|
get_device( const char *path )
|
|
{
|
|
int i;
|
|
static char buf[1024];
|
|
|
|
for (i = 0; i < sizeof(buf) && path[i] && path[i] != ':'; i++)
|
|
buf[i] = path[i];
|
|
buf[i] = 0;
|
|
|
|
return buf;
|
|
}
|
|
|
|
static int
|
|
get_partition( const char *path )
|
|
{
|
|
while ( *path && *path != ':' )
|
|
path++;
|
|
|
|
if (!*path)
|
|
return -1;
|
|
path++;
|
|
|
|
if (!strchr(path, ',')) /* check if there is a ',' */
|
|
return -1;
|
|
|
|
return atol(path);
|
|
}
|
|
|
|
static char *
|
|
get_filename( const char * path , char **dirname)
|
|
{
|
|
static char buf[1024];
|
|
char *filename;
|
|
|
|
while ( *path && *path != ':' )
|
|
path++;
|
|
|
|
if (!*path) {
|
|
*dirname = NULL;
|
|
return NULL;
|
|
}
|
|
path++;
|
|
|
|
while ( *path && isdigit(*path) )
|
|
path++;
|
|
|
|
if (*path == ',')
|
|
path++;
|
|
|
|
strncpy(buf, path, sizeof(buf));
|
|
buf[sizeof(buf) - 1] = 0;
|
|
|
|
filename = strrchr(buf, '\\');
|
|
if (filename) {
|
|
*dirname = buf;
|
|
(*filename++) = 0;
|
|
} else {
|
|
*dirname = NULL;
|
|
filename = buf;
|
|
}
|
|
|
|
return filename;
|
|
}
|
|
|
|
static void
|
|
encode_bootpath( const char *spec, const char *args )
|
|
{
|
|
char path[1024];
|
|
phandle_t chosen_ph = find_dev("/chosen");
|
|
char *filename, *directory;
|
|
int partition;
|
|
|
|
filename = get_filename(spec, &directory);
|
|
partition = get_partition(spec);
|
|
if (partition == -1)
|
|
snprintf(path, sizeof(path), "%s:,%s\\%s", get_device(spec),
|
|
directory, filename);
|
|
else
|
|
snprintf(path, sizeof(path), "%s:%d,%s\\%s", get_device(spec),
|
|
partition, directory, filename);
|
|
|
|
ELF_DPRINTF("bootpath %s bootargs %s\n", path, args);
|
|
set_property( chosen_ph, "bootpath", path, strlen(spec)+1 );
|
|
if (args)
|
|
set_property( chosen_ph, "bootargs", args, strlen(args)+1 );
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* qemu booting */
|
|
/************************************************************************/
|
|
static void
|
|
try_path(const char *path, const char *param)
|
|
{
|
|
ulong elf_entry;
|
|
int fd, ret;
|
|
|
|
ELF_DPRINTF("Trying %s %s\n", path, param);
|
|
if ((fd = open_io(path)) == -1) {
|
|
ELF_DPRINTF("Can't open %s\n", path);
|
|
return;
|
|
}
|
|
ret = load_elf_rom( &elf_entry, fd );
|
|
if (ret < 0)
|
|
return;
|
|
close_io( fd );
|
|
encode_bootpath( path, param );
|
|
|
|
update_nvram();
|
|
ELF_DPRINTF("Transfering control to %s %s\n",
|
|
path, param);
|
|
transfer_control_to_elf( elf_entry );
|
|
/* won't come here */
|
|
}
|
|
|
|
/*
|
|
Parse SGML structure like:
|
|
<chrp-boot>
|
|
<description>Debian/GNU Linux Installation on IBM CHRP hardware</description>
|
|
<os-name>Debian/GNU Linux for PowerPC</os-name>
|
|
<boot-script>boot &device;:\install\yaboot</boot-script>
|
|
<icon size=64,64 color-space=3,3,2>
|
|
|
|
CHRP system bindings are described at:
|
|
http://playground.sun.com/1275/bindings/chrp/chrp1_7a.ps
|
|
*/
|
|
static void
|
|
try_chrp_script(const char *of_path, const char *param, const char *script_path)
|
|
{
|
|
int fd, len, tag, taglen, script, scriptlen, entity;
|
|
char tagbuf[256], bootscript[2048], c;
|
|
char *device, *filename, *directory;
|
|
int partition;
|
|
|
|
device = get_device(of_path);
|
|
partition = get_partition(of_path);
|
|
filename = get_filename(of_path, &directory);
|
|
|
|
CHRP_DPRINTF("device %s partition %d filename %s\n", device, partition, filename);
|
|
|
|
/* read boot script */
|
|
|
|
if (partition == -1)
|
|
snprintf(bootscript, sizeof(bootscript), "%s:,%s",
|
|
device, script_path);
|
|
else
|
|
snprintf(bootscript, sizeof(bootscript), "%s:%d,%s",
|
|
device, partition, script_path);
|
|
|
|
CHRP_DPRINTF("Trying %s\n", bootscript);
|
|
if ((fd = open_io(bootscript)) == -1) {
|
|
ELF_DPRINTF("Can't open %s\n", bootscript);
|
|
return;
|
|
}
|
|
len = read_io(fd, tagbuf, 11);
|
|
tagbuf[11] = '\0';
|
|
if (len < 0 || strcasecmp(tagbuf, "<chrp-boot>") != 0)
|
|
goto badf;
|
|
|
|
tag = 0;
|
|
taglen = 0;
|
|
script = 0;
|
|
scriptlen = 0;
|
|
entity = 0;
|
|
do {
|
|
len = read_io(fd, &c, 1);
|
|
if (len < 0)
|
|
goto badf;
|
|
if (c == '<') {
|
|
script = 0;
|
|
tag = 1;
|
|
taglen = 0;
|
|
} else if (c == '>') {
|
|
tag = 0;
|
|
tagbuf[taglen] = '\0';
|
|
if (strcasecmp(tagbuf, "boot-script") == 0)
|
|
script = 1;
|
|
else if (strcasecmp(tagbuf, "/boot-script") == 0)
|
|
bootscript[scriptlen] = '\0';
|
|
else if (strcasecmp(tagbuf, "/chrp-boot") == 0)
|
|
break;
|
|
} else if (tag && taglen < sizeof(tagbuf)) {
|
|
tagbuf[taglen++] = c;
|
|
} else if (script && c == '&') {
|
|
entity = 1;
|
|
taglen = 0;
|
|
} else if (entity && c ==';') {
|
|
entity = 0;
|
|
tagbuf[taglen] = '\0';
|
|
if (strcasecmp(tagbuf, "device") == 0) {
|
|
strcpy(bootscript + scriptlen, device);
|
|
scriptlen += strlen(device);
|
|
} else if (strcasecmp(tagbuf, "partition") == 0) {
|
|
if (partition != -1)
|
|
sprintf(bootscript + scriptlen, "%d", partition);
|
|
else
|
|
*(bootscript + scriptlen) = 0;
|
|
scriptlen = strlen(bootscript);
|
|
} else if (strcasecmp(tagbuf, "directory") == 0) {
|
|
strcpy(bootscript + scriptlen, directory);
|
|
scriptlen += strlen(directory);
|
|
} else if (strcasecmp(tagbuf, "filename") == 0) {
|
|
strcpy(bootscript + scriptlen, filename);
|
|
scriptlen += strlen(filename);
|
|
} else if (strcasecmp(tagbuf, "full-path") == 0) {
|
|
strcpy(bootscript + scriptlen, of_path);
|
|
scriptlen += strlen(of_path);
|
|
} else { /* unknown, keep it */
|
|
bootscript[scriptlen] = '&';
|
|
strcpy(bootscript + scriptlen + 1, tagbuf);
|
|
scriptlen += taglen + 1;
|
|
bootscript[scriptlen] = ';';
|
|
scriptlen++;
|
|
}
|
|
} else if (entity && taglen < sizeof(tagbuf)) {
|
|
tagbuf[taglen++] = c;
|
|
} else if (script && scriptlen < sizeof(bootscript)) {
|
|
bootscript[scriptlen++] = c;
|
|
}
|
|
} while (1);
|
|
|
|
CHRP_DPRINTF("got bootscript %s\n", bootscript);
|
|
|
|
encode_bootpath(of_path, param);
|
|
|
|
feval(bootscript);
|
|
badf:
|
|
close_io( fd );
|
|
}
|
|
|
|
#define OLDWORLD_BOOTCODE_BASEADDR (0x3f4000)
|
|
|
|
static void
|
|
oldworld_boot( void )
|
|
{
|
|
int fd;
|
|
int len, total;
|
|
const char *path = "hd:,%BOOT";
|
|
char *bootcode;
|
|
|
|
if ((fd = open_io(path)) == -1) {
|
|
ELF_DPRINTF("Can't open %s\n", path);
|
|
return;
|
|
}
|
|
|
|
|
|
total = 0;
|
|
bootcode = (char*)OLDWORLD_BOOTCODE_BASEADDR;
|
|
while(1) {
|
|
if (seek_io(fd, total) == -1)
|
|
break;
|
|
len = read_io(fd, bootcode, 512);
|
|
bootcode += len;
|
|
total += len;
|
|
}
|
|
|
|
close_io( fd );
|
|
|
|
if (total == 0) {
|
|
ELF_DPRINTF("Can't read %s\n", path);
|
|
return;
|
|
}
|
|
|
|
encode_bootpath(path, "Linux");
|
|
|
|
if( ofmem_claim( OLDWORLD_BOOTCODE_BASEADDR, total, 0 ) == -1 )
|
|
fatal_error("Claim failed!\n");
|
|
|
|
call_elf(0, 0, OLDWORLD_BOOTCODE_BASEADDR);
|
|
|
|
return;
|
|
}
|
|
|
|
static void
|
|
newworld_boot( void )
|
|
{
|
|
static const char * const chrp_path = "\\\\:tbxi" ;
|
|
char *path = pop_fstr_copy(), *param;
|
|
|
|
param = strchr(path, ' ');
|
|
if (param) {
|
|
*param = 0;
|
|
param++;
|
|
}
|
|
|
|
if (!path) {
|
|
NEWWORLD_DPRINTF("Entering boot, no path\n");
|
|
push_str("boot-device");
|
|
push_str("/options");
|
|
fword("(find-dev)");
|
|
POP();
|
|
fword("get-package-property");
|
|
if (!POP()) {
|
|
path = pop_fstr_copy();
|
|
param = strchr(path, ' ');
|
|
if (param) {
|
|
*param = '\0';
|
|
param++;
|
|
} else {
|
|
push_str("boot-args");
|
|
push_str("/options");
|
|
fword("(find-dev)");
|
|
POP();
|
|
fword("get-package-property");
|
|
POP();
|
|
param = pop_fstr_copy();
|
|
}
|
|
try_path(path, param);
|
|
try_chrp_script(path, param, chrp_path);
|
|
} else {
|
|
uint16_t boot_device = fw_cfg_read_i16(FW_CFG_BOOT_DEVICE);
|
|
switch (boot_device) {
|
|
case 'c':
|
|
path = strdup("hd:");
|
|
break;
|
|
default:
|
|
case 'd':
|
|
path = strdup("cd:");
|
|
break;
|
|
}
|
|
try_chrp_script(path, param, chrp_path);
|
|
}
|
|
} else {
|
|
NEWWORLD_DPRINTF("Entering boot, path %s\n", path);
|
|
try_path(path, param);
|
|
try_chrp_script(path, param, chrp_path);
|
|
}
|
|
printk("*** Boot failure! No secondary bootloader specified ***\n");
|
|
}
|
|
|
|
static void check_preloaded_kernel(void)
|
|
{
|
|
unsigned long kernel_image, kernel_size;
|
|
unsigned long initrd_image, initrd_size;
|
|
const char * kernel_cmdline;
|
|
|
|
kernel_size = fw_cfg_read_i32(FW_CFG_KERNEL_SIZE);
|
|
if (kernel_size) {
|
|
kernel_image = fw_cfg_read_i32(FW_CFG_KERNEL_ADDR);
|
|
kernel_cmdline = (const char *) fw_cfg_read_i32(FW_CFG_KERNEL_CMDLINE);
|
|
initrd_image = fw_cfg_read_i32(FW_CFG_INITRD_ADDR);
|
|
initrd_size = fw_cfg_read_i32(FW_CFG_INITRD_SIZE);
|
|
printk("[ppc] Kernel already loaded (0x%8.8lx + 0x%8.8lx) "
|
|
"(initrd 0x%8.8lx + 0x%8.8lx)\n",
|
|
kernel_image, kernel_size, initrd_image, initrd_size);
|
|
if (kernel_cmdline) {
|
|
phandle_t ph;
|
|
printk("[ppc] Kernel command line: %s\n", kernel_cmdline);
|
|
ph = find_dev("/chosen");
|
|
set_property(ph, "bootargs", strdup(kernel_cmdline), strlen(kernel_cmdline) + 1);
|
|
}
|
|
call_elf(initrd_image, initrd_size, kernel_image);
|
|
}
|
|
}
|
|
|
|
/************************************************************************/
|
|
/* entry */
|
|
/************************************************************************/
|
|
|
|
void
|
|
boot( void )
|
|
{
|
|
uint16_t boot_device = fw_cfg_read_i16(FW_CFG_BOOT_DEVICE);
|
|
|
|
fword("update-chosen");
|
|
if (boot_device == 'm') {
|
|
check_preloaded_kernel();
|
|
}
|
|
if (boot_device == 'c') {
|
|
oldworld_boot();
|
|
}
|
|
newworld_boot();
|
|
}
|