From: Thomas Renninger <trenn@suse.de>
Subject: Reserve mmconf areas for kdump kernel via memmap=X$Y in cmdline
References: bnc#804800
Signed-off-by: Thomas Renninger <trenn@suse.de>
Signed-off-by: Tony Jones <tonyj@suse.de>
Upstream: no

[limited variant of reverted mainline commit e35aa29fb40b37bf86d980b2e19af5e01c2d2549]

This is a SLE11 SP3 only workaround to make mmconf work in the kdump kernel.
mmconf region is explicitly checked to be reserved, because of specific
Intel platforms where bad MMCONF table data is passed.

Therefore all mmconf areas must be set reserved in the kdump kernel which
is achieved by reading mmconf areas in the productive kernel from /proc/iomem
and those are passed via memmap= command line to the kdump kernel.

Passing all reserved regions may overflow the commandline, therefore this
workaround approach is taken.

Additional change by Cliff Wickman <cpw@sgi.com>:
On larger SGI hardware this will still overflow command line, so if overflow 
occurs, coalesce any mmconf regions higher than system ram into one region, 
hopefully this coalescing will be limited to SGI hardware

---
 kexec/arch/i386/crashdump-x86.c |  109 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 109 insertions(+)

--- a/kexec/arch/i386/crashdump-x86.c
+++ b/kexec/arch/i386/crashdump-x86.c
@@ -51,6 +51,7 @@
 #include <x86/x86-linux.h>
 
 extern struct arch_options_t arch_options;
+static int nr_mem_ranges;
 
 static int get_kernel_page_offset(struct kexec_info *UNUSED(info),
 				  struct crash_elf_info *elf_info)
@@ -186,6 +187,18 @@ static struct memory_range crash_memory_
 static struct memory_range crash_reserved_mem[CRASH_RESERVED_MEM_NR];
 static int crash_reserved_mem_nr;
 
+static struct memory_range mmconf_mem[CRASH_MAX_MEMORY_RANGES];
+static int mmconf_ranges;
+
+static void add_mmconf_range(unsigned long long start, unsigned long long end)
+{
+	if (mmconf_ranges >= CRASH_MAX_MEMORY_RANGES)
+		return;
+	mmconf_mem[mmconf_ranges].start = start;
+	mmconf_mem[mmconf_ranges].end = end;
+	mmconf_ranges++;
+}
+
 /* Reads the appropriate file and retrieves the SYSTEM RAM regions for whom to
  * create Elf headers. Keeping it separate from get_memory_ranges() as
  * requirements are different in the case of normal kexec and crashdumps.
@@ -246,6 +259,9 @@ static int get_crash_memory_ranges(struc
 			gart_end = end;
 			gart = 1;
 			continue;
+		} else if (memcmp(str, "PCI MMCONFIG", 12) == 0) {
+			add_mmconf_range(start, end);
+			continue;
 		} else {
 			continue;
 		}
@@ -601,6 +617,49 @@ static void cmdline_add_memmap_internal(
 		die("Command line overflow\n");
 	strcat(cmdline, str_mmap);
 }
+/* Appends memmap=X$Y commandline for reserved memory to command line*/
+static int
+cmdline_add_mmconf_reserved(char *cmdline)
+{
+	int i, cmdlen, len, align = 1024;
+	unsigned long startk, endk;
+	char str_mmap[256], str_tmp[20];
+
+
+	for (i = 0; i < mmconf_ranges; i++){
+
+		startk = mmconf_mem[i].start/1024;
+		endk = (mmconf_mem[i].end + align - 1)/1024;
+	/*
+	 * This is a minimal SLES11 SP3 specific workaround.
+	 * Passing all reserved areas could be many and mmconf one might
+	 * be missing or other side effects may happen (later added important
+	 * boot parameters might be missing).
+	 * Try to identify mmconf area and only pass this one as reserved for
+	 * now.
+	 */
+		strcpy (str_mmap, " memmap=");
+		ultoa((endk - startk), str_tmp);
+		strcat (str_mmap, str_tmp);
+		strcat (str_mmap, "K$");
+		ultoa(startk, str_tmp);
+		strcat (str_mmap, str_tmp);
+		strcat (str_mmap, "K");
+		len = strlen(str_mmap);
+		cmdlen = strlen(cmdline) + len;
+		if (cmdlen > (COMMAND_LINE_SIZE - 1))
+			return 1;
+		strcat(cmdline, str_mmap);
+
+#ifdef DEBUG
+		printf("mmconf area found and reserved 0x%016llx-0x%016llx\n",
+		       mmconf_mem[i].start, mmconf_mem[i].end);
+		printf("Command line after adding reserved memmap\n");
+		printf("%s\n", cmdline);
+#endif
+	}
+	return 0;
+}
 
 /* Adds the appropriate memmap= options to command line, indicating the
  * memory regions the new kernel can use to boot into. */
@@ -797,6 +856,45 @@ static void get_backup_area(struct kexec
 	info->backup_src_size = BACKUP_SRC_END - BACKUP_SRC_START + 1;
 }
 
+/*
+ * Coalesce all MMCONF areas about the highest usable RAM area into one.
+ * This is done to save memmap= entries on the boot command line.
+ */
+static void
+coalesce_mmconf_ranges(void)
+{
+	int i, bigentry = -1;
+	unsigned long long highram = 0, prev = 0;
+
+	/* assure that all mmconf_ranges[] are in ascending order */
+	for (i = 0; i < mmconf_ranges; i++) {
+		if (mmconf_mem[i].start <= prev) {
+			fprintf(stderr, "Error: mmconf[] not ascending\n");
+			return;
+		}
+		prev = mmconf_mem[i].start;
+	}
+
+	/* compute high water mark for normal RAM */
+	for (i = 0; i < nr_mem_ranges; i++) {
+		if ((crash_memory_range[i].type == RANGE_RAM) &&
+		    (crash_memory_range[i].end > highram))
+			highram = crash_memory_range[i].end;
+	}
+
+	/* coalesce all entries starting above 'highram' into one */
+	for (i = 0; i < mmconf_ranges; i++) {
+		if (mmconf_mem[i].start > highram) {
+			if (bigentry >= 0)
+				mmconf_mem[bigentry].end = mmconf_mem[i].end;
+			else
+				bigentry = i;
+		}
+	}
+	if (bigentry >= 0)
+		mmconf_ranges = bigentry + 1;
+}
+
 /* Loads additional segments in case of a panic kernel is being loaded.
  * One segment for backup region, another segment for storing elf headers
  * for crash memory image.
@@ -807,6 +905,7 @@ int load_crashdump_segments(struct kexec
	void *tmp;
	unsigned long sz, bufsz, memsz, elfcorehdr;
	int nr_ranges = 0, nr_memmap = 0, align = 1024, i;
+	char *oldend;
	struct memory_range *mem_range, *memmap_p;
	struct crash_elf_info elf_info;
	unsigned kexec_arch;
@@ -855,6 +954,8 @@ int load_crashdump_segments(struct kexec
	
	dbgprint_mem_range("CRASH MEMORY RANGES", mem_range, nr_ranges);
 
+	nr_mem_ranges = nr_ranges;
+
 	/*
 	 * if the core type has not been set on command line, set it here
 	 * automatically
@@ -951,6 +1052,14 @@ int load_crashdump_segments(struct kexec
 		end = mem_range[i].end;
 		cmdline_add_memmap_acpi(mod_cmdline, start, end);
 	}
+	/* Inform second kernel about PCI MMCONF reserved areas. */
+	oldend = mod_cmdline + strlen(mod_cmdline);
+	if (cmdline_add_mmconf_reserved(mod_cmdline)) {
+		/* reset the command line and coalesce the mmconf's */
+		*oldend = '\0';
+		coalesce_mmconf_ranges();
+		cmdline_add_mmconf_reserved(mod_cmdline);
+	}
 	return 0;
 }
 
