18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci#include <linux/kernel.h>
38c2ecf20Sopenharmony_ci#include <linux/init.h>
48c2ecf20Sopenharmony_ci#include <linux/ctype.h>
58c2ecf20Sopenharmony_ci#include <linux/pgtable.h>
68c2ecf20Sopenharmony_ci#include <asm/ebcdic.h>
78c2ecf20Sopenharmony_ci#include <asm/sclp.h>
88c2ecf20Sopenharmony_ci#include <asm/sections.h>
98c2ecf20Sopenharmony_ci#include <asm/boot_data.h>
108c2ecf20Sopenharmony_ci#include <asm/facility.h>
118c2ecf20Sopenharmony_ci#include <asm/uv.h>
128c2ecf20Sopenharmony_ci#include "boot.h"
138c2ecf20Sopenharmony_ci
148c2ecf20Sopenharmony_cichar __bootdata(early_command_line)[COMMAND_LINE_SIZE];
158c2ecf20Sopenharmony_cistruct ipl_parameter_block __bootdata_preserved(ipl_block);
168c2ecf20Sopenharmony_ciint __bootdata_preserved(ipl_block_valid);
178c2ecf20Sopenharmony_ciunsigned int __bootdata_preserved(zlib_dfltcc_support) = ZLIB_DFLTCC_FULL;
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ciunsigned long __bootdata(vmalloc_size) = VMALLOC_DEFAULT_SIZE;
208c2ecf20Sopenharmony_ciunsigned long __bootdata(memory_end);
218c2ecf20Sopenharmony_ciint __bootdata(memory_end_set);
228c2ecf20Sopenharmony_ciint __bootdata(noexec_disabled);
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ciint kaslr_enabled;
258c2ecf20Sopenharmony_ci
268c2ecf20Sopenharmony_cistatic inline int __diag308(unsigned long subcode, void *addr)
278c2ecf20Sopenharmony_ci{
288c2ecf20Sopenharmony_ci	register unsigned long _addr asm("0") = (unsigned long)addr;
298c2ecf20Sopenharmony_ci	register unsigned long _rc asm("1") = 0;
308c2ecf20Sopenharmony_ci	unsigned long reg1, reg2;
318c2ecf20Sopenharmony_ci	psw_t old;
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	asm volatile(
348c2ecf20Sopenharmony_ci		"	mvc	0(16,%[psw_old]),0(%[psw_pgm])\n"
358c2ecf20Sopenharmony_ci		"	epsw	%0,%1\n"
368c2ecf20Sopenharmony_ci		"	st	%0,0(%[psw_pgm])\n"
378c2ecf20Sopenharmony_ci		"	st	%1,4(%[psw_pgm])\n"
388c2ecf20Sopenharmony_ci		"	larl	%0,1f\n"
398c2ecf20Sopenharmony_ci		"	stg	%0,8(%[psw_pgm])\n"
408c2ecf20Sopenharmony_ci		"	diag	%[addr],%[subcode],0x308\n"
418c2ecf20Sopenharmony_ci		"1:	mvc	0(16,%[psw_pgm]),0(%[psw_old])\n"
428c2ecf20Sopenharmony_ci		: "=&d" (reg1), "=&a" (reg2),
438c2ecf20Sopenharmony_ci		  "+Q" (S390_lowcore.program_new_psw),
448c2ecf20Sopenharmony_ci		  "=Q" (old),
458c2ecf20Sopenharmony_ci		  [addr] "+d" (_addr), "+d" (_rc)
468c2ecf20Sopenharmony_ci		: [subcode] "d" (subcode),
478c2ecf20Sopenharmony_ci		  [psw_old] "a" (&old),
488c2ecf20Sopenharmony_ci		  [psw_pgm] "a" (&S390_lowcore.program_new_psw)
498c2ecf20Sopenharmony_ci		: "cc", "memory");
508c2ecf20Sopenharmony_ci	return _rc;
518c2ecf20Sopenharmony_ci}
528c2ecf20Sopenharmony_ci
538c2ecf20Sopenharmony_civoid store_ipl_parmblock(void)
548c2ecf20Sopenharmony_ci{
558c2ecf20Sopenharmony_ci	int rc;
568c2ecf20Sopenharmony_ci
578c2ecf20Sopenharmony_ci	rc = __diag308(DIAG308_STORE, &ipl_block);
588c2ecf20Sopenharmony_ci	if (rc == DIAG308_RC_OK &&
598c2ecf20Sopenharmony_ci	    ipl_block.hdr.version <= IPL_MAX_SUPPORTED_VERSION)
608c2ecf20Sopenharmony_ci		ipl_block_valid = 1;
618c2ecf20Sopenharmony_ci}
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_cistatic size_t scpdata_length(const u8 *buf, size_t count)
648c2ecf20Sopenharmony_ci{
658c2ecf20Sopenharmony_ci	while (count) {
668c2ecf20Sopenharmony_ci		if (buf[count - 1] != '\0' && buf[count - 1] != ' ')
678c2ecf20Sopenharmony_ci			break;
688c2ecf20Sopenharmony_ci		count--;
698c2ecf20Sopenharmony_ci	}
708c2ecf20Sopenharmony_ci	return count;
718c2ecf20Sopenharmony_ci}
728c2ecf20Sopenharmony_ci
738c2ecf20Sopenharmony_cistatic size_t ipl_block_get_ascii_scpdata(char *dest, size_t size,
748c2ecf20Sopenharmony_ci					  const struct ipl_parameter_block *ipb)
758c2ecf20Sopenharmony_ci{
768c2ecf20Sopenharmony_ci	const __u8 *scp_data;
778c2ecf20Sopenharmony_ci	__u32 scp_data_len;
788c2ecf20Sopenharmony_ci	int has_lowercase;
798c2ecf20Sopenharmony_ci	size_t count = 0;
808c2ecf20Sopenharmony_ci	size_t i;
818c2ecf20Sopenharmony_ci
828c2ecf20Sopenharmony_ci	switch (ipb->pb0_hdr.pbt) {
838c2ecf20Sopenharmony_ci	case IPL_PBT_FCP:
848c2ecf20Sopenharmony_ci		scp_data_len = ipb->fcp.scp_data_len;
858c2ecf20Sopenharmony_ci		scp_data = ipb->fcp.scp_data;
868c2ecf20Sopenharmony_ci		break;
878c2ecf20Sopenharmony_ci	case IPL_PBT_NVME:
888c2ecf20Sopenharmony_ci		scp_data_len = ipb->nvme.scp_data_len;
898c2ecf20Sopenharmony_ci		scp_data = ipb->nvme.scp_data;
908c2ecf20Sopenharmony_ci		break;
918c2ecf20Sopenharmony_ci	default:
928c2ecf20Sopenharmony_ci		goto out;
938c2ecf20Sopenharmony_ci	}
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci	count = min(size - 1, scpdata_length(scp_data, scp_data_len));
968c2ecf20Sopenharmony_ci	if (!count)
978c2ecf20Sopenharmony_ci		goto out;
988c2ecf20Sopenharmony_ci
998c2ecf20Sopenharmony_ci	has_lowercase = 0;
1008c2ecf20Sopenharmony_ci	for (i = 0; i < count; i++) {
1018c2ecf20Sopenharmony_ci		if (!isascii(scp_data[i])) {
1028c2ecf20Sopenharmony_ci			count = 0;
1038c2ecf20Sopenharmony_ci			goto out;
1048c2ecf20Sopenharmony_ci		}
1058c2ecf20Sopenharmony_ci		if (!has_lowercase && islower(scp_data[i]))
1068c2ecf20Sopenharmony_ci			has_lowercase = 1;
1078c2ecf20Sopenharmony_ci	}
1088c2ecf20Sopenharmony_ci
1098c2ecf20Sopenharmony_ci	if (has_lowercase)
1108c2ecf20Sopenharmony_ci		memcpy(dest, scp_data, count);
1118c2ecf20Sopenharmony_ci	else
1128c2ecf20Sopenharmony_ci		for (i = 0; i < count; i++)
1138c2ecf20Sopenharmony_ci			dest[i] = tolower(scp_data[i]);
1148c2ecf20Sopenharmony_ciout:
1158c2ecf20Sopenharmony_ci	dest[count] = '\0';
1168c2ecf20Sopenharmony_ci	return count;
1178c2ecf20Sopenharmony_ci}
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_cistatic void append_ipl_block_parm(void)
1208c2ecf20Sopenharmony_ci{
1218c2ecf20Sopenharmony_ci	char *parm, *delim;
1228c2ecf20Sopenharmony_ci	size_t len, rc = 0;
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ci	len = strlen(early_command_line);
1258c2ecf20Sopenharmony_ci
1268c2ecf20Sopenharmony_ci	delim = early_command_line + len;    /* '\0' character position */
1278c2ecf20Sopenharmony_ci	parm = early_command_line + len + 1; /* append right after '\0' */
1288c2ecf20Sopenharmony_ci
1298c2ecf20Sopenharmony_ci	switch (ipl_block.pb0_hdr.pbt) {
1308c2ecf20Sopenharmony_ci	case IPL_PBT_CCW:
1318c2ecf20Sopenharmony_ci		rc = ipl_block_get_ascii_vmparm(
1328c2ecf20Sopenharmony_ci			parm, COMMAND_LINE_SIZE - len - 1, &ipl_block);
1338c2ecf20Sopenharmony_ci		break;
1348c2ecf20Sopenharmony_ci	case IPL_PBT_FCP:
1358c2ecf20Sopenharmony_ci	case IPL_PBT_NVME:
1368c2ecf20Sopenharmony_ci		rc = ipl_block_get_ascii_scpdata(
1378c2ecf20Sopenharmony_ci			parm, COMMAND_LINE_SIZE - len - 1, &ipl_block);
1388c2ecf20Sopenharmony_ci		break;
1398c2ecf20Sopenharmony_ci	}
1408c2ecf20Sopenharmony_ci	if (rc) {
1418c2ecf20Sopenharmony_ci		if (*parm == '=')
1428c2ecf20Sopenharmony_ci			memmove(early_command_line, parm + 1, rc);
1438c2ecf20Sopenharmony_ci		else
1448c2ecf20Sopenharmony_ci			*delim = ' '; /* replace '\0' with space */
1458c2ecf20Sopenharmony_ci	}
1468c2ecf20Sopenharmony_ci}
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cistatic inline int has_ebcdic_char(const char *str)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	int i;
1518c2ecf20Sopenharmony_ci
1528c2ecf20Sopenharmony_ci	for (i = 0; str[i]; i++)
1538c2ecf20Sopenharmony_ci		if (str[i] & 0x80)
1548c2ecf20Sopenharmony_ci			return 1;
1558c2ecf20Sopenharmony_ci	return 0;
1568c2ecf20Sopenharmony_ci}
1578c2ecf20Sopenharmony_ci
1588c2ecf20Sopenharmony_civoid setup_boot_command_line(void)
1598c2ecf20Sopenharmony_ci{
1608c2ecf20Sopenharmony_ci	COMMAND_LINE[ARCH_COMMAND_LINE_SIZE - 1] = 0;
1618c2ecf20Sopenharmony_ci	/* convert arch command line to ascii if necessary */
1628c2ecf20Sopenharmony_ci	if (has_ebcdic_char(COMMAND_LINE))
1638c2ecf20Sopenharmony_ci		EBCASC(COMMAND_LINE, ARCH_COMMAND_LINE_SIZE);
1648c2ecf20Sopenharmony_ci	/* copy arch command line */
1658c2ecf20Sopenharmony_ci	strcpy(early_command_line, strim(COMMAND_LINE));
1668c2ecf20Sopenharmony_ci
1678c2ecf20Sopenharmony_ci	/* append IPL PARM data to the boot command line */
1688c2ecf20Sopenharmony_ci	if (!is_prot_virt_guest() && ipl_block_valid)
1698c2ecf20Sopenharmony_ci		append_ipl_block_parm();
1708c2ecf20Sopenharmony_ci}
1718c2ecf20Sopenharmony_ci
1728c2ecf20Sopenharmony_cistatic void modify_facility(unsigned long nr, bool clear)
1738c2ecf20Sopenharmony_ci{
1748c2ecf20Sopenharmony_ci	if (clear)
1758c2ecf20Sopenharmony_ci		__clear_facility(nr, S390_lowcore.stfle_fac_list);
1768c2ecf20Sopenharmony_ci	else
1778c2ecf20Sopenharmony_ci		__set_facility(nr, S390_lowcore.stfle_fac_list);
1788c2ecf20Sopenharmony_ci}
1798c2ecf20Sopenharmony_ci
1808c2ecf20Sopenharmony_cistatic void check_cleared_facilities(void)
1818c2ecf20Sopenharmony_ci{
1828c2ecf20Sopenharmony_ci	unsigned long als[] = { FACILITIES_ALS };
1838c2ecf20Sopenharmony_ci	int i;
1848c2ecf20Sopenharmony_ci
1858c2ecf20Sopenharmony_ci	for (i = 0; i < ARRAY_SIZE(als); i++) {
1868c2ecf20Sopenharmony_ci		if ((S390_lowcore.stfle_fac_list[i] & als[i]) != als[i]) {
1878c2ecf20Sopenharmony_ci			sclp_early_printk("Warning: The Linux kernel requires facilities cleared via command line option\n");
1888c2ecf20Sopenharmony_ci			print_missing_facilities();
1898c2ecf20Sopenharmony_ci			break;
1908c2ecf20Sopenharmony_ci		}
1918c2ecf20Sopenharmony_ci	}
1928c2ecf20Sopenharmony_ci}
1938c2ecf20Sopenharmony_ci
1948c2ecf20Sopenharmony_cistatic void modify_fac_list(char *str)
1958c2ecf20Sopenharmony_ci{
1968c2ecf20Sopenharmony_ci	unsigned long val, endval;
1978c2ecf20Sopenharmony_ci	char *endp;
1988c2ecf20Sopenharmony_ci	bool clear;
1998c2ecf20Sopenharmony_ci
2008c2ecf20Sopenharmony_ci	while (*str) {
2018c2ecf20Sopenharmony_ci		clear = false;
2028c2ecf20Sopenharmony_ci		if (*str == '!') {
2038c2ecf20Sopenharmony_ci			clear = true;
2048c2ecf20Sopenharmony_ci			str++;
2058c2ecf20Sopenharmony_ci		}
2068c2ecf20Sopenharmony_ci		val = simple_strtoull(str, &endp, 0);
2078c2ecf20Sopenharmony_ci		if (str == endp)
2088c2ecf20Sopenharmony_ci			break;
2098c2ecf20Sopenharmony_ci		str = endp;
2108c2ecf20Sopenharmony_ci		if (*str == '-') {
2118c2ecf20Sopenharmony_ci			str++;
2128c2ecf20Sopenharmony_ci			endval = simple_strtoull(str, &endp, 0);
2138c2ecf20Sopenharmony_ci			if (str == endp)
2148c2ecf20Sopenharmony_ci				break;
2158c2ecf20Sopenharmony_ci			str = endp;
2168c2ecf20Sopenharmony_ci			while (val <= endval) {
2178c2ecf20Sopenharmony_ci				modify_facility(val, clear);
2188c2ecf20Sopenharmony_ci				val++;
2198c2ecf20Sopenharmony_ci			}
2208c2ecf20Sopenharmony_ci		} else {
2218c2ecf20Sopenharmony_ci			modify_facility(val, clear);
2228c2ecf20Sopenharmony_ci		}
2238c2ecf20Sopenharmony_ci		if (*str != ',')
2248c2ecf20Sopenharmony_ci			break;
2258c2ecf20Sopenharmony_ci		str++;
2268c2ecf20Sopenharmony_ci	}
2278c2ecf20Sopenharmony_ci	check_cleared_facilities();
2288c2ecf20Sopenharmony_ci}
2298c2ecf20Sopenharmony_ci
2308c2ecf20Sopenharmony_cistatic char command_line_buf[COMMAND_LINE_SIZE];
2318c2ecf20Sopenharmony_civoid parse_boot_command_line(void)
2328c2ecf20Sopenharmony_ci{
2338c2ecf20Sopenharmony_ci	char *param, *val;
2348c2ecf20Sopenharmony_ci	bool enabled;
2358c2ecf20Sopenharmony_ci	char *args;
2368c2ecf20Sopenharmony_ci	int rc;
2378c2ecf20Sopenharmony_ci
2388c2ecf20Sopenharmony_ci	kaslr_enabled = IS_ENABLED(CONFIG_RANDOMIZE_BASE);
2398c2ecf20Sopenharmony_ci	args = strcpy(command_line_buf, early_command_line);
2408c2ecf20Sopenharmony_ci	while (*args) {
2418c2ecf20Sopenharmony_ci		args = next_arg(args, &param, &val);
2428c2ecf20Sopenharmony_ci
2438c2ecf20Sopenharmony_ci		if (!strcmp(param, "mem") && val) {
2448c2ecf20Sopenharmony_ci			memory_end = round_down(memparse(val, NULL), PAGE_SIZE);
2458c2ecf20Sopenharmony_ci			memory_end_set = 1;
2468c2ecf20Sopenharmony_ci		}
2478c2ecf20Sopenharmony_ci
2488c2ecf20Sopenharmony_ci		if (!strcmp(param, "vmalloc") && val)
2498c2ecf20Sopenharmony_ci			vmalloc_size = round_up(memparse(val, NULL), PAGE_SIZE);
2508c2ecf20Sopenharmony_ci
2518c2ecf20Sopenharmony_ci		if (!strcmp(param, "dfltcc") && val) {
2528c2ecf20Sopenharmony_ci			if (!strcmp(val, "off"))
2538c2ecf20Sopenharmony_ci				zlib_dfltcc_support = ZLIB_DFLTCC_DISABLED;
2548c2ecf20Sopenharmony_ci			else if (!strcmp(val, "on"))
2558c2ecf20Sopenharmony_ci				zlib_dfltcc_support = ZLIB_DFLTCC_FULL;
2568c2ecf20Sopenharmony_ci			else if (!strcmp(val, "def_only"))
2578c2ecf20Sopenharmony_ci				zlib_dfltcc_support = ZLIB_DFLTCC_DEFLATE_ONLY;
2588c2ecf20Sopenharmony_ci			else if (!strcmp(val, "inf_only"))
2598c2ecf20Sopenharmony_ci				zlib_dfltcc_support = ZLIB_DFLTCC_INFLATE_ONLY;
2608c2ecf20Sopenharmony_ci			else if (!strcmp(val, "always"))
2618c2ecf20Sopenharmony_ci				zlib_dfltcc_support = ZLIB_DFLTCC_FULL_DEBUG;
2628c2ecf20Sopenharmony_ci		}
2638c2ecf20Sopenharmony_ci
2648c2ecf20Sopenharmony_ci		if (!strcmp(param, "noexec")) {
2658c2ecf20Sopenharmony_ci			rc = kstrtobool(val, &enabled);
2668c2ecf20Sopenharmony_ci			if (!rc && !enabled)
2678c2ecf20Sopenharmony_ci				noexec_disabled = 1;
2688c2ecf20Sopenharmony_ci		}
2698c2ecf20Sopenharmony_ci
2708c2ecf20Sopenharmony_ci		if (!strcmp(param, "facilities") && val)
2718c2ecf20Sopenharmony_ci			modify_fac_list(val);
2728c2ecf20Sopenharmony_ci
2738c2ecf20Sopenharmony_ci		if (!strcmp(param, "nokaslr"))
2748c2ecf20Sopenharmony_ci			kaslr_enabled = 0;
2758c2ecf20Sopenharmony_ci
2768c2ecf20Sopenharmony_ci#if IS_ENABLED(CONFIG_KVM)
2778c2ecf20Sopenharmony_ci		if (!strcmp(param, "prot_virt")) {
2788c2ecf20Sopenharmony_ci			rc = kstrtobool(val, &enabled);
2798c2ecf20Sopenharmony_ci			if (!rc && enabled)
2808c2ecf20Sopenharmony_ci				prot_virt_host = 1;
2818c2ecf20Sopenharmony_ci		}
2828c2ecf20Sopenharmony_ci#endif
2838c2ecf20Sopenharmony_ci	}
2848c2ecf20Sopenharmony_ci}
2858c2ecf20Sopenharmony_ci
2868c2ecf20Sopenharmony_cistatic inline bool is_ipl_block_dump(void)
2878c2ecf20Sopenharmony_ci{
2888c2ecf20Sopenharmony_ci	if (ipl_block.pb0_hdr.pbt == IPL_PBT_FCP &&
2898c2ecf20Sopenharmony_ci	    ipl_block.fcp.opt == IPL_PB0_FCP_OPT_DUMP)
2908c2ecf20Sopenharmony_ci		return true;
2918c2ecf20Sopenharmony_ci	if (ipl_block.pb0_hdr.pbt == IPL_PBT_NVME &&
2928c2ecf20Sopenharmony_ci	    ipl_block.nvme.opt == IPL_PB0_NVME_OPT_DUMP)
2938c2ecf20Sopenharmony_ci		return true;
2948c2ecf20Sopenharmony_ci	return false;
2958c2ecf20Sopenharmony_ci}
2968c2ecf20Sopenharmony_ci
2978c2ecf20Sopenharmony_civoid setup_memory_end(void)
2988c2ecf20Sopenharmony_ci{
2998c2ecf20Sopenharmony_ci#ifdef CONFIG_CRASH_DUMP
3008c2ecf20Sopenharmony_ci	if (OLDMEM_BASE) {
3018c2ecf20Sopenharmony_ci		kaslr_enabled = 0;
3028c2ecf20Sopenharmony_ci	} else if (ipl_block_valid && is_ipl_block_dump()) {
3038c2ecf20Sopenharmony_ci		kaslr_enabled = 0;
3048c2ecf20Sopenharmony_ci		if (!sclp_early_get_hsa_size(&memory_end) && memory_end)
3058c2ecf20Sopenharmony_ci			memory_end_set = 1;
3068c2ecf20Sopenharmony_ci	}
3078c2ecf20Sopenharmony_ci#endif
3088c2ecf20Sopenharmony_ci}
309