summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMichael Holzheu <holzheu@linux.vnet.ibm.com>2011-08-19 15:09:50 +0200
committerSimon Horman <horms@verge.net.au>2011-08-20 18:02:29 +0900
commitd866fa444cc47097c07b3d31c494fdacd977f6d8 (patch)
tree6c902b0b94b1f8fa02459e52c3af269c6a4066cd
parent7c7caf11813cf9993201285886ba9661b3de2d18 (diff)
kexec-tools: Add s390 kdump support
This patch adds kdump support for s390 to the kexec tool and enables the "--load-panic" option. When loading the kdump kernel and ramdisk we add the address of the crashkernel memory to the normal load address. Signed-off-by: Michael Holzheu <holzheu@linux.vnet.ibm.com> Signed-off-by: Simon Horman <horms@verge.net.au>
-rw-r--r--include/elf.h8
-rw-r--r--kexec/arch/s390/Makefile1
-rw-r--r--kexec/arch/s390/crashdump-s390.c74
-rw-r--r--kexec/arch/s390/kexec-elf-rel-s390.c66
-rw-r--r--kexec/arch/s390/kexec-image.c69
-rw-r--r--kexec/arch/s390/kexec-s390.c68
-rw-r--r--kexec/arch/s390/kexec-s390.h9
-rw-r--r--purgatory/arch/s390/Makefile5
-rw-r--r--purgatory/arch/s390/console-s390.c14
-rw-r--r--purgatory/arch/s390/purgatory-s390.c93
-rw-r--r--purgatory/arch/s390/setup-s390.S33
11 files changed, 390 insertions, 50 deletions
diff --git a/include/elf.h b/include/elf.h
index c958a40..1a2eb5a 100644
--- a/include/elf.h
+++ b/include/elf.h
@@ -2304,9 +2304,13 @@ typedef Elf32_Addr Elf32_Conflict;
#define R_390_TLS_DTPOFF 55 /* Offset in TLS block. */
#define R_390_TLS_TPOFF 56 /* Negated offset in static TLS
block. */
-
+#define R_390_20 57 /* Direct 20 bit. */
+#define R_390_GOT20 58 /* 20 bit GOT offset. */
+#define R_390_GOTPLT20 59 /* 20 bit offset to jump slot. */
+#define R_390_TLS_GOTIE20 60 /* 20 bit GOT offset for static TLS
+ block offset. */
/* Keep this the last entry. */
-#define R_390_NUM 57
+#define R_390_NUM 61
/* CRIS relocations. */
#define R_CRIS_NONE 0
diff --git a/kexec/arch/s390/Makefile b/kexec/arch/s390/Makefile
index 9fddc60..fab3e68 100644
--- a/kexec/arch/s390/Makefile
+++ b/kexec/arch/s390/Makefile
@@ -4,6 +4,7 @@
s390_KEXEC_SRCS = kexec/arch/s390/kexec-s390.c
s390_KEXEC_SRCS += kexec/arch/s390/kexec-image.c
s390_KEXEC_SRCS += kexec/arch/s390/kexec-elf-rel-s390.c
+s390_KEXEC_SRCS += kexec/arch/s390/crashdump-s390.c
dist += kexec/arch/s390/Makefile $(s390_KEXEC_SRCS) \
kexec/arch/s390/kexec-s390.h \
diff --git a/kexec/arch/s390/crashdump-s390.c b/kexec/arch/s390/crashdump-s390.c
new file mode 100644
index 0000000..86a30a8
--- /dev/null
+++ b/kexec/arch/s390/crashdump-s390.c
@@ -0,0 +1,74 @@
+/*
+ * kexec/arch/s390/crashdump-s390.c
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
+ */
+
+#ifdef __s390x__
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <elf.h>
+#include <limits.h>
+#include "../../kexec.h"
+#include "../../kexec-syscall.h"
+#include "../../kexec/crashdump.h"
+#include "kexec-s390.h"
+
+/*
+ * Load additional segments for kdump kernel
+ */
+int load_crashdump_segments(struct kexec_info *info, unsigned long crash_base,
+ unsigned long crash_end)
+{
+ static struct memory_range crash_memory_range[MAX_MEMORY_RANGES];
+ unsigned long bufsz, elfcorehdr, elfcorehdr_size, crash_size;
+ struct crash_elf_info elf_info;
+ char str[COMMAND_LINESIZE];
+ int ranges;
+ void *tmp;
+
+ crash_size = crash_end - crash_base + 1;
+ memset(&elf_info, 0, sizeof(elf_info));
+
+ elf_info.data = ELFDATA2MSB;
+ elf_info.machine = EM_S390;
+ elf_info.class = ELFCLASS64;
+ elf_info.get_note_info = get_crash_notes_per_cpu;
+
+ if (get_memory_ranges_s390(crash_memory_range, &ranges, 0))
+ return -1;
+
+ if (crash_create_elf64_headers(info, &elf_info, crash_memory_range,
+ ranges, &tmp, &bufsz,
+ ELF_CORE_HEADER_ALIGN))
+ return -1;
+
+ elfcorehdr = add_buffer(info, tmp, bufsz, bufsz, 1024,
+ crash_base, crash_end, -1);
+ elfcorehdr_size = bufsz;
+ elf_rel_build_load(info, &info->rhdr, (const char *) purgatory,
+ purgatory_size, crash_base + 0x2000,
+ crash_base + 0x10000, -1, 0);
+ elf_rel_set_symbol(&info->rhdr, "crash_base", &crash_base,
+ sizeof(crash_base));
+ elf_rel_set_symbol(&info->rhdr, "crash_size", &crash_size,
+ sizeof(crash_size));
+ info->entry = (void *) elf_rel_get_addr(&info->rhdr, "purgatory_start");
+ snprintf(str, sizeof(str), " elfcorehdr=%ld@%ldK\n",
+ elfcorehdr_size, elfcorehdr / 1024);
+ command_line_add(str);
+ return 0;
+}
+#else
+/*
+ * kdump is not available for s390
+ */
+int load_crashdump_segments(struct kexec_info *info, unsigned long crash_base,
+ unsigned long crash_end)
+{
+ return -1;
+}
+#endif
diff --git a/kexec/arch/s390/kexec-elf-rel-s390.c b/kexec/arch/s390/kexec-elf-rel-s390.c
index ffa0347..80bcd1b 100644
--- a/kexec/arch/s390/kexec-elf-rel-s390.c
+++ b/kexec/arch/s390/kexec-elf-rel-s390.c
@@ -1,7 +1,7 @@
/*
* kexec/arch/s390/kexec-elf-rel-s390.c
*
- * (C) Copyright IBM Corp. 2005
+ * Copyright IBM Corp. 2005,2011
*
* Author(s): Heiko Carstens <heiko.carstens@de.ibm.com>
*
@@ -12,15 +12,65 @@
#include "../../kexec.h"
#include "../../kexec-elf.h"
-int machine_verify_elf_rel(struct mem_ehdr *UNUSED(ehdr))
+int machine_verify_elf_rel(struct mem_ehdr *ehdr)
{
- return 0;
+ if (ehdr->ei_data != ELFDATA2MSB)
+ return 0;
+ if (ehdr->ei_class != ELFCLASS64)
+ return 0;
+ if (ehdr->e_machine != EM_S390)
+ return 0;
+ return 1;
}
-void machine_apply_elf_rel(struct mem_ehdr *UNUSED(ehdr),
- unsigned long UNUSED(r_type),
- void *UNUSED(location),
- unsigned long UNUSED(address),
- unsigned long UNUSED(value))
+void machine_apply_elf_rel(struct mem_ehdr *ehdr,
+ unsigned long r_type,
+ void *loc,
+ unsigned long address,
+ unsigned long val)
{
+ switch (r_type) {
+ case R_390_8: /* Direct 8 bit. */
+ case R_390_12: /* Direct 12 bit. */
+ case R_390_16: /* Direct 16 bit. */
+ case R_390_20: /* Direct 20 bit. */
+ case R_390_32: /* Direct 32 bit. */
+ case R_390_64: /* Direct 64 bit. */
+ if (r_type == R_390_8)
+ *(unsigned char *) loc = val;
+ else if (r_type == R_390_12)
+ *(unsigned short *) loc = (val & 0xfff) |
+ (*(unsigned short *) loc & 0xf000);
+ else if (r_type == R_390_16)
+ *(unsigned short *) loc = val;
+ else if (r_type == R_390_20)
+ *(unsigned int *) loc =
+ (*(unsigned int *) loc & 0xf00000ff) |
+ (val & 0xfff) << 16 | (val & 0xff000) >> 4;
+ else if (r_type == R_390_32)
+ *(unsigned int *) loc = val;
+ else if (r_type == R_390_64)
+ *(unsigned long *) loc = val;
+ break;
+ case R_390_PC16: /* PC relative 16 bit. */
+ case R_390_PC16DBL: /* PC relative 16 bit shifted by 1. */
+ case R_390_PC32DBL: /* PC relative 32 bit shifted by 1. */
+ case R_390_PC32: /* PC relative 32 bit. */
+ case R_390_PC64: /* PC relative 64 bit. */
+ val -= address;
+ if (r_type == R_390_PC16)
+ *(unsigned short *) loc = val;
+ else if (r_type == R_390_PC16DBL)
+ *(unsigned short *) loc = val >> 1;
+ else if (r_type == R_390_PC32DBL)
+ *(unsigned int *) loc = val >> 1;
+ else if (r_type == R_390_PC32)
+ *(unsigned int *) loc = val;
+ else if (r_type == R_390_PC64)
+ *(unsigned long *) loc = val;
+ break;
+ default:
+ die("Unknown rela relocation: 0x%lx 0x%lx\n", r_type, address);
+ break;
+ }
}
diff --git a/kexec/arch/s390/kexec-image.c b/kexec/arch/s390/kexec-image.c
index 7ef8e29..bed67cd 100644
--- a/kexec/arch/s390/kexec-image.c
+++ b/kexec/arch/s390/kexec-image.c
@@ -18,18 +18,41 @@
#include <unistd.h>
#include <getopt.h>
#include "../../kexec.h"
+#include "../../kexec-syscall.h"
+#include "../../kexec/crashdump.h"
#include "kexec-s390.h"
+#include "elf.h"
#include <arch/options.h>
+static uint64_t crash_base, crash_end;
+static char command_line[COMMAND_LINESIZE];
+
+static void add_segment_check(struct kexec_info *info, const void *buf,
+ size_t bufsz, unsigned long base, size_t memsz)
+{
+ if (info->kexec_flags & KEXEC_ON_CRASH)
+ if (base + memsz > crash_end - crash_base)
+ die("Not enough crashkernel memory to load segments\n");
+ add_segment(info, buf, bufsz, crash_base + base, memsz);
+}
+
+int command_line_add(const char *str)
+{
+ if (strlen(command_line) + strlen(str) + 1 > COMMAND_LINESIZE) {
+ fprintf(stderr, "Command line too long.\n");
+ return -1;
+ }
+ strcat(command_line, str);
+ return 0;
+}
+
int
image_s390_load(int argc, char **argv, const char *kernel_buf,
off_t kernel_size, struct kexec_info *info)
{
void *krnl_buffer;
char *rd_buffer;
- const char *command_line;
const char *ramdisk;
- int command_line_len;
off_t ramdisk_len;
unsigned int ramdisk_origin;
int opt;
@@ -44,7 +67,6 @@ image_s390_load(int argc, char **argv, const char *kernel_buf,
static const char short_options[] = KEXEC_OPT_STR "";
ramdisk = NULL;
- command_line = NULL;
ramdisk_len = 0;
ramdisk_origin = 0;
@@ -55,7 +77,8 @@ image_s390_load(int argc, char **argv, const char *kernel_buf,
return -1;
break;
case OPT_APPEND:
- command_line = optarg;
+ if (command_line_add(optarg))
+ return -1;
break;
case OPT_RAMDISK:
ramdisk = optarg;
@@ -63,17 +86,14 @@ image_s390_load(int argc, char **argv, const char *kernel_buf,
}
}
- /* Process a given command_line: */
- if (command_line) {
- command_line_len = strlen(command_line) + 1; /* Remember the '\0' */
- if (command_line_len > COMMAND_LINESIZE) {
- fprintf(stderr, "Command line too long.\n");
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ if (parse_iomem_single("Crash kernel\n", &crash_base,
+ &crash_end))
return -1;
- }
}
/* Add kernel segment */
- add_segment(info, kernel_buf + IMAGE_READ_OFFSET,
+ add_segment_check(info, kernel_buf + IMAGE_READ_OFFSET,
kernel_size - IMAGE_READ_OFFSET, IMAGE_READ_OFFSET,
kernel_size - IMAGE_READ_OFFSET);
@@ -88,10 +108,17 @@ image_s390_load(int argc, char **argv, const char *kernel_buf,
return -1;
}
ramdisk_origin = RAMDISK_ORIGIN_ADDR;
- add_segment(info, rd_buffer, ramdisk_len, RAMDISK_ORIGIN_ADDR, ramdisk_len);
+ add_segment_check(info, rd_buffer, ramdisk_len,
+ RAMDISK_ORIGIN_ADDR, ramdisk_len);
}
-
- /* Register the ramdisk in the kernel. */
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ if (load_crashdump_segments(info, crash_base, crash_end))
+ return -1;
+ } else {
+ info->entry = (void *) IMAGE_READ_OFFSET;
+ }
+
+ /* Register the ramdisk and crashkernel memory in the kernel. */
{
unsigned long long *tmp;
@@ -100,19 +127,23 @@ image_s390_load(int argc, char **argv, const char *kernel_buf,
tmp = krnl_buffer + INITRD_SIZE_OFFS;
*tmp = (unsigned long long) ramdisk_len;
- }
+ if (info->kexec_flags & KEXEC_ON_CRASH) {
+ tmp = krnl_buffer + OLDMEM_BASE_OFFS;
+ *tmp = crash_base;
+
+ tmp = krnl_buffer + OLDMEM_SIZE_OFFS;
+ *tmp = crash_end - crash_base + 1;
+ }
+ }
/*
* We will write a probably given command line.
* First, erase the old area, then setup the new parameters:
*/
- if (command_line) {
+ if (strlen(command_line) != 0) {
memset(krnl_buffer + COMMAND_LINE_OFFS, 0, COMMAND_LINESIZE);
memcpy(krnl_buffer + COMMAND_LINE_OFFS, command_line, strlen(command_line));
}
-
- info->entry = (void *) IMAGE_READ_OFFSET;
-
return 0;
}
diff --git a/kexec/arch/s390/kexec-s390.c b/kexec/arch/s390/kexec-s390.c
index ce9564d..81e0324 100644
--- a/kexec/arch/s390/kexec-s390.c
+++ b/kexec/arch/s390/kexec-s390.c
@@ -1,9 +1,10 @@
/*
* kexec/arch/s390/kexec-s390.c
*
- * (C) Copyright IBM Corp. 2005
+ * Copyright IBM Corp. 2005,2011
*
* Author(s): Rolf Adelsberger <adelsberger@de.ibm.com>
+ * Michael Holzheu <holzheu@linux.vnet.ibm.com>
*
*/
@@ -19,26 +20,16 @@
#include "kexec-s390.h"
#include <arch/options.h>
-#define MAX_MEMORY_RANGES 64
static struct memory_range memory_range[MAX_MEMORY_RANGES];
/*
- * get_memory_ranges:
- * Return a list of memory ranges by parsing the file returned by
- * proc_iomem()
- *
- * INPUT:
- * - Pointer to an array of memory_range structures.
- * - Pointer to an integer with holds the number of memory ranges.
- *
- * RETURN:
- * - 0 on normal execution.
- * - (-1) if something went wrong.
+ * Get memory ranges of type "System RAM" from /proc/iomem. If with_crashk=1
+ * then also type "Crash kernel" is added.
*/
-
-int get_memory_ranges(struct memory_range **range, int *ranges,
- unsigned long UNUSED(flags))
+int get_memory_ranges_s390(struct memory_range memory_range[], int *ranges,
+ int with_crashk)
{
+ char crash_kernel[] = "Crash kernel\n";
char sys_ram[] = "System RAM\n";
const char *iomem = proc_iomem();
FILE *fp;
@@ -52,7 +43,7 @@ int get_memory_ranges(struct memory_range **range, int *ranges,
}
/* Setup the compare string properly. */
- while(fgets(line,sizeof(line),fp) != 0) {
+ while (fgets(line, sizeof(line), fp) != 0) {
unsigned long long start, end;
int cons;
char *str;
@@ -62,7 +53,9 @@ int get_memory_ranges(struct memory_range **range, int *ranges,
sscanf(line,"%Lx-%Lx : %n", &start, &end, &cons);
str = line+cons;
- if(memcmp(str,sys_ram,strlen(sys_ram)) == 0) {
+ if ((memcmp(str, sys_ram, strlen(sys_ram)) == 0) ||
+ ((memcmp(str, crash_kernel, strlen(crash_kernel)) == 0) &&
+ with_crashk)) {
memory_range[current_range].start = start;
memory_range[current_range].end = end;
memory_range[current_range].type = RANGE_RAM;
@@ -73,9 +66,41 @@ int get_memory_ranges(struct memory_range **range, int *ranges,
}
}
fclose(fp);
- *range = memory_range;
*ranges = current_range;
+ return 0;
+}
+/*
+ * get_memory_ranges:
+ * Return a list of memory ranges by parsing the file returned by
+ * proc_iomem()
+ *
+ * INPUT:
+ * - Pointer to an array of memory_range structures.
+ * - Pointer to an integer with holds the number of memory ranges.
+ *
+ * RETURN:
+ * - 0 on normal execution.
+ * - (-1) if something went wrong.
+ */
+
+int get_memory_ranges(struct memory_range **range, int *ranges,
+ unsigned long flags)
+{
+ uint64_t start, end;
+
+ if (get_memory_ranges_s390(memory_range, ranges,
+ flags & KEXEC_ON_CRASH))
+ return -1;
+ *range = memory_range;
+ if ((flags & KEXEC_ON_CRASH) && !(flags & KEXEC_PRESERVE_CONTEXT)) {
+ if (parse_iomem_single("Crash kernel\n", &start, &end))
+ return -1;
+ if (start > mem_min)
+ mem_min = start;
+ if (end < mem_max)
+ mem_max = end;
+ }
return 0;
}
@@ -112,5 +137,8 @@ void arch_update_purgatory(struct kexec_info *UNUSED(info))
int is_crashkernel_mem_reserved(void)
{
- return 0; /* kdump is not supported on this platform (yet) */
+ uint64_t start, end;
+
+ return parse_iomem_single("Crash kernel\n", &start, &end) == 0 ?
+ (start != end) : 0;
}
diff --git a/kexec/arch/s390/kexec-s390.h b/kexec/arch/s390/kexec-s390.h
index 1159ceb..f98795e 100644
--- a/kexec/arch/s390/kexec-s390.h
+++ b/kexec/arch/s390/kexec-s390.h
@@ -15,11 +15,20 @@
#define RAMDISK_ORIGIN_ADDR 0x800000
#define INITRD_START_OFFS 0x408
#define INITRD_SIZE_OFFS 0x410
+#define OLDMEM_BASE_OFFS 0x418
+#define OLDMEM_SIZE_OFFS 0x420
#define COMMAND_LINE_OFFS 0x480
#define COMMAND_LINESIZE 896
+#define MAX_MEMORY_RANGES 64
extern int image_s390_load(int, char **, const char *, off_t, struct kexec_info *);
extern int image_s390_probe(const char *, off_t);
extern void image_s390_usage(void);
+extern int load_crashdump_segments(struct kexec_info *info,
+ unsigned long crash_base,
+ unsigned long crash_end);
+extern int get_memory_ranges_s390(struct memory_range range[], int *ranges,
+ int with_crashk);
+extern int command_line_add(const char *str);
#endif /* KEXEC_IA64_H */
diff --git a/purgatory/arch/s390/Makefile b/purgatory/arch/s390/Makefile
index d5f3068..09749bd 100644
--- a/purgatory/arch/s390/Makefile
+++ b/purgatory/arch/s390/Makefile
@@ -2,7 +2,10 @@
# Purgatory s390
#
-s390_PURGATORY_SRCS =
+s390_PURGATORY_EXTRA_CFLAGS += -fno-stack-protector
+s390_PURGATORY_SRCS += purgatory/arch/s390/console-s390.c
+s390_PURGATORY_SRCS += purgatory/arch/s390/setup-s390.S
+s390_PURGATORY_SRCS += purgatory/arch/s390/purgatory-s390.c
dist += purgatory/arch/s390/Makefile $(s390_PURGATORY_SRCS)
diff --git a/purgatory/arch/s390/console-s390.c b/purgatory/arch/s390/console-s390.c
new file mode 100644
index 0000000..fe4bd91
--- /dev/null
+++ b/purgatory/arch/s390/console-s390.c
@@ -0,0 +1,14 @@
+/*
+ * S390 console code (currently not implemented)
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
+ */
+
+#include <purgatory.h>
+#include "unused.h"
+
+void putchar(int UNUSED(ch))
+{
+}
diff --git a/purgatory/arch/s390/purgatory-s390.c b/purgatory/arch/s390/purgatory-s390.c
new file mode 100644
index 0000000..f438cf6
--- /dev/null
+++ b/purgatory/arch/s390/purgatory-s390.c
@@ -0,0 +1,93 @@
+/*
+ * S390 purgatory
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
+ */
+
+#include <stdint.h>
+#include <stddef.h>
+#include <string.h>
+#include "../../../kexec/kexec-sha256.h"
+
+#define MIN(x, y) ((x) < (y) ? (x) : (y))
+#define MAX(x, y) ((x) > (y) ? (x) : (y))
+
+extern struct sha256_region sha256_regions[SHA256_REGIONS];
+
+unsigned long crash_base = (unsigned long) -1;
+unsigned long crash_size = (unsigned long) -1;
+
+/*
+ * Implement memcpy using the mvcle instruction
+ */
+static void memcpy_fast(void *target, void *src, unsigned long size)
+{
+ register unsigned long __target asm("2") = (unsigned long) target;
+ register unsigned long __size1 asm("3") = size;
+ register unsigned long __src asm("4") = (unsigned long) src;
+ register unsigned long __size2 asm("5") = size;
+
+ asm volatile (
+ "0: mvcle %0,%2,0\n"
+ " jo 0b\n"
+ : "+d" (__target), "+d" (__size1), "+d" (__src), "+d" (__size2)
+ :
+ : "cc", "memory"
+ );
+}
+
+/*
+ * Swap memory areas
+ */
+static void memswap(void *addr1, void *addr2, unsigned long size)
+{
+ unsigned long off, copy_len;
+ static char buf[1024];
+
+ for (off = 0; off < size; off += sizeof(buf)) {
+ copy_len = MIN(size - off, sizeof(buf));
+ memcpy_fast(buf, (void *) addr2 + off, copy_len);
+ memcpy_fast(addr2 + off, addr1 + off, copy_len);
+ memcpy_fast(addr1 + off, buf, copy_len);
+ }
+}
+
+/*
+ * Nothing to do
+ */
+void setup_arch(void)
+{
+}
+
+/*
+ * Do swap of [crash base - crash base + size] with [0 - crash size]
+ *
+ * We swap all kexec segments except of purgatory. The rest is copied
+ * from [0 - crash size] to [crash base - crash base + size].
+ * We use [0x2000 - 0x10000] for purgatory. This area is never used
+ * by s390 Linux kernels.
+ *
+ * This functions assumes that the sha256_regions[] is sorted.
+ */
+void post_verification_setup_arch(void)
+{
+ unsigned long start, len, last = crash_base + 0x10000;
+ struct sha256_region *ptr, *end;
+
+ end = &sha256_regions[sizeof(sha256_regions)/sizeof(sha256_regions[0])];
+ for (ptr = sha256_regions; ptr < end; ptr++) {
+ if (!ptr->start)
+ continue;
+ start = MAX(ptr->start, crash_base + 0x10000);
+ len = ptr->len - (start - ptr->start);
+ memcpy_fast((void *) last, (void *) last - crash_base,
+ start - last);
+ memswap((void *) start - crash_base, (void *) start, len);
+ last = start + len;
+ }
+ memcpy_fast((void *) last, (void *) last - crash_base,
+ crash_base + crash_size - last);
+ memcpy_fast((void *) crash_base, (void *) 0, 0x2000);
+}
diff --git a/purgatory/arch/s390/setup-s390.S b/purgatory/arch/s390/setup-s390.S
new file mode 100644
index 0000000..6db75fd
--- /dev/null
+++ b/purgatory/arch/s390/setup-s390.S
@@ -0,0 +1,33 @@
+/*
+ * Purgatory setup code
+ *
+ * Copyright IBM Corp. 2011
+ *
+ * Author(s): Michael Holzheu <holzheu@linux.vnet.ibm.com>
+ */
+
+ .text
+ .globl purgatory_start
+ .balign 16
+purgatory_start:
+#ifdef __s390x__
+ larl %r15,lstack_end
+ aghi %r15,-160
+ brasl %r14,purgatory
+ larl %r14,kdump_psw
+ lpswe 0(%r14)
+
+ .section ".data"
+ .balign 16
+kdump_psw:
+ .quad 0x0000000180000000
+ .quad 0x0000000000010010
+
+ .bss
+ .balign 4096
+lstack:
+ .skip 4096
+lstack_end:
+#else
+0: j 0
+#endif