From: Michael Holzheu <[email-masked]>

On s390 we want to be able to trigger kdump via our stand-alone dump tools,
if a valid kdump kernel has been pre-loaded. The mechanism works as follows:
We establish a ABI defined kdump entry point in the old kernel at 0x10018 and
a "kdump active" flag at 0xe18. The dump tools first verify 0xe18 in
order to find out if kdump kernel has been loaded. If kdump is loaded
they jump to 0x10008 which then triggers crash_kexec(). The dump tools
establish a program check handler so that if crash_kexec() or purgatory
is corrupted we return to the dump tools and create a full-blown stand-alone
dump as backup mechanism.

Signed-off-by: Michael Holzheu <[email-masked]>
---
 arch/s390/include/asm/kexec.h    |    1 
 arch/s390/include/asm/lowcore.h  |    4 +-
 arch/s390/kernel/entry64.S       |   16 ++++++++++
 arch/s390/kernel/head.S          |    8 ++++-
 arch/s390/kernel/head64.S        |    3 +
 arch/s390/kernel/head_kdump.S    |   13 ++++++++
 arch/s390/kernel/ipl.c           |    8 -----
 arch/s390/kernel/machine_kexec.c |   61 +++++++++++++++++++++++++++++++++++++++
 8 files changed, 105 insertions(+), 9 deletions(-)

--- a/arch/s390/include/asm/kexec.h
+++ b/arch/s390/include/asm/kexec.h
@@ -40,4 +40,5 @@
 #define KEXEC_ARCH KEXEC_ARCH_S390
 
 extern void crash_setup_regs(struct pt_regs *newregs, struct pt_regs *oldregs);
+extern void s390_kdump_int(void);
 #endif /*_S390_KEXEC_H */
--- a/arch/s390/include/asm/lowcore.h
+++ b/arch/s390/include/asm/lowcore.h
@@ -165,6 +165,7 @@ struct _lowcore {
 
 #define LC_ORDER 1
 #define LC_PAGES 2
+#define LC_KDUMP_ACTIVE_FLAG 0x4b44554d50414354ULL
 
 struct save_area {
 	u64	fp_regs[16];
@@ -295,7 +296,8 @@ struct _lowcore {
 	/* 64 bit save area */
 	__u64	save_area_64;			/* 0x0e0c */
 	__u32	ipib_checksum_perm;		/* 0x0e14 */
-	__u8	pad_0x0e18[0x0f00-0x0e18];	/* 0x0e18 */
+	__u64	kdump_active;			/* 0x0e18 */
+	__u8	pad_0x0e20[0x0f00-0x0e20];	/* 0x0e20 */
 
 	/* Extended facility list */
 	__u64	stfle_fac_list[32];		/* 0x0f00 */
--- a/arch/s390/kernel/entry64.S
+++ b/arch/s390/kernel/entry64.S
@@ -885,6 +885,22 @@ ENTRY(psw_restart_int_handler)
 restart_psw_crash:
 	.quad	0x0002000080000000,0x0000000000000000 + restart_psw_crash
 
+#
+# kdump external entry (can be called from stand-alone dump tools)
+#
+ENTRY(kdump_ext_handler)
+	stg	%r15,__LC_SAVE_AREA_64(%r0)	# save r15
+	larl	%r15,restart_stack		# load restart stack
+	lg	%r15,0(%r15)
+	aghi	%r15,-SP_SIZE			# make room for pt_regs
+	stmg	%r0,%r14,SP_R0(%r15)		# store gprs %r0-%r14 to stack
+	mvc	SP_R15(8,%r15),__LC_SAVE_AREA_64(%r0)# store saved %r15 to stack
+	xc	__SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15) # set backchain to 0
+
+	brasl	%r14,s390_kdump_ext
+0:	j	0b+2				# Force program check
+#endif
+
 	.section .kprobes.text, "ax"
 
 #ifdef CONFIG_CHECK_STACK
--- a/arch/s390/kernel/head.S
+++ b/arch/s390/kernel/head.S
@@ -452,13 +452,19 @@ ENTRY(startup)
 	j	.Lep_startup_normal
 	.org	0x10008
 	.ascii	"S390EP"
-	.byte	0x00,0x01
+	.byte	0x00,0x02
 #
 # kdump startup-code at 0x10010, running in 64 bit absolute addressing mode
 #
 	.org	0x10010
 ENTRY(startup_kdump)
 	j	.Lep_startup_kdump
+#
+# External kdump startup-code at 0x10018
+#
+	.org	0x10018
+ENTRY(startup_kdump_ext)
+	j	.Lep_startup_kdump_ext
 .Lep_startup_normal:
 	basr	%r13,0			# get base
 .LPG0:
--- a/arch/s390/kernel/head64.S
+++ b/arch/s390/kernel/head64.S
@@ -88,6 +88,9 @@ ENTRY(_ehead)
 ENTRY(_stext)
 	basr	%r13,0			# get base
 .LPG3:
+	larl	%r2,kdump_ext_handler		# Set kdump ext handler address
+	larl	%r3,kdump_ext_handler_addr	# This is needed
+	stg	%r2,0(%r3)			# because of bzImage
 # check control registers
 	stctg	%c0,%c15,0(%r15)
 	oi	6(%r15),0x40		# enable sigp emergency signal
--- a/arch/s390/kernel/head_kdump.S
+++ b/arch/s390/kernel/head_kdump.S
@@ -97,8 +97,21 @@ startup_kdump_relocated:
 	.long	0x00080000,0x80000000 + startup
 .Lpgm_psw:
 	.quad	0x0000000180000000,0x0000000000000000 + .Lno_diag308
+
+.align 2
+.Lep_startup_kdump_ext:
+	larl	%r14,kdump_ext_handler_addr
+	lg	%r14,0(%r14)
+	br	%r14
+
+ENTRY(kdump_ext_handler_addr)
+	.quad	0xffffffffffffffff
 #else
 .align 2
+.Lep_startup_kdump_ext:
+	br	%r14
+
+.align 2
 .Lep_startup_kdump:
 #ifdef CONFIG_64BIT
 	larl	%r13,startup_kdump_crash
--- a/arch/s390/kernel/ipl.c
+++ b/arch/s390/kernel/ipl.c
@@ -1651,13 +1651,7 @@ static int kdump_init(void)
 
 static void kdump_run(struct shutdown_trigger *trigger)
 {
-	pfault_fini();
-	s390_reset_system();
-	__arch_local_irq_stnsm(0xfb); /* disable DAT */
-	store_status();
-
-	crash_kexec(NULL);
-	disabled_wait((unsigned long) __builtin_return_address(0));
+	s390_kdump_int();
 }
 
 static struct shutdown_action kdump_action = {SHUTDOWN_ACTION_KDUMP_STR,
--- a/arch/s390/kernel/machine_kexec.c
+++ b/arch/s390/kernel/machine_kexec.c
@@ -53,6 +53,19 @@ int kimage_load_crash_segment(struct kim
 }
 
 /*
+ * kdump program check handler
+ */
+static void kdump_pgm_handler(void)
+{
+	psw_t kdump_failed_psw;
+
+	kdump_failed_psw.mask = PSW_BASE_BITS | PSW_MASK_WAIT;
+	kdump_failed_psw.addr = (unsigned long) kdump_pgm_handler;
+	_sclp_print_early("kdump failed: Use stand-alone dump tool");
+	__load_psw(kdump_failed_psw);
+}
+
+/*
  * Start kdump
  */
 static void __machine_kdump(void *data)
@@ -82,6 +95,40 @@ static int machine_kexec_prepare_kdump(v
 #endif
 }
 
+/*
+ * Externally triggered kdump: Emit program check if kdump is not loaded
+ */
+void s390_kdump_ext(void)
+{
+#ifdef CONFIG_CRASH_DUMP
+	if (!kexec_crash_image)
+		BUG();
+	crash_kexec(NULL);
+#else
+	return;
+#endif
+}
+
+/*
+ * Internally triggered kdump: Reset system and set PGM handler
+ */
+void s390_kdump_int(void)
+{
+	pfault_fini();
+	s390_reset_system();
+	__arch_local_irq_stnsm(0xfb); /* disable DAT */
+	store_status();
+	S390_lowcore.program_new_psw.mask = PSW_BASE_BITS | PSW_DEFAULT_KEY;
+	S390_lowcore.program_new_psw.addr = PSW_ADDR_AMODE |
+		(unsigned long) kdump_pgm_handler;
+	if (!kexec_crash_image) {
+		_sclp_print_early("kdump not loaded");
+		BUG();
+	}
+	crash_kexec(NULL);
+	BUG();
+}
+
 int machine_kexec_prepare(struct kimage *image)
 {
 	void *reboot_code_buffer;
@@ -109,6 +156,20 @@ void machine_kexec_cleanup(struct kimage
 {
 }
 
+/*
+ * Update kdump active flag for s390 stand-alone dump tools
+ */
+void machine_kexec_finish(struct kimage *image, int kexec_flags)
+{
+	u64 kdump_active;
+
+	if (!(kexec_flags & KEXEC_ON_CRASH))
+		return;
+	kdump_active = image ? LC_KDUMP_ACTIVE_FLAG : 0;
+	copy_to_absolute_zero(&S390_lowcore.kdump_active, &kdump_active,
+			      sizeof(kdump_active));
+}
+
 void machine_shutdown(void)
 {
 }