| /* | |
| * Definitions and wrapper functions for kernel decompressor | |
| * | |
| * (C) 2017 Helge Deller <deller@gmx.de> | |
| */ | |
| #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; | |
| } |