// 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 #include #include #include #include #include #include #include #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); }