mirror of
				https://github.com/meizu-m86/kexec-tools-arm64
				synced 2025-11-04 13:56:01 +08:00 
			
		
		
		
	- Initial import into git - initial nbi image formage support - ppc32 initial register setting fixes. - gzipped multiboot file support
		
			
				
	
	
		
			394 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			394 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 *  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
 | 
						|
 *    - support for the MULTIBOOT_AOUT_KLUDGE 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 <sys/mman.h>
 | 
						|
#include <fcntl.h>
 | 
						|
#include <unistd.h>
 | 
						|
#include <getopt.h>
 | 
						|
#include <elf.h>
 | 
						|
#include <boot/elf_boot.h>
 | 
						|
#include <asm/page.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>
 | 
						|
 | 
						|
/* Static storage */
 | 
						|
static char headerbuf[MULTIBOOT_SEARCH];
 | 
						|
static struct multiboot_header *mbh = NULL;
 | 
						|
 | 
						|
#define BOOTLOADER "kexec"
 | 
						|
#define BOOTLOADER_VERSION VERSION
 | 
						|
#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;
 | 
						|
	/* First of all, check that this is an ELF file */
 | 
						|
	if ((i=elf_x86_probe(buf, buf_len)) < 0) {
 | 
						|
		return i;
 | 
						|
	}
 | 
						|
	/* 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 (i = 0; i <= (len - 12); i += 4)
 | 
						|
	{
 | 
						|
		/* Search for a multiboot header */
 | 
						|
		mbh = (struct multiboot_header *)(headerbuf + i);
 | 
						|
		if (mbh->magic != MULTIBOOT_MAGIC 
 | 
						|
		    || ((mbh->magic+mbh->flags+mbh->checksum) & 0xffffffff))
 | 
						|
		{
 | 
						|
			/* Not a multiboot header */
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		if (mbh->flags & MULTIBOOT_AOUT_KLUDGE) {
 | 
						|
			/* Requires options we don't support */
 | 
						|
			fprintf(stderr, 
 | 
						|
				"Found a multiboot header, but it uses "
 | 
						|
				"a non-ELF header layout,\n"
 | 
						|
				"and I can't do that (yet).  Sorry.\n");
 | 
						|
			return -1;
 | 
						|
		} 
 | 
						|
		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;
 | 
						|
		} 
 | 
						|
		if (mbh->flags & MULTIBOOT_VIDEO_MODE) { 
 | 
						|
			/* Asked for screen mode information */
 | 
						|
			/* XXX carry on regardless */
 | 
						|
			fprintf(stderr, 
 | 
						|
				"BEWARE!  Found a multiboot header which asks "
 | 
						|
				"for screen mode information.\n"
 | 
						|
				"BEWARE!  I am NOT supplying screen mode "
 | 
						|
                                "information, but loading it regardless.\n");
 | 
						|
			
 | 
						|
		}
 | 
						|
		/* 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("    --module=\"MOD arg1 arg2...\"  Load module MOD with command-line \"arg1...\"\n");
 | 
						|
	printf("                                 (can be used multiple times).\n");
 | 
						|
}
 | 
						|
 | 
						|
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;
 | 
						|
	const char *command_line=NULL;
 | 
						|
	char *imagename, *cp;
 | 
						|
	struct memory_range *range;
 | 
						|
	int ranges;
 | 
						|
	struct AddrRangeDesc *mmap;
 | 
						|
	int command_line_len;
 | 
						|
	int i;
 | 
						|
	int opt;
 | 
						|
	int modules, mod_command_line_space;
 | 
						|
#define OPT_CL  	(OPT_ARCH_MAX+0)
 | 
						|
#define OPT_MOD 	(OPT_ARCH_MAX+1)
 | 
						|
#define OPT_VGA 	(OPT_ARCH_MAX+2)
 | 
						|
	static const struct option options[] = {
 | 
						|
		KEXEC_ARCH_OPTIONS
 | 
						|
		{ "command-line",		1, 0, OPT_CL },
 | 
						|
		{ "append",			1, 0, OPT_CL },
 | 
						|
		{ "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 = "";
 | 
						|
	command_line_len = 0;
 | 
						|
	modules = 0;
 | 
						|
	mod_command_line_space = 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 '?':
 | 
						|
			usage();
 | 
						|
			return -1;
 | 
						|
		case OPT_CL:
 | 
						|
			command_line = optarg;
 | 
						|
			break;
 | 
						|
		case OPT_MOD:
 | 
						|
			modules++;
 | 
						|
			mod_command_line_space += strlen(optarg) + 1;
 | 
						|
			break;
 | 
						|
		}
 | 
						|
	}
 | 
						|
	imagename = argv[optind];
 | 
						|
	command_line_len = strlen(command_line) + strlen(imagename) + 2;
 | 
						|
 | 
						|
 | 
						|
	
 | 
						|
	/* Load the ELF executable */
 | 
						|
	elf_exec_build_load(info, &ehdr, buf, len);
 | 
						|
 | 
						|
	/* Load the setup code */
 | 
						|
	elf_rel_build_load(info, &info->rhdr, purgatory, purgatory_size, 0, ULONG_MAX, 1);
 | 
						|
	
 | 
						|
	/* The first segment will contain the multiboot headers:
 | 
						|
	 * =============
 | 
						|
	 * multiboot information (mbi)
 | 
						|
	 * -------------
 | 
						|
	 * kernel command line
 | 
						|
	 * -------------
 | 
						|
	 * bootloader name
 | 
						|
	 * -------------
 | 
						|
	 * module information entries
 | 
						|
	 * -------------
 | 
						|
	 * module command lines
 | 
						|
	 * ==============
 | 
						|
	 */
 | 
						|
	mbi_bytes = (sizeof(*mbi) + command_line_len 
 | 
						|
		     + strlen (BOOTLOADER " " BOOTLOADER_VERSION) + 1
 | 
						|
		     + 3) & ~3;
 | 
						|
	mbi_buf = xmalloc(mbi_bytes);
 | 
						|
	mbi = mbi_buf;
 | 
						|
	memset(mbi, 0, sizeof(*mbi));
 | 
						|
	sprintf(((char *)mbi) + sizeof(*mbi), "%s %s",
 | 
						|
		imagename, 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 */
 | 
						|
	if ((get_memory_ranges(&range, &ranges) < 0) || ranges == 0) {
 | 
						|
		fprintf(stderr, "Cannot get memory information\n");
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
	mmap = xmalloc(ranges * sizeof(*mmap));
 | 
						|
	for (i=0; i<ranges; i++) {
 | 
						|
		unsigned long long length;
 | 
						|
		length = range[i].end - range[i].start;
 | 
						|
		/* 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;
 | 
						|
		if (range[i].type == RANGE_RAM) {
 | 
						|
			mmap[i].Type = 1; /* RAM */
 | 
						|
			/* Is this the "low" memory? */
 | 
						|
			if ((range[i].start == 0)
 | 
						|
			    && (range[i].end > mem_lower))
 | 
						|
				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;
 | 
						|
		}
 | 
						|
		else
 | 
						|
		mmap[i].Type = 0xbad;  /* Not RAM */
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	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 */
 | 
						|
	}
 | 
						|
 | 
						|
 | 
						|
	/* 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,
 | 
						|
				PAGE_SIZE, 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) {
 | 
						|
                return -1;
 | 
						|
        }
 | 
						|
	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 (i=0; i<mbi->mods_count; i++) {
 | 
						|
		modp[i].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", ®s, sizeof(regs));
 | 
						|
	regs.eax = 0x2BADB002;
 | 
						|
	regs.ebx = mbi_offset;
 | 
						|
	regs.eip = ehdr.e_entry;
 | 
						|
	elf_rel_set_symbol(&info->rhdr, "entry32_regs", ®s, sizeof(regs));
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 *  EOF (kexec-multiboot-x86.c)
 | 
						|
 */
 | 
						|
 |