[S390] kernel: Add OS info memory interface
authorMichael Holzheu <holzheu@linux.vnet.ibm.com>
Sun, 11 Mar 2012 15:59:34 +0000 (11:59 -0400)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Sun, 11 Mar 2012 15:59:29 +0000 (11:59 -0400)
In order to allow kdump based stand-alone dump, some information
has to be passed from the old kernel to the new dump kernel. This
is done via a the struct "os_info" that contains the following fields:
 * crashkernel base and size
 * reipl block
 * vmcoreinfo
 * init function
A pointer to os_info is stored at a well known storage location
and the whole structure as well as all fields are secured with
checksums.

Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
arch/s390/include/asm/lowcore.h
arch/s390/include/asm/os_info.h [new file with mode: 0644]
arch/s390/kernel/Makefile
arch/s390/kernel/crash_dump.c
arch/s390/kernel/ipl.c
arch/s390/kernel/os_info.c [new file with mode: 0644]
arch/s390/kernel/setup.c
arch/s390/kernel/smp.c

index 4e69563bc956fd55b3220490621578659eb2d8b0..a47c6e221a9528c190a5ae979bdd3dadd4390850 100644 (file)
@@ -154,7 +154,9 @@ struct _lowcore {
        __u32   ipib;                           /* 0x0e00 */
        __u32   ipib_checksum;                  /* 0x0e04 */
        __u32   vmcore_info;                    /* 0x0e08 */
-       __u8    pad_0x0e0c[0x0f00-0x0e0c];      /* 0x0e0c */
+       __u8    pad_0x0e0c[0x0e18-0x0e0c];      /* 0x0e0c */
+       __u32   os_info;                        /* 0x0e18 */
+       __u8    pad_0x0e1c[0x0f00-0x0e1c];      /* 0x0e1c */
 
        /* Extended facility list */
        __u64   stfle_fac_list[32];             /* 0x0f00 */
@@ -301,7 +303,9 @@ struct _lowcore {
        __u64   ipib;                           /* 0x0e00 */
        __u32   ipib_checksum;                  /* 0x0e08 */
        __u64   vmcore_info;                    /* 0x0e0c */
-       __u8    pad_0x0e14[0x0f00-0x0e14];      /* 0x0e14 */
+       __u8    pad_0x0e14[0x0e18-0x0e14];      /* 0x0e14 */
+       __u64   os_info;                        /* 0x0e18 */
+       __u8    pad_0x0e20[0x0f00-0x0e20];      /* 0x0e20 */
 
        /* Extended facility list */
        __u64   stfle_fac_list[32];             /* 0x0f00 */
diff --git a/arch/s390/include/asm/os_info.h b/arch/s390/include/asm/os_info.h
new file mode 100644 (file)
index 0000000..d07518a
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * OS info memory interface
+ *
+ * Copyright IBM Corp. 2012
+ * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
+ */
+#ifndef _ASM_S390_OS_INFO_H
+#define _ASM_S390_OS_INFO_H
+
+#define OS_INFO_VERSION_MAJOR  1
+#define OS_INFO_VERSION_MINOR  1
+#define OS_INFO_MAGIC          0x4f53494e464f535aULL /* OSINFOSZ */
+
+#define OS_INFO_VMCOREINFO     0
+#define OS_INFO_REIPL_BLOCK    1
+#define OS_INFO_INIT_FN                2
+
+struct os_info_entry {
+       u64     addr;
+       u64     size;
+       u32     csum;
+} __packed;
+
+struct os_info {
+       u64     magic;
+       u32     csum;
+       u16     version_major;
+       u16     version_minor;
+       u64     crashkernel_addr;
+       u64     crashkernel_size;
+       struct os_info_entry entry[3];
+       u8      reserved[4004];
+} __packed;
+
+void os_info_init(void);
+void os_info_entry_add(int nr, void *ptr, u64 len);
+void os_info_crashkernel_add(unsigned long base, unsigned long size);
+u32 os_info_csum(struct os_info *os_info);
+
+#ifdef CONFIG_CRASH_DUMP
+void *os_info_old_entry(int nr, unsigned long *size);
+int copy_from_oldmem(void *dest, void *src, size_t count);
+#else
+static inline void *os_info_old_entry(int nr, unsigned long *size)
+{
+       return NULL;
+}
+#endif
+
+#endif /* _ASM_S390_OS_INFO_H */
index b215950904993f07b321459102384dc742296eb9..16b0b433f1f48510a7978814beed7f265cf628ad 100644 (file)
@@ -23,7 +23,7 @@ CFLAGS_sysinfo.o += -Iinclude/math-emu -Iarch/s390/math-emu -w
 obj-y  :=  bitmap.o traps.o time.o process.o base.o early.o setup.o vtime.o \
            processor.o sys_s390.o ptrace.o signal.o cpcmd.o ebcdic.o nmi.o \
            debug.o irq.o ipl.o dis.o diag.o mem_detect.o sclp.o vdso.o \
-           sysinfo.o jump_label.o lgr.o
+           sysinfo.o jump_label.o lgr.o os_info.o
 
 obj-y  += $(if $(CONFIG_64BIT),entry64.o,entry.o)
 obj-y  += $(if $(CONFIG_64BIT),reipl64.o,reipl.o)
index c383ce440d99952149895b0094d6794a22af2d5c..cc1172b26873ee0f3903231571a6e40a561ad435 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/bootmem.h>
 #include <linux/elf.h>
 #include <asm/ipl.h>
+#include <asm/os_info.h>
 
 #define PTR_ADD(x, y) (((char *) (x)) + ((unsigned long) (y)))
 #define PTR_SUB(x, y) (((char *) (x)) - ((unsigned long) (y)))
@@ -51,7 +52,7 @@ ssize_t copy_oldmem_page(unsigned long pfn, char *buf,
 /*
  * Copy memory from old kernel
  */
-static int copy_from_oldmem(void *dest, void *src, size_t count)
+int copy_from_oldmem(void *dest, void *src, size_t count)
 {
        unsigned long copied = 0;
        int rc;
@@ -224,28 +225,44 @@ static void *nt_prpsinfo(void *ptr)
 }
 
 /*
- * Initialize vmcoreinfo note (new kernel)
+ * Get vmcoreinfo using lowcore->vmcore_info (new kernel)
  */
-static void *nt_vmcoreinfo(void *ptr)
+static void *get_vmcoreinfo_old(unsigned long *size)
 {
        char nt_name[11], *vmcoreinfo;
        Elf64_Nhdr note;
        void *addr;
 
        if (copy_from_oldmem(&addr, &S390_lowcore.vmcore_info, sizeof(addr)))
-               return ptr;
+               return NULL;
        memset(nt_name, 0, sizeof(nt_name));
        if (copy_from_oldmem(&note, addr, sizeof(note)))
-               return ptr;
+               return NULL;
        if (copy_from_oldmem(nt_name, addr + sizeof(note), sizeof(nt_name) - 1))
-               return ptr;
+               return NULL;
        if (strcmp(nt_name, "VMCOREINFO") != 0)
-               return ptr;
-       vmcoreinfo = kzalloc_panic(note.n_descsz + 1);
+               return NULL;
+       vmcoreinfo = kzalloc_panic(note.n_descsz);
        if (copy_from_oldmem(vmcoreinfo, addr + 24, note.n_descsz))
+               return NULL;
+       *size = note.n_descsz;
+       return vmcoreinfo;
+}
+
+/*
+ * Initialize vmcoreinfo note (new kernel)
+ */
+static void *nt_vmcoreinfo(void *ptr)
+{
+       unsigned long size;
+       void *vmcoreinfo;
+
+       vmcoreinfo = os_info_old_entry(OS_INFO_VMCOREINFO, &size);
+       if (!vmcoreinfo)
+               vmcoreinfo = get_vmcoreinfo_old(&size);
+       if (!vmcoreinfo)
                return ptr;
-       vmcoreinfo[note.n_descsz + 1] = 0;
-       return nt_init(ptr, 0, vmcoreinfo, note.n_descsz, "VMCOREINFO");
+       return nt_init(ptr, 0, vmcoreinfo, size, "VMCOREINFO");
 }
 
 /*
index 153e21ce2336ede8c468a792d204c8c4e3aa5f77..8342e65a140daf7bb3fc9f589fbcd1f8d417f322 100644 (file)
@@ -28,6 +28,7 @@
 #include <asm/sclp.h>
 #include <asm/checksum.h>
 #include <asm/debug.h>
+#include <asm/os_info.h>
 #include "entry.h"
 
 #define IPL_PARM_BLOCK_VERSION 0
@@ -951,6 +952,13 @@ static struct attribute_group reipl_nss_attr_group = {
        .attrs = reipl_nss_attrs,
 };
 
+static void set_reipl_block_actual(struct ipl_parameter_block *reipl_block)
+{
+       reipl_block_actual = reipl_block;
+       os_info_entry_add(OS_INFO_REIPL_BLOCK, reipl_block_actual,
+                         reipl_block->hdr.len);
+}
+
 /* reipl type */
 
 static int reipl_set_type(enum ipl_type type)
@@ -966,7 +974,7 @@ static int reipl_set_type(enum ipl_type type)
                        reipl_method = REIPL_METHOD_CCW_VM;
                else
                        reipl_method = REIPL_METHOD_CCW_CIO;
-               reipl_block_actual = reipl_block_ccw;
+               set_reipl_block_actual(reipl_block_ccw);
                break;
        case IPL_TYPE_FCP:
                if (diag308_set_works)
@@ -975,7 +983,7 @@ static int reipl_set_type(enum ipl_type type)
                        reipl_method = REIPL_METHOD_FCP_RO_VM;
                else
                        reipl_method = REIPL_METHOD_FCP_RO_DIAG;
-               reipl_block_actual = reipl_block_fcp;
+               set_reipl_block_actual(reipl_block_fcp);
                break;
        case IPL_TYPE_FCP_DUMP:
                reipl_method = REIPL_METHOD_FCP_DUMP;
@@ -985,7 +993,7 @@ static int reipl_set_type(enum ipl_type type)
                        reipl_method = REIPL_METHOD_NSS_DIAG;
                else
                        reipl_method = REIPL_METHOD_NSS;
-               reipl_block_actual = reipl_block_nss;
+               set_reipl_block_actual(reipl_block_nss);
                break;
        case IPL_TYPE_UNKNOWN:
                reipl_method = REIPL_METHOD_DEFAULT;
@@ -1257,6 +1265,29 @@ static int __init reipl_fcp_init(void)
        return 0;
 }
 
+static int __init reipl_type_init(void)
+{
+       enum ipl_type reipl_type = ipl_info.type;
+       struct ipl_parameter_block *reipl_block;
+       unsigned long size;
+
+       reipl_block = os_info_old_entry(OS_INFO_REIPL_BLOCK, &size);
+       if (!reipl_block)
+               goto out;
+       /*
+        * If we have an OS info reipl block, this will be used
+        */
+       if (reipl_block->hdr.pbt == DIAG308_IPL_TYPE_FCP) {
+               memcpy(reipl_block_fcp, reipl_block, size);
+               reipl_type = IPL_TYPE_FCP;
+       } else if (reipl_block->hdr.pbt == DIAG308_IPL_TYPE_CCW) {
+               memcpy(reipl_block_ccw, reipl_block, size);
+               reipl_type = IPL_TYPE_CCW;
+       }
+out:
+       return reipl_set_type(reipl_type);
+}
+
 static int __init reipl_init(void)
 {
        int rc;
@@ -1278,10 +1309,7 @@ static int __init reipl_init(void)
        rc = reipl_nss_init();
        if (rc)
                return rc;
-       rc = reipl_set_type(ipl_info.type);
-       if (rc)
-               return rc;
-       return 0;
+       return reipl_type_init();
 }
 
 static struct shutdown_action __refdata reipl_action = {
diff --git a/arch/s390/kernel/os_info.c b/arch/s390/kernel/os_info.c
new file mode 100644 (file)
index 0000000..bbe5226
--- /dev/null
@@ -0,0 +1,169 @@
+/*
+ * OS info memory interface
+ *
+ * Copyright IBM Corp. 2012
+ * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
+ */
+
+#define KMSG_COMPONENT "os_info"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
+#include <linux/crash_dump.h>
+#include <linux/kernel.h>
+#include <asm/checksum.h>
+#include <asm/lowcore.h>
+#include <asm/system.h>
+#include <asm/os_info.h>
+
+/*
+ * OS info structure has to be page aligned
+ */
+static struct os_info os_info __page_aligned_data;
+
+/*
+ * Compute checksum over OS info structure
+ */
+u32 os_info_csum(struct os_info *os_info)
+{
+       int size = sizeof(*os_info) - offsetof(struct os_info, version_major);
+       return csum_partial(&os_info->version_major, size, 0);
+}
+
+/*
+ * Add crashkernel info to OS info and update checksum
+ */
+void os_info_crashkernel_add(unsigned long base, unsigned long size)
+{
+       os_info.crashkernel_addr = (u64)(unsigned long)base;
+       os_info.crashkernel_size = (u64)(unsigned long)size;
+       os_info.csum = os_info_csum(&os_info);
+}
+
+/*
+ * Add OS info entry and update checksum
+ */
+void os_info_entry_add(int nr, void *ptr, u64 size)
+{
+       os_info.entry[nr].addr = (u64)(unsigned long)ptr;
+       os_info.entry[nr].size = size;
+       os_info.entry[nr].csum = csum_partial(ptr, size, 0);
+       os_info.csum = os_info_csum(&os_info);
+}
+
+/*
+ * Initialize OS info struture and set lowcore pointer
+ */
+void __init os_info_init(void)
+{
+       void *ptr = &os_info;
+
+       os_info.version_major = OS_INFO_VERSION_MAJOR;
+       os_info.version_minor = OS_INFO_VERSION_MINOR;
+       os_info.magic = OS_INFO_MAGIC;
+       os_info.csum = os_info_csum(&os_info);
+       copy_to_absolute_zero(&S390_lowcore.os_info, &ptr, sizeof(ptr));
+}
+
+#ifdef CONFIG_CRASH_DUMP
+
+static struct os_info *os_info_old;
+
+/*
+ * Allocate and copy OS info entry from oldmem
+ */
+static void os_info_old_alloc(int nr, int align)
+{
+       unsigned long addr, size = 0;
+       char *buf, *buf_align, *msg;
+       u32 csum;
+
+       addr = os_info_old->entry[nr].addr;
+       if (!addr) {
+               msg = "not available";
+               goto fail;
+       }
+       size = os_info_old->entry[nr].size;
+       buf = kmalloc(size + align - 1, GFP_KERNEL);
+       if (!buf) {
+               msg = "alloc failed";
+               goto fail;
+       }
+       buf_align = PTR_ALIGN(buf, align);
+       if (copy_from_oldmem(buf_align, (void *) addr, size)) {
+               msg = "copy failed";
+               goto fail_free;
+       }
+       csum = csum_partial(buf_align, size, 0);
+       if (csum != os_info_old->entry[nr].csum) {
+               msg = "checksum failed";
+               goto fail_free;
+       }
+       os_info_old->entry[nr].addr = (u64)(unsigned long)buf_align;
+       msg = "copied";
+       goto out;
+fail_free:
+       kfree(buf);
+fail:
+       os_info_old->entry[nr].addr = 0;
+out:
+       pr_info("entry %i: %s (addr=0x%lx size=%lu)\n",
+               nr, msg, addr, size);
+}
+
+/*
+ * Initialize os info and os info entries from oldmem
+ */
+static void os_info_old_init(void)
+{
+       static int os_info_init;
+       unsigned long addr;
+
+       if (os_info_init)
+               return;
+       if (!OLDMEM_BASE)
+               goto fail;
+       if (copy_from_oldmem(&addr, &S390_lowcore.os_info, sizeof(addr)))
+               goto fail;
+       if (addr == 0 || addr % PAGE_SIZE)
+               goto fail;
+       os_info_old = kzalloc(sizeof(*os_info_old), GFP_KERNEL);
+       if (!os_info_old)
+               goto fail;
+       if (copy_from_oldmem(os_info_old, (void *) addr, sizeof(*os_info_old)))
+               goto fail_free;
+       if (os_info_old->magic != OS_INFO_MAGIC)
+               goto fail_free;
+       if (os_info_old->csum != os_info_csum(os_info_old))
+               goto fail_free;
+       if (os_info_old->version_major > OS_INFO_VERSION_MAJOR)
+               goto fail_free;
+       os_info_old_alloc(OS_INFO_VMCOREINFO, 1);
+       os_info_old_alloc(OS_INFO_REIPL_BLOCK, 1);
+       os_info_old_alloc(OS_INFO_INIT_FN, PAGE_SIZE);
+       pr_info("crashkernel: addr=0x%lx size=%lu\n",
+               (unsigned long) os_info_old->crashkernel_addr,
+               (unsigned long) os_info_old->crashkernel_size);
+       os_info_init = 1;
+       return;
+fail_free:
+       kfree(os_info_old);
+fail:
+       os_info_init = 1;
+       os_info_old = NULL;
+}
+
+/*
+ * Return pointer to os infor entry and its size
+ */
+void *os_info_old_entry(int nr, unsigned long *size)
+{
+       os_info_old_init();
+
+       if (!os_info_old)
+               return NULL;
+       if (!os_info_old->entry[nr].addr)
+               return NULL;
+       *size = (unsigned long) os_info_old->entry[nr].size;
+       return (void *)(unsigned long)os_info_old->entry[nr].addr;
+}
+#endif
index 9a3edb5f2c9296c4026ac062ef9a821e9a2a4596..38e751278bf75680fcc5320f51c3cf7f4fd1f878 100644 (file)
@@ -62,6 +62,7 @@
 #include <asm/ebcdic.h>
 #include <asm/kvm_virtio.h>
 #include <asm/diag.h>
+#include <asm/os_info.h>
 #include "entry.h"
 
 long psw_kernel_bits   = PSW_DEFAULT_KEY | PSW_MASK_BASE | PSW_ASC_PRIMARY |
@@ -778,6 +779,7 @@ static void __init reserve_crashkernel(void)
        pr_info("Reserving %lluMB of memory at %lluMB "
                "for crashkernel (System RAM: %luMB)\n",
                crash_size >> 20, crash_base >> 20, memory_end >> 20);
+       os_info_crashkernel_add(crash_base, crash_size);
 #endif
 }
 
@@ -1057,6 +1059,7 @@ void __init setup_arch(char **cmdline_p)
 
        parse_early_param();
 
+       os_info_init();
        setup_ipl();
        setup_memory_end();
        setup_addressing_mode();
index d15b6c937088ec817824b196f8cf01c500027373..3b9e5c9f4c0bf32dbcf3f0b2928584580d6c0427 100644 (file)
@@ -41,6 +41,7 @@
 #include <asm/sclp.h>
 #include <asm/vdso.h>
 #include <asm/debug.h>
+#include <asm/os_info.h>
 #include "entry.h"
 
 enum {
@@ -826,6 +827,17 @@ void __noreturn cpu_die(void)
 
 #endif /* CONFIG_HOTPLUG_CPU */
 
+static void smp_call_os_info_init_fn(void)
+{
+       int (*init_fn)(void);
+       unsigned long size;
+
+       init_fn = os_info_old_entry(OS_INFO_INIT_FN, &size);
+       if (!init_fn)
+               return;
+       init_fn();
+}
+
 void __init smp_prepare_cpus(unsigned int max_cpus)
 {
        /* request the 0x1201 emergency signal external interrupt */
@@ -834,6 +846,7 @@ void __init smp_prepare_cpus(unsigned int max_cpus)
        /* request the 0x1202 external call external interrupt */
        if (register_external_interrupt(0x1202, do_ext_call_interrupt) != 0)
                panic("Couldn't request external interrupt 0x1202");
+       smp_call_os_info_init_fn();
        smp_detect_cpus();
 }