text: // SPDX-License-Identifier: GPL-2.0
  #include <linux/kernel.h>
  #include <linux/init.h>
  #include <linux/ctype.h>
  #include <asm/ebcdic.h>
  #include <asm/sclp.h>
  #include <asm/sections.h>
  #include <asm/boot_data.h>
  #include <asm/facility.h>
  #include <asm/pgtable.h>
  #include <asm/uv.h>
  #include "boot.h"
  
  char __bootdata(early_command_line)[COMMAND_LINE_SIZE];
  struct ipl_parameter_block __bootdata_preserved(ipl_block);
  int __bootdata_preserved(ipl_block_valid);
  unsigned int __bootdata_preserved(zlib_dfltcc_support) = ZLIB_DFLTCC_FULL;
  
  unsigned long __bootdata(vmalloc_size) = VMALLOC_DEFAULT_SIZE;
  unsigned long __bootdata(memory_end);
  int __bootdata(memory_end_set);
  int __bootdata(noexec_disabled);
  
  int kaslr_enabled __section(.data);
  
  static inline int __diag308(unsigned long subcode, void *addr)
  {
  	register unsigned long _addr asm("0") = (unsigned long)addr;
  	register unsigned long _rc asm("1") = 0;
  	unsigned long reg1, reg2;
  	psw_t old = S390_lowcore.program_new_psw;
  
  	asm volatile(
  		"	epsw	%0,%1\n"
  		"	st	%0,%[psw_pgm]\n"
  		"	st	%1,%[psw_pgm]+4\n"
  		"	larl	%0,1f\n"
  		"	stg	%0,%[psw_pgm]+8\n"
  		"	diag	%[addr],%[subcode],0x308\n"
  		"1:	nopr	%%r7\n"
  		: "=&d" (reg1), "=&a" (reg2),
  		  [psw_pgm] "=Q" (S390_lowcore.program_new_psw),
  		  [addr] "+d" (_addr), "+d" (_rc)
  		: [subcode] "d" (subcode)
  		: "cc", "memory");
  	S390_lowcore.program_new_psw = old;
  	return _rc;
  }
  
  void store_ipl_parmblock(void)
  {
  	int rc;
  
  	rc = __diag308(DIAG308_STORE, &ipl_block);
  	if (rc == DIAG308_RC_OK &&
  	    ipl_block.hdr.version <= IPL_MAX_SUPPORTED_VERSION)
  		ipl_block_valid = 1;
  }
  
  static size_t scpdata_length(const u8 *buf, size_t count)
  {
  	while (count) {
  		if (buf[count - 1] != '\0' && buf[count - 1] != ' ')
  			break;
  		count--;
  	}
  	return count;
  }
  
  static size_t ipl_block_get_ascii_scpdata(char *dest, size_t size,
  					  const struct ipl_parameter_block *ipb)
  {
  	size_t count;
  	size_t i;
  	int has_lowercase;
  
  	count = min(size - 1, scpdata_length(ipb->fcp.scp_data,
  					     ipb->fcp.scp_data_len));
  	if (!count)
  		goto out;
  
  	has_lowercase = 0;
  	for (i = 0; i < count; i++) {
  		if (!isascii(ipb->fcp.scp_data[i])) {
  			count = 0;
  			goto out;
  		}
  		if (!has_lowercase && islower(ipb->fcp.scp_data[i]))
  			has_lowercase = 1;
  	}
  
  	if (has_lowercase)
  		memcpy(dest, ipb->fcp.scp_data, count);
  	else
  		for (i = 0; i < count; i++)
  			dest[i] = tolower(ipb->fcp.scp_data[i]);
  out:
  	dest[count] = '\0';
  	return count;
  }
  
  static void append_ipl_block_parm(void)
  {
  	char *parm, *delim;
  	size_t len, rc = 0;
  
  	len = strlen(early_command_line);
  
  	delim = early_command_line + len;    /* '\0' character position */
  	parm = early_command_line + len + 1; /* append right after '\0' */
  
  	switch (ipl_block.pb0_hdr.pbt) {
  	case IPL_PBT_CCW:
  		rc = ipl_block_get_ascii_vmparm(
  			parm, COMMAND_LINE_SIZE - len - 1, &ipl_block);
  		break;
  	case IPL_PBT_FCP:
  		rc = ipl_block_get_ascii_scpdata(
  			parm, COMMAND_LINE_SIZE - len - 1, &ipl_block);
  		break;
  	}
  	if (rc) {
  		if (*parm == '=')
  			memmove(early_command_line, parm + 1, rc);
  		else
  			*delim = ' '; /* replace '\0' with space */
  	}
  }
  
  static inline int has_ebcdic_char(const char *str)
  {
  	int i;
  
  	for (i = 0; str[i]; i++)
  		if (str[i] & 0x80)
  			return 1;
  	return 0;
  }
  
  void setup_boot_command_line(void)
  {
  	COMMAND_LINE[ARCH_COMMAND_LINE_SIZE - 1] = 0;
  	/* convert arch command line to ascii if necessary */
  	if (has_ebcdic_char(COMMAND_LINE))
  		EBCASC(COMMAND_LINE, ARCH_COMMAND_LINE_SIZE);
  	/* copy arch command line */
  	strcpy(early_command_line, strim(COMMAND_LINE));
  
  	/* append IPL PARM data to the boot command line */
  	if (!is_prot_virt_guest() && ipl_block_valid)
  		append_ipl_block_parm();
  }
  
  static void modify_facility(unsigned long nr, bool clear)
  {
  	if (clear)
  		__clear_facility(nr, S390_lowcore.stfle_fac_list);
  	else
  		__set_facility(nr, S390_lowcore.stfle_fac_list);
  }
  
  static void check_cleared_facilities(void)
  {
  	unsigned long als[] = { FACILITIES_ALS };
  	int i;
  
  	for (i = 0; i < ARRAY_SIZE(als); i++) {
  		if ((S390_lowcore.stfle_fac_list[i] & als[i]) != als[i]) {
  			sclp_early_printk("Warning: The Linux kernel requires facilities cleared via command line option\n");
  			print_missing_facilities();
  			break;
  		}
  	}
  }
  
  static void modify_fac_list(char *str)
  {
  	unsigned long val, endval;
  	char *endp;
  	bool clear;
  
  	while (*str) {
  		clear = false;
  		if (*str == '!') {
  			clear = true;
  			str++;
  		}
  		val = simple_strtoull(str, &endp, 0);
  		if (str == endp)
  			break;
  		str = endp;
  		if (*str == '-') {
  			str++;
  			endval = simple_strtoull(str, &endp, 0);
  			if (str == endp)
  				break;
  			str = endp;
  			while (val <= endval) {
  				modify_facility(val, clear);
  				val++;
  			}
  		} else {
  			modify_facility(val, clear);
  		}
  		if (*str != ',')
  			break;
  		str++;
  	}
  	check_cleared_facilities();
  }
  
  static char command_line_buf[COMMAND_LINE_SIZE] __section(.data);
  void parse_boot_command_line(void)
  {
  	char *param, *val;
  	bool enabled;
  	char *args;
  	int rc;
  
  	kaslr_enabled = IS_ENABLED(CONFIG_RANDOMIZE_BASE);
  	args = strcpy(command_line_buf, early_command_line);
  	while (*args) {
  		args = next_arg(args, &param, &val);
  
  		if (!strcmp(param, "mem") && val) {
  			memory_end = round_down(memparse(val, NULL), PAGE_SIZE);
  			memory_end_set = 1;
  		}
  
  		if (!strcmp(param, "vmalloc") && val)
  			vmalloc_size = round_up(memparse(val, NULL), PAGE_SIZE);
  
  		if (!strcmp(param, "dfltcc")) {
  			if (!strcmp(val, "off"))
  				zlib_dfltcc_support = ZLIB_DFLTCC_DISABLED;
  			else if (!strcmp(val, "on"))
  				zlib_dfltcc_support = ZLIB_DFLTCC_FULL;
  			else if (!strcmp(val, "def_only"))
  				zlib_dfltcc_support = ZLIB_DFLTCC_DEFLATE_ONLY;
  			else if (!strcmp(val, "inf_only"))
  				zlib_dfltcc_support = ZLIB_DFLTCC_INFLATE_ONLY;
  			else if (!strcmp(val, "always"))
  				zlib_dfltcc_support = ZLIB_DFLTCC_FULL_DEBUG;
  		}
  
  		if (!strcmp(param, "noexec")) {
  			rc = kstrtobool(val, &enabled);
  			if (!rc && !enabled)
  				noexec_disabled = 1;
  		}
  
  		if (!strcmp(param, "facilities") && val)
  			modify_fac_list(val);
  
  		if (!strcmp(param, "nokaslr"))
  			kaslr_enabled = 0;
  	}
  }
  
  void setup_memory_end(void)
  {
  #ifdef CONFIG_CRASH_DUMP
  	if (OLDMEM_BASE) {
  		kaslr_enabled = 0;
  	} else if (ipl_block_valid &&
  		   ipl_block.pb0_hdr.pbt == IPL_PBT_FCP &&
  		   ipl_block.fcp.opt == IPL_PB0_FCP_OPT_DUMP) {
  		kaslr_enabled = 0;
  		if (!sclp_early_get_hsa_size(&memory_end) && memory_end)
  			memory_end_set = 1;
  	}
  #endif
  }
  