diff options
author | Matt Fleming <matt.fleming@intel.com> | 2013-07-25 20:14:09 +0100 |
---|---|---|
committer | Matt Fleming <matt.fleming@intel.com> | 2013-07-25 22:39:57 +0100 |
commit | 77cadda8fa63289dbd3d2bbc6eaff7c7e42688c4 (patch) | |
tree | f376c57888d0e89fa1ef6f3934844c4b63c28bed | |
parent | c8524364ff29e3f0bef0b21d66e571ba76e8d9d4 (diff) | |
download | syslinux-77cadda8fa63289dbd3d2bbc6eaff7c7e42688c4.tar.gz syslinux-77cadda8fa63289dbd3d2bbc6eaff7c7e42688c4.tar.xz syslinux-77cadda8fa63289dbd3d2bbc6eaff7c7e42688c4.zip |
load_linux: dynamically calculate the cmdline regionsyslinux-6.02-pre12
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@zytor.com>
Signed-off-by: Matt Fleming <matt.fleming@intel.com>
-rw-r--r-- | com32/include/syslinux/movebits.h | 4 | ||||
-rw-r--r-- | com32/lib/syslinux/load_linux.c | 29 | ||||
-rw-r--r-- | 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 d12fb584..f35418f9 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 851d4678..37c8df02 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 f869bc6f..337eb867 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. |