mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
370 lines
7.1 KiB
370 lines
7.1 KiB
/* |
|
* Definitions and wrapper functions for kernel decompressor |
|
* |
|
* (C) 2017 Helge Deller <[email protected]> |
|
*/ |
|
|
|
#include <linux/uaccess.h> |
|
#include <linux/elf.h> |
|
#include <asm/unaligned.h> |
|
#include <asm/page.h> |
|
#include "sizes.h" |
|
|
|
/* |
|
* gzip declarations |
|
*/ |
|
#define STATIC static |
|
|
|
#undef memmove |
|
#define memmove memmove |
|
#define memzero(s, n) memset((s), 0, (n)) |
|
|
|
#define malloc malloc_gzip |
|
#define free free_gzip |
|
|
|
/* Symbols defined by linker scripts */ |
|
extern char input_data[]; |
|
extern int input_len; |
|
/* output_len is inserted by the linker possibly at an unaligned address */ |
|
extern __le32 output_len __aligned(1); |
|
extern char _text, _end; |
|
extern char _bss, _ebss; |
|
extern char _startcode_end; |
|
extern void startup_continue(void *entry, unsigned long cmdline, |
|
unsigned long rd_start, unsigned long rd_end) __noreturn; |
|
|
|
void error(char *m) __noreturn; |
|
|
|
static unsigned long free_mem_ptr; |
|
static unsigned long free_mem_end_ptr; |
|
|
|
#ifdef CONFIG_KERNEL_GZIP |
|
#include "../../../../lib/decompress_inflate.c" |
|
#endif |
|
|
|
#ifdef CONFIG_KERNEL_BZIP2 |
|
#include "../../../../lib/decompress_bunzip2.c" |
|
#endif |
|
|
|
#ifdef CONFIG_KERNEL_LZ4 |
|
#include "../../../../lib/decompress_unlz4.c" |
|
#endif |
|
|
|
#ifdef CONFIG_KERNEL_LZMA |
|
#include "../../../../lib/decompress_unlzma.c" |
|
#endif |
|
|
|
#ifdef CONFIG_KERNEL_LZO |
|
#include "../../../../lib/decompress_unlzo.c" |
|
#endif |
|
|
|
#ifdef CONFIG_KERNEL_XZ |
|
#include "../../../../lib/decompress_unxz.c" |
|
#endif |
|
|
|
void *memmove(void *dest, const void *src, size_t n) |
|
{ |
|
const char *s = src; |
|
char *d = dest; |
|
|
|
if (d <= s) { |
|
while (n--) |
|
*d++ = *s++; |
|
} else { |
|
d += n; |
|
s += n; |
|
while (n--) |
|
*--d = *--s; |
|
} |
|
return dest; |
|
} |
|
|
|
void *memset(void *s, int c, size_t count) |
|
{ |
|
char *xs = (char *)s; |
|
|
|
while (count--) |
|
*xs++ = c; |
|
return s; |
|
} |
|
|
|
void *memcpy(void *d, const void *s, size_t len) |
|
{ |
|
char *dest = (char *)d; |
|
const char *source = (const char *)s; |
|
|
|
while (len--) |
|
*dest++ = *source++; |
|
return d; |
|
} |
|
|
|
size_t strlen(const char *s) |
|
{ |
|
const char *sc; |
|
|
|
for (sc = s; *sc != '\0'; ++sc) |
|
; |
|
return sc - s; |
|
} |
|
|
|
char *strchr(const char *s, int c) |
|
{ |
|
while (*s) { |
|
if (*s == (char)c) |
|
return (char *)s; |
|
++s; |
|
} |
|
return NULL; |
|
} |
|
|
|
int puts(const char *s) |
|
{ |
|
const char *nuline = s; |
|
|
|
while ((nuline = strchr(s, '\n')) != NULL) { |
|
if (nuline != s) |
|
pdc_iodc_print(s, nuline - s); |
|
pdc_iodc_print("\r\n", 2); |
|
s = nuline + 1; |
|
} |
|
if (*s != '\0') |
|
pdc_iodc_print(s, strlen(s)); |
|
|
|
return 0; |
|
} |
|
|
|
static int putchar(int c) |
|
{ |
|
char buf[2]; |
|
|
|
buf[0] = c; |
|
buf[1] = '\0'; |
|
puts(buf); |
|
return c; |
|
} |
|
|
|
void __noreturn error(char *x) |
|
{ |
|
if (x) puts(x); |
|
puts("\n -- System halted\n"); |
|
while (1) /* wait forever */ |
|
; |
|
} |
|
|
|
static int print_num(unsigned long num, int base) |
|
{ |
|
const char hex[] = "0123456789abcdef"; |
|
char str[40]; |
|
int i = sizeof(str)-1; |
|
|
|
str[i--] = '\0'; |
|
do { |
|
str[i--] = hex[num % base]; |
|
num = num / base; |
|
} while (num); |
|
|
|
if (base == 16) { |
|
str[i--] = 'x'; |
|
str[i] = '0'; |
|
} else i++; |
|
puts(&str[i]); |
|
|
|
return 0; |
|
} |
|
|
|
int printf(const char *fmt, ...) |
|
{ |
|
va_list args; |
|
int i = 0; |
|
|
|
va_start(args, fmt); |
|
|
|
while (fmt[i]) { |
|
if (fmt[i] != '%') { |
|
put: |
|
putchar(fmt[i++]); |
|
continue; |
|
} |
|
|
|
if (fmt[++i] == '%') |
|
goto put; |
|
print_num(va_arg(args, unsigned long), |
|
fmt[i] == 'x' ? 16:10); |
|
++i; |
|
} |
|
|
|
va_end(args); |
|
return 0; |
|
} |
|
|
|
/* helper functions for libgcc */ |
|
void abort(void) |
|
{ |
|
error("aborted."); |
|
} |
|
|
|
#undef malloc |
|
void *malloc(size_t size) |
|
{ |
|
return malloc_gzip(size); |
|
} |
|
|
|
#undef free |
|
void free(void *ptr) |
|
{ |
|
return free_gzip(ptr); |
|
} |
|
|
|
|
|
static void flush_data_cache(char *start, unsigned long length) |
|
{ |
|
char *end = start + length; |
|
|
|
do { |
|
asm volatile("fdc 0(%0)" : : "r" (start)); |
|
asm volatile("fic 0(%%sr0,%0)" : : "r" (start)); |
|
start += 16; |
|
} while (start < end); |
|
asm volatile("fdc 0(%0)" : : "r" (end)); |
|
|
|
asm ("sync"); |
|
} |
|
|
|
static void parse_elf(void *output) |
|
{ |
|
#ifdef CONFIG_64BIT |
|
Elf64_Ehdr ehdr; |
|
Elf64_Phdr *phdrs, *phdr; |
|
#else |
|
Elf32_Ehdr ehdr; |
|
Elf32_Phdr *phdrs, *phdr; |
|
#endif |
|
void *dest; |
|
int i; |
|
|
|
memcpy(&ehdr, output, sizeof(ehdr)); |
|
if (ehdr.e_ident[EI_MAG0] != ELFMAG0 || |
|
ehdr.e_ident[EI_MAG1] != ELFMAG1 || |
|
ehdr.e_ident[EI_MAG2] != ELFMAG2 || |
|
ehdr.e_ident[EI_MAG3] != ELFMAG3) { |
|
error("Kernel is not a valid ELF file"); |
|
return; |
|
} |
|
|
|
#ifdef DEBUG |
|
printf("Parsing ELF... "); |
|
#endif |
|
|
|
phdrs = malloc(sizeof(*phdrs) * ehdr.e_phnum); |
|
if (!phdrs) |
|
error("Failed to allocate space for phdrs"); |
|
|
|
memcpy(phdrs, output + ehdr.e_phoff, sizeof(*phdrs) * ehdr.e_phnum); |
|
|
|
for (i = 0; i < ehdr.e_phnum; i++) { |
|
phdr = &phdrs[i]; |
|
|
|
switch (phdr->p_type) { |
|
case PT_LOAD: |
|
dest = (void *)((unsigned long) phdr->p_paddr & |
|
(__PAGE_OFFSET_DEFAULT-1)); |
|
memmove(dest, output + phdr->p_offset, phdr->p_filesz); |
|
break; |
|
default: |
|
break; |
|
} |
|
} |
|
|
|
free(phdrs); |
|
} |
|
|
|
unsigned long decompress_kernel(unsigned int started_wide, |
|
unsigned int command_line, |
|
const unsigned int rd_start, |
|
const unsigned int rd_end) |
|
{ |
|
char *output; |
|
unsigned long vmlinux_addr, vmlinux_len; |
|
unsigned long kernel_addr, kernel_len; |
|
|
|
#ifdef CONFIG_64BIT |
|
parisc_narrow_firmware = 0; |
|
#endif |
|
|
|
set_firmware_width_unlocked(); |
|
|
|
putchar('D'); /* if you get this D and no more, string storage */ |
|
/* in $GLOBAL$ is wrong or %dp is wrong */ |
|
puts("ecompressing Linux... "); |
|
|
|
/* where the final bits are stored */ |
|
kernel_addr = KERNEL_BINARY_TEXT_START; |
|
kernel_len = __pa(SZ_end) - __pa(SZparisc_kernel_start); |
|
if ((unsigned long) &_startcode_end > kernel_addr) |
|
error("Bootcode overlaps kernel code"); |
|
|
|
/* |
|
* Calculate addr to where the vmlinux ELF file shall be decompressed. |
|
* Assembly code in head.S positioned the stack directly behind bss, so |
|
* leave 2 MB for the stack. |
|
*/ |
|
vmlinux_addr = (unsigned long) &_ebss + 2*1024*1024; |
|
vmlinux_len = get_unaligned_le32(&output_len); |
|
output = (char *) vmlinux_addr; |
|
|
|
/* |
|
* Initialize free_mem_ptr and free_mem_end_ptr. |
|
*/ |
|
free_mem_ptr = vmlinux_addr + vmlinux_len; |
|
|
|
/* Limit memory for bootoader to 1GB */ |
|
#define ARTIFICIAL_LIMIT (1*1024*1024*1024) |
|
free_mem_end_ptr = PAGE0->imm_max_mem; |
|
if (free_mem_end_ptr > ARTIFICIAL_LIMIT) |
|
free_mem_end_ptr = ARTIFICIAL_LIMIT; |
|
|
|
#ifdef CONFIG_BLK_DEV_INITRD |
|
/* if we have ramdisk this is at end of memory */ |
|
if (rd_start && rd_start < free_mem_end_ptr) |
|
free_mem_end_ptr = rd_start; |
|
#endif |
|
|
|
if (free_mem_ptr >= free_mem_end_ptr) { |
|
int free_ram; |
|
free_ram = (free_mem_ptr >> 20) + 1; |
|
if (free_ram < 32) |
|
free_ram = 32; |
|
printf("\nKernel requires at least %d MB RAM.\n", |
|
free_ram); |
|
error(NULL); |
|
} |
|
|
|
#ifdef DEBUG |
|
printf("\n"); |
|
printf("startcode_end = %x\n", &_startcode_end); |
|
printf("commandline = %x\n", command_line); |
|
printf("rd_start = %x\n", rd_start); |
|
printf("rd_end = %x\n", rd_end); |
|
|
|
printf("free_ptr = %x\n", free_mem_ptr); |
|
printf("free_ptr_end = %x\n", free_mem_end_ptr); |
|
|
|
printf("input_data = %x\n", input_data); |
|
printf("input_len = %x\n", input_len); |
|
printf("output = %x\n", output); |
|
printf("output_len = %x\n", vmlinux_len); |
|
printf("kernel_addr = %x\n", kernel_addr); |
|
printf("kernel_len = %x\n", kernel_len); |
|
#endif |
|
|
|
__decompress(input_data, input_len, NULL, NULL, |
|
output, 0, NULL, error); |
|
parse_elf(output); |
|
|
|
output = (char *) kernel_addr; |
|
flush_data_cache(output, kernel_len); |
|
|
|
printf("done.\nBooting the kernel.\n"); |
|
|
|
return (unsigned long) output; |
|
}
|
|
|