|
@@ -0,0 +1,307 @@
|
|
|
+/*
|
|
|
+ * arch/s390/kernel/early.c
|
|
|
+ *
|
|
|
+ * Copyright IBM Corp. 2007
|
|
|
+ * Author(s): Hongjie Yang <hongjie@us.ibm.com>,
|
|
|
+ * Heiko Carstens <heiko.carstens@de.ibm.com>
|
|
|
+ */
|
|
|
+
|
|
|
+#include <linux/init.h>
|
|
|
+#include <linux/errno.h>
|
|
|
+#include <linux/string.h>
|
|
|
+#include <linux/ctype.h>
|
|
|
+#include <linux/lockdep.h>
|
|
|
+#include <linux/module.h>
|
|
|
+#include <linux/pfn.h>
|
|
|
+#include <linux/uaccess.h>
|
|
|
+#include <asm/lowcore.h>
|
|
|
+#include <asm/processor.h>
|
|
|
+#include <asm/sections.h>
|
|
|
+#include <asm/setup.h>
|
|
|
+#include <asm/cpcmd.h>
|
|
|
+#include <asm/sclp.h>
|
|
|
+
|
|
|
+/*
|
|
|
+ * Create a Kernel NSS if the SAVESYS= parameter is defined
|
|
|
+ */
|
|
|
+#define DEFSYS_CMD_SIZE 96
|
|
|
+#define SAVESYS_CMD_SIZE 32
|
|
|
+
|
|
|
+extern int _eshared;
|
|
|
+char kernel_nss_name[NSS_NAME_SIZE + 1];
|
|
|
+
|
|
|
+#ifdef CONFIG_SHARED_KERNEL
|
|
|
+static noinline __init void create_kernel_nss(void)
|
|
|
+{
|
|
|
+ unsigned int i, stext_pfn, eshared_pfn, end_pfn, min_size;
|
|
|
+#ifdef CONFIG_BLK_DEV_INITRD
|
|
|
+ unsigned int sinitrd_pfn, einitrd_pfn;
|
|
|
+#endif
|
|
|
+ int response;
|
|
|
+ char *savesys_ptr;
|
|
|
+ char upper_command_line[COMMAND_LINE_SIZE];
|
|
|
+ char defsys_cmd[DEFSYS_CMD_SIZE];
|
|
|
+ char savesys_cmd[SAVESYS_CMD_SIZE];
|
|
|
+
|
|
|
+ /* Do nothing if we are not running under VM */
|
|
|
+ if (!MACHINE_IS_VM)
|
|
|
+ return;
|
|
|
+
|
|
|
+ /* Convert COMMAND_LINE to upper case */
|
|
|
+ for (i = 0; i < strlen(COMMAND_LINE); i++)
|
|
|
+ upper_command_line[i] = toupper(COMMAND_LINE[i]);
|
|
|
+
|
|
|
+ savesys_ptr = strstr(upper_command_line, "SAVESYS=");
|
|
|
+
|
|
|
+ if (!savesys_ptr)
|
|
|
+ return;
|
|
|
+
|
|
|
+ savesys_ptr += 8; /* Point to the beginning of the NSS name */
|
|
|
+ for (i = 0; i < NSS_NAME_SIZE; i++) {
|
|
|
+ if (savesys_ptr[i] == ' ' || savesys_ptr[i] == '\0')
|
|
|
+ break;
|
|
|
+ kernel_nss_name[i] = savesys_ptr[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ stext_pfn = PFN_DOWN(__pa(&_stext));
|
|
|
+ eshared_pfn = PFN_DOWN(__pa(&_eshared));
|
|
|
+ end_pfn = PFN_UP(__pa(&_end));
|
|
|
+ min_size = end_pfn << 2;
|
|
|
+
|
|
|
+ sprintf(defsys_cmd, "DEFSYS %s 00000-%.5X EW %.5X-%.5X SR %.5X-%.5X",
|
|
|
+ kernel_nss_name, stext_pfn - 1, stext_pfn, eshared_pfn - 1,
|
|
|
+ eshared_pfn, end_pfn);
|
|
|
+
|
|
|
+#ifdef CONFIG_BLK_DEV_INITRD
|
|
|
+ if (INITRD_START && INITRD_SIZE) {
|
|
|
+ sinitrd_pfn = PFN_DOWN(__pa(INITRD_START));
|
|
|
+ einitrd_pfn = PFN_UP(__pa(INITRD_START + INITRD_SIZE));
|
|
|
+ min_size = einitrd_pfn << 2;
|
|
|
+ sprintf(defsys_cmd, "%s EW %.5X-%.5X", defsys_cmd,
|
|
|
+ sinitrd_pfn, einitrd_pfn);
|
|
|
+ }
|
|
|
+#endif
|
|
|
+
|
|
|
+ sprintf(defsys_cmd, "%s EW MINSIZE=%.7iK", defsys_cmd, min_size);
|
|
|
+ sprintf(savesys_cmd, "SAVESYS %s \n IPL %s",
|
|
|
+ kernel_nss_name, kernel_nss_name);
|
|
|
+
|
|
|
+ __cpcmd(defsys_cmd, NULL, 0, &response);
|
|
|
+
|
|
|
+ if (response != 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ __cpcmd(savesys_cmd, NULL, 0, &response);
|
|
|
+
|
|
|
+ if (response != strlen(savesys_cmd))
|
|
|
+ return;
|
|
|
+
|
|
|
+ ipl_flags = IPL_NSS_VALID;
|
|
|
+}
|
|
|
+
|
|
|
+#else /* CONFIG_SHARED_KERNEL */
|
|
|
+
|
|
|
+static inline void create_kernel_nss(void) { }
|
|
|
+
|
|
|
+#endif /* CONFIG_SHARED_KERNEL */
|
|
|
+
|
|
|
+/*
|
|
|
+ * Clear bss memory
|
|
|
+ */
|
|
|
+static noinline __init void clear_bss_section(void)
|
|
|
+{
|
|
|
+ memset(__bss_start, 0, _end - __bss_start);
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Initialize storage key for kernel pages
|
|
|
+ */
|
|
|
+static noinline __init void init_kernel_storage_key(void)
|
|
|
+{
|
|
|
+ unsigned long end_pfn, init_pfn;
|
|
|
+
|
|
|
+ end_pfn = PFN_UP(__pa(&_end));
|
|
|
+
|
|
|
+ for (init_pfn = 0 ; init_pfn < end_pfn; init_pfn++)
|
|
|
+ page_set_storage_key(init_pfn << PAGE_SHIFT, PAGE_DEFAULT_KEY);
|
|
|
+}
|
|
|
+
|
|
|
+static noinline __init void detect_machine_type(void)
|
|
|
+{
|
|
|
+ struct cpuinfo_S390 *cpuinfo = &S390_lowcore.cpu_data;
|
|
|
+
|
|
|
+ asm volatile("stidp %0" : "=m" (S390_lowcore.cpu_data.cpu_id));
|
|
|
+
|
|
|
+ /* Running under z/VM ? */
|
|
|
+ if (cpuinfo->cpu_id.version == 0xff)
|
|
|
+ machine_flags |= 1;
|
|
|
+
|
|
|
+ /* Running on a P/390 ? */
|
|
|
+ if (cpuinfo->cpu_id.machine == 0x7490)
|
|
|
+ machine_flags |= 4;
|
|
|
+}
|
|
|
+
|
|
|
+static noinline __init int memory_fast_detect(void)
|
|
|
+{
|
|
|
+
|
|
|
+ unsigned long val0 = 0;
|
|
|
+ unsigned long val1 = 0xc;
|
|
|
+ int ret = -ENOSYS;
|
|
|
+
|
|
|
+ if (ipl_flags & IPL_NSS_VALID)
|
|
|
+ return -ENOSYS;
|
|
|
+
|
|
|
+ asm volatile(
|
|
|
+ " diag %1,%2,0x260\n"
|
|
|
+ "0: lhi %0,0\n"
|
|
|
+ "1:\n"
|
|
|
+ EX_TABLE(0b,1b)
|
|
|
+ : "+d" (ret), "+d" (val0), "+d" (val1) : : "cc");
|
|
|
+
|
|
|
+ if (ret || val0 != val1)
|
|
|
+ return -ENOSYS;
|
|
|
+
|
|
|
+ memory_chunk[0].size = val0;
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+#define ADDR2G (1UL << 31)
|
|
|
+
|
|
|
+static noinline __init unsigned long sclp_memory_detect(void)
|
|
|
+{
|
|
|
+ struct sclp_readinfo_sccb *sccb;
|
|
|
+ unsigned long long memsize;
|
|
|
+
|
|
|
+ sccb = &s390_readinfo_sccb;
|
|
|
+
|
|
|
+ if (sccb->header.response_code != 0x10)
|
|
|
+ return 0;
|
|
|
+
|
|
|
+ if (sccb->rnsize)
|
|
|
+ memsize = sccb->rnsize << 20;
|
|
|
+ else
|
|
|
+ memsize = sccb->rnsize2 << 20;
|
|
|
+ if (sccb->rnmax)
|
|
|
+ memsize *= sccb->rnmax;
|
|
|
+ else
|
|
|
+ memsize *= sccb->rnmax2;
|
|
|
+#ifndef CONFIG_64BIT
|
|
|
+ /*
|
|
|
+ * Can't deal with more than 2G in 31 bit addressing mode, so
|
|
|
+ * limit the value in order to avoid strange side effects.
|
|
|
+ */
|
|
|
+ if (memsize > ADDR2G)
|
|
|
+ memsize = ADDR2G;
|
|
|
+#endif
|
|
|
+ return (unsigned long) memsize;
|
|
|
+}
|
|
|
+
|
|
|
+static inline __init unsigned long __tprot(unsigned long addr)
|
|
|
+{
|
|
|
+ int cc = -1;
|
|
|
+
|
|
|
+ asm volatile(
|
|
|
+ " tprot 0(%1),0\n"
|
|
|
+ "0: ipm %0\n"
|
|
|
+ " srl %0,28\n"
|
|
|
+ "1:\n"
|
|
|
+ EX_TABLE(0b,1b)
|
|
|
+ : "+d" (cc) : "a" (addr) : "cc");
|
|
|
+ return (unsigned long)cc;
|
|
|
+}
|
|
|
+
|
|
|
+/* Checking memory in 128KB increments. */
|
|
|
+#define CHUNK_INCR (1UL << 17)
|
|
|
+
|
|
|
+static noinline __init void find_memory_chunks(unsigned long memsize)
|
|
|
+{
|
|
|
+ unsigned long addr = 0, old_addr = 0;
|
|
|
+ unsigned long old_cc = CHUNK_READ_WRITE;
|
|
|
+ unsigned long cc;
|
|
|
+ int chunk = 0;
|
|
|
+
|
|
|
+ while (chunk < MEMORY_CHUNKS) {
|
|
|
+ cc = __tprot(addr);
|
|
|
+ while (cc == old_cc) {
|
|
|
+ addr += CHUNK_INCR;
|
|
|
+ cc = __tprot(addr);
|
|
|
+#ifndef CONFIG_64BIT
|
|
|
+ if (addr == ADDR2G)
|
|
|
+ break;
|
|
|
+#endif
|
|
|
+ }
|
|
|
+
|
|
|
+ if (old_addr != addr &&
|
|
|
+ (old_cc == CHUNK_READ_WRITE || old_cc == CHUNK_READ_ONLY)) {
|
|
|
+ memory_chunk[chunk].addr = old_addr;
|
|
|
+ memory_chunk[chunk].size = addr - old_addr;
|
|
|
+ memory_chunk[chunk].type = old_cc;
|
|
|
+ chunk++;
|
|
|
+ }
|
|
|
+
|
|
|
+ old_addr = addr;
|
|
|
+ old_cc = cc;
|
|
|
+
|
|
|
+#ifndef CONFIG_64BIT
|
|
|
+ if (addr == ADDR2G)
|
|
|
+ break;
|
|
|
+#endif
|
|
|
+ /*
|
|
|
+ * Finish memory detection at the first hole, unless
|
|
|
+ * - we reached the hsa -> skip it.
|
|
|
+ * - we know there must be more.
|
|
|
+ */
|
|
|
+ if (cc == -1UL && !memsize && old_addr != ADDR2G)
|
|
|
+ break;
|
|
|
+ if (memsize && addr >= memsize)
|
|
|
+ break;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static __init void early_pgm_check_handler(void)
|
|
|
+{
|
|
|
+ unsigned long addr;
|
|
|
+ const struct exception_table_entry *fixup;
|
|
|
+
|
|
|
+ addr = S390_lowcore.program_old_psw.addr;
|
|
|
+ fixup = search_exception_tables(addr & PSW_ADDR_INSN);
|
|
|
+ if (!fixup)
|
|
|
+ disabled_wait(0);
|
|
|
+ S390_lowcore.program_old_psw.addr = fixup->fixup | PSW_ADDR_AMODE;
|
|
|
+}
|
|
|
+
|
|
|
+static noinline __init void setup_lowcore_early(void)
|
|
|
+{
|
|
|
+ psw_t psw;
|
|
|
+
|
|
|
+ psw.mask = PSW_BASE_BITS | PSW_DEFAULT_KEY;
|
|
|
+ psw.addr = PSW_ADDR_AMODE | (unsigned long) s390_base_ext_handler;
|
|
|
+ S390_lowcore.external_new_psw = psw;
|
|
|
+ psw.addr = PSW_ADDR_AMODE | (unsigned long) s390_base_pgm_handler;
|
|
|
+ S390_lowcore.program_new_psw = psw;
|
|
|
+ s390_base_pgm_handler_fn = early_pgm_check_handler;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * Save ipl parameters, clear bss memory, initialize storage keys
|
|
|
+ * and create a kernel NSS at startup if the SAVESYS= parm is defined
|
|
|
+ */
|
|
|
+void __init startup_init(void)
|
|
|
+{
|
|
|
+ unsigned long memsize;
|
|
|
+
|
|
|
+ ipl_save_parameters();
|
|
|
+ clear_bss_section();
|
|
|
+ init_kernel_storage_key();
|
|
|
+ lockdep_init();
|
|
|
+ lockdep_off();
|
|
|
+ detect_machine_type();
|
|
|
+ create_kernel_nss();
|
|
|
+ sort_main_extable();
|
|
|
+ setup_lowcore_early();
|
|
|
+ sclp_readinfo_early();
|
|
|
+ memsize = sclp_memory_detect();
|
|
|
+ if (memory_fast_detect() < 0)
|
|
|
+ find_memory_chunks(memsize);
|
|
|
+ lockdep_on();
|
|
|
+}
|