diff options
Diffstat (limited to 'arch/x86/boot/compressed/misc.c')
| -rw-r--r-- | arch/x86/boot/compressed/misc.c | 187 | 
1 files changed, 111 insertions, 76 deletions
diff --git a/arch/x86/boot/compressed/misc.c b/arch/x86/boot/compressed/misc.c index 23f315c9f21..57ab74df7ee 100644 --- a/arch/x86/boot/compressed/misc.c +++ b/arch/x86/boot/compressed/misc.c @@ -10,6 +10,7 @@   */  #include "misc.h" +#include "../string.h"  /* WARNING!!   * This code is compiled with -fPIC and it is relocated dynamically @@ -97,8 +98,14 @@   */  #define STATIC		static -#undef memset  #undef memcpy + +/* + * Use a normal definition of memset() from string.c. There are already + * included header files which expect a definition of memset() and by + * the time we define memset macro, it is too late. + */ +#undef memset  #define memzero(s, n)	memset((s), 0, (n)) @@ -108,20 +115,9 @@ static void error(char *m);   * This is set up by the setup-routine at boot-time   */  struct boot_params *real_mode;		/* Pointer to real-mode data */ -static int quiet; -static int debug; - -void *memset(void *s, int c, size_t n); -void *memcpy(void *dest, const void *src, size_t n); - -#ifdef CONFIG_X86_64 -#define memptr long -#else -#define memptr unsigned -#endif -static memptr free_mem_ptr; -static memptr free_mem_end_ptr; +memptr free_mem_ptr; +memptr free_mem_end_ptr;  static char *vidmem;  static int vidport; @@ -139,10 +135,18 @@ static int lines, cols;  #include "../../../../lib/decompress_unlzma.c"  #endif +#ifdef CONFIG_KERNEL_XZ +#include "../../../../lib/decompress_unxz.c" +#endif +  #ifdef CONFIG_KERNEL_LZO  #include "../../../../lib/decompress_unlzo.c"  #endif +#ifdef CONFIG_KERNEL_LZ4 +#include "../../../../lib/decompress_unlz4.c" +#endif +  static void scroll(void)  {  	int i; @@ -166,15 +170,11 @@ static void serial_putchar(int ch)  	outb(ch, early_serial_base + TXR);  } -void __putstr(int error, const char *s) +void __putstr(const char *s)  {  	int x, y, pos;  	char c; -#ifndef CONFIG_X86_VERBOSE_BOOTUP -	if (!error) -		return; -#endif  	if (early_serial_base) {  		const char *str = s;  		while (*str) { @@ -220,54 +220,88 @@ void __putstr(int error, const char *s)  	outb(0xff & (pos >> 1), vidport+1);  } -void *memset(void *s, int c, size_t n) +static void error(char *x)  { -	int i; -	char *ss = s; +	error_putstr("\n\n"); +	error_putstr(x); +	error_putstr("\n\n -- System halted"); -	for (i = 0; i < n; i++) -		ss[i] = c; -	return s; -} -#ifdef CONFIG_X86_32 -void *memcpy(void *dest, const void *src, size_t n) -{ -	int d0, d1, d2; -	asm volatile( -		"rep ; movsl\n\t" -		"movl %4,%%ecx\n\t" -		"rep ; movsb\n\t" -		: "=&c" (d0), "=&D" (d1), "=&S" (d2) -		: "0" (n >> 2), "g" (n & 3), "1" (dest), "2" (src) -		: "memory"); - -	return dest; -} -#else -void *memcpy(void *dest, const void *src, size_t n) -{ -	long d0, d1, d2; -	asm volatile( -		"rep ; movsq\n\t" -		"movq %4,%%rcx\n\t" -		"rep ; movsb\n\t" -		: "=&c" (d0), "=&D" (d1), "=&S" (d2) -		: "0" (n >> 3), "g" (n & 7), "1" (dest), "2" (src) -		: "memory"); - -	return dest; +	while (1) +		asm("hlt");  } -#endif -static void error(char *x) +#if CONFIG_X86_NEED_RELOCS +static void handle_relocations(void *output, unsigned long output_len)  { -	__putstr(1, "\n\n"); -	__putstr(1, x); -	__putstr(1, "\n\n -- System halted"); +	int *reloc; +	unsigned long delta, map, ptr; +	unsigned long min_addr = (unsigned long)output; +	unsigned long max_addr = min_addr + output_len; + +	/* +	 * Calculate the delta between where vmlinux was linked to load +	 * and where it was actually loaded. +	 */ +	delta = min_addr - LOAD_PHYSICAL_ADDR; +	if (!delta) { +		debug_putstr("No relocation needed... "); +		return; +	} +	debug_putstr("Performing relocations... "); + +	/* +	 * The kernel contains a table of relocation addresses. Those +	 * addresses have the final load address of the kernel in virtual +	 * memory. We are currently working in the self map. So we need to +	 * create an adjustment for kernel memory addresses to the self map. +	 * This will involve subtracting out the base address of the kernel. +	 */ +	map = delta - __START_KERNEL_map; + +	/* +	 * Process relocations: 32 bit relocations first then 64 bit after. +	 * Two sets of binary relocations are added to the end of the kernel +	 * before compression. Each relocation table entry is the kernel +	 * address of the location which needs to be updated stored as a +	 * 32-bit value which is sign extended to 64 bits. +	 * +	 * Format is: +	 * +	 * kernel bits... +	 * 0 - zero terminator for 64 bit relocations +	 * 64 bit relocation repeated +	 * 0 - zero terminator for 32 bit relocations +	 * 32 bit relocation repeated +	 * +	 * So we work backwards from the end of the decompressed image. +	 */ +	for (reloc = output + output_len - sizeof(*reloc); *reloc; reloc--) { +		int extended = *reloc; +		extended += map; + +		ptr = (unsigned long)extended; +		if (ptr < min_addr || ptr > max_addr) +			error("32-bit relocation outside of kernel!\n"); + +		*(uint32_t *)ptr += delta; +	} +#ifdef CONFIG_X86_64 +	for (reloc--; *reloc; reloc--) { +		long extended = *reloc; +		extended += map; -	while (1) -		asm("hlt"); +		ptr = (unsigned long)extended; +		if (ptr < min_addr || ptr > max_addr) +			error("64-bit relocation outside of kernel!\n"); + +		*(uint64_t *)ptr += delta; +	} +#endif  } +#else +static inline void handle_relocations(void *output, unsigned long output_len) +{ } +#endif  static void parse_elf(void *output)  { @@ -290,8 +324,7 @@ static void parse_elf(void *output)  		return;  	} -	if (!quiet) -		putstr("Parsing ELF... "); +	debug_putstr("Parsing ELF... ");  	phdrs = malloc(sizeof(*phdrs) * ehdr.e_phnum);  	if (!phdrs) @@ -317,19 +350,19 @@ static void parse_elf(void *output)  		default: /* Ignore other PT_* */ break;  		}  	} + +	free(phdrs);  } -asmlinkage void decompress_kernel(void *rmode, memptr heap, +asmlinkage __visible void *decompress_kernel(void *rmode, memptr heap,  				  unsigned char *input_data,  				  unsigned long input_len, -				  unsigned char *output) +				  unsigned char *output, +				  unsigned long output_len)  {  	real_mode = rmode; -	if (cmdline_find_option_bool("quiet")) -		quiet = 1; -	if (cmdline_find_option_bool("debug")) -		debug = 1; +	sanitize_boot_params(real_mode);  	if (real_mode->screen_info.orig_video_mode == 7) {  		vidmem = (char *) 0xb0000; @@ -343,19 +376,22 @@ asmlinkage void decompress_kernel(void *rmode, memptr heap,  	cols = real_mode->screen_info.orig_video_cols;  	console_init(); -	if (debug) -		putstr("early console in decompress_kernel\n"); +	debug_putstr("early console in decompress_kernel\n");  	free_mem_ptr     = heap;	/* Heap */  	free_mem_end_ptr = heap + BOOT_HEAP_SIZE; +	output = choose_kernel_location(input_data, input_len, +					output, output_len); + +	/* Validate memory location choices. */  	if ((unsigned long)output & (MIN_KERNEL_ALIGN - 1))  		error("Destination address inappropriately aligned");  #ifdef CONFIG_X86_64  	if (heap > 0x3fffffffffffUL)  		error("Destination address too large");  #else -	if (heap > ((-__PAGE_OFFSET-(512<<20)-1) & 0x7fffffff)) +	if (heap > ((-__PAGE_OFFSET-(128<<20)-1) & 0x7fffffff))  		error("Destination address too large");  #endif  #ifndef CONFIG_RELOCATABLE @@ -363,11 +399,10 @@ asmlinkage void decompress_kernel(void *rmode, memptr heap,  		error("Wrong destination address");  #endif -	if (!quiet) -		putstr("\nDecompressing Linux... "); +	debug_putstr("\nDecompressing Linux... ");  	decompress(input_data, input_len, NULL, NULL, output, NULL, error);  	parse_elf(output); -	if (!quiet) -		putstr("done.\nBooting the kernel.\n"); -	return; +	handle_relocations(output, output_len); +	debug_putstr("done.\nBooting the kernel.\n"); +	return output;  }  | 
