162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 262306a36Sopenharmony_ci#include <linux/kernel.h> 362306a36Sopenharmony_ci#include <linux/init.h> 462306a36Sopenharmony_ci#include <linux/ctype.h> 562306a36Sopenharmony_ci#include <linux/pgtable.h> 662306a36Sopenharmony_ci#include <asm/ebcdic.h> 762306a36Sopenharmony_ci#include <asm/sclp.h> 862306a36Sopenharmony_ci#include <asm/sections.h> 962306a36Sopenharmony_ci#include <asm/boot_data.h> 1062306a36Sopenharmony_ci#include <asm/facility.h> 1162306a36Sopenharmony_ci#include <asm/setup.h> 1262306a36Sopenharmony_ci#include <asm/uv.h> 1362306a36Sopenharmony_ci#include "boot.h" 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistruct parmarea parmarea __section(".parmarea") = { 1662306a36Sopenharmony_ci .kernel_version = (unsigned long)kernel_version, 1762306a36Sopenharmony_ci .max_command_line_size = COMMAND_LINE_SIZE, 1862306a36Sopenharmony_ci .command_line = "root=/dev/ram0 ro", 1962306a36Sopenharmony_ci}; 2062306a36Sopenharmony_ci 2162306a36Sopenharmony_cichar __bootdata(early_command_line)[COMMAND_LINE_SIZE]; 2262306a36Sopenharmony_ci 2362306a36Sopenharmony_ciunsigned int __bootdata_preserved(zlib_dfltcc_support) = ZLIB_DFLTCC_FULL; 2462306a36Sopenharmony_cistruct ipl_parameter_block __bootdata_preserved(ipl_block); 2562306a36Sopenharmony_ciint __bootdata_preserved(ipl_block_valid); 2662306a36Sopenharmony_ciint __bootdata_preserved(__kaslr_enabled); 2762306a36Sopenharmony_ci 2862306a36Sopenharmony_ciunsigned long vmalloc_size = VMALLOC_DEFAULT_SIZE; 2962306a36Sopenharmony_ciunsigned long memory_limit; 3062306a36Sopenharmony_ciint vmalloc_size_set; 3162306a36Sopenharmony_ci 3262306a36Sopenharmony_cistatic inline int __diag308(unsigned long subcode, void *addr) 3362306a36Sopenharmony_ci{ 3462306a36Sopenharmony_ci unsigned long reg1, reg2; 3562306a36Sopenharmony_ci union register_pair r1; 3662306a36Sopenharmony_ci psw_t old; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci r1.even = (unsigned long) addr; 3962306a36Sopenharmony_ci r1.odd = 0; 4062306a36Sopenharmony_ci asm volatile( 4162306a36Sopenharmony_ci " mvc 0(16,%[psw_old]),0(%[psw_pgm])\n" 4262306a36Sopenharmony_ci " epsw %[reg1],%[reg2]\n" 4362306a36Sopenharmony_ci " st %[reg1],0(%[psw_pgm])\n" 4462306a36Sopenharmony_ci " st %[reg2],4(%[psw_pgm])\n" 4562306a36Sopenharmony_ci " larl %[reg1],1f\n" 4662306a36Sopenharmony_ci " stg %[reg1],8(%[psw_pgm])\n" 4762306a36Sopenharmony_ci " diag %[r1],%[subcode],0x308\n" 4862306a36Sopenharmony_ci "1: mvc 0(16,%[psw_pgm]),0(%[psw_old])\n" 4962306a36Sopenharmony_ci : [r1] "+&d" (r1.pair), 5062306a36Sopenharmony_ci [reg1] "=&d" (reg1), 5162306a36Sopenharmony_ci [reg2] "=&a" (reg2), 5262306a36Sopenharmony_ci "+Q" (S390_lowcore.program_new_psw), 5362306a36Sopenharmony_ci "=Q" (old) 5462306a36Sopenharmony_ci : [subcode] "d" (subcode), 5562306a36Sopenharmony_ci [psw_old] "a" (&old), 5662306a36Sopenharmony_ci [psw_pgm] "a" (&S390_lowcore.program_new_psw) 5762306a36Sopenharmony_ci : "cc", "memory"); 5862306a36Sopenharmony_ci return r1.odd; 5962306a36Sopenharmony_ci} 6062306a36Sopenharmony_ci 6162306a36Sopenharmony_civoid store_ipl_parmblock(void) 6262306a36Sopenharmony_ci{ 6362306a36Sopenharmony_ci int rc; 6462306a36Sopenharmony_ci 6562306a36Sopenharmony_ci rc = __diag308(DIAG308_STORE, &ipl_block); 6662306a36Sopenharmony_ci if (rc == DIAG308_RC_OK && 6762306a36Sopenharmony_ci ipl_block.hdr.version <= IPL_MAX_SUPPORTED_VERSION) 6862306a36Sopenharmony_ci ipl_block_valid = 1; 6962306a36Sopenharmony_ci} 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_cibool is_ipl_block_dump(void) 7262306a36Sopenharmony_ci{ 7362306a36Sopenharmony_ci if (ipl_block.pb0_hdr.pbt == IPL_PBT_FCP && 7462306a36Sopenharmony_ci ipl_block.fcp.opt == IPL_PB0_FCP_OPT_DUMP) 7562306a36Sopenharmony_ci return true; 7662306a36Sopenharmony_ci if (ipl_block.pb0_hdr.pbt == IPL_PBT_NVME && 7762306a36Sopenharmony_ci ipl_block.nvme.opt == IPL_PB0_NVME_OPT_DUMP) 7862306a36Sopenharmony_ci return true; 7962306a36Sopenharmony_ci if (ipl_block.pb0_hdr.pbt == IPL_PBT_ECKD && 8062306a36Sopenharmony_ci ipl_block.eckd.opt == IPL_PB0_ECKD_OPT_DUMP) 8162306a36Sopenharmony_ci return true; 8262306a36Sopenharmony_ci return false; 8362306a36Sopenharmony_ci} 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_cistatic size_t scpdata_length(const u8 *buf, size_t count) 8662306a36Sopenharmony_ci{ 8762306a36Sopenharmony_ci while (count) { 8862306a36Sopenharmony_ci if (buf[count - 1] != '\0' && buf[count - 1] != ' ') 8962306a36Sopenharmony_ci break; 9062306a36Sopenharmony_ci count--; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci return count; 9362306a36Sopenharmony_ci} 9462306a36Sopenharmony_ci 9562306a36Sopenharmony_cistatic size_t ipl_block_get_ascii_scpdata(char *dest, size_t size, 9662306a36Sopenharmony_ci const struct ipl_parameter_block *ipb) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci const __u8 *scp_data; 9962306a36Sopenharmony_ci __u32 scp_data_len; 10062306a36Sopenharmony_ci int has_lowercase; 10162306a36Sopenharmony_ci size_t count = 0; 10262306a36Sopenharmony_ci size_t i; 10362306a36Sopenharmony_ci 10462306a36Sopenharmony_ci switch (ipb->pb0_hdr.pbt) { 10562306a36Sopenharmony_ci case IPL_PBT_FCP: 10662306a36Sopenharmony_ci scp_data_len = ipb->fcp.scp_data_len; 10762306a36Sopenharmony_ci scp_data = ipb->fcp.scp_data; 10862306a36Sopenharmony_ci break; 10962306a36Sopenharmony_ci case IPL_PBT_NVME: 11062306a36Sopenharmony_ci scp_data_len = ipb->nvme.scp_data_len; 11162306a36Sopenharmony_ci scp_data = ipb->nvme.scp_data; 11262306a36Sopenharmony_ci break; 11362306a36Sopenharmony_ci case IPL_PBT_ECKD: 11462306a36Sopenharmony_ci scp_data_len = ipb->eckd.scp_data_len; 11562306a36Sopenharmony_ci scp_data = ipb->eckd.scp_data; 11662306a36Sopenharmony_ci break; 11762306a36Sopenharmony_ci 11862306a36Sopenharmony_ci default: 11962306a36Sopenharmony_ci goto out; 12062306a36Sopenharmony_ci } 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci count = min(size - 1, scpdata_length(scp_data, scp_data_len)); 12362306a36Sopenharmony_ci if (!count) 12462306a36Sopenharmony_ci goto out; 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_ci has_lowercase = 0; 12762306a36Sopenharmony_ci for (i = 0; i < count; i++) { 12862306a36Sopenharmony_ci if (!isascii(scp_data[i])) { 12962306a36Sopenharmony_ci count = 0; 13062306a36Sopenharmony_ci goto out; 13162306a36Sopenharmony_ci } 13262306a36Sopenharmony_ci if (!has_lowercase && islower(scp_data[i])) 13362306a36Sopenharmony_ci has_lowercase = 1; 13462306a36Sopenharmony_ci } 13562306a36Sopenharmony_ci 13662306a36Sopenharmony_ci if (has_lowercase) 13762306a36Sopenharmony_ci memcpy(dest, scp_data, count); 13862306a36Sopenharmony_ci else 13962306a36Sopenharmony_ci for (i = 0; i < count; i++) 14062306a36Sopenharmony_ci dest[i] = tolower(scp_data[i]); 14162306a36Sopenharmony_ciout: 14262306a36Sopenharmony_ci dest[count] = '\0'; 14362306a36Sopenharmony_ci return count; 14462306a36Sopenharmony_ci} 14562306a36Sopenharmony_ci 14662306a36Sopenharmony_cistatic void append_ipl_block_parm(void) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci char *parm, *delim; 14962306a36Sopenharmony_ci size_t len, rc = 0; 15062306a36Sopenharmony_ci 15162306a36Sopenharmony_ci len = strlen(early_command_line); 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci delim = early_command_line + len; /* '\0' character position */ 15462306a36Sopenharmony_ci parm = early_command_line + len + 1; /* append right after '\0' */ 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci switch (ipl_block.pb0_hdr.pbt) { 15762306a36Sopenharmony_ci case IPL_PBT_CCW: 15862306a36Sopenharmony_ci rc = ipl_block_get_ascii_vmparm( 15962306a36Sopenharmony_ci parm, COMMAND_LINE_SIZE - len - 1, &ipl_block); 16062306a36Sopenharmony_ci break; 16162306a36Sopenharmony_ci case IPL_PBT_FCP: 16262306a36Sopenharmony_ci case IPL_PBT_NVME: 16362306a36Sopenharmony_ci case IPL_PBT_ECKD: 16462306a36Sopenharmony_ci rc = ipl_block_get_ascii_scpdata( 16562306a36Sopenharmony_ci parm, COMMAND_LINE_SIZE - len - 1, &ipl_block); 16662306a36Sopenharmony_ci break; 16762306a36Sopenharmony_ci } 16862306a36Sopenharmony_ci if (rc) { 16962306a36Sopenharmony_ci if (*parm == '=') 17062306a36Sopenharmony_ci memmove(early_command_line, parm + 1, rc); 17162306a36Sopenharmony_ci else 17262306a36Sopenharmony_ci *delim = ' '; /* replace '\0' with space */ 17362306a36Sopenharmony_ci } 17462306a36Sopenharmony_ci} 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_cistatic inline int has_ebcdic_char(const char *str) 17762306a36Sopenharmony_ci{ 17862306a36Sopenharmony_ci int i; 17962306a36Sopenharmony_ci 18062306a36Sopenharmony_ci for (i = 0; str[i]; i++) 18162306a36Sopenharmony_ci if (str[i] & 0x80) 18262306a36Sopenharmony_ci return 1; 18362306a36Sopenharmony_ci return 0; 18462306a36Sopenharmony_ci} 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_civoid setup_boot_command_line(void) 18762306a36Sopenharmony_ci{ 18862306a36Sopenharmony_ci parmarea.command_line[COMMAND_LINE_SIZE - 1] = 0; 18962306a36Sopenharmony_ci /* convert arch command line to ascii if necessary */ 19062306a36Sopenharmony_ci if (has_ebcdic_char(parmarea.command_line)) 19162306a36Sopenharmony_ci EBCASC(parmarea.command_line, COMMAND_LINE_SIZE); 19262306a36Sopenharmony_ci /* copy arch command line */ 19362306a36Sopenharmony_ci strcpy(early_command_line, strim(parmarea.command_line)); 19462306a36Sopenharmony_ci 19562306a36Sopenharmony_ci /* append IPL PARM data to the boot command line */ 19662306a36Sopenharmony_ci if (!is_prot_virt_guest() && ipl_block_valid) 19762306a36Sopenharmony_ci append_ipl_block_parm(); 19862306a36Sopenharmony_ci} 19962306a36Sopenharmony_ci 20062306a36Sopenharmony_cistatic void modify_facility(unsigned long nr, bool clear) 20162306a36Sopenharmony_ci{ 20262306a36Sopenharmony_ci if (clear) 20362306a36Sopenharmony_ci __clear_facility(nr, stfle_fac_list); 20462306a36Sopenharmony_ci else 20562306a36Sopenharmony_ci __set_facility(nr, stfle_fac_list); 20662306a36Sopenharmony_ci} 20762306a36Sopenharmony_ci 20862306a36Sopenharmony_cistatic void check_cleared_facilities(void) 20962306a36Sopenharmony_ci{ 21062306a36Sopenharmony_ci unsigned long als[] = { FACILITIES_ALS }; 21162306a36Sopenharmony_ci int i; 21262306a36Sopenharmony_ci 21362306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(als); i++) { 21462306a36Sopenharmony_ci if ((stfle_fac_list[i] & als[i]) != als[i]) { 21562306a36Sopenharmony_ci sclp_early_printk("Warning: The Linux kernel requires facilities cleared via command line option\n"); 21662306a36Sopenharmony_ci print_missing_facilities(); 21762306a36Sopenharmony_ci break; 21862306a36Sopenharmony_ci } 21962306a36Sopenharmony_ci } 22062306a36Sopenharmony_ci} 22162306a36Sopenharmony_ci 22262306a36Sopenharmony_cistatic void modify_fac_list(char *str) 22362306a36Sopenharmony_ci{ 22462306a36Sopenharmony_ci unsigned long val, endval; 22562306a36Sopenharmony_ci char *endp; 22662306a36Sopenharmony_ci bool clear; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci while (*str) { 22962306a36Sopenharmony_ci clear = false; 23062306a36Sopenharmony_ci if (*str == '!') { 23162306a36Sopenharmony_ci clear = true; 23262306a36Sopenharmony_ci str++; 23362306a36Sopenharmony_ci } 23462306a36Sopenharmony_ci val = simple_strtoull(str, &endp, 0); 23562306a36Sopenharmony_ci if (str == endp) 23662306a36Sopenharmony_ci break; 23762306a36Sopenharmony_ci str = endp; 23862306a36Sopenharmony_ci if (*str == '-') { 23962306a36Sopenharmony_ci str++; 24062306a36Sopenharmony_ci endval = simple_strtoull(str, &endp, 0); 24162306a36Sopenharmony_ci if (str == endp) 24262306a36Sopenharmony_ci break; 24362306a36Sopenharmony_ci str = endp; 24462306a36Sopenharmony_ci while (val <= endval) { 24562306a36Sopenharmony_ci modify_facility(val, clear); 24662306a36Sopenharmony_ci val++; 24762306a36Sopenharmony_ci } 24862306a36Sopenharmony_ci } else { 24962306a36Sopenharmony_ci modify_facility(val, clear); 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci if (*str != ',') 25262306a36Sopenharmony_ci break; 25362306a36Sopenharmony_ci str++; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci check_cleared_facilities(); 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_cistatic char command_line_buf[COMMAND_LINE_SIZE]; 25962306a36Sopenharmony_civoid parse_boot_command_line(void) 26062306a36Sopenharmony_ci{ 26162306a36Sopenharmony_ci char *param, *val; 26262306a36Sopenharmony_ci bool enabled; 26362306a36Sopenharmony_ci char *args; 26462306a36Sopenharmony_ci int rc; 26562306a36Sopenharmony_ci 26662306a36Sopenharmony_ci __kaslr_enabled = IS_ENABLED(CONFIG_RANDOMIZE_BASE); 26762306a36Sopenharmony_ci args = strcpy(command_line_buf, early_command_line); 26862306a36Sopenharmony_ci while (*args) { 26962306a36Sopenharmony_ci args = next_arg(args, ¶m, &val); 27062306a36Sopenharmony_ci 27162306a36Sopenharmony_ci if (!strcmp(param, "mem") && val) 27262306a36Sopenharmony_ci memory_limit = round_down(memparse(val, NULL), PAGE_SIZE); 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci if (!strcmp(param, "vmalloc") && val) { 27562306a36Sopenharmony_ci vmalloc_size = round_up(memparse(val, NULL), _SEGMENT_SIZE); 27662306a36Sopenharmony_ci vmalloc_size_set = 1; 27762306a36Sopenharmony_ci } 27862306a36Sopenharmony_ci 27962306a36Sopenharmony_ci if (!strcmp(param, "dfltcc") && val) { 28062306a36Sopenharmony_ci if (!strcmp(val, "off")) 28162306a36Sopenharmony_ci zlib_dfltcc_support = ZLIB_DFLTCC_DISABLED; 28262306a36Sopenharmony_ci else if (!strcmp(val, "on")) 28362306a36Sopenharmony_ci zlib_dfltcc_support = ZLIB_DFLTCC_FULL; 28462306a36Sopenharmony_ci else if (!strcmp(val, "def_only")) 28562306a36Sopenharmony_ci zlib_dfltcc_support = ZLIB_DFLTCC_DEFLATE_ONLY; 28662306a36Sopenharmony_ci else if (!strcmp(val, "inf_only")) 28762306a36Sopenharmony_ci zlib_dfltcc_support = ZLIB_DFLTCC_INFLATE_ONLY; 28862306a36Sopenharmony_ci else if (!strcmp(val, "always")) 28962306a36Sopenharmony_ci zlib_dfltcc_support = ZLIB_DFLTCC_FULL_DEBUG; 29062306a36Sopenharmony_ci } 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci if (!strcmp(param, "facilities") && val) 29362306a36Sopenharmony_ci modify_fac_list(val); 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci if (!strcmp(param, "nokaslr")) 29662306a36Sopenharmony_ci __kaslr_enabled = 0; 29762306a36Sopenharmony_ci 29862306a36Sopenharmony_ci#if IS_ENABLED(CONFIG_KVM) 29962306a36Sopenharmony_ci if (!strcmp(param, "prot_virt")) { 30062306a36Sopenharmony_ci rc = kstrtobool(val, &enabled); 30162306a36Sopenharmony_ci if (!rc && enabled) 30262306a36Sopenharmony_ci prot_virt_host = 1; 30362306a36Sopenharmony_ci } 30462306a36Sopenharmony_ci#endif 30562306a36Sopenharmony_ci } 30662306a36Sopenharmony_ci} 307