Handle loading above 1 MB, for bug-compatibility with Grub
authorH. Peter Anvin <hpa@linux.intel.com>
Fri, 28 May 2010 21:15:50 +0000 (14:15 -0700)
committerH. Peter Anvin <hpa@linux.intel.com>
Fri, 28 May 2010 21:15:50 +0000 (14:15 -0700)
Handle being loaded by bootloaders which can only load above 1 MB.
This is for bug-compatibility with Grub, which will err out with an
angry message if it sees addresses below 1 MB in Multiboot.

Thus add a new --loadhigh option, and make it the default when
building a --multiboot image.

Signed-off-by: H. Peter Anvin <hpa@linux.intel.com>
Makefile
highmove.S [new file with mode: 0644]
highmove/highmove.S [new file with mode: 0644]
linux.c
main.c
setup.h
wraplinux.h

index 42ae862..17388a0 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -4,6 +4,9 @@ PERL                    ?= perl
 RELOC_OBJS             = $(patsubst %.c,%.o,$(wildcard reloc/*.c)) \
                          $(patsubst %.S,%.o,$(wildcard reloc/*.S))
 
+HIGHMOVE_OBJS          = $(patsubst %.c,%.o,$(wildcard highmove/*.c)) \
+                         $(patsubst %.S,%.o,$(wildcard highmove/*.S))
+
 all: wraplinux
 
 reloc/%.o: reloc/%.c
@@ -12,6 +15,12 @@ reloc/%.o: reloc/%.c
 reloc/%.o: reloc/%.S
        $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) -c -D__ASSEMBLY__ -o $@ $<
 
+highmove/%.o: highmove/%.c
+       $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) -c -o $@ $<
+
+highmove/%.o: highmove/%.S
+       $(CC_FOR_TARGET) $(CFLAGS_FOR_TARGET) -c -D__ASSEMBLY__ -o $@ $<
+
 %.o: %.S
        $(CC) $(CFLAGS) -c -o $@ $<
 
@@ -36,7 +45,16 @@ reloc/reloc.elf: $(RELOC_OBJS) reloc/reloc.ld
 
 reloc.o: reloc.S reloc/reloc.bin
 
-wraplinux: main.o linux.o reloc.o elf.o nbi.o segment.o mapfile.o \
+highmove/highmove.bin: highmove/highmove.elf
+       $(OBJCOPY_FOR_TARGET) -O binary $< $@
+
+highmove/highmove.elf: $(HIGHMOVE_OBJS) reloc/reloc.ld
+       $(LD_FOR_TARGET) $(LDFLAGS_FOR_TARGET) -T reloc/reloc.ld \
+               -o $@ $(HIGHMOVE_OBJS)
+
+highmove.o: highmove.S highmove/highmove.bin
+
+wraplinux: main.o linux.o reloc.o highmove.o elf.o nbi.o segment.o mapfile.o \
           cwrite.o xmalloc.o
        $(CC) $(LDFLAGS) -o $@ $^
 
@@ -53,7 +71,9 @@ install: all
 # Cleanup
 #
 clean:
-       rm -f wraplinux *.o *.s *.i reloc/*.o reloc/*.bin reloc/*.elf
+       rm -f wraplinux *.o *.s *.i
+       rm -f reloc/*.o reloc/*.bin reloc/*.elf
+       rm -f highmove/*.o highmove/*.bin highmove/*.elf
 
 cleaner: clean
        rm -rf MCONFIG config.h *.cache config.status config.log
@@ -114,6 +134,6 @@ depend: .depend
 
 .depend:
        : > $@
-       $(PERL) mkdep.pl -M $@ -- . reloc
+       $(PERL) mkdep.pl -M $@ -- . reloc highmove
 
 -include .depend
diff --git a/highmove.S b/highmove.S
new file mode 100644 (file)
index 0000000..ae30826
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * All this does is it wraps highmove/highmove.bin in a .o file.
+ * This could also be done with a binary-to-C converter.
+ */
+
+               .data
+               .balign 4
+               .globl  highmove_size
+highmove_size:
+               .long   .L_highmove_end-highmove
+               .size   highmove_size, 4
+
+               .globl  highmove
+highmove:
+               .incbin "highmove/highmove.bin"
+               .size   highmove,.-highmove
+.L_highmove_end:
+
+/*
+ * This is necessary to keep the whole executable
+ * from needing a writable stack.
+ */
+               .section        .note.GNU-stack,"",@progbits
diff --git a/highmove/highmove.S b/highmove/highmove.S
new file mode 100644 (file)
index 0000000..ac1cc2a
--- /dev/null
@@ -0,0 +1,73 @@
+/* ----------------------------------------------------------------------- *
+ *
+ *   Copyright 2008 rPath, Inc. - All Rights Reserved
+ *   Copyright 2010 Intel Corporation; author: H. Peter Anvin
+ *
+ *   Permission is hereby granted, free of charge, to any person
+ *   obtaining a copy of this software and associated documentation
+ *   files (the "Software"), to deal in the Software without
+ *   restriction, including without limitation the rights to use,
+ *   copy, modify, merge, publish, distribute, sublicense, and/or
+ *   sell copies of the Software, and to permit persons to whom
+ *   the Software is furnished to do so, subject to the following
+ *   conditions:
+ *
+ *   The above copyright notice and this permission notice shall
+ *   be included in all copies or substantial portions of the Software.
+ *
+ *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+ *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+ *   OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * Stub to work around Grub, or other loaders which can't load directly
+ * into low memory.  This code needs four parameters which should
+ * immediately precede it: source of code to be copied, target of code
+ * to be copied, length of code to be copied, post-copy entry point.
+ */
+               /* Parameters */
+               .section ".startupinfo","a",@progbits
+mv_src:                .long   0
+mv_dst:                .long   0
+mv_len:                .long   0
+mv_entry:      .long   0
+
+               /* We will be entered in flat 32-bit protected mode... */
+               .section ".start","ax",@progbits
+               .globl  _start
+               .code32
+_start:
+               call    1f
+1:             pushfl
+               pushl   %ecx
+               pushl   %ebx
+               pushl   %esi
+               pushl   %edi
+               movl    (5*4)(%esp), %ebx
+               subl    $1b, %ebx
+
+               movl    mv_entry(%ebx), %ecx
+               movl    %ecx, (5*4)(%esp)       /* Where to go next */
+
+               movl    mv_src(%ebx), %esi
+               movl    mv_dst(%ebx), %edi
+               movl    mv_len(%ebx), %ecx
+               addl    $3, %ecx
+               shrl    $2, %ecx
+
+               cld
+               rep ; movsl
+
+               popl    %edi
+               popl    %esi
+               popl    %ebx
+               popl    %ecx
+               popfl
+               retl                            /* Goes to the entry point */
diff --git a/linux.c b/linux.c
index 4f97d88..9aab786 100644 (file)
--- a/linux.c
+++ b/linux.c
@@ -1,6 +1,7 @@
 /* ----------------------------------------------------------------------- *
  *
  *   Copyright 2008 rPath, Inc. - All Rights Reserved
+ *   Copyright 2010 Intel Corporation; author: H. Peter Anvin
  *
  *   This program is free software; you can redistribute it and/or modify
  *   it under the terms of the GNU General Public License as published by
@@ -34,6 +35,8 @@
 
 extern char reloc[];
 extern uint32_t reloc_size;
+extern char highmove[];
+extern uint32_t highmove_size;
 
 /* Find the last instance of a particular command line argument
    (which should include the final =; do not use for boolean arguments) */
@@ -105,9 +108,10 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
        int kernel_fd = -1;
        char *kernel = NULL;
        size_t kernel_len = 0;
-       struct segment srel, ssup, scmd, skrn;
+       struct segment srel, ssup, scmd, skrn, shmv;
        struct startup_info *info = (void *)reloc;
        struct setup_header *hdr;
+       struct highmove_info *hmv = (void *)highmove;
        size_t setup_len;
        size_t initrd_len;
        size_t initrd_addr;
@@ -119,6 +123,7 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
        int setup_space;        /* How much space for the setup */
        const char *cmd;
        uint32_t initrd_max;
+       uint32_t entry;
 
        /* Process the kernel file */
 
@@ -288,6 +293,12 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
                hdr->loadflags |= CAN_USE_HEAP;
        }
 
+
+       /* Setup information */
+       wrle32(ssup.address, &info->setup_addr);
+       wrle32(scmd.address, &info->cmdline_addr);
+       entry = srel.address + sizeof *info;
+
        /* Segment: Linux kernel proper */
        skrn.next = ninitrd ? &ird[0].seg : NULL;
        skrn.align = 4;         /* 2**4 = 16 bytes */
@@ -297,12 +308,54 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
        skrn.name = "kernel";
        skrn.data = kernel + setup_len;
 
-       /* Additional segments: initrd */
        if (skrn.address < 0x100000)
                initrd_addr = 0x100000;
        else
                initrd_addr = skrn.address + skrn.length;
 
+       /* Loadhigh modifications */
+       if (opt.loadhigh) {
+               uint32_t rm_base, rm_len, delta;
+
+               rm_base = ssup.address;
+               if (skrn.address < 0x100000) {
+                       rm_base = skrn.address;
+                       skrn.address = 0x100000;
+                       initrd_addr = 0x100000 + skrn.length;
+               }
+
+               shmv.next = skrn.next;
+               skrn.next = &shmv;
+               shmv.address = align_up(initrd_addr, 4);
+               shmv.align = 4;
+               shmv.length = highmove_size;
+               shmv.sh_type = SHT_PROGBITS;
+               shmv.sh_flags = SHF_ALLOC | SHF_EXECINSTR;
+               shmv.name = "highmove";
+               shmv.data = highmove;
+
+               rm_len = scmd.address + scmd.length - rm_base;
+               wrle32(rm_base, &hmv->mv_dst);
+               wrle32(rm_len, &hmv->mv_len);
+               wrle32(entry, &hmv->mv_entry);
+
+               entry = shmv.address + sizeof *hmv;
+
+               initrd_addr = shmv.address + shmv.length;
+               initrd_addr = align_up(initrd_addr, 4);
+
+               wrle32(initrd_addr, &hmv->mv_src);
+
+               delta = initrd_addr - ssup.address;
+
+               ssup.address += delta;
+               srel.address += delta;
+               scmd.address += delta;
+
+               initrd_addr += rm_len;
+       }
+
+       /* Additional segments: initrd */
        for (i = 0; i < ninitrd; i++) {
                char *name;
 
@@ -321,14 +374,12 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
        if (setup_ver >= 0x200)
                wrle32(initrd_len, &hdr->ramdisk_size);
 
-       /* Set up the startup info */
+       /* Initrd information in the startup info */
        wrle32(ninitrd ? ird[0].seg.address : 0, &info->rd_addr);
        wrle32(initrd_len, &info->rd_len);
        wrle32(initrd_max, &info->rd_maxaddr);
-       wrle32(ssup.address, &info->setup_addr);
-       wrle32(scmd.address, &info->cmdline_addr);
 
-       rv = opt.output(&srel, srel.address + sizeof *info, out);
+       rv = opt.output(&srel, entry, out);
 
  err:
        if (ird) {
diff --git a/main.c b/main.c
index 347ad45..568db4c 100644 (file)
--- a/main.c
+++ b/main.c
 
 const char *program;
 
+enum longopts {
+       OPT_NOLOADHIGH  = 256,
+};
+
 const struct option long_options[] = {
        {"params",      1, 0, 'p'},
        {"cmdline",     1, 0, 'p'},
        {"commandline", 1, 0, 'p'},
        {"initrd",      1, 0, 'i'},
        {"output",      1, 0, 'o'},
+       {"loadhigh",    0, 0, 'l'},
+       {"noloadhigh",  0, 0,  OPT_NOLOADHIGH},
        {"elf",         0, 0, 'E'},
        {"multiboot",   0, 0, 'M'},
        {"nbi",         0, 0, 'N'},
@@ -45,7 +51,7 @@ const struct option long_options[] = {
        {0, 0, 0, 0}
 };
 
-#define OPTSTRING "p:i:o:EMNhV"
+#define OPTSTRING "p:i:o:lEMNhV"
 
 static void usage(int err)
 {
@@ -58,6 +64,7 @@ static void usage(int err)
                "  --elf          -E    output in ELF format (default)\n"
                "  --multiboot    -M    output in Multiboot ELF format\n"
                "  --nbi          -N    output in NBI format\n"
+               "  --loadhigh     -l    load entirely above 1 MB\n"
                "  --help         -h    display this help text\n"
                "  --version      -V    print the program version\n",
                WRAPLINUX_PACKAGE, WRAPLINUX_VERSION,
@@ -71,6 +78,7 @@ int main(int argc, char *argv[])
        const char *kernel;
        struct string_list *ird = NULL, **irdp = &ird, *ip;
        FILE *out = stdout;
+       bool loadhighopt = false;
 
        program = argv[0];
 
@@ -101,6 +109,14 @@ int main(int argc, char *argv[])
                                }
                        }
                        break;
+               case 'l':
+                       opt.loadhigh = true;
+                       loadhighopt  = true;
+                       break;
+               case OPT_NOLOADHIGH:
+                       opt.loadhigh = false;
+                       loadhighopt  = true;
+                       break;
                case 'E':
                        opt.output = output_elf;
                        break;
@@ -125,6 +141,9 @@ int main(int argc, char *argv[])
        if ((argc - optind) != 1)
                usage(EX_USAGE);
 
+       if (opt.output == output_multiboot && !loadhighopt)
+               opt.loadhigh = true;
+
        kernel = argv[optind];
 
        if (!opt.params)
diff --git a/setup.h b/setup.h
index a28a94c..4313bc7 100644 (file)
--- a/setup.h
+++ b/setup.h
@@ -12,6 +12,13 @@ struct startup_info {
        uint32_t reloc_size;
 };
 
+struct highmove_info {
+       uint32_t mv_src;
+       uint32_t mv_dst;
+       uint32_t mv_len;
+       uint32_t mv_entry;
+};
+
 #define LINUX_MAGIC ('H' + ('d' << 8) + ('r' << 16) + ('S' << 24))
 #define OLD_CMDLINE_MAGIC 0xA33F
 
index 7e22b02..d655ef9 100644 (file)
@@ -30,6 +30,7 @@ extern const char *program;
 struct opt {
        const char *params;
        int (*output) (struct segment *, addr_t, FILE *);
+       bool loadhigh;
 } opt;
 
 struct string_list {