Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * Copyright IBM Corp. 2007, 2009 |
| 4 | * Author(s): Hongjie Yang <hongjie@us.ibm.com>, |
| 5 | * Heiko Carstens <heiko.carstens@de.ibm.com> |
| 6 | */ |
| 7 | |
| 8 | #define KMSG_COMPONENT "setup" |
| 9 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt |
| 10 | |
| 11 | #include <linux/compiler.h> |
| 12 | #include <linux/init.h> |
| 13 | #include <linux/errno.h> |
| 14 | #include <linux/string.h> |
| 15 | #include <linux/ctype.h> |
| 16 | #include <linux/lockdep.h> |
| 17 | #include <linux/extable.h> |
| 18 | #include <linux/pfn.h> |
| 19 | #include <linux/uaccess.h> |
| 20 | #include <linux/kernel.h> |
| 21 | #include <asm/diag.h> |
| 22 | #include <asm/ebcdic.h> |
| 23 | #include <asm/ipl.h> |
| 24 | #include <asm/lowcore.h> |
| 25 | #include <asm/processor.h> |
| 26 | #include <asm/sections.h> |
| 27 | #include <asm/setup.h> |
| 28 | #include <asm/sysinfo.h> |
| 29 | #include <asm/cpcmd.h> |
| 30 | #include <asm/sclp.h> |
| 31 | #include <asm/facility.h> |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 32 | #include <asm/boot_data.h> |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 33 | #include "entry.h" |
| 34 | |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 35 | static void __init reset_tod_clock(void) |
| 36 | { |
| 37 | u64 time; |
| 38 | |
| 39 | if (store_tod_clock(&time) == 0) |
| 40 | return; |
| 41 | /* TOD clock not running. Set the clock to Unix Epoch. */ |
| 42 | if (set_tod_clock(TOD_UNIX_EPOCH) != 0 || store_tod_clock(&time) != 0) |
| 43 | disabled_wait(); |
| 44 | |
| 45 | memset(tod_clock_base, 0, 16); |
| 46 | *(__u64 *) &tod_clock_base[1] = TOD_UNIX_EPOCH; |
| 47 | S390_lowcore.last_update_clock = TOD_UNIX_EPOCH; |
| 48 | } |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 49 | |
| 50 | /* |
| 51 | * Initialize storage key for kernel pages |
| 52 | */ |
| 53 | static noinline __init void init_kernel_storage_key(void) |
| 54 | { |
| 55 | #if PAGE_DEFAULT_KEY |
| 56 | unsigned long end_pfn, init_pfn; |
| 57 | |
| 58 | end_pfn = PFN_UP(__pa(_end)); |
| 59 | |
| 60 | for (init_pfn = 0 ; init_pfn < end_pfn; init_pfn++) |
| 61 | page_set_storage_key(init_pfn << PAGE_SHIFT, |
| 62 | PAGE_DEFAULT_KEY, 0); |
| 63 | #endif |
| 64 | } |
| 65 | |
| 66 | static __initdata char sysinfo_page[PAGE_SIZE] __aligned(PAGE_SIZE); |
| 67 | |
| 68 | static noinline __init void detect_machine_type(void) |
| 69 | { |
| 70 | struct sysinfo_3_2_2 *vmms = (struct sysinfo_3_2_2 *)&sysinfo_page; |
| 71 | |
| 72 | /* Check current-configuration-level */ |
| 73 | if (stsi(NULL, 0, 0, 0) <= 2) { |
| 74 | S390_lowcore.machine_flags |= MACHINE_FLAG_LPAR; |
| 75 | return; |
| 76 | } |
| 77 | /* Get virtual-machine cpu information. */ |
| 78 | if (stsi(vmms, 3, 2, 2) || !vmms->count) |
| 79 | return; |
| 80 | |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 81 | /* Detect known hypervisors */ |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 82 | if (!memcmp(vmms->vm[0].cpi, "\xd2\xe5\xd4", 3)) |
| 83 | S390_lowcore.machine_flags |= MACHINE_FLAG_KVM; |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 84 | else if (!memcmp(vmms->vm[0].cpi, "\xa9\x61\xe5\xd4", 4)) |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 85 | S390_lowcore.machine_flags |= MACHINE_FLAG_VM; |
| 86 | } |
| 87 | |
| 88 | /* Remove leading, trailing and double whitespace. */ |
| 89 | static inline void strim_all(char *str) |
| 90 | { |
| 91 | char *s; |
| 92 | |
| 93 | s = strim(str); |
| 94 | if (s != str) |
| 95 | memmove(str, s, strlen(s)); |
| 96 | while (*str) { |
| 97 | if (!isspace(*str++)) |
| 98 | continue; |
| 99 | if (isspace(*str)) { |
| 100 | s = skip_spaces(str); |
| 101 | memmove(str, s, strlen(s) + 1); |
| 102 | } |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | static noinline __init void setup_arch_string(void) |
| 107 | { |
| 108 | struct sysinfo_1_1_1 *mach = (struct sysinfo_1_1_1 *)&sysinfo_page; |
| 109 | struct sysinfo_3_2_2 *vm = (struct sysinfo_3_2_2 *)&sysinfo_page; |
| 110 | char mstr[80], hvstr[17]; |
| 111 | |
| 112 | if (stsi(mach, 1, 1, 1)) |
| 113 | return; |
| 114 | EBCASC(mach->manufacturer, sizeof(mach->manufacturer)); |
| 115 | EBCASC(mach->type, sizeof(mach->type)); |
| 116 | EBCASC(mach->model, sizeof(mach->model)); |
| 117 | EBCASC(mach->model_capacity, sizeof(mach->model_capacity)); |
| 118 | sprintf(mstr, "%-16.16s %-4.4s %-16.16s %-16.16s", |
| 119 | mach->manufacturer, mach->type, |
| 120 | mach->model, mach->model_capacity); |
| 121 | strim_all(mstr); |
| 122 | if (stsi(vm, 3, 2, 2) == 0 && vm->count) { |
| 123 | EBCASC(vm->vm[0].cpi, sizeof(vm->vm[0].cpi)); |
| 124 | sprintf(hvstr, "%-16.16s", vm->vm[0].cpi); |
| 125 | strim_all(hvstr); |
| 126 | } else { |
| 127 | sprintf(hvstr, "%s", |
| 128 | MACHINE_IS_LPAR ? "LPAR" : |
| 129 | MACHINE_IS_VM ? "z/VM" : |
| 130 | MACHINE_IS_KVM ? "KVM" : "unknown"); |
| 131 | } |
| 132 | dump_stack_set_arch_desc("%s (%s)", mstr, hvstr); |
| 133 | } |
| 134 | |
| 135 | static __init void setup_topology(void) |
| 136 | { |
| 137 | int max_mnest; |
| 138 | |
| 139 | if (!test_facility(11)) |
| 140 | return; |
| 141 | S390_lowcore.machine_flags |= MACHINE_FLAG_TOPOLOGY; |
| 142 | for (max_mnest = 6; max_mnest > 1; max_mnest--) { |
| 143 | if (stsi(&sysinfo_page, 15, 1, max_mnest) == 0) |
| 144 | break; |
| 145 | } |
| 146 | topology_max_mnest = max_mnest; |
| 147 | } |
| 148 | |
| 149 | static void early_pgm_check_handler(void) |
| 150 | { |
| 151 | const struct exception_table_entry *fixup; |
| 152 | unsigned long cr0, cr0_new; |
| 153 | unsigned long addr; |
| 154 | |
| 155 | addr = S390_lowcore.program_old_psw.addr; |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 156 | fixup = s390_search_extables(addr); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 157 | if (!fixup) |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 158 | disabled_wait(); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 159 | /* Disable low address protection before storing into lowcore. */ |
| 160 | __ctl_store(cr0, 0, 0); |
| 161 | cr0_new = cr0 & ~(1UL << 28); |
| 162 | __ctl_load(cr0_new, 0, 0); |
| 163 | S390_lowcore.program_old_psw.addr = extable_fixup(fixup); |
| 164 | __ctl_load(cr0, 0, 0); |
| 165 | } |
| 166 | |
| 167 | static noinline __init void setup_lowcore_early(void) |
| 168 | { |
| 169 | psw_t psw; |
| 170 | |
| 171 | psw.mask = PSW_MASK_BASE | PSW_DEFAULT_KEY | PSW_MASK_EA | PSW_MASK_BA; |
Olivier Deprez | 0e64123 | 2021-09-23 10:07:05 +0200 | [diff] [blame^] | 172 | if (IS_ENABLED(CONFIG_KASAN)) |
| 173 | psw.mask |= PSW_MASK_DAT; |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 174 | psw.addr = (unsigned long) s390_base_ext_handler; |
| 175 | S390_lowcore.external_new_psw = psw; |
| 176 | psw.addr = (unsigned long) s390_base_pgm_handler; |
| 177 | S390_lowcore.program_new_psw = psw; |
| 178 | s390_base_pgm_handler_fn = early_pgm_check_handler; |
| 179 | S390_lowcore.preempt_count = INIT_PREEMPT_COUNT; |
| 180 | } |
| 181 | |
| 182 | static noinline __init void setup_facility_list(void) |
| 183 | { |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 184 | memcpy(S390_lowcore.alt_stfle_fac_list, |
| 185 | S390_lowcore.stfle_fac_list, |
| 186 | sizeof(S390_lowcore.alt_stfle_fac_list)); |
| 187 | if (!IS_ENABLED(CONFIG_KERNEL_NOBP)) |
| 188 | __clear_facility(82, S390_lowcore.alt_stfle_fac_list); |
| 189 | } |
| 190 | |
| 191 | static __init void detect_diag9c(void) |
| 192 | { |
| 193 | unsigned int cpu_address; |
| 194 | int rc; |
| 195 | |
| 196 | cpu_address = stap(); |
| 197 | diag_stat_inc(DIAG_STAT_X09C); |
| 198 | asm volatile( |
| 199 | " diag %2,0,0x9c\n" |
| 200 | "0: la %0,0\n" |
| 201 | "1:\n" |
| 202 | EX_TABLE(0b,1b) |
| 203 | : "=d" (rc) : "0" (-EOPNOTSUPP), "d" (cpu_address) : "cc"); |
| 204 | if (!rc) |
| 205 | S390_lowcore.machine_flags |= MACHINE_FLAG_DIAG9C; |
| 206 | } |
| 207 | |
| 208 | static __init void detect_diag44(void) |
| 209 | { |
| 210 | int rc; |
| 211 | |
| 212 | diag_stat_inc(DIAG_STAT_X044); |
| 213 | asm volatile( |
| 214 | " diag 0,0,0x44\n" |
| 215 | "0: la %0,0\n" |
| 216 | "1:\n" |
| 217 | EX_TABLE(0b,1b) |
| 218 | : "=d" (rc) : "0" (-EOPNOTSUPP) : "cc"); |
| 219 | if (!rc) |
| 220 | S390_lowcore.machine_flags |= MACHINE_FLAG_DIAG44; |
| 221 | } |
| 222 | |
| 223 | static __init void detect_machine_facilities(void) |
| 224 | { |
| 225 | if (test_facility(8)) { |
| 226 | S390_lowcore.machine_flags |= MACHINE_FLAG_EDAT1; |
| 227 | __ctl_set_bit(0, 23); |
| 228 | } |
| 229 | if (test_facility(78)) |
| 230 | S390_lowcore.machine_flags |= MACHINE_FLAG_EDAT2; |
| 231 | if (test_facility(3)) |
| 232 | S390_lowcore.machine_flags |= MACHINE_FLAG_IDTE; |
| 233 | if (test_facility(50) && test_facility(73)) { |
| 234 | S390_lowcore.machine_flags |= MACHINE_FLAG_TE; |
| 235 | __ctl_set_bit(0, 55); |
| 236 | } |
| 237 | if (test_facility(51)) |
| 238 | S390_lowcore.machine_flags |= MACHINE_FLAG_TLB_LC; |
| 239 | if (test_facility(129)) { |
| 240 | S390_lowcore.machine_flags |= MACHINE_FLAG_VX; |
| 241 | __ctl_set_bit(0, 17); |
| 242 | } |
| 243 | if (test_facility(130)) { |
| 244 | S390_lowcore.machine_flags |= MACHINE_FLAG_NX; |
| 245 | __ctl_set_bit(0, 20); |
| 246 | } |
| 247 | if (test_facility(133)) |
| 248 | S390_lowcore.machine_flags |= MACHINE_FLAG_GS; |
| 249 | if (test_facility(139) && (tod_clock_base[1] & 0x80)) { |
| 250 | /* Enabled signed clock comparator comparisons */ |
| 251 | S390_lowcore.machine_flags |= MACHINE_FLAG_SCC; |
| 252 | clock_comparator_max = -1ULL >> 1; |
| 253 | __ctl_set_bit(0, 53); |
| 254 | } |
Olivier Deprez | 0e64123 | 2021-09-23 10:07:05 +0200 | [diff] [blame^] | 255 | if (IS_ENABLED(CONFIG_PCI) && test_facility(153)) { |
| 256 | S390_lowcore.machine_flags |= MACHINE_FLAG_PCI_MIO; |
| 257 | /* the control bit is set during PCI initialization */ |
| 258 | } |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 259 | } |
| 260 | |
| 261 | static inline void save_vector_registers(void) |
| 262 | { |
| 263 | #ifdef CONFIG_CRASH_DUMP |
| 264 | if (test_facility(129)) |
| 265 | save_vx_regs(boot_cpu_vector_save_area); |
| 266 | #endif |
| 267 | } |
| 268 | |
| 269 | static int __init disable_vector_extension(char *str) |
| 270 | { |
| 271 | S390_lowcore.machine_flags &= ~MACHINE_FLAG_VX; |
| 272 | __ctl_clear_bit(0, 17); |
| 273 | return 0; |
| 274 | } |
| 275 | early_param("novx", disable_vector_extension); |
| 276 | |
| 277 | static int __init noexec_setup(char *str) |
| 278 | { |
| 279 | bool enabled; |
| 280 | int rc; |
| 281 | |
| 282 | rc = kstrtobool(str, &enabled); |
| 283 | if (!rc && !enabled) { |
| 284 | /* Disable no-execute support */ |
| 285 | S390_lowcore.machine_flags &= ~MACHINE_FLAG_NX; |
| 286 | __ctl_clear_bit(0, 20); |
| 287 | } |
| 288 | return rc; |
| 289 | } |
| 290 | early_param("noexec", noexec_setup); |
| 291 | |
| 292 | static int __init cad_setup(char *str) |
| 293 | { |
| 294 | bool enabled; |
| 295 | int rc; |
| 296 | |
| 297 | rc = kstrtobool(str, &enabled); |
| 298 | if (!rc && enabled && test_facility(128)) |
| 299 | /* Enable problem state CAD. */ |
| 300 | __ctl_set_bit(2, 3); |
| 301 | return rc; |
| 302 | } |
| 303 | early_param("cad", cad_setup); |
| 304 | |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 305 | char __bootdata(early_command_line)[COMMAND_LINE_SIZE]; |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 306 | static void __init setup_boot_command_line(void) |
| 307 | { |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 308 | /* copy arch command line */ |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 309 | strlcpy(boot_command_line, early_command_line, ARCH_COMMAND_LINE_SIZE); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 310 | } |
| 311 | |
| 312 | static void __init check_image_bootable(void) |
| 313 | { |
| 314 | if (!memcmp(EP_STRING, (void *)EP_OFFSET, strlen(EP_STRING))) |
| 315 | return; |
| 316 | |
| 317 | sclp_early_printk("Linux kernel boot failure: An attempt to boot a vmlinux ELF image failed.\n"); |
| 318 | sclp_early_printk("This image does not contain all parts necessary for starting up. Use\n"); |
| 319 | sclp_early_printk("bzImage or arch/s390/boot/compressed/vmlinux instead.\n"); |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 320 | disabled_wait(); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 321 | } |
| 322 | |
| 323 | void __init startup_init(void) |
| 324 | { |
David Brazdil | 0f672f6 | 2019-12-10 10:32:29 +0000 | [diff] [blame] | 325 | reset_tod_clock(); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 326 | check_image_bootable(); |
| 327 | time_early_init(); |
| 328 | init_kernel_storage_key(); |
| 329 | lockdep_off(); |
| 330 | setup_lowcore_early(); |
| 331 | setup_facility_list(); |
| 332 | detect_machine_type(); |
| 333 | setup_arch_string(); |
Andrew Scull | b4b6d4a | 2019-01-02 15:54:55 +0000 | [diff] [blame] | 334 | setup_boot_command_line(); |
| 335 | detect_diag9c(); |
| 336 | detect_diag44(); |
| 337 | detect_machine_facilities(); |
| 338 | save_vector_registers(); |
| 339 | setup_topology(); |
| 340 | sclp_early_detect(); |
| 341 | lockdep_on(); |
| 342 | } |