summaryrefslogtreecommitdiffstats
path: root/kexec/arch/i386/kexec-multiboot-x86.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 02:56:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 02:56:35 +0000
commiteba0cfa6b0bef4f2e73c8630a7efa3944df8b0f8 (patch)
tree74c37eede1f0634cc5de1c63c934edaa1630c6bc /kexec/arch/i386/kexec-multiboot-x86.c
parentInitial commit. (diff)
downloadkexec-tools-upstream/1%2.0.27.tar.xz
kexec-tools-upstream/1%2.0.27.zip
Adding upstream version 1:2.0.27.upstream/1%2.0.27upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--kexec/arch/i386/kexec-multiboot-x86.c505
1 files changed, 505 insertions, 0 deletions
diff --git a/kexec/arch/i386/kexec-multiboot-x86.c b/kexec/arch/i386/kexec-multiboot-x86.c
new file mode 100644
index 0000000..33c885a
--- /dev/null
+++ b/kexec/arch/i386/kexec-multiboot-x86.c
@@ -0,0 +1,505 @@
+/*
+ * kexec-multiboot-x86.c
+ *
+ * (partial) multiboot support for kexec. Only supports ELF32
+ * kernels, and a subset of the multiboot info page options
+ * (i.e. enough to boot the Xen hypervisor).
+ *
+ * TODO:
+ * - smarter allocation of new segments
+ * - proper support for the MULTIBOOT_VIDEO_MODE bit
+ *
+ *
+ * Copyright (C) 2003 Tim Deegan (tjd21 at cl.cam.ac.uk)
+ *
+ * Parts based on GNU GRUB, Copyright (C) 2000 Free Software Foundation, Inc
+ * Parts copied from kexec-elf32-x86.c, written by Eric Biederman
+ *
+ * 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., 59 Temple Place - Suite 330, Boston, MA
+ * 02111-1307, USA.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <elf.h>
+#include <boot/elf_boot.h>
+#include <ip_checksum.h>
+#include "../../kexec.h"
+#include "../../kexec-elf.h"
+#include "kexec-x86.h"
+#include <arch/options.h>
+
+/* From GNU GRUB */
+#include <x86/mb_header.h>
+#include <x86/mb_info.h>
+
+/* Framebuffer */
+#include <sys/ioctl.h>
+#include <linux/fb.h>
+
+extern struct arch_options_t arch_options;
+
+/* Static storage */
+static char headerbuf[MULTIBOOT_SEARCH];
+static struct multiboot_header *mbh = NULL;
+static off_t mbh_offset = 0;
+
+#define MIN(_x,_y) (((_x)<=(_y))?(_x):(_y))
+
+
+int multiboot_x86_probe(const char *buf, off_t buf_len)
+/* Is it a good idea to try booting this file? */
+{
+ int i, len;
+ /* Now look for a multiboot header in the first 8KB */
+ len = MULTIBOOT_SEARCH;
+ if (len > buf_len) {
+ len = buf_len;
+ }
+ memcpy(headerbuf, buf, len);
+ if (len < 12) {
+ /* Short file */
+ return -1;
+ }
+ for (mbh_offset = 0; mbh_offset <= (len - 12); mbh_offset += 4)
+ {
+ /* Search for a multiboot header */
+ mbh = (struct multiboot_header *)(headerbuf + mbh_offset);
+ if (mbh->magic != MULTIBOOT_MAGIC
+ || ((mbh->magic+mbh->flags+mbh->checksum) & 0xffffffff))
+ {
+ /* Not a multiboot header */
+ continue;
+ }
+ if (mbh->flags & MULTIBOOT_AOUT_KLUDGE) {
+ if (mbh->load_addr & 0xfff) {
+ fprintf(stderr, "multiboot load address not 4k aligned\n");
+ return -1;
+ }
+ if (mbh->load_addr > mbh->header_addr) {
+ fprintf(stderr, "multiboot header address > load address\n");
+ return -1;
+ }
+ if (mbh->load_end_addr < mbh->load_addr) {
+ fprintf(stderr, "multiboot load end address < load address\n");
+ return -1;
+ }
+ if (mbh->bss_end_addr < mbh->load_end_addr) {
+ fprintf(stderr, "multiboot bss end address < load end address\n");
+ return -1;
+ }
+ if (mbh->load_end_addr - mbh->header_addr > buf_len - mbh_offset) {
+ fprintf(stderr, "multiboot file truncated\n");
+ return -1;
+ }
+ if (mbh->entry_addr < mbh->load_addr || mbh->entry_addr >= mbh->load_end_addr) {
+ fprintf(stderr, "multiboot entry out of range\n");
+ return -1;
+ }
+ } else {
+ if ((i=elf_x86_probe(buf, buf_len)) < 0)
+ return i;
+ }
+ if (mbh->flags & MULTIBOOT_UNSUPPORTED) {
+ /* Requires options we don't support */
+ fprintf(stderr,
+ "Found a multiboot header, but it "
+ "requires multiboot options that I\n"
+ "don't understand. Sorry.\n");
+ return -1;
+ }
+ /* Bootable */
+ return 0;
+ }
+ /* Not multiboot */
+ return -1;
+}
+
+
+void multiboot_x86_usage(void)
+/* Multiboot-specific options */
+{
+ printf(" --command-line=STRING Set the kernel command line to STRING.\n");
+ printf(" --reuse-cmdline Use kernel command line from running system.\n");
+ printf(" --module=\"MOD arg1 arg2...\" Load module MOD with command-line \"arg1...\"\n");
+ printf(" (can be used multiple times).\n");
+}
+
+
+static int framebuffer_info(struct multiboot_info *mbi)
+{
+ struct fb_fix_screeninfo info;
+ struct fb_var_screeninfo mode;
+ int fd;
+
+ /* check if purgatory will reset to standard ega text mode */
+ if (arch_options.reset_vga || arch_options.console_vga) {
+ mbi->framebuffer_type = MB_FRAMEBUFFER_TYPE_EGA_TEXT;
+ mbi->framebuffer_addr = 0xb8000;
+ mbi->framebuffer_pitch = 80*2;
+ mbi->framebuffer_width = 80;
+ mbi->framebuffer_height = 25;
+ mbi->framebuffer_bpp = 16;
+
+ mbi->flags |= MB_INFO_FRAMEBUFFER_INFO;
+ return 0;
+ }
+
+ /* use current graphics framebuffer settings */
+ fd = open("/dev/fb0", O_RDONLY);
+ if (fd < 0) {
+ fprintf(stderr, "can't open /dev/fb0: %s\n", strerror(errno));
+ return -1;
+ }
+ if (ioctl(fd, FBIOGET_FSCREENINFO, &info) < 0){
+ fprintf(stderr, "can't get screeninfo: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ if (ioctl(fd, FBIOGET_VSCREENINFO, &mode) < 0){
+ fprintf(stderr, "can't get modeinfo: %s\n", strerror(errno));
+ close(fd);
+ return -1;
+ }
+ close(fd);
+
+ if (info.smem_start == 0 || info.smem_len == 0) {
+ fprintf(stderr, "can't get linerar framebuffer address\n");
+ return -1;
+ }
+
+ if (info.type != FB_TYPE_PACKED_PIXELS) {
+ fprintf(stderr, "unsupported framebuffer type\n");
+ return -1;
+ }
+
+ if (info.visual != FB_VISUAL_TRUECOLOR) {
+ fprintf(stderr, "unsupported framebuffer visual\n");
+ return -1;
+ }
+
+ mbi->framebuffer_type = MB_FRAMEBUFFER_TYPE_RGB;
+ mbi->framebuffer_addr = info.smem_start;
+ mbi->framebuffer_pitch = info.line_length;
+ mbi->framebuffer_width = mode.xres;
+ mbi->framebuffer_height = mode.yres;
+ mbi->framebuffer_bpp = mode.bits_per_pixel;
+ mbi->framebuffer_red_field_position = mode.red.offset;
+ mbi->framebuffer_red_mask_size = mode.red.length;
+ mbi->framebuffer_green_field_position = mode.green.offset;
+ mbi->framebuffer_green_mask_size = mode.green.length;
+ mbi->framebuffer_blue_field_position = mode.blue.offset;
+ mbi->framebuffer_blue_mask_size = mode.blue.length;
+
+ mbi->flags |= MB_INFO_FRAMEBUFFER_INFO;
+ return 0;
+}
+
+int multiboot_x86_load(int argc, char **argv, const char *buf, off_t len,
+ struct kexec_info *info)
+/* Marshal up a multiboot-style kernel */
+{
+ struct multiboot_info *mbi;
+ void *mbi_buf;
+ struct mod_list *modp;
+ unsigned long freespace;
+ unsigned long long mem_lower = 0, mem_upper = 0;
+ struct mem_ehdr ehdr;
+ unsigned long mbi_base;
+ struct entry32_regs regs;
+ size_t mbi_bytes, mbi_offset;
+ char *command_line = NULL, *tmp_cmdline = NULL;
+ char *imagename, *cp, *append = NULL;;
+ struct memory_range *range;
+ int ranges;
+ struct AddrRangeDesc *mmap;
+ int command_line_len;
+ int i, result;
+ uint32_t u, entry;
+ int opt;
+ int modules, mod_command_line_space;
+ /* See options.h -- add any more there, too. */
+ static const struct option options[] = {
+ KEXEC_ARCH_OPTIONS
+ { "command-line", 1, 0, OPT_CL },
+ { "append", 1, 0, OPT_CL },
+ { "reuse-cmdline", 0, 0, OPT_REUSE_CMDLINE },
+ { "module", 1, 0, OPT_MOD },
+ { 0, 0, 0, 0 },
+ };
+ static const char short_options[] = KEXEC_ARCH_OPT_STR "";
+
+ /* Probe for the MB header if it's not already found */
+ if (mbh == NULL && multiboot_x86_probe(buf, len) != 1) {
+ fprintf(stderr, "Cannot find a loadable multiboot header.\n");
+ return -1;
+ }
+
+ /* Parse the command line */
+ command_line_len = 0;
+ modules = 0;
+ mod_command_line_space = 0;
+ result = 0;
+ while((opt = getopt_long(argc, argv, short_options, options, 0)) != -1) {
+ switch(opt) {
+ default:
+ /* Ignore core options */
+ if (opt < OPT_ARCH_MAX) {
+ break;
+ }
+ case OPT_CL:
+ append = optarg;
+ break;
+ case OPT_REUSE_CMDLINE:
+ command_line = get_command_line();
+ break;
+ case OPT_MOD:
+ modules++;
+ mod_command_line_space += strlen(optarg) + 1;
+ break;
+ }
+ }
+ imagename = argv[optind];
+
+ /* Final command line = imagename + <OPT_REUSE_CMDLINE> + <OPT_CL> */
+ tmp_cmdline = concat_cmdline(command_line, append);
+ if (command_line) {
+ free(command_line);
+ }
+ command_line = concat_cmdline(imagename, tmp_cmdline);
+ if (tmp_cmdline) {
+ free(tmp_cmdline);
+ }
+ command_line_len = strlen(command_line) + 1;
+
+ if (mbh->flags & MULTIBOOT_AOUT_KLUDGE) {
+ add_segment(info,
+ buf + (mbh_offset - (mbh->header_addr - mbh->load_addr)),
+ mbh->load_end_addr - mbh->load_addr,
+ mbh->load_addr,
+ mbh->bss_end_addr - mbh->load_addr);
+ entry = mbh->entry_addr;
+ } else {
+ /* Load the ELF executable */
+ elf_exec_build_load(info, &ehdr, buf, len, 0);
+ entry = ehdr.e_entry;
+ }
+
+ /* Load the setup code */
+ elf_rel_build_load(info, &info->rhdr, purgatory, purgatory_size, 0,
+ ULONG_MAX, 1, 0);
+
+ /* The first segment will contain the multiboot headers:
+ * =============
+ * multiboot information (mbi)
+ * -------------
+ * kernel command line
+ * -------------
+ * bootloader name
+ * -------------
+ * module information entries
+ * -------------
+ * module command lines
+ * ==============
+ */
+ mbi_bytes = _ALIGN(sizeof(*mbi) + command_line_len
+ + strlen (BOOTLOADER " " BOOTLOADER_VERSION) + 1, 4);
+ mbi_buf = xmalloc(mbi_bytes);
+ mbi = mbi_buf;
+ memset(mbi, 0, sizeof(*mbi));
+ sprintf(((char *)mbi) + sizeof(*mbi), "%s", command_line);
+ sprintf(((char *)mbi) + sizeof(*mbi) + command_line_len, "%s",
+ BOOTLOADER " " BOOTLOADER_VERSION);
+ mbi->flags = MB_INFO_CMDLINE | MB_INFO_BOOT_LOADER_NAME;
+ /* We'll relocate these to absolute addresses later. For now,
+ * all addresses within the first segment are relative to the
+ * start of the MBI. */
+ mbi->cmdline = sizeof(*mbi);
+ mbi->boot_loader_name = sizeof(*mbi) + command_line_len;
+
+ /* Memory map */
+ range = info->memory_range;
+ ranges = info->memory_ranges;
+ mmap = xmalloc(ranges * sizeof(*mmap));
+ for (i=0; i<ranges; i++) {
+ unsigned long long length;
+ length = range[i].end - range[i].start + 1;
+ /* Translate bzImage mmap to multiboot-speak */
+ mmap[i].size = sizeof(mmap[i]) - 4;
+ mmap[i].base_addr_low = range[i].start & 0xffffffff;
+ mmap[i].base_addr_high = range[i].start >> 32;
+ mmap[i].length_low = length & 0xffffffff;
+ mmap[i].length_high = length >> 32;
+ switch (range[i].type) {
+ case RANGE_RAM:
+ mmap[i].Type = 1; /* RAM */
+ /*
+ * Is this the "low" memory? Can't just test
+ * against zero, because Linux protects (and
+ * hides) the first few pages of physical
+ * memory.
+ */
+
+ if ((range[i].start <= 64*1024)
+ && (range[i].end > mem_lower)) {
+ range[i].start = 0;
+ mem_lower = range[i].end;
+ }
+ /* Is this the "high" memory? */
+ if ((range[i].start <= 0x100000)
+ && (range[i].end > mem_upper + 0x100000))
+ mem_upper = range[i].end - 0x100000;
+ break;
+ case RANGE_ACPI:
+ mmap[i].Type = 3;
+ break;
+ case RANGE_ACPI_NVS:
+ mmap[i].Type = 4;
+ break;
+ case RANGE_RESERVED:
+ default:
+ mmap[i].Type = 2; /* Not RAM (reserved) */
+ }
+ }
+
+ if (mbh->flags & MULTIBOOT_MEMORY_INFO) {
+ /* Provide a copy of the memory map to the kernel */
+
+ mbi->flags |= MB_INFO_MEMORY | MB_INFO_MEM_MAP;
+
+ freespace = add_buffer(info,
+ mmap, ranges * sizeof(*mmap), ranges * sizeof(*mmap),
+ 4, 0, 0xFFFFFFFFUL, 1);
+
+ mbi->mmap_addr = freespace;
+ mbi->mmap_length = ranges * sizeof(*mmap);
+
+ /* For kernels that care naught for fancy memory maps
+ * and just want the size of low and high memory */
+ mbi->mem_lower = MIN(mem_lower>>10, 0xffffffff);
+ mbi->mem_upper = MIN(mem_upper>>10, 0xffffffff);
+
+ /* done */
+ }
+
+ /* Video */
+ if (mbh->flags & MULTIBOOT_VIDEO_MODE) {
+ if (framebuffer_info(mbi) < 0)
+ fprintf(stderr, "not providing framebuffer information.\n");
+ }
+
+ /* Load modules */
+ if (modules) {
+ char *mod_filename, *mod_command_line, *mod_clp, *buf;
+ off_t mod_size;
+
+ /* We'll relocate this to an absolute address later */
+ mbi->mods_addr = mbi_bytes;
+ mbi->mods_count = 0;
+ mbi->flags |= MB_INFO_MODS;
+
+ /* Add room for the module descriptors to the MBI buffer */
+ mbi_bytes += (sizeof(*modp) * modules)
+ + mod_command_line_space;
+ mbi_buf = xrealloc(mbi_buf, mbi_bytes);
+
+ /* mbi might have moved */
+ mbi = mbi_buf;
+ /* module descriptors go in the newly added space */
+ modp = ((void *)mbi) + mbi->mods_addr;
+ /* module command lines go after the descriptors */
+ mod_clp = ((void *)modp) + (sizeof(*modp) * modules);
+
+ /* Go back and parse the module command lines */
+ optind = opterr = 1;
+ while((opt = getopt_long(argc, argv,
+ short_options, options, 0)) != -1) {
+ if (opt != OPT_MOD) continue;
+
+ /* Split module filename from command line */
+ mod_command_line = mod_filename = optarg;
+ if ((cp = strchr(mod_filename, ' ')) != NULL) {
+ /* See as I discard the 'const' modifier */
+ *cp = '\0';
+ }
+
+ /* Load the module */
+ buf = slurp_decompress_file(mod_filename, &mod_size);
+
+ if (cp != NULL) *cp = ' ';
+
+ /* Pick the next aligned spot to load it in */
+ freespace = add_buffer(info,
+ buf, mod_size, mod_size,
+ getpagesize(), 0, 0xffffffffUL, 1);
+
+ /* Add the module command line */
+ sprintf(mod_clp, "%s", mod_command_line);
+
+ modp->mod_start = freespace;
+ modp->mod_end = freespace + mod_size;
+ modp->cmdline = (void *)mod_clp - (void *)mbi;
+ modp->pad = 0;
+
+ /* Done */
+ mbi->mods_count++;
+ mod_clp += strlen(mod_clp) + 1;
+ modp++;
+ }
+ }
+
+ /* Find a place for the MBI to live */
+ if (sort_segments(info) < 0) {
+ result = -1;
+ goto out;
+ }
+ mbi_base = add_buffer(info,
+ mbi_buf, mbi_bytes, mbi_bytes, 4, 0, 0xFFFFFFFFUL, 1);
+
+ /* Relocate offsets in the MBI to absolute addresses */
+ mbi_offset = mbi_base;
+ modp = ((void *)mbi) + mbi->mods_addr;
+ for (u = 0; u < mbi->mods_count; u++) {
+ modp[u].cmdline += mbi_offset;
+ }
+ mbi->mods_addr += mbi_offset;
+ mbi->cmdline += mbi_offset;
+ mbi->boot_loader_name += mbi_offset;
+
+ /* Specify the initial CPU state and copy the setup code */
+ elf_rel_get_symbol(&info->rhdr, "entry32_regs", &regs, sizeof(regs));
+ regs.eax = 0x2BADB002;
+ regs.ebx = mbi_offset;
+ regs.eip = entry;
+ elf_rel_set_symbol(&info->rhdr, "entry32_regs", &regs, sizeof(regs));
+
+out:
+ free(command_line);
+ return result;
+}
+
+/*
+ * EOF (kexec-multiboot-x86.c)
+ */