diff --git a/arch/alpha/Kconfig b/arch/alpha/Kconfig index 04511242506b..2688bb00f175 100644 --- a/arch/alpha/Kconfig +++ b/arch/alpha/Kconfig @@ -30,6 +30,7 @@ config ALPHA select HAVE_ASM_MODVERSIONS select HAVE_DEBUG_KMEMLEAK select HAVE_GUP_FAST + select HAVE_KERNEL_GZIP select HAVE_PAGE_SIZE_8KB select HAVE_PCSPKR_PLATFORM select HAVE_PERF_EVENTS diff --git a/arch/alpha/Makefile b/arch/alpha/Makefile index 7074602dca94..405d4bfd644c 100644 --- a/arch/alpha/Makefile +++ b/arch/alpha/Makefile @@ -47,9 +47,9 @@ export LIBS_Y boot := arch/alpha/boot #Default target when executing make with no arguments -all boot: $(boot)/vmlinux.gz +all boot: $(boot)/vmlinuz -$(boot)/vmlinux.gz: vmlinux +$(boot)/vmlinuz: vmlinux $(Q)$(MAKE) $(build)=$(boot) $@ bootimage bootpfile bootpzfile: vmlinux @@ -59,7 +59,7 @@ archheaders: $(Q)$(MAKE) $(build)=arch/alpha/kernel/syscalls all define archhelp - echo '* boot - Compressed kernel image (arch/alpha/boot/vmlinux.gz)' + echo '* boot - Compressed kernel image (arch/alpha/boot/vmlinuz)' echo ' bootimage - SRM bootable image (arch/alpha/boot/bootimage)' echo ' bootpfile - BOOTP bootable image (arch/alpha/boot/bootpfile)' echo ' bootpzfile - compressed kernel BOOTP image (arch/alpha/boot/bootpzfile)' diff --git a/arch/alpha/boot/.gitignore b/arch/alpha/boot/.gitignore new file mode 100644 index 000000000000..4125506521f6 --- /dev/null +++ b/arch/alpha/boot/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +vmlinuz diff --git a/arch/alpha/boot/Makefile b/arch/alpha/boot/Makefile index d8dba85e606c..7c2a41c1e2a0 100644 --- a/arch/alpha/boot/Makefile +++ b/arch/alpha/boot/Makefile @@ -9,9 +9,10 @@ # hostprogs := tools/mkbb tools/objstrip -targets := vmlinux.gz vmlinux \ +targets := vmlinuz vmlinux \ vmlinux.nh tools/lxboot tools/bootlx tools/bootph \ - tools/bootpzh bootloader bootpheader bootpzheader + tools/bootpzh bootloader bootpheader bootpzheader +subdir- := compressed OBJSTRIP := $(obj)/tools/objstrip KBUILD_HOSTCFLAGS := -Wall -I$(objtree)/usr/include @@ -38,9 +39,14 @@ ifdef INITRD cat $(INITRD) >> $@ endif -# Compressed kernel image -$(obj)/vmlinux.gz: $(obj)/vmlinux FORCE - $(call if_changed,gzip) +# Self-extracting compressed kernel image: a small ELF stub (see +# compressed/) with the real vmlinux linked in compressed, which aboot +# loads and enters like a plain vmlinux. +$(obj)/compressed/vmlinux: vmlinux FORCE + $(Q)$(MAKE) $(build)=$(obj)/compressed $@ + +$(obj)/vmlinuz: $(obj)/compressed/vmlinux FORCE + $(call if_changed,copy) @echo ' Kernel $@ is ready' $(obj)/main.o: $(obj)/ksize.h diff --git a/arch/alpha/boot/compressed/.gitignore b/arch/alpha/boot/compressed/.gitignore new file mode 100644 index 000000000000..765a08f1bd77 --- /dev/null +++ b/arch/alpha/boot/compressed/.gitignore @@ -0,0 +1,3 @@ +# SPDX-License-Identifier: GPL-2.0-only +vmlinux +vmlinux.lds diff --git a/arch/alpha/boot/compressed/Makefile b/arch/alpha/boot/compressed/Makefile new file mode 100644 index 000000000000..fe7eac100617 --- /dev/null +++ b/arch/alpha/boot/compressed/Makefile @@ -0,0 +1,62 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Create a self-extracting compressed vmlinux image from the original +# vmlinux. aboot loads and enters it like a plain vmlinux. +# + +OBJECTS := head.o misc.o trampoline.o piggy.o +OBJECTS += memcpy.o memmove.o memset.o +OBJECTS += __divqu.o __remqu.o __divlu.o __remlu.o +targets := vmlinux.lds vmlinux vmlinux.bin vmlinux.bin.gz +targets += $(OBJECTS) + +# The stub runs before the kernel has identified the CPU it booted on, so +# build it for the base Alpha instruction set rather than inheriting the +# kernel's -mcpu=. +KBUILD_CFLAGS := -D__KERNEL__ -std=gnu11 -Os -fno-strict-aliasing +KBUILD_CFLAGS += -fno-PIE -ffreestanding -fno-stack-protector +KBUILD_CFLAGS += -fno-asynchronous-unwind-tables +KBUILD_CFLAGS += -pipe -mcpu=ev4 -mno-fp-regs -ffixed-8 +KBUILD_CFLAGS += -DDISABLE_BRANCH_PROFILING -D__DISABLE_EXPORTS +KBUILD_CFLAGS += $(CC_FLAGS_DIALECT) +KBUILD_AFLAGS := -D__ASSEMBLY__ $(KBUILD_CFLAGS) + +# The stub has no sanitizer or coverage runtime. arch/alpha/lib/lib.a is +# built with the kernel's instrumentation and -mcpu=, so build private +# copies of the library routines the stub needs instead of linking it. +UBSAN_SANITIZE := n +GCOV_PROFILE := n + +$(obj)/memcpy.o: $(srctree)/arch/alpha/lib/memcpy.c FORCE + $(call if_changed_dep,cc_o_c) + +$(obj)/memmove.o: $(srctree)/arch/alpha/lib/memmove.S FORCE + $(call if_changed_dep,as_o_S) + +$(obj)/memset.o: $(srctree)/arch/alpha/lib/memset.S FORCE + $(call if_changed_dep,as_o_S) + +AFLAGS___divqu.o := -DDIV +AFLAGS___remqu.o := -DREM +AFLAGS___divlu.o := -DDIV -DINTSIZE +AFLAGS___remlu.o := -DREM -DINTSIZE + +$(addprefix $(obj)/,__divqu.o __remqu.o __divlu.o __remlu.o): $(srctree)/arch/alpha/lib/divide.S FORCE + $(call if_changed_dep,as_o_S) + +LDFLAGS_vmlinux := -X --orphan-handling=warn -T +$(obj)/vmlinux: $(obj)/vmlinux.lds $(addprefix $(obj)/, $(OBJECTS)) FORCE + $(call if_changed,ld) + +OBJCOPYFLAGS_vmlinux.bin := -R .comment -R .note -S +$(obj)/vmlinux.bin: vmlinux FORCE + $(call if_changed,objcopy) + +suffix-$(CONFIG_KERNEL_GZIP) := gz + +$(obj)/vmlinux.bin.gz: $(obj)/vmlinux.bin FORCE + $(call if_changed,gzip) + +LDFLAGS_piggy.o := -r --format binary --oformat elf64-alpha -T +$(obj)/piggy.o: $(obj)/vmlinux.scr $(obj)/vmlinux.bin.$(suffix-y) FORCE + $(call if_changed,ld) diff --git a/arch/alpha/boot/compressed/head.S b/arch/alpha/boot/compressed/head.S new file mode 100644 index 000000000000..c689ac439d15 --- /dev/null +++ b/arch/alpha/boot/compressed/head.S @@ -0,0 +1,36 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Entry point for the self-extracting compressed kernel stub. aboot + * jumps here with the OSF/1 parameter page (ZERO_PGE) already populated + * and $30 set to INIT_STACK, exactly as it would for an uncompressed + * vmlinux. + */ + +#include +#include + + .set noreorder + +__HEAD + .globl __start + .ent __start +__start: + br $29,1f +1: ldgp $29,0($29) + + /* + * Clear our own bss. The decompressors keep state there, and + * nothing guarantees that aboot zeroed the part of our PT_LOAD + * segment that has no file backing. + */ + lda $1,_bss + lda $2,_ebss +2: cmpult $1,$2,$3 + beq $3,3f + stq $31,0($1) + addq $1,8,$1 + br $31,2b + +3: jsr $26,decompress_and_boot + call_pal PAL_halt + .end __start diff --git a/arch/alpha/boot/compressed/misc.c b/arch/alpha/boot/compressed/misc.c new file mode 100644 index 000000000000..72998582be68 --- /dev/null +++ b/arch/alpha/boot/compressed/misc.c @@ -0,0 +1,178 @@ +// 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)) + +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_GZIP +#include "../../../../lib/decompress_inflate.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); +} diff --git a/arch/alpha/boot/compressed/trampoline.S b/arch/alpha/boot/compressed/trampoline.S new file mode 100644 index 000000000000..bf57d3b0fd7f --- /dev/null +++ b/arch/alpha/boot/compressed/trampoline.S @@ -0,0 +1,46 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copy the decompressed kernel onto START_ADDR, clear its bss and jump to + * its entry point. See relocate_and_jump() in misc.c for why this cannot + * run from the stub itself. It is position independent (no data or + * rodata references, no gp) and clobbers only its argument registers and + * $1, so misc.c can copy it anywhere and call it there. + */ + + .set noreorder + + .globl trampoline_start + .globl trampoline_end + .ent trampoline_start +trampoline_start: + /* $16 = dest, $17 = src, $18 = p_filesz, $19 = p_memsz, $20 = entry */ + + /* + * dest and src are both page aligned, and everything above + * p_filesz is bss, so round both lengths up and work in quadwords + * only, using instructions every Alpha implements. + */ + addq $18,7,$18 + bic $18,7,$18 + addq $19,7,$19 + bic $19,7,$19 + subq $19,$18,$19 /* bytes to zero after the copy */ + +1: beq $18,2f + ldq $1,0($17) + stq $1,0($16) + addq $17,8,$17 + addq $16,8,$16 + subq $18,8,$18 + br $31,1b + +2: beq $19,3f + stq $31,0($16) + addq $16,8,$16 + subq $19,8,$19 + br $31,2b + +3: imb + jmp $31,($20) + .end trampoline_start +trampoline_end: diff --git a/arch/alpha/boot/compressed/vmlinux.lds.S b/arch/alpha/boot/compressed/vmlinux.lds.S new file mode 100644 index 000000000000..5328c1ccaccf --- /dev/null +++ b/arch/alpha/boot/compressed/vmlinux.lds.S @@ -0,0 +1,83 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include +#include +#include + +OUTPUT_FORMAT("elf64-alpha") +OUTPUT_ARCH(alpha) +ENTRY(__start) + +SECTIONS +{ + /* + * Link the stub at the address the real, uncompressed vmlinux has + * always occupied. aboot derives the OSF/1 parameter-page + * (ZERO_PGE) and initial stack (INIT_STACK) addresses from the + * loaded image's own p_vaddr, so a stub linked anywhere else would + * hand the real kernel a parameter page it does not expect. + */ + . = START_ADDR; + + .head.text : { + _head = .; + HEAD_TEXT + _ehead = .; + } + + . = ALIGN(8); + .rodata.compressed : { + *(.rodata.compressed) + } + + . = ALIGN(PAGE_SIZE); + .text : { + _text = .; + *(.text) + *(.text.*) + _etext = .; + } + . = ALIGN(8); + .rodata : { + *(.rodata) + *(.rodata.*) + } + . = ALIGN(8); + .data : { + *(.data) + *(.data.*) + } + .got : { + *(.got) + } + .sdata : { + *(.sdata) + } + /* + * misc.c decompresses to _ebss, so nothing that the stub still + * needs may be placed after .bss. --orphan-handling=warn keeps a + * new section from silently landing there. + */ + . = ALIGN(8); + .bss : { + _bss = .; + *(.sbss) + *(.scommon) + *(.bss) + *(.bss.*) + *(COMMON) + . = ALIGN(8); + _ebss = .; + } + + STABS_DEBUG + DWARF_DEBUG + ELF_DETAILS + + DISCARDS + /DISCARD/ : { + *(.eh_frame) + *(.modinfo) + *(.note) + *(.note.*) + } +} diff --git a/arch/alpha/boot/compressed/vmlinux.scr b/arch/alpha/boot/compressed/vmlinux.scr new file mode 100644 index 000000000000..dac2d142bcfa --- /dev/null +++ b/arch/alpha/boot/compressed/vmlinux.scr @@ -0,0 +1,10 @@ +SECTIONS +{ + .rodata.compressed : { + input_len = .; + LONG(input_data_end - input_data) input_data = .; + *(.data) + output_len = . - 4; /* can be at unaligned address */ + input_data_end = .; + } +}