diff options
Diffstat (limited to 'package/aboot/src/disk.c')
-rw-r--r-- | package/aboot/src/disk.c | 854 |
1 files changed, 854 insertions, 0 deletions
diff --git a/package/aboot/src/disk.c b/package/aboot/src/disk.c new file mode 100644 index 000000000..fd9af8f6a --- /dev/null +++ b/package/aboot/src/disk.c @@ -0,0 +1,854 @@ +/* + * aboot/disk.c + * + * This file is part of aboot, the SRM bootloader for Linux/Alpha + * Copyright (C) 1996 Linus Torvalds, David Mosberger, and Michael Schwingen. + * + * 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; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#ifdef TESTING +# include <stdlib.h> +# include <stdio.h> +# include <fcntl.h> +# include <unistd.h> +#endif + +#include "config.h" +#include "aboot.h" +#include "bootfs.h" +#include "cons.h" +#include "disklabel.h" +#include "utils.h" +#include "string.h" + +#include <linux/elf.h> +#include <asm/console.h> +#include "system.h" + +extern struct bootfs ext2fs; +extern struct bootfs iso; +extern struct bootfs ufs; +extern struct bootfs dummyfs; + +struct disklabel * label; +int boot_part = -1; + +static struct bootfs *bootfs[] = { + &ext2fs, + &iso, + &ufs +}; + +/* + * Attempt a "raw" boot (uncompressed ELF kernel follows right after aboot). + * + * This will eventually be rewritten to accept compressed kernels + * (along with net_aboot). It should also be merged with the other + * load methods as there is some code duplication here we don't want. + */ + +int +load_raw (long dev) +{ + extern char _end; + char *buf; + long aboot_size = &_end - (char *) BOOT_ADDR; + long ksect = (aboot_size + SECT_SIZE - 1) / SECT_SIZE + BOOT_SECTOR; + long nread; + int i; + + printf("aboot: loading kernel from boot sectors...\n"); + + /* We only need the program headers so this should be fine */ + buf = malloc(SECT_SIZE); + + /* Read ELF headers: */ + nread = cons_read(dev, buf, SECT_SIZE, ksect * SECT_SIZE); + if (nread != SECT_SIZE) { + printf("aboot: read returned %ld instead of %ld bytes\n", + nread, (long) SECT_SIZE); + return -1; + } + if (first_block(buf, SECT_SIZE) < 0) { + return -1; + } + + for (i = 0; i < nchunks; ++i) { + char *dest; + + printf("aboot: segment %d, %ld bytes at %#lx\n", i, chunks[i].size, + chunks[i].addr); +#ifdef TESTING + dest = malloc(chunks[i].size); +#else + dest = (char *) chunks[i].addr; +#endif + + nread = cons_read(dev, dest, chunks[i].size, + chunks[i].offset + ksect * SECT_SIZE); + if (nread != chunks[i].size) { + printf("aboot: read returned %ld instead of %ld bytes\n", + nread, chunks[i].size); + return -1; + } + } + return 0; +} + + +int +load_uncompressed (int fd) +{ + long nread, nblocks; + unsigned char *buf; + int i; + + buf = malloc(bfs->blocksize); + + /* read ELF headers: */ + nread = (*bfs->bread)(fd, 0, 1, buf); + if (nread != bfs->blocksize) { + printf("aboot: read returned %ld instead of %ld bytes\n", + nread, sizeof(buf)); + return -1; + } +#ifdef DEBUG + { + int i,j,c; + + for(i = 0; i < 16; i++) { + for (j = 0; j < 16; j++) + printf("%02X ", buf[j+16*i]); + for(j = 0; j < 16; j++) { + c = buf[j+16*i]; + printf("%c", (c >= ' ') ? c : ' '); + } + printf("\n"); + } + } +#endif + if (first_block(buf, bfs->blocksize) < 0) { + return -1; + } + + /* read one segment at a time */ + for (i = 0; i < nchunks; ++i) { + char *dest; + + /* include any unaligned bits of the offset */ + nblocks = (chunks[i].size + (chunks[i].offset & (bfs->blocksize - 1)) + + bfs->blocksize - 1) / bfs->blocksize; + printf("aboot: segment %d, %ld bytes at %#lx\n", i, chunks[i].size, + chunks[i].addr); +#ifdef TESTING + dest = malloc(nblocks * bfs->blocksize); +#else + dest = (char *) chunks[i].addr; +#endif + + nread = (*bfs->bread)(fd, chunks[i].offset / bfs->blocksize, + nblocks, dest); + if (nread != nblocks * bfs->blocksize) { + printf("aboot: read returned %ld instead of %ld bytes\n", + nread, nblocks * bfs->blocksize); + return -1; + } + /* In practice, they will always be aligned */ + if ((chunks[i].offset & (bfs->blocksize - 1)) != 0) + memmove(dest, + dest + (chunks[i].offset & (bfs->blocksize - 1)), + chunks[i].size); + } + return 0; +} + +static long +read_kernel (const char *filename) +{ + volatile int attempt, method; + long len; + int fd; + static struct { + const char *name; + int (*func)(int fd); + } read_method[]= { + {"uncompressed", load_uncompressed}, + {"compressed", uncompress_kernel} + }; + long res; +# define NUM_METHODS ((int)(sizeof(read_method)/sizeof(read_method[0]))) + +#ifdef DEBUG + printf("read_kernel(%s)\n", filename); +#endif + + method = 0; + len = strlen(filename); + if (len > 3 && filename[len - 3] == '.' + && filename[len - 2] == 'g' && filename[len - 1] == 'z') + { + /* if filename ends in .gz we don't try plain method: */ + method = 1; + } + + for (attempt = 0; attempt < NUM_METHODS; ++attempt) { + fd = (*bfs->open)(filename); + if (fd < 0) { + printf("%s: file not found\n", filename); + return -1; + } + printf("aboot: loading %s %s...\n", + read_method[method].name, filename); + + if (!_setjmp(jump_buffer)) { + res = (*read_method[method].func)(fd); + + (*bfs->close)(fd); + if (res >= 0) { + return 0; + } + } + method = (method + 1) % NUM_METHODS; + } + return -1; +} + +long +read_initrd() +{ + int nblocks, nread, fd; + struct stat buf; + + fd = (*bfs->open)(initrd_file); + if (fd < 0) { + printf("%s: file not found\n", initrd_file); + return -1; + } + (*bfs->fstat)(fd, &buf); + initrd_size = buf.st_size; + +#ifdef TESTING + initrd_start = (unsigned long) malloc(initrd_size); +#else + /* put it as high up in memory as possible */ + if (!free_mem_ptr) + free_mem_ptr = memory_end(); + /* page aligned (downward) */ + initrd_start = (free_mem_ptr - initrd_size) & ~(PAGE_SIZE-1); + /* update free_mem_ptr so malloc() still works */ + free_mem_ptr = initrd_start; +#endif + + nblocks = initrd_size / bfs->blocksize; + printf("aboot: loading initrd (%ld bytes/%d blocks) at %#lx\n", + initrd_size, nblocks, initrd_start); + if (nblocks & (bfs->blocksize - 1)) nblocks++; + nread = (*bfs->bread)(fd, 0, nblocks, (char*) initrd_start); + if (nread != nblocks * bfs->blocksize) { + printf("aboot: read returned %d instead of %d (%d*%d) bytes\n", + nread, nblocks * bfs->blocksize, + nblocks, bfs->blocksize); + return -1; + } + return 0; +} + +static void +get_disklabel (long dev) +{ + static char lsect[512]; + long nread; + +#ifdef DEBUG + printf("load_label(dev=%lx)\n", dev); +#endif + nread = cons_read(dev, &lsect, LABELOFFSET + sizeof(*label), + LABELSECTOR); + if (nread != LABELOFFSET + sizeof(*label)) { + printf("aboot: read of disklabel sector failed (nread=%ld)\n", + nread); + return; + } + label = (struct disklabel*) &lsect[LABELOFFSET]; + if (label->d_magic == DISKLABELMAGIC && + label->d_magic2 == DISKLABELMAGIC) + { + printf("aboot: valid disklabel found: %d partitions.\n", + label->d_npartitions); + } else { + printf("aboot: no disklabel found.\n"); + label = 0; + } +} + + +struct bootfs * +mount_fs (long dev, int partition) +{ + struct d_partition * part; + struct bootfs * fs = 0; + int i; + +#ifdef DEBUG + printf("mount_fs(%lx, %d)\n", dev, partition); +#endif + if (partition == 0) { + fs = &dummyfs; + if ((*fs->mount)(dev, 0, 0) < 0) { + printf("aboot: disk mount failed\n"); + return 0; + } + } else if (!label) { + /* floppies and such, no disklabel */ + for (i = 0; i < (int)(sizeof(bootfs)/sizeof(bootfs[0])); ++i) { + if ((*bootfs[i]->mount)(dev, 0, 1) >= 0) { + fs = bootfs[i]; + break; + } + } + if (!fs) { + printf("aboot: unknown filesystem type\n"); + return 0; + } + } else { + if ((unsigned) (partition - 1) >= label->d_npartitions) { + printf("aboot: invalid partition %u\n", partition); + return 0; + } + part = &label->d_partitions[partition - 1]; + for (i = 0; bootfs[i]->fs_type != part->p_fstype; ++i) { + if (i + 1 + >= (int) (sizeof(bootfs)/sizeof(bootfs[0]))) + { + printf("aboot: don't know how to mount " + "partition %d (filesystem type %d)\n", + partition, part->p_fstype); + return 0; + } + } + fs = bootfs[i]; + if ((*fs->mount)(dev, (long)(part->p_offset) * (long)(label->d_secsize), 0) + < 0) { + printf("aboot: mount of partition %d failed\n", + partition); + return 0; + } + } + return fs; +} + +void +list_directory (struct bootfs *fs, char *dir) +{ + int fd = (*fs->open)(dir); + /* yes, our readdir() is not exactly like the real one */ + int rewind = 0; + const char * ent; + + if (fd < 0) { + printf("%s: directory not found\n", dir); + return; + } + + while ((ent = (*fs->readdir)(fd, !rewind++))) { + printf("%s\n", ent); + } + (*fs->close)(fd); +} + +int +open_config_file(struct bootfs *fs) +{ + static const char *configs[] = { + "/etc/aboot.conf", + "/aboot.conf", + "/etc/aboot.cfg", + "/aboot.cfg" + }; + const int nconfigs = sizeof(configs) / sizeof(configs[0]); + int i, fd = -1; + + for (i = 0; i < nconfigs; i++) { + fd = (*fs->open)(configs[i]); + if (fd >= 0) + break; + } + return fd; +} + +void +print_config_file (struct bootfs *fs) +{ + int fd, nread, blkno = 0; + char *buf; + + fd = open_config_file(fs); + if (fd < 0) { + printf("%s: file not found\n", CONFIG_FILE); + return; + } + buf = malloc(fs->blocksize + 1); + if (!buf) { + printf("aboot: malloc failed!\n"); + return; + } + do { + nread = (*fs->bread)(fd, blkno++, 1, buf); + buf[nread] = '\0'; + printf("%s", buf); + } while (nread > 0); + (*fs->close)(fd); +} + + +int +get_default_args (struct bootfs *fs, char *str, int num) +{ + int fd, nread, state, line, blkno = 0; + char *buf, *d, *p; + + *str = '\0'; + fd = open_config_file(fs); + if (fd < 0) { + printf("%s: file not found\n", CONFIG_FILE); + return -1; + } + buf = malloc(fs->blocksize); + if (!buf) { + printf("aboot: malloc failed!\n"); + return -1; + } + d = str; + line = 1; + state = 2; + do { + nread = (*fs->bread)(fd, blkno++, 1, buf); + p = buf; + while (p < buf + nread && *p && state != 5) { + switch (state) { + case 0: /* ignore rest of line */ + case 1: /* in comment */ + if (*p == '\n') state = 2; + break; + + case 2: /* after end of line */ + line++; + if (*p == num) { + state = 3; /* found it... */ + break; + } + if (*p == '#') { + state = 1; /* comment */ + break; + } + if (*p == '-') { + state = 5; /* end-of-file mark */ + break; + } + state = 0; /* ignore rest of line */ + break; + + case 3: /* after matched number */ + if (*p == ':') { + state = 4; /* copy string */ + } else { + state = 2; /* ignore rest */ + printf("aboot: syntax error in line " + "%d: `:' expected\n", line); + } + break; + + case 4: /* copy until EOL */ + if (*p == '\n') { + *d = 0; + state=5; + } else { + *d++ = *p; + } + break; + + default: + break; + } + p++; + } + } while (nread > 0 && state != 5); + (*fs->close)(fd); +#ifdef DEBUG + printf("get_default done\n"); +#endif + + if (state != 5) { + printf("aboot: could not find default config `%c'\n", num); + return -1; + } +#ifdef DEBUG + printf("get_default_args(%s,%d)\n", str, num); +#endif + return 0; +} + + +static void +print_help(void) +{ + printf("Commands:\n" + " h, ? Display this message\n" + " q Halt the system and return to SRM\n" + " p 1-8 Look in partition <num> for configuration/kernel\n" + " l List preconfigured kernels\n" + " d <dir> List directory <dir> in current filesystem\n" + " b <file> <args> Boot kernel in <file> (- for raw boot)\n" + " i <file> Use <file> as initial ramdisk\n" + " with arguments <args>\n" + " 0-9 Boot preconfiguration 0-9 (list with 'l')\n"); +} + +static void +get_aboot_options (long dev) +{ + int preset = 0; /* no preset */ + int interactive = 0; /* non-interactive */ + char *extra_args = NULL; + +#ifdef DEBUG + printf("get_aboot_options(%lx)\n",dev); + printf("kernel_args=\"%s\"\n",kernel_args); +#endif + + /* Forms of -flags argument from SRM */ + if (kernel_args[0] >= '1' && kernel_args[0] <= '9' + && kernel_args[1] == ':' && kernel_args[2] + && (kernel_args[3] == '\0' || kernel_args[3] == ' ')) + { + /* <partition>:<preset> - where <preset> is an entry + in /etc/aboot.conf (to be found on <partition>), or + 'i' for interactive */ + config_file_partition = kernel_args[0] - '0'; + preset = kernel_args[2]; + if (kernel_args[3]) { + extra_args=&kernel_args[3]; + while (*extra_args == ' ') { extra_args++; } + if (*extra_args == '\0') extra_args = NULL; + } +#ifdef DEBUG + printf("partition:preset = %ld:%c\n", config_file_partition, + preset); +#endif + } else if (kernel_args[0] + && (kernel_args[1] == '\0' || kernel_args[1] == ' ')) { + /* Single character option, for Jensen and friends - + this is either a preconfigured entry in + /etc/aboot.conf or 'i' for interactive*/ + if (kernel_args[0] == 'i') interactive = 1; + else { + preset = kernel_args[0]; + if (kernel_args[1]) { + /* are there actually extra args? */ + extra_args=&kernel_args[1]; + while (*extra_args == ' ') { extra_args++; } + if (*extra_args == '\0') extra_args = NULL; + } + } + } else if (kernel_args[0] == '\0') { + interactive = 1; + } else { + /* attempt to parse the arguments given */ + } + +#ifdef DEBUG + if (extra_args) printf("extra args: \"%s\"\n",extra_args); +#endif + + if (preset || interactive) { + char buf[256], *p; + struct bootfs *fs = 0; + static int first = 1; + int done = 0; + + while (!done) { + /* If we have a setting from /etc/aboot.conf, use it */ + if (preset) { +#ifdef DEBUG + printf("trying preset %c\n", preset); +#endif + if (!fs) { + fs = mount_fs(dev, config_file_partition); + if (!fs) { + preset = 0; + continue; + } + } + if (get_default_args(fs, buf, preset) >= 0) + break; + + /* Doh, keep on going */ + preset = 0; + continue; + } + + /* Otherwise, clear out kernel_args and prompt the user */ + kernel_args[0] = 0; + if (first) { + printf("Welcome to aboot " ABOOT_VERSION "\n"); + print_help(); + first = 0; + } + printf("aboot> "); +#ifdef TESTING + fgets(buf, sizeof(buf), stdin); + buf[strlen(buf)-1] = 0; +#else + getline(buf, sizeof(buf)); +#endif + printf("\n"); + + switch (buf[0]) { + case 'h': + case '?': + print_help(); + break; + case 'q': + halt(); + break; + case 'p': + p = strchr(buf, ' '); + while (p && *p == ' ') ++p; + + if (p && p[0] >= '1' && p[0] <= '8' + && (p[1] == '\0' || p[1] == ' ')) { + config_file_partition = p[0] - '0'; + fs = 0; /* force reread */ + } else { + printf("Please specify a number between 1 and 8\n"); + } + break; + case 'l': + if (!fs) { + fs = mount_fs(dev, config_file_partition); + if (!fs) { + printf("Partition %ld is invalid. " + "Please specify another with 'p'\n", + config_file_partition); + continue; + } + } + print_config_file(fs); + break; + case 'd': + if (!fs) { + fs = mount_fs(dev, config_file_partition); + if (!fs) { + printf("Partition %ld is invalid. " + "Please specify another with 'p'\n", + config_file_partition); + continue; + } + } + /* skip past whitespace */ + p = strchr(buf, ' '); + while (p && *p == ' ') ++p; + if (p) + list_directory(fs, p); + else + list_directory(fs, "/"); + break; + case 'b': + /* skip past whitespace */ + p = strchr(buf, ' '); + while (p && *p == ' ') ++p; + if (p) { + strcpy(buf, p); + done = 1; + } else { + printf("Please specify a file to load the kernel from, " + "or '-' to load the kernel from the boot sector\n"); + } + break; + case 'i': + /* skip past whitespace */ + p = strchr(buf, ' '); + while (p && *p == ' ') ++p; + if (p) + strcpy(initrd_file, p); + else { + printf("Please specify a file to use as initial ramdisk\n"); + } + break; + case '0' ... '9': + preset = buf[0]; + p = strchr(buf, ' '); + while (p && *p == ' ') ++p; + if (p) { + strcpy(kernel_args, p); + extra_args=kernel_args; + } + break; + default: + break; + + } + } + + /* if we have extra args, append them to buf */ + if (extra_args) { + p=buf; while (p && *p) p++; + *p++=' '; + strcpy(p, extra_args); + } + + /* split on space into kernel + args */ + p = strchr(buf, ' '); + if (p) { + /* skip past leading whitespace */ + *p++ = '\0'; + while (p && *p == ' ') ++p; + strcpy(kernel_args, p); + } + strcpy(boot_file, buf); + } + +#ifdef DEBUG + printf("boot_file=\"%s\", kernel_args=\"%s\"\n",boot_file,kernel_args); +#endif + + { + /* parse off initrd= option from kernel_args if any */ + char *p = kernel_args; + /* poor man's strstr */ + do { + if (strncmp(p, "initrd=", 7) == 0) + break; + } while (*p++); + + if (*p) { + char *a = p + 7; /* argument */ + char *e = strchr (a, ' '); + if (e) { + strncpy(initrd_file, a, e-a); + initrd_file[e-a] = 0; + strcpy(p, e); + } else { + strcpy(initrd_file, a); + *p = 0; + } + } + } + + + /* parse off partition number from boot_file if any: */ + if (boot_file[0] >= '0' && boot_file[0] <= '9' && boot_file[1] == '/') + { + boot_part = boot_file[0] - '0'; + strcpy(boot_file, boot_file + 2); + } else { + boot_part = config_file_partition; + } +} + +static long +load (long dev) +{ + char *fname; + +#ifdef DEBUG + printf("load(%lx)\n", dev); +#endif + fname = boot_file; + if (fname[0] == '-' && fname[1] == '\0') { + /* a single "-" implies raw boot: */ + if (load_raw(dev) < 0) { + return -1; + } + } else { + /* if there's no disklabel, boot_part will be ignored anyway */ + bfs = mount_fs(dev, boot_part); + if (!bfs) { + printf("aboot: mount of partition %d failed\n", boot_part); + return -1; + } + if (read_kernel(fname) < 0) { + return -1; + } + } + /* clear bss: */ + printf("aboot: zero-filling %ld bytes at 0x%p\n", bss_size, bss_start); +#ifndef TESTING + memset((char*)bss_start, 0, bss_size); +#endif + + if (initrd_file[0] == 0) + return 0; + + /* work around a bug in the ext2 code */ + bfs = mount_fs(dev, boot_part); + if (!bfs) { + printf("aboot: mount of partition %d failed\n", boot_part); + return -1; + } + if (read_initrd() < 0) { + return -1; + } + return 0; +} + + +long +load_kernel (void) +{ + char envval[256]; + long result; + long dev; + +#ifdef TESTING + const char *e; + if ((e = getenv("BOOTED_DEV"))) { + strncpy(envval, e, sizeof(envval)-1); + envval[sizeof(envval)-1] = 0; + } else { + printf("aboot: Can't get BOOTED_DEV environment variable!\n"); + return -1; + } +#else + if (cons_getenv(ENV_BOOTED_DEV, envval, sizeof(envval)) < 0) { + printf("aboot: Can't get BOOTED_DEV environment variable!\n"); + return -1; + } +#endif + + printf("aboot: booting from device '%s'\n", envval); + dev = cons_open(envval); + if (dev < 0) { + printf("aboot: unable to open boot device `%s': %lx\n", + envval, dev); + return -1; + } + dev &= 0xffffffff; + get_disklabel(dev); + + while (1) { + get_aboot_options(dev); + result = load(dev); + if (result != -1) + break; + /* load failed---query user interactively */ + strcpy(kernel_args, "i"); + } +#ifdef DEBUG + printf("load done\n"); +#endif + cons_close(dev); + return result; +} |