[syslinux:firmware] load_linux: dynamically calculate the cmdline region

syslinux-bot for Matt Fleming matt.fleming at intel.com
Fri Jul 26 01:27:11 PDT 2013


Commit-ID:  77cadda8fa63289dbd3d2bbc6eaff7c7e42688c4
Gitweb:     http://www.syslinux.org/commit/77cadda8fa63289dbd3d2bbc6eaff7c7e42688c4
Author:     Matt Fleming <matt.fleming at intel.com>
AuthorDate: Thu, 25 Jul 2013 20:14:09 +0100
Committer:  Matt Fleming <matt.fleming at intel.com>
CommitDate: Thu, 25 Jul 2013 22:39:57 +0100

load_linux: dynamically calculate the cmdline region

Users are hitting issues where the offset calculated by,

    (0x9ff0 - cmdline_size) & ~15;

is not useable memory, e.g. it is SMT_RESERVED. Instead we should be
trying to find the highest lowmem address.

Cc: H. Peter Anvin <hpa at zytor.com>
Signed-off-by: Matt Fleming <matt.fleming at intel.com>

---
 com32/include/syslinux/movebits.h |  4 ++++
 com32/lib/syslinux/load_linux.c   | 29 +++++++++++++++++++++----
 com32/lib/syslinux/zonelist.c     | 45 +++++++++++++++++++++++++++++++++++++++
 3 files changed, 74 insertions(+), 4 deletions(-)

diff --git a/com32/include/syslinux/movebits.h b/com32/include/syslinux/movebits.h
index d12fb58..f35418f 100644
--- a/com32/include/syslinux/movebits.h
+++ b/com32/include/syslinux/movebits.h
@@ -83,6 +83,10 @@ enum syslinux_memmap_types syslinux_memmap_type(struct syslinux_memmap *list,
 int syslinux_memmap_largest(struct syslinux_memmap *list,
 			    enum syslinux_memmap_types type,
 			    addr_t * start, addr_t * len);
+int syslinux_memmap_highest(struct syslinux_memmap *list,
+			    enum syslinux_memmap_types types,
+			    addr_t *start, addr_t len,
+			    addr_t ceiling, addr_t align);
 void syslinux_free_memmap(struct syslinux_memmap *list);
 struct syslinux_memmap *syslinux_dup_memmap(struct syslinux_memmap *list);
 int syslinux_memmap_find_type(struct syslinux_memmap *list,
diff --git a/com32/lib/syslinux/load_linux.c b/com32/lib/syslinux/load_linux.c
index 851d467..37c8df0 100644
--- a/com32/lib/syslinux/load_linux.c
+++ b/com32/lib/syslinux/load_linux.c
@@ -125,10 +125,29 @@ static int map_initramfs(struct syslinux_movelist **fraglist,
 }
 
 static size_t calc_cmdline_offset(struct linux_header *hdr,
-				  size_t cmdline_size)
+				  size_t cmdline_size, addr_t base,
+				  addr_t start)
 {
-    if (hdr->version < 0x0202 || !(hdr->loadflags & 0x01))
+    if (hdr->version < 0x0202 || !(hdr->loadflags & 0x01)) {
+	struct syslinux_memmap *mmap;
+
+	mmap = syslinux_memory_map();
+	if (mmap && !syslinux_memmap_highest(mmap, SMT_FREE, &start,
+					     cmdline_size, 0xa0000, 16)) {
+	    syslinux_free_memmap(mmap);
+	    return start - base;
+	}
+
+	if (mmap && !syslinux_memmap_highest(mmap, SMT_TERMINAL, &start,
+					     cmdline_size, 0xa0000, 16)) {
+	    syslinux_free_memmap(mmap);
+	    return start - base;
+	}
+
+	syslinux_free_memmap(mmap);
+	dprintf("Unable to find lowmem for cmdline\n");
 	return (0x9ff0 - cmdline_size) & ~15;
+    }
 
     return 0x10000;
 }
@@ -221,13 +240,15 @@ int bios_boot_linux(void *kernel_buf, size_t kernel_size,
 	cmdline[cmdline_size - 1] = '\0';
     }
 
-    cmdline_offset = calc_cmdline_offset(&hdr, cmdline_size);
-
     real_mode_size = (hdr.setup_sects + 1) << 9;
     real_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x10000 : 0x90000;
     prot_mode_base = (hdr.loadflags & LOAD_HIGH) ? 0x100000 : 0x10000;
     prot_mode_size = kernel_size - real_mode_size;
 
+    cmdline_offset = calc_cmdline_offset(&hdr, cmdline_size, real_mode_base,
+					 real_mode_base + real_mode_size);
+    dprintf("cmdline_offset at 0x%x\n", real_mode_base + cmdline_offset);
+
     if (hdr.version < 0x020a) {
 	/*
 	 * The 3* here is a total fudge factor... it's supposed to
diff --git a/com32/lib/syslinux/zonelist.c b/com32/lib/syslinux/zonelist.c
index f869bc6..337eb86 100644
--- a/com32/lib/syslinux/zonelist.c
+++ b/com32/lib/syslinux/zonelist.c
@@ -219,6 +219,51 @@ int syslinux_memmap_largest(struct syslinux_memmap *list,
 }
 
 /*
+ * Find the highest zone of a specific type that satisfies the
+ * constraints.
+ *
+ * 'start' is updated with the highest address on success. 'start' can
+ * be used to set a minimum address to begin searching from.
+ *
+ * Returns -1 on failure.
+ */
+int syslinux_memmap_highest(struct syslinux_memmap *list,
+			    enum syslinux_memmap_types type,
+			    addr_t *start, addr_t len,
+			    addr_t ceiling, addr_t align)
+{
+    addr_t size, best;
+
+    for (best = 0; list->type != SMT_END; list = list->next) {
+	size = list->next->start - list->start;
+
+	if (list->type != type)
+	    continue;
+
+	if (list->start + size <= *start)
+	    continue;
+
+	if (list->start + len >= ceiling)
+	    continue;
+
+	if (list->start + size < ceiling)
+	    best = ALIGN_DOWN(list->start + size - len, align);
+	else
+	    best = ALIGN_DOWN(ceiling - len, align);
+
+	if (best < *start)
+	    best = 0;
+    }
+
+    if (!best)
+	return -1;
+
+    *start = best;
+
+    return 0;
+}
+
+/*
  * Find the first (lowest address) zone of a specific type and of
  * a certain minimum size, with an optional starting address.
  * The input values of start and len are used as minima.


More information about the Syslinux-commits mailing list