[S390] kernel: Add z/VM LGR detection
Michael Holzheu [Sun, 11 Mar 2012 15:59:32 +0000 (11:59 -0400)]
Currently the following mechanisms are available to move active
Linux on System z instances between machines:
* z/VM 6.2 SSI (Single System Image)
* Suspend/resume
For moving Linux instances in this patch the term LGR (Linux Guest
Relocation) is used. Because such an operation is critical, it
should be detectable from Linux. With this patch for both, a live
system and a kernel dump, the information about LGRs is accessible.
To identify a guest, stsi and stfle data is used. A new function
lgr_info_log() compares the current data (lgr_info_cur) with the
last recorded one (lgr_info_last). In case the two data sets differ,
lgr_info_cur is logged to the "lgr" s390dbf.

The following trigger points call lgr_info_log():
* panic
* die
* kdump
* LGR timer
* PSW restart
* QDIO recovery
* resume

This patch also changes the s390dbf hex_ascii view. Now only printable ASCII
characters are shown.

Reviewed-by: Heiko Carstens <heiko.carstens@de.ibm.com>
Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>

13 files changed:
arch/s390/include/asm/debug.h
arch/s390/include/asm/ipl.h
arch/s390/include/asm/system.h
arch/s390/kernel/Makefile
arch/s390/kernel/debug.c
arch/s390/kernel/early.c
arch/s390/kernel/ipl.c
arch/s390/kernel/lgr.c [new file with mode: 0644]
arch/s390/kernel/machine_kexec.c
arch/s390/kernel/smp.c
arch/s390/kernel/swsusp_asm64.S
arch/s390/kernel/traps.c
drivers/s390/cio/qdio_main.c

index 9d88db1..8a8245e 100644 (file)
@@ -131,6 +131,7 @@ void debug_unregister(debug_info_t* id);
 
 void debug_set_level(debug_info_t* id, int new_level);
 
+void debug_set_critical(void);
 void debug_stop_all(void);
 
 static inline debug_entry_t*
index 6940abf..2bd6cb8 100644 (file)
@@ -169,5 +169,6 @@ enum diag308_rc {
 extern int diag308(unsigned long subcode, void *addr);
 extern void diag308_reset(void);
 extern void store_status(void);
+extern void lgr_info_log(void);
 
 #endif /* _ASM_S390_IPL_H */
index d73cc6b..2e0bb7f 100644 (file)
@@ -7,8 +7,10 @@
 #ifndef __ASM_SYSTEM_H
 #define __ASM_SYSTEM_H
 
+#include <linux/preempt.h>
 #include <linux/kernel.h>
 #include <linux/errno.h>
+#include <linux/string.h>
 #include <asm/types.h>
 #include <asm/ptrace.h>
 #include <asm/setup.h>
@@ -248,6 +250,38 @@ static inline int test_facility(unsigned long nr)
        return (*ptr & (0x80 >> (nr & 7))) != 0;
 }
 
+/**
+ * stfle - Store facility list extended
+ * @stfle_fac_list: array where facility list can be stored
+ * @size: size of passed in array in double words
+ */
+static inline void stfle(u64 *stfle_fac_list, int size)
+{
+       unsigned long nr;
+
+       preempt_disable();
+       S390_lowcore.stfl_fac_list = 0;
+       asm volatile(
+               "       .insn s,0xb2b10000,0(0)\n" /* stfl */
+               "0:\n"
+               EX_TABLE(0b, 0b)
+               : "=m" (S390_lowcore.stfl_fac_list));
+       nr = 4; /* bytes stored by stfl */
+       memcpy(stfle_fac_list, &S390_lowcore.stfl_fac_list, 4);
+       if (S390_lowcore.stfl_fac_list & 0x01000000) {
+               /* More facility bits available with stfle */
+               register unsigned long reg0 asm("0") = size - 1;
+
+               asm volatile(".insn s,0xb2b00000,0(%1)" /* stfle */
+                            : "+d" (reg0)
+                            : "a" (stfle_fac_list)
+                            : "memory", "cc");
+               nr = (reg0 + 1) * 8; /* # bytes stored by stfle */
+       }
+       memset((char *) stfle_fac_list + nr, 0, size * 8 - nr);
+       preempt_enable();
+}
+
 static inline unsigned short stap(void)
 {
        unsigned short cpu_address;
index d0a4826..b215950 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
+           sysinfo.o jump_label.o lgr.o
 
 obj-y  += $(if $(CONFIG_64BIT),entry64.o,entry.o)
 obj-y  += $(if $(CONFIG_64BIT),reipl64.o,reipl.o)
index 6848828..19e5e9e 100644 (file)
@@ -2,8 +2,8 @@
  *  arch/s390/kernel/debug.c
  *   S/390 debug facility
  *
- *    Copyright (C) 1999, 2000 IBM Deutschland Entwicklung GmbH,
- *                             IBM Corporation
+ *    Copyright IBM Corp. 1999, 2012
+ *
  *    Author(s): Michael Holzheu (holzheu@de.ibm.com),
  *               Holger Smolinski (Holger.Smolinski@de.ibm.com)
  *
@@ -167,6 +167,7 @@ static debug_info_t *debug_area_last = NULL;
 static DEFINE_MUTEX(debug_mutex);
 
 static int initialized;
+static int debug_critical;
 
 static const struct file_operations debug_file_ops = {
        .owner   = THIS_MODULE,
@@ -932,6 +933,11 @@ debug_stop_all(void)
 }
 
 
+void debug_set_critical(void)
+{
+       debug_critical = 1;
+}
+
 /*
  * debug_event_common:
  * - write debug entry with given size
@@ -945,7 +951,11 @@ debug_event_common(debug_info_t * id, int level, const void *buf, int len)
 
        if (!debug_active || !id->areas)
                return NULL;
-       spin_lock_irqsave(&id->lock, flags);
+       if (debug_critical) {
+               if (!spin_trylock_irqsave(&id->lock, flags))
+                       return NULL;
+       } else
+               spin_lock_irqsave(&id->lock, flags);
        active = get_active_entry(id);
        memset(DEBUG_DATA(active), 0, id->buf_size);
        memcpy(DEBUG_DATA(active), buf, min(len, id->buf_size));
@@ -968,7 +978,11 @@ debug_entry_t
 
        if (!debug_active || !id->areas)
                return NULL;
-       spin_lock_irqsave(&id->lock, flags);
+       if (debug_critical) {
+               if (!spin_trylock_irqsave(&id->lock, flags))
+                       return NULL;
+       } else
+               spin_lock_irqsave(&id->lock, flags);
        active = get_active_entry(id);
        memset(DEBUG_DATA(active), 0, id->buf_size);
        memcpy(DEBUG_DATA(active), buf, min(len, id->buf_size));
@@ -1013,7 +1027,11 @@ debug_sprintf_event(debug_info_t* id, int level,char *string,...)
                return NULL;
        numargs=debug_count_numargs(string);
 
-       spin_lock_irqsave(&id->lock, flags);
+       if (debug_critical) {
+               if (!spin_trylock_irqsave(&id->lock, flags))
+                       return NULL;
+       } else
+               spin_lock_irqsave(&id->lock, flags);
        active = get_active_entry(id);
        curr_event=(debug_sprintf_entry_t *) DEBUG_DATA(active);
        va_start(ap,string);
@@ -1047,7 +1065,11 @@ debug_sprintf_exception(debug_info_t* id, int level,char *string,...)
 
        numargs=debug_count_numargs(string);
 
-       spin_lock_irqsave(&id->lock, flags);
+       if (debug_critical) {
+               if (!spin_trylock_irqsave(&id->lock, flags))
+                       return NULL;
+       } else
+               spin_lock_irqsave(&id->lock, flags);
        active = get_active_entry(id);
        curr_event=(debug_sprintf_entry_t *)DEBUG_DATA(active);
        va_start(ap,string);
@@ -1428,10 +1450,10 @@ debug_hex_ascii_format_fn(debug_info_t * id, struct debug_view *view,
        rc += sprintf(out_buf + rc, "| ");
        for (i = 0; i < id->buf_size; i++) {
                unsigned char c = in_buf[i];
-               if (!isprint(c))
-                       rc += sprintf(out_buf + rc, ".");
-               else
+               if (isascii(c) && isprint(c))
                        rc += sprintf(out_buf + rc, "%c", c);
+               else
+                       rc += sprintf(out_buf + rc, ".");
        }
        rc += sprintf(out_buf + rc, "\n");
        return rc;
index 52098d6..578eb4e 100644 (file)
@@ -29,6 +29,7 @@
 #include <asm/sysinfo.h>
 #include <asm/cpcmd.h>
 #include <asm/sclp.h>
+#include <asm/system.h>
 #include "entry.h"
 
 /*
@@ -262,25 +263,8 @@ static noinline __init void setup_lowcore_early(void)
 
 static noinline __init void setup_facility_list(void)
 {
-       unsigned long nr;
-
-       S390_lowcore.stfl_fac_list = 0;
-       asm volatile(
-               "       .insn   s,0xb2b10000,0(0)\n" /* stfl */
-               "0:\n"
-               EX_TABLE(0b,0b) : "=m" (S390_lowcore.stfl_fac_list));
-       memcpy(&S390_lowcore.stfle_fac_list, &S390_lowcore.stfl_fac_list, 4);
-       nr = 4;                         /* # bytes stored by stfl */
-       if (test_facility(7)) {
-               /* More facility bits available with stfle */
-               register unsigned long reg0 asm("0") = MAX_FACILITY_BIT/64 - 1;
-               asm volatile(".insn s,0xb2b00000,%0" /* stfle */
-                            : "=m" (S390_lowcore.stfle_fac_list), "+d" (reg0)
-                            : : "cc");
-               nr = (reg0 + 1) * 8;    /* # bytes stored by stfle */
-       }
-       memset((char *) S390_lowcore.stfle_fac_list + nr, 0,
-              MAX_FACILITY_BIT/8 - nr);
+       stfle(S390_lowcore.stfle_fac_list,
+             ARRAY_SIZE(S390_lowcore.stfle_fac_list));
 }
 
 static noinline __init void setup_hpage(void)
index 9e2f6f7..153e21c 100644 (file)
@@ -17,6 +17,7 @@
 #include <linux/fs.h>
 #include <linux/gfp.h>
 #include <linux/crash_dump.h>
+#include <linux/debug_locks.h>
 #include <asm/ipl.h>
 #include <asm/smp.h>
 #include <asm/setup.h>
@@ -26,6 +27,7 @@
 #include <asm/reset.h>
 #include <asm/sclp.h>
 #include <asm/checksum.h>
+#include <asm/debug.h>
 #include "entry.h"
 
 #define IPL_PARM_BLOCK_VERSION 0
@@ -1692,6 +1694,7 @@ static struct kobj_attribute on_panic_attr =
 
 static void do_panic(void)
 {
+       lgr_info_log();
        on_panic_trigger.action->fn(&on_panic_trigger);
        stop_run(&on_panic_trigger);
 }
@@ -1729,6 +1732,9 @@ static void __do_restart(void *ignore)
 
 void do_restart(void)
 {
+       tracing_off();
+       debug_locks_off();
+       lgr_info_log();
        smp_call_online_cpu(__do_restart, NULL);
 }
 
diff --git a/arch/s390/kernel/lgr.c b/arch/s390/kernel/lgr.c
new file mode 100644 (file)
index 0000000..8431b92
--- /dev/null
@@ -0,0 +1,200 @@
+/*
+ * Linux Guest Relocation (LGR) detection
+ *
+ * Copyright IBM Corp. 2012
+ * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
+ */
+
+#include <linux/module.h>
+#include <linux/timer.h>
+#include <linux/slab.h>
+#include <asm/sysinfo.h>
+#include <asm/ebcdic.h>
+#include <asm/system.h>
+#include <asm/debug.h>
+#include <asm/ipl.h>
+
+#define LGR_TIMER_INTERVAL_SECS (30 * 60)
+#define VM_LEVEL_MAX 2 /* Maximum is 8, but we only record two levels */
+
+/*
+ * LGR info: Contains stfle and stsi data
+ */
+struct lgr_info {
+       /* Bit field with facility information: 4 DWORDs are stored */
+       u64 stfle_fac_list[4];
+       /* Level of system (1 = CEC, 2 = LPAR, 3 = z/VM */
+       u32 level;
+       /* Level 1: CEC info (stsi 1.1.1) */
+       char manufacturer[16];
+       char type[4];
+       char sequence[16];
+       char plant[4];
+       char model[16];
+       /* Level 2: LPAR info (stsi 2.2.2) */
+       u16 lpar_number;
+       char name[8];
+       /* Level 3: VM info (stsi 3.2.2) */
+       u8 vm_count;
+       struct {
+               char name[8];
+               char cpi[16];
+       } vm[VM_LEVEL_MAX];
+} __packed __aligned(8);
+
+/*
+ * LGR globals
+ */
+static void *lgr_page;
+static struct lgr_info lgr_info_last;
+static struct lgr_info lgr_info_cur;
+static struct debug_info *lgr_dbf;
+
+/*
+ * Return number of valid stsi levels
+ */
+static inline int stsi_0(void)
+{
+       int rc = stsi(NULL, 0, 0, 0);
+
+       return rc == -ENOSYS ? rc : (((unsigned int) rc) >> 28);
+}
+
+/*
+ * Copy buffer and then convert it to ASCII
+ */
+static void cpascii(char *dst, char *src, int size)
+{
+       memcpy(dst, src, size);
+       EBCASC(dst, size);
+}
+
+/*
+ * Fill LGR info with 1.1.1 stsi data
+ */
+static void lgr_stsi_1_1_1(struct lgr_info *lgr_info)
+{
+       struct sysinfo_1_1_1 *si = lgr_page;
+
+       if (stsi(si, 1, 1, 1) == -ENOSYS)
+               return;
+       cpascii(lgr_info->manufacturer, si->manufacturer,
+               sizeof(si->manufacturer));
+       cpascii(lgr_info->type, si->type, sizeof(si->type));
+       cpascii(lgr_info->model, si->model, sizeof(si->model));
+       cpascii(lgr_info->sequence, si->sequence, sizeof(si->sequence));
+       cpascii(lgr_info->plant, si->plant, sizeof(si->plant));
+}
+
+/*
+ * Fill LGR info with 2.2.2 stsi data
+ */
+static void lgr_stsi_2_2_2(struct lgr_info *lgr_info)
+{
+       struct sysinfo_2_2_2 *si = lgr_page;
+
+       if (stsi(si, 2, 2, 2) == -ENOSYS)
+               return;
+       cpascii(lgr_info->name, si->name, sizeof(si->name));
+       memcpy(&lgr_info->lpar_number, &si->lpar_number,
+              sizeof(lgr_info->lpar_number));
+}
+
+/*
+ * Fill LGR info with 3.2.2 stsi data
+ */
+static void lgr_stsi_3_2_2(struct lgr_info *lgr_info)
+{
+       struct sysinfo_3_2_2 *si = lgr_page;
+       int i;
+
+       if (stsi(si, 3, 2, 2) == -ENOSYS)
+               return;
+       for (i = 0; i < min_t(u8, si->count, VM_LEVEL_MAX); i++) {
+               cpascii(lgr_info->vm[i].name, si->vm[i].name,
+                       sizeof(si->vm[i].name));
+               cpascii(lgr_info->vm[i].cpi, si->vm[i].cpi,
+                       sizeof(si->vm[i].cpi));
+       }
+       lgr_info->vm_count = si->count;
+}
+
+/*
+ * Fill LGR info with current data
+ */
+static void lgr_info_get(struct lgr_info *lgr_info)
+{
+       memset(lgr_info, 0, sizeof(*lgr_info));
+       stfle(lgr_info->stfle_fac_list, ARRAY_SIZE(lgr_info->stfle_fac_list));
+       lgr_info->level = stsi_0();
+       if (lgr_info->level == -ENOSYS)
+               return;
+       if (lgr_info->level >= 1)
+               lgr_stsi_1_1_1(lgr_info);
+       if (lgr_info->level >= 2)
+               lgr_stsi_2_2_2(lgr_info);
+       if (lgr_info->level >= 3)
+               lgr_stsi_3_2_2(lgr_info);
+}
+
+/*
+ * Check if LGR info has changed and if yes log new LGR info to s390dbf
+ */
+void lgr_info_log(void)
+{
+       static DEFINE_SPINLOCK(lgr_info_lock);
+       unsigned long flags;
+
+       if (!spin_trylock_irqsave(&lgr_info_lock, flags))
+               return;
+       lgr_info_get(&lgr_info_cur);
+       if (memcmp(&lgr_info_last, &lgr_info_cur, sizeof(lgr_info_cur)) != 0) {
+               debug_event(lgr_dbf, 1, &lgr_info_cur, sizeof(lgr_info_cur));
+               lgr_info_last = lgr_info_cur;
+       }
+       spin_unlock_irqrestore(&lgr_info_lock, flags);
+}
+EXPORT_SYMBOL_GPL(lgr_info_log);
+
+static void lgr_timer_set(void);
+
+/*
+ * LGR timer callback
+ */
+static void lgr_timer_fn(unsigned long ignored)
+{
+       lgr_info_log();
+       lgr_timer_set();
+}
+
+static struct timer_list lgr_timer =
+       TIMER_DEFERRED_INITIALIZER(lgr_timer_fn, 0, 0);
+
+/*
+ * Setup next LGR timer
+ */
+static void lgr_timer_set(void)
+{
+       mod_timer(&lgr_timer, jiffies + LGR_TIMER_INTERVAL_SECS * HZ);
+}
+
+/*
+ * Initialize LGR: Add s390dbf, write initial lgr_info and setup timer
+ */
+static int __init lgr_init(void)
+{
+       lgr_page = (void *) __get_free_pages(GFP_KERNEL, 0);
+       if (!lgr_page)
+               return -ENOMEM;
+       lgr_dbf = debug_register("lgr", 1, 1, sizeof(struct lgr_info));
+       if (!lgr_dbf) {
+               free_page((unsigned long) lgr_page);
+               return -ENOMEM;
+       }
+       debug_register_view(lgr_dbf, &debug_hex_ascii_view);
+       lgr_info_get(&lgr_info_last);
+       debug_event(lgr_dbf, 1, &lgr_info_last, sizeof(lgr_info_last));
+       lgr_timer_set();
+       return 0;
+}
+module_init(lgr_init);
index bf6fbc0..0f8cdf1 100644 (file)
@@ -14,6 +14,7 @@
 #include <linux/delay.h>
 #include <linux/reboot.h>
 #include <linux/ftrace.h>
+#include <linux/debug_locks.h>
 #include <asm/cio.h>
 #include <asm/setup.h>
 #include <asm/pgtable.h>
@@ -209,10 +210,14 @@ static void __machine_kexec(void *data)
        struct kimage *image = data;
 
        pfault_fini();
-       if (image->type == KEXEC_TYPE_CRASH)
+       tracing_off();
+       debug_locks_off();
+       if (image->type == KEXEC_TYPE_CRASH) {
+               lgr_info_log();
                s390_reset_system(__do_machine_kdump, data);
-       else
+       } else {
                s390_reset_system(__do_machine_kexec, data);
+       }
        disabled_wait((unsigned long) __builtin_return_address(0));
 }
 
index 734e644..d15b6c9 100644 (file)
@@ -40,6 +40,7 @@
 #include <asm/lowcore.h>
 #include <asm/sclp.h>
 #include <asm/vdso.h>
+#include <asm/debug.h>
 #include "entry.h"
 
 enum {
@@ -406,6 +407,7 @@ void smp_send_stop(void)
        __load_psw_mask(psw_kernel_bits | PSW_MASK_DAT);
        trace_hardirqs_off();
 
+       debug_set_critical();
        cpumask_copy(&cpumask, cpu_online_mask);
        cpumask_clear_cpu(smp_processor_id(), &cpumask);
 
index ad3c79e..dd70ef0 100644 (file)
@@ -257,6 +257,9 @@ restore_registers:
        lghi    %r2,0
        brasl   %r14,arch_set_page_states
 
+       /* Log potential guest relocation */
+       brasl   %r14,lgr_info_log
+
        /* Reinitialize the channel subsystem */
        brasl   %r14,channel_subsystem_reinit
 
index 5ce3750..8894ac4 100644 (file)
@@ -41,6 +41,7 @@
 #include <asm/cpcmd.h>
 #include <asm/lowcore.h>
 #include <asm/debug.h>
+#include <asm/ipl.h>
 #include "entry.h"
 
 void (*pgm_check_table[128])(struct pt_regs *regs);
@@ -239,6 +240,7 @@ void die(struct pt_regs *regs, const char *str)
        static int die_counter;
 
        oops_enter();
+       lgr_info_log();
        debug_stop_all();
        console_verbose();
        spin_lock_irq(&die_lock);
index 770a740..2a0dfcb 100644 (file)
@@ -18,6 +18,7 @@
 #include <linux/atomic.h>
 #include <asm/debug.h>
 #include <asm/qdio.h>
+#include <asm/ipl.h>
 
 #include "cio.h"
 #include "css.h"
@@ -1093,6 +1094,11 @@ static void qdio_handle_activate_check(struct ccw_device *cdev,
                   q->nr, q->first_to_kick, count, irq_ptr->int_parm);
 no_handler:
        qdio_set_state(irq_ptr, QDIO_IRQ_STATE_STOPPED);
+       /*
+        * In case of z/VM LGR (Live Guest Migration) QDIO recovery will happen.
+        * Therefore we call the LGR detection function here.
+        */
+       lgr_info_log();
 }
 
 static void qdio_establish_handle_irq(struct ccw_device *cdev, int cstat,