| // SPDX-License-Identifier: GPL-2.0 |
| #include <linux/kernel.h> |
| #include <linux/init.h> |
| #include <linux/ctype.h> |
| #include <linux/pgtable.h> |
| #include <asm/page-states.h> |
| #include <asm/ebcdic.h> |
| #include <asm/sclp.h> |
| #include <asm/sections.h> |
| #include <asm/boot_data.h> |
| #include <asm/facility.h> |
| #include <asm/setup.h> |
| #include <asm/uv.h> |
| #include "boot.h" |
| |
| struct parmarea parmarea __section(".parmarea") = { |
| .kernel_version = (unsigned long)kernel_version, |
| .max_command_line_size = COMMAND_LINE_SIZE, |
| .command_line = "root=/dev/ram0 ro", |
| }; |
| |
| char __bootdata(early_command_line)[COMMAND_LINE_SIZE]; |
| |
| unsigned int __bootdata_preserved(zlib_dfltcc_support) = ZLIB_DFLTCC_FULL; |
| struct ipl_parameter_block __bootdata_preserved(ipl_block); |
| int __bootdata_preserved(ipl_block_valid); |
| int __bootdata_preserved(__kaslr_enabled); |
| int __bootdata_preserved(cmma_flag) = 1; |
| |
| unsigned long vmalloc_size = VMALLOC_DEFAULT_SIZE; |
| unsigned long memory_limit; |
| int vmalloc_size_set; |
| |
| static inline int __diag308(unsigned long subcode, void *addr) |
| { |
| unsigned long reg1, reg2; |
| union register_pair r1; |
| psw_t old; |
| |
| r1.even = (unsigned long) addr; |
| r1.odd = 0; |
| asm volatile( |
| " mvc 0(16,%[psw_old]),0(%[psw_pgm])\n" |
| " epsw %[reg1],%[reg2]\n" |
| " st %[reg1],0(%[psw_pgm])\n" |
| " st %[reg2],4(%[psw_pgm])\n" |
| " larl %[reg1],1f\n" |
| " stg %[reg1],8(%[psw_pgm])\n" |
| " diag %[r1],%[subcode],0x308\n" |
| "1: mvc 0(16,%[psw_pgm]),0(%[psw_old])\n" |
| : [r1] "+&d" (r1.pair), |
| [reg1] "=&d" (reg1), |
| [reg2] "=&a" (reg2), |
| "+Q" (S390_lowcore.program_new_psw), |
| "=Q" (old) |
| : [subcode] "d" (subcode), |
| [psw_old] "a" (&old), |
| [psw_pgm] "a" (&S390_lowcore.program_new_psw) |
| : "cc", "memory"); |
| return r1.odd; |
| } |
| |
| 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; |
| } |
| |
| bool is_ipl_block_dump(void) |
| { |
| if (ipl_block.pb0_hdr.pbt == IPL_PBT_FCP && |
| ipl_block.fcp.opt == IPL_PB0_FCP_OPT_DUMP) |
| return true; |
| if (ipl_block.pb0_hdr.pbt == IPL_PBT_NVME && |
| ipl_block.nvme.opt == IPL_PB0_NVME_OPT_DUMP) |
| return true; |
| if (ipl_block.pb0_hdr.pbt == IPL_PBT_ECKD && |
| ipl_block.eckd.opt == IPL_PB0_ECKD_OPT_DUMP) |
| return true; |
| return false; |
| } |
| |
| 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) |
| { |
| const __u8 *scp_data; |
| __u32 scp_data_len; |
| int has_lowercase; |
| size_t count = 0; |
| size_t i; |
| |
| switch (ipb->pb0_hdr.pbt) { |
| case IPL_PBT_FCP: |
| scp_data_len = ipb->fcp.scp_data_len; |
| scp_data = ipb->fcp.scp_data; |
| break; |
| case IPL_PBT_NVME: |
| scp_data_len = ipb->nvme.scp_data_len; |
| scp_data = ipb->nvme.scp_data; |
| break; |
| case IPL_PBT_ECKD: |
| scp_data_len = ipb->eckd.scp_data_len; |
| scp_data = ipb->eckd.scp_data; |
| break; |
| |
| default: |
| goto out; |
| } |
| |
| count = min(size - 1, scpdata_length(scp_data, scp_data_len)); |
| if (!count) |
| goto out; |
| |
| has_lowercase = 0; |
| for (i = 0; i < count; i++) { |
| if (!isascii(scp_data[i])) { |
| count = 0; |
| goto out; |
| } |
| if (!has_lowercase && islower(scp_data[i])) |
| has_lowercase = 1; |
| } |
| |
| if (has_lowercase) |
| memcpy(dest, scp_data, count); |
| else |
| for (i = 0; i < count; i++) |
| dest[i] = tolower(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: |
| case IPL_PBT_NVME: |
| case IPL_PBT_ECKD: |
| 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) |
| { |
| parmarea.command_line[COMMAND_LINE_SIZE - 1] = 0; |
| /* convert arch command line to ascii if necessary */ |
| if (has_ebcdic_char(parmarea.command_line)) |
| EBCASC(parmarea.command_line, COMMAND_LINE_SIZE); |
| /* copy arch command line */ |
| strcpy(early_command_line, strim(parmarea.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, stfle_fac_list); |
| else |
| __set_facility(nr, 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 ((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]; |
| 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, ¶m, &val); |
| |
| if (!strcmp(param, "mem") && val) |
| memory_limit = round_down(memparse(val, NULL), PAGE_SIZE); |
| |
| if (!strcmp(param, "vmalloc") && val) { |
| vmalloc_size = round_up(memparse(val, NULL), _SEGMENT_SIZE); |
| vmalloc_size_set = 1; |
| } |
| |
| if (!strcmp(param, "dfltcc") && val) { |
| 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, "facilities") && val) |
| modify_fac_list(val); |
| |
| if (!strcmp(param, "nokaslr")) |
| __kaslr_enabled = 0; |
| |
| if (!strcmp(param, "cmma")) { |
| rc = kstrtobool(val, &enabled); |
| if (!rc && !enabled) |
| cmma_flag = 0; |
| } |
| |
| #if IS_ENABLED(CONFIG_KVM) |
| if (!strcmp(param, "prot_virt")) { |
| rc = kstrtobool(val, &enabled); |
| if (!rc && enabled) |
| prot_virt_host = 1; |
| } |
| #endif |
| } |
| } |