// SPDX-License-Identifier: GPL-2.0
/*
 * Decompression and self-relocation for the compressed kernel boot stub,
 * based on arch/parisc/boot/compressed/misc.c.
 *
 * aboot loads the stub as an ordinary ELF at START_ADDR and jumps to it
 * with the parameter page and stack already set up; vmlinux.lds.S
 * explains why the stub has to be linked there. We decompress above our
 * own bss and never write below START_ADDR, so what aboot prepared stays
 * valid for the real kernel.
 */

#include <linux/elf.h>
#include <linux/kernel.h>
#include <linux/string.h>
#include <linux/types.h>

#include <asm/hwrpb.h>
#include <asm/page.h>
#include <asm/pal.h>
#include <asm/setup.h>

#define STATIC static
#define memzero(s, n) memset((s), 0, (n))

/*
 * decompress_unxz.c supplies its own memmove() unless the identifier is
 * already a macro. We link our own build of arch/alpha/lib/memmove.S,
 * which is better, so take that instead.
 */
#define memmove memmove

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 _ebss[];
extern char trampoline_start[], trampoline_end[];

static unsigned long free_mem_ptr;
static unsigned long free_mem_end_ptr;
static unsigned long mem_end;

/* The stub has no console, so the message is only for debuggers. */
void __noreturn error(char *m)
{
	while (1)
		__halt();
}

#ifdef CONFIG_KERNEL_BZIP2
#include "../../../../lib/decompress_bunzip2.c"
#endif

#ifdef CONFIG_KERNEL_GZIP
#include "../../../../lib/decompress_inflate.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

#ifdef CONFIG_KERNEL_ZSTD
#include "../../../../lib/decompress_unzstd.c"
#endif

/* Scratch heap for the decompressors, larger than any of them needs. */
#define STUB_HEAP_SIZE	(4 * 1024 * 1024)

/*
 * Type punning through a packed struct, to avoid the warnings the regular
 * get_unaligned_le32() produces when reading a char-typed symbol as an
 * integer.
 */
static u32 punned_get_unaligned_le32(const void *p)
{
	const struct { __le32 x; } __packed *pp = p;

	return le32_to_cpu(pp->x);
}

/*
 * End of the usable memory cluster that holds the kernel, or 0 if the
 * HWRPB does not describe one. Used to check that the decompressed image
 * and the stub's scratch space fit before anything is written.
 */
static unsigned long usable_memory_end(void)
{
	struct hwrpb_struct *hwrpb;
	struct memdesc_struct *memdesc;
	unsigned long pfn = (START_ADDR - PAGE_OFFSET) >> PAGE_SHIFT;
	unsigned long i;

	hwrpb = (struct hwrpb_struct *)(PAGE_OFFSET + INIT_HWRPB->phys_addr);
	memdesc = (struct memdesc_struct *)((unsigned long)hwrpb +
					    hwrpb->mddt_offset);

	for (i = 0; i < memdesc->numclusters; i++) {
		struct memclust_struct *cluster = &memdesc->cluster[i];
		unsigned long end_pfn = cluster->start_pfn + cluster->numpages;

		if (cluster->usage)
			continue;
		if (pfn >= cluster->start_pfn && pfn < end_pfn)
			return PAGE_OFFSET + (end_pfn << PAGE_SHIFT);
	}

	return 0;
}

/*
 * Move the decompressed kernel's single PT_LOAD segment down onto
 * START_ADDR and jump to its entry point. The kernel is always linked
 * with exactly one PT_LOAD whose p_vaddr equals its e_entry equals
 * START_ADDR (see arch/alpha/kernel/vmlinux.lds.S); anything else we do
 * not know how to boot.
 *
 * The copy cannot run from this stub, which is itself executing at
 * START_ADDR; see trampoline.S.
 */
static void __noreturn relocate_and_jump(void *image)
{
	void (*trampoline)(unsigned long dest, unsigned long src,
			   unsigned long filesz, unsigned long memsz,
			   unsigned long entry);
	unsigned long tramp_len = trampoline_end - trampoline_start;
	Elf64_Phdr phdr, load_phdr = {};
	Elf64_Ehdr ehdr;
	int i, found_load = 0;
	unsigned long tramp;

	memcpy(&ehdr, image, 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 ||
	    ehdr.e_ident[EI_CLASS] != ELFCLASS64 ||
	    ehdr.e_ident[EI_DATA] != ELFDATA2LSB ||
	    ehdr.e_machine != EM_ALPHA ||
	    ehdr.e_type != ET_EXEC ||
	    ehdr.e_entry != START_ADDR)
		error("Not a bootable Alpha kernel image");

	for (i = 0; i < ehdr.e_phnum; i++) {
		memcpy(&phdr, image + ehdr.e_phoff + i * ehdr.e_phentsize,
		       sizeof(phdr));

		if (phdr.p_type != PT_LOAD)
			continue;
		if (found_load || phdr.p_vaddr != START_ADDR)
			error("Unexpected kernel segment layout");
		found_load = 1;
		load_phdr = phdr;
	}
	if (!found_load)
		error("Kernel image has no PT_LOAD segment");

	/*
	 * The trampoline has to outlive both the scratch heap it is copied
	 * into and the region it is about to write, which extends past the
	 * kernel's file image to the end of its bss.
	 */
	tramp = ALIGN(max(free_mem_end_ptr, START_ADDR + load_phdr.p_memsz), 8);
	if (mem_end && tramp + tramp_len > mem_end)
		error("Not enough memory to relocate the kernel");

	memcpy((void *)tramp, trampoline_start, tramp_len);
	imb();

	trampoline = (void *)tramp;
	trampoline(load_phdr.p_vaddr,
		   (unsigned long)image + load_phdr.p_offset,
		   load_phdr.p_filesz, load_phdr.p_memsz, ehdr.e_entry);
	unreachable();
}

void __noreturn decompress_and_boot(void)
{
	unsigned long image_len = punned_get_unaligned_le32(&output_len);
	void *image = (void *)ALIGN((unsigned long)_ebss, PAGE_SIZE);

	free_mem_ptr = ALIGN((unsigned long)image + image_len, PAGE_SIZE);
	free_mem_end_ptr = free_mem_ptr + STUB_HEAP_SIZE;

	mem_end = usable_memory_end();
	if (mem_end && free_mem_end_ptr > mem_end)
		error("Not enough memory to decompress the kernel");

	/* Some decompressors report failure only through the return value. */
	if (__decompress(input_data, input_len, NULL, NULL, image, image_len,
			 NULL, error))
		error("Kernel decompression failed");

	relocate_and_jump(image);
}
