[S390] kernel: Add z/VM LGR detection
authorMichael Holzheu <holzheu@linux.vnet.ibm.com>
Sun, 11 Mar 2012 15:59:32 +0000 (11:59 -0400)
committerMartin Schwidefsky <schwidefsky@de.ibm.com>
Sun, 11 Mar 2012 15:59:29 +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 9d88db1f55d0e9637746546df38ea25e3b7478cd..8a8245ed14d22b888c9691e43de7e9c573fd88cf 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 6940abfbe1d93aaab0189bb8e77a0b2b99f8caa7..2bd6cb897b90e86f129a6ae2b4eeca6bd495de95 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 d73cc6b60000044e48aa5253eeea2707d5d5ffba..2e0bb7f0f9b2fa75433397c3bf27354ff49a9c5d 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 d0a48268eb27008dd1ed12b80d75acd013f0bfcc..b215950904993f07b321459102384dc742296eb9 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 6848828b962ea083b268b345992228e8dd13ccb4..19e5e9eba546e26995f08d04f73dd74cc7fc45b0 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 52098d6dfaa71dabed19f287aaa225672aba6c55..578eb4e6d1577ebcbe641533481bfdc2101e6832 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 9e2f6f7c0e5a9729cc3623e1b140f3e5cff97374..153e21ce2336ede8c468a792d204c8c4e3aa5f77 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 bf6fbc03ebafbfc3cb9f889cef3f3f1868fe7dc9..0f8cdf1268d038715483ae5d81dfa8df2080ad25 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 734e644972ab488d1b289b730295819006d17076..d15b6c937088ec817824b196f8cf01c500027373 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 ad3c79eceed74190ef250f38d1b863451fdd6a75..dd70ef046058b22c2dc35a959a0793eaafb4b23b 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 5ce3750b181fc748a94263bf04dca61f6a5df005..8894ac435d8de1f749411d403c7c4834a2dc84fd 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 770a740a393cd57bd24de3e0e19c1b852d9cd771..2a0dfcb0bc42d2e54c230e4216890ff0133282cf 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,