18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <linux/string.h>
38c2ecf20Sopenharmony_ci#include <linux/elf.h>
48c2ecf20Sopenharmony_ci#include <asm/sections.h>
58c2ecf20Sopenharmony_ci#include <asm/setup.h>
68c2ecf20Sopenharmony_ci#include <asm/kexec.h>
78c2ecf20Sopenharmony_ci#include <asm/sclp.h>
88c2ecf20Sopenharmony_ci#include <asm/diag.h>
98c2ecf20Sopenharmony_ci#include <asm/uv.h>
108c2ecf20Sopenharmony_ci#include "compressed/decompressor.h"
118c2ecf20Sopenharmony_ci#include "boot.h"
128c2ecf20Sopenharmony_ci
138c2ecf20Sopenharmony_ciextern char __boot_data_start[], __boot_data_end[];
148c2ecf20Sopenharmony_ciextern char __boot_data_preserved_start[], __boot_data_preserved_end[];
158c2ecf20Sopenharmony_ciunsigned long __bootdata_preserved(__kaslr_offset);
168c2ecf20Sopenharmony_ci
178c2ecf20Sopenharmony_ci/*
188c2ecf20Sopenharmony_ci * Some code and data needs to stay below 2 GB, even when the kernel would be
198c2ecf20Sopenharmony_ci * relocated above 2 GB, because it has to use 31 bit addresses.
208c2ecf20Sopenharmony_ci * Such code and data is part of the .dma section, and its location is passed
218c2ecf20Sopenharmony_ci * over to the decompressed / relocated kernel via the .boot.preserved.data
228c2ecf20Sopenharmony_ci * section.
238c2ecf20Sopenharmony_ci */
248c2ecf20Sopenharmony_ciextern char _sdma[], _edma[];
258c2ecf20Sopenharmony_ciextern char _stext_dma[], _etext_dma[];
268c2ecf20Sopenharmony_ciextern struct exception_table_entry _start_dma_ex_table[];
278c2ecf20Sopenharmony_ciextern struct exception_table_entry _stop_dma_ex_table[];
288c2ecf20Sopenharmony_ciunsigned long __bootdata_preserved(__sdma) = __pa(&_sdma);
298c2ecf20Sopenharmony_ciunsigned long __bootdata_preserved(__edma) = __pa(&_edma);
308c2ecf20Sopenharmony_ciunsigned long __bootdata_preserved(__stext_dma) = __pa(&_stext_dma);
318c2ecf20Sopenharmony_ciunsigned long __bootdata_preserved(__etext_dma) = __pa(&_etext_dma);
328c2ecf20Sopenharmony_cistruct exception_table_entry *
338c2ecf20Sopenharmony_ci	__bootdata_preserved(__start_dma_ex_table) = _start_dma_ex_table;
348c2ecf20Sopenharmony_cistruct exception_table_entry *
358c2ecf20Sopenharmony_ci	__bootdata_preserved(__stop_dma_ex_table) = _stop_dma_ex_table;
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ciint _diag210_dma(struct diag210 *addr);
388c2ecf20Sopenharmony_ciint _diag26c_dma(void *req, void *resp, enum diag26c_sc subcode);
398c2ecf20Sopenharmony_ciint _diag14_dma(unsigned long rx, unsigned long ry1, unsigned long subcode);
408c2ecf20Sopenharmony_civoid _diag0c_dma(struct hypfs_diag0c_entry *entry);
418c2ecf20Sopenharmony_civoid _diag308_reset_dma(void);
428c2ecf20Sopenharmony_cistruct diag_ops __bootdata_preserved(diag_dma_ops) = {
438c2ecf20Sopenharmony_ci	.diag210 = _diag210_dma,
448c2ecf20Sopenharmony_ci	.diag26c = _diag26c_dma,
458c2ecf20Sopenharmony_ci	.diag14 = _diag14_dma,
468c2ecf20Sopenharmony_ci	.diag0c = _diag0c_dma,
478c2ecf20Sopenharmony_ci	.diag308_reset = _diag308_reset_dma
488c2ecf20Sopenharmony_ci};
498c2ecf20Sopenharmony_cistatic struct diag210 _diag210_tmp_dma __section(".dma.data");
508c2ecf20Sopenharmony_cistruct diag210 *__bootdata_preserved(__diag210_tmp_dma) = &_diag210_tmp_dma;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_civoid error(char *x)
538c2ecf20Sopenharmony_ci{
548c2ecf20Sopenharmony_ci	sclp_early_printk("\n\n");
558c2ecf20Sopenharmony_ci	sclp_early_printk(x);
568c2ecf20Sopenharmony_ci	sclp_early_printk("\n\n -- System halted");
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_ci	disabled_wait();
598c2ecf20Sopenharmony_ci}
608c2ecf20Sopenharmony_ci
618c2ecf20Sopenharmony_ci#ifdef CONFIG_KERNEL_UNCOMPRESSED
628c2ecf20Sopenharmony_ciunsigned long mem_safe_offset(void)
638c2ecf20Sopenharmony_ci{
648c2ecf20Sopenharmony_ci	return vmlinux.default_lma + vmlinux.image_size + vmlinux.bss_size;
658c2ecf20Sopenharmony_ci}
668c2ecf20Sopenharmony_ci#endif
678c2ecf20Sopenharmony_ci
688c2ecf20Sopenharmony_cistatic void rescue_initrd(unsigned long addr)
698c2ecf20Sopenharmony_ci{
708c2ecf20Sopenharmony_ci	if (!IS_ENABLED(CONFIG_BLK_DEV_INITRD))
718c2ecf20Sopenharmony_ci		return;
728c2ecf20Sopenharmony_ci	if (!INITRD_START || !INITRD_SIZE)
738c2ecf20Sopenharmony_ci		return;
748c2ecf20Sopenharmony_ci	if (addr <= INITRD_START)
758c2ecf20Sopenharmony_ci		return;
768c2ecf20Sopenharmony_ci	memmove((void *)addr, (void *)INITRD_START, INITRD_SIZE);
778c2ecf20Sopenharmony_ci	INITRD_START = addr;
788c2ecf20Sopenharmony_ci}
798c2ecf20Sopenharmony_ci
808c2ecf20Sopenharmony_cistatic void copy_bootdata(void)
818c2ecf20Sopenharmony_ci{
828c2ecf20Sopenharmony_ci	if (__boot_data_end - __boot_data_start != vmlinux.bootdata_size)
838c2ecf20Sopenharmony_ci		error(".boot.data section size mismatch");
848c2ecf20Sopenharmony_ci	memcpy((void *)vmlinux.bootdata_off, __boot_data_start, vmlinux.bootdata_size);
858c2ecf20Sopenharmony_ci	if (__boot_data_preserved_end - __boot_data_preserved_start != vmlinux.bootdata_preserved_size)
868c2ecf20Sopenharmony_ci		error(".boot.preserved.data section size mismatch");
878c2ecf20Sopenharmony_ci	memcpy((void *)vmlinux.bootdata_preserved_off, __boot_data_preserved_start, vmlinux.bootdata_preserved_size);
888c2ecf20Sopenharmony_ci}
898c2ecf20Sopenharmony_ci
908c2ecf20Sopenharmony_cistatic void handle_relocs(unsigned long offset)
918c2ecf20Sopenharmony_ci{
928c2ecf20Sopenharmony_ci	Elf64_Rela *rela_start, *rela_end, *rela;
938c2ecf20Sopenharmony_ci	int r_type, r_sym, rc;
948c2ecf20Sopenharmony_ci	Elf64_Addr loc, val;
958c2ecf20Sopenharmony_ci	Elf64_Sym *dynsym;
968c2ecf20Sopenharmony_ci
978c2ecf20Sopenharmony_ci	rela_start = (Elf64_Rela *) vmlinux.rela_dyn_start;
988c2ecf20Sopenharmony_ci	rela_end = (Elf64_Rela *) vmlinux.rela_dyn_end;
998c2ecf20Sopenharmony_ci	dynsym = (Elf64_Sym *) vmlinux.dynsym_start;
1008c2ecf20Sopenharmony_ci	for (rela = rela_start; rela < rela_end; rela++) {
1018c2ecf20Sopenharmony_ci		loc = rela->r_offset + offset;
1028c2ecf20Sopenharmony_ci		val = rela->r_addend;
1038c2ecf20Sopenharmony_ci		r_sym = ELF64_R_SYM(rela->r_info);
1048c2ecf20Sopenharmony_ci		if (r_sym) {
1058c2ecf20Sopenharmony_ci			if (dynsym[r_sym].st_shndx != SHN_UNDEF)
1068c2ecf20Sopenharmony_ci				val += dynsym[r_sym].st_value + offset;
1078c2ecf20Sopenharmony_ci		} else {
1088c2ecf20Sopenharmony_ci			/*
1098c2ecf20Sopenharmony_ci			 * 0 == undefined symbol table index (STN_UNDEF),
1108c2ecf20Sopenharmony_ci			 * used for R_390_RELATIVE, only add KASLR offset
1118c2ecf20Sopenharmony_ci			 */
1128c2ecf20Sopenharmony_ci			val += offset;
1138c2ecf20Sopenharmony_ci		}
1148c2ecf20Sopenharmony_ci		r_type = ELF64_R_TYPE(rela->r_info);
1158c2ecf20Sopenharmony_ci		rc = arch_kexec_do_relocs(r_type, (void *) loc, val, 0);
1168c2ecf20Sopenharmony_ci		if (rc)
1178c2ecf20Sopenharmony_ci			error("Unknown relocation type");
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci}
1208c2ecf20Sopenharmony_ci
1218c2ecf20Sopenharmony_ci/*
1228c2ecf20Sopenharmony_ci * This function clears the BSS section of the decompressed Linux kernel and NOT the decompressor's.
1238c2ecf20Sopenharmony_ci */
1248c2ecf20Sopenharmony_cistatic void clear_bss_section(void)
1258c2ecf20Sopenharmony_ci{
1268c2ecf20Sopenharmony_ci	memset((void *)vmlinux.default_lma + vmlinux.image_size, 0, vmlinux.bss_size);
1278c2ecf20Sopenharmony_ci}
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_civoid startup_kernel(void)
1308c2ecf20Sopenharmony_ci{
1318c2ecf20Sopenharmony_ci	unsigned long random_lma;
1328c2ecf20Sopenharmony_ci	unsigned long safe_addr;
1338c2ecf20Sopenharmony_ci	void *img;
1348c2ecf20Sopenharmony_ci
1358c2ecf20Sopenharmony_ci	store_ipl_parmblock();
1368c2ecf20Sopenharmony_ci	safe_addr = mem_safe_offset();
1378c2ecf20Sopenharmony_ci	safe_addr = read_ipl_report(safe_addr);
1388c2ecf20Sopenharmony_ci	uv_query_info();
1398c2ecf20Sopenharmony_ci	rescue_initrd(safe_addr);
1408c2ecf20Sopenharmony_ci	sclp_early_read_info();
1418c2ecf20Sopenharmony_ci	setup_boot_command_line();
1428c2ecf20Sopenharmony_ci	parse_boot_command_line();
1438c2ecf20Sopenharmony_ci	setup_memory_end();
1448c2ecf20Sopenharmony_ci	detect_memory();
1458c2ecf20Sopenharmony_ci
1468c2ecf20Sopenharmony_ci	random_lma = __kaslr_offset = 0;
1478c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_RANDOMIZE_BASE) && kaslr_enabled) {
1488c2ecf20Sopenharmony_ci		random_lma = get_random_base(safe_addr);
1498c2ecf20Sopenharmony_ci		if (random_lma) {
1508c2ecf20Sopenharmony_ci			__kaslr_offset = random_lma - vmlinux.default_lma;
1518c2ecf20Sopenharmony_ci			img = (void *)vmlinux.default_lma;
1528c2ecf20Sopenharmony_ci			vmlinux.default_lma += __kaslr_offset;
1538c2ecf20Sopenharmony_ci			vmlinux.entry += __kaslr_offset;
1548c2ecf20Sopenharmony_ci			vmlinux.bootdata_off += __kaslr_offset;
1558c2ecf20Sopenharmony_ci			vmlinux.bootdata_preserved_off += __kaslr_offset;
1568c2ecf20Sopenharmony_ci			vmlinux.rela_dyn_start += __kaslr_offset;
1578c2ecf20Sopenharmony_ci			vmlinux.rela_dyn_end += __kaslr_offset;
1588c2ecf20Sopenharmony_ci			vmlinux.dynsym_start += __kaslr_offset;
1598c2ecf20Sopenharmony_ci		}
1608c2ecf20Sopenharmony_ci	}
1618c2ecf20Sopenharmony_ci
1628c2ecf20Sopenharmony_ci	if (!IS_ENABLED(CONFIG_KERNEL_UNCOMPRESSED)) {
1638c2ecf20Sopenharmony_ci		img = decompress_kernel();
1648c2ecf20Sopenharmony_ci		memmove((void *)vmlinux.default_lma, img, vmlinux.image_size);
1658c2ecf20Sopenharmony_ci	} else if (__kaslr_offset)
1668c2ecf20Sopenharmony_ci		memcpy((void *)vmlinux.default_lma, img, vmlinux.image_size);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	clear_bss_section();
1698c2ecf20Sopenharmony_ci	copy_bootdata();
1708c2ecf20Sopenharmony_ci	if (IS_ENABLED(CONFIG_RELOCATABLE))
1718c2ecf20Sopenharmony_ci		handle_relocs(__kaslr_offset);
1728c2ecf20Sopenharmony_ci
1738c2ecf20Sopenharmony_ci	if (__kaslr_offset) {
1748c2ecf20Sopenharmony_ci		/*
1758c2ecf20Sopenharmony_ci		 * Save KASLR offset for early dumps, before vmcore_info is set.
1768c2ecf20Sopenharmony_ci		 * Mark as uneven to distinguish from real vmcore_info pointer.
1778c2ecf20Sopenharmony_ci		 */
1788c2ecf20Sopenharmony_ci		S390_lowcore.vmcore_info = __kaslr_offset | 0x1UL;
1798c2ecf20Sopenharmony_ci		/* Clear non-relocated kernel */
1808c2ecf20Sopenharmony_ci		if (IS_ENABLED(CONFIG_KERNEL_UNCOMPRESSED))
1818c2ecf20Sopenharmony_ci			memset(img, 0, vmlinux.image_size);
1828c2ecf20Sopenharmony_ci	}
1838c2ecf20Sopenharmony_ci	vmlinux.entry();
1848c2ecf20Sopenharmony_ci}
185