root/arch/parisc/boot/compressed/misc.c
/*
 * Definitions and wrapper functions for kernel decompressor
 *
 *   (C) 2017 Helge Deller <deller@gmx.de>
 */

#include <linux/uaccess.h>
#include <linux/elf.h>
#include <linux/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 char output_len;
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;
}

static 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;
}

static 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
static void *malloc(size_t size)
{
        return malloc_gzip(size);
}

#undef free
static 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);
}

/*
 * The regular get_unaligned_le32 uses __builtin_memcpy which can trigger
 * warnings when reading a byte/char output_len as an integer, as the size of a
 * char is less than that of an integer. Use type punning and the packed
 * attribute, which requires -fno-strict-aliasing, to work around the problem.
 */
static u32 punned_get_unaligned_le32(const void *p)
{
        const struct { __le32 x; } __packed * __get_pptr = p;

        return le32_to_cpu(__get_pptr->x);
}

asmlinkage unsigned long __visible 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 = punned_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;
}