Cleaner memory map; avoid < 64K; handle older kernels
authorH. Peter Anvin <hpa@zytor.com>
Fri, 4 Jan 2008 20:51:01 +0000 (12:51 -0800)
committerH. Peter Anvin <hpa@zytor.com>
Fri, 4 Jan 2008 20:51:01 +0000 (12:51 -0800)
Clean up the memory map by making the startup info part of the reloc
segment; this also allows it to be easily prepopulated.

Avoid using < 64K by putting the reloc between the setup and cmdline,
with proper guarding for overflow.

Handle older kernel protocols, and zImage kernels.

linux.c
reloc.S
reloc/reloc.ld
reloc/reloc_linux.c
setup.h

diff --git a/linux.c b/linux.c
index 71ded0f..28f8cf1 100644 (file)
--- a/linux.c
+++ b/linux.c
@@ -33,7 +33,7 @@
 #include "le.h"
 #include "wraplinux.h"
 
-extern const char reloc[];
+extern char reloc[];
 extern uint32_t reloc_size;
 
 int wrap_kernel(const char *kernel_file, const char *cmdline,
@@ -47,8 +47,8 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
   char *kernel = NULL;
   size_t kernel_len = 0;
   struct stat st;
-  struct segment srel, sinf, ssup, scmd, skrn;
-  struct startup_info info;
+  struct segment srel, ssup, scmd, skrn;
+  struct startup_info *info = (void *)reloc;
   struct setup_header *hdr;
   size_t setup_len;
   size_t initrd_len;
@@ -57,16 +57,10 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
   int ninitrd = 0;
   int i;
   const struct string_list *ip;
+  int setup_ver;               /* Setup protocol version */
+  int setup_space;             /* How much space for the setup */
 
-  ninitrd = 0;
-  for (ip = initrd_list; ip; ip = ip->next)
-    ninitrd++;
-
-  if (ninitrd) {
-    ird = calloc(ninitrd, sizeof *ird);
-    if (!ird)
-      goto err;
-  }
+  /* Process the kernel file */
 
   kernel_fd = open(kernel_file, O_RDONLY);
   if (kernel_fd < 0)
@@ -87,6 +81,43 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
     goto err;
   }
 
+  /* Pick apart the header... */
+
+  hdr = (struct setup_header *)(kernel + 0x1f1);
+  setup_len = (hdr->setup_sects + 1) << 9;
+  if (setup_len == 512)
+    setup_len += 2048;         /* Really old kernel */
+
+  if (rdle32(&hdr->header) != 0x53726448)
+    setup_ver = 0;             /* Ancient kernel */
+  else
+    setup_ver = rdle16(&hdr->version);
+
+  if (setup_ver >= 0x200 && (hdr->loadflags & LOADED_HIGH)) {
+    skrn.address = 0x100000;
+    ssup.address = 0x10000;
+    setup_space  = setup_ver >= 0x202 ? 0x10000 : 0xa000;
+  } else {
+    skrn.address = 0x10000;
+    ssup.address = 0x90000;
+    setup_space  = 0xa000;
+  }
+
+  if (setup_ver <= 0x200)
+    initrd_list = NULL;                /* No initrd for ancient kernel */
+
+  /* Process the initrd file(s) */
+  
+  ninitrd = 0;
+  for (ip = initrd_list; ip; ip = ip->next)
+    ninitrd++;
+
+  if (ninitrd) {
+    ird = calloc(ninitrd, sizeof *ird);
+    if (!ird)
+      goto err;
+  }
+
   initrd_len = 0;
   for (ip = initrd_list, i = 0; ip; ip = ip->next, i++) {
     ird[i].fd = open(ip->str, O_RDONLY);
@@ -113,62 +144,61 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
     initrd_len += ird[i].seg.length;
   }
 
-  hdr = (struct setup_header *)(kernel + 0x1f1);
-  setup_len = (hdr->setup_sects + 1) << 9;
-  if (setup_len == 512)
-    setup_len += 2048;         /* Really old kernel */
-  
-  /* Segment 1 is the relocation code */
+  /* Segment: relocation code */
+  /* We put this immediately after the kernel setup, in the memory
+     which will be reclaimed for setup. */
   srel.next     = &ssup;
-  srel.address  = 0xc000;      /* Can be adjusted (align 16),
-                                  code is relocatable */
+  srel.address  = (ssup.address + setup_len + 15) & ~15;
   srel.length   = reloc_size;
   srel.sh_type  = SHT_PROGBITS;
   srel.sh_flags = SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR;
   srel.name     = "reloc";
   srel.data    = reloc;
 
-  /* Segment 0 is the struct startup_info */
-  sinf.next     = &srel;
-  sinf.address  = srel.address - sizeof info;
-  sinf.length   = sizeof info;
-  sinf.sh_type  = SHT_PROGBITS;
-  sinf.sh_flags = SHF_ALLOC;
-  sinf.name     = "startup_info";
-  sinf.data     = &info;
-
-  /* Segment 2 is the Linux kernel setup */
+  /* Segment: Linux kernel setup */
   ssup.next     = &scmd;
-  ssup.address  = 0x10000;     /* XXX */
   ssup.length   = setup_len;
   ssup.sh_type  = SHT_PROGBITS;
   ssup.sh_flags = SHF_ALLOC|SHF_WRITE|SHF_EXECINSTR;
   ssup.name     = "setup";
   ssup.data     = kernel;
-  hdr->type_of_loader = 0xff;  /* "Other modern loader" */
+  if (setup_ver >= 0x200)
+    hdr->type_of_loader = 0xff;        /* "Other modern loader" */
 
-  /* Segment 3 is the kernel command line */
+  /* Segment: kernel command line */
   scmd.next     = &skrn;
-  scmd.address  = 0x20000;     /* XXX */
   scmd.length   = strlen(cmdline)+1;
+  scmd.address  = (ssup.address + setup_space - scmd.length) & ~15;
+  if (srel.address + reloc_size > scmd.address) {
+    /* Uh-oh, we're short on space... push the command line
+       higher. */
+    scmd.address = (srel.address + reloc_size + 15) & ~15;
+  }
   scmd.sh_type  = SHT_PROGBITS;
   scmd.sh_flags = SHF_ALLOC;
   scmd.name     = "cmdline";
   scmd.data     = cmdline;
-  wrle32(scmd.address, &hdr->cmd_line_ptr); /* XXX */
-  wrle16(scmd.address - ssup.address - 0x200, &hdr->heap_end_ptr);
-  hdr->loadflags |= CAN_USE_HEAP;
+  if (setup_ver >= 0x202) {
+    wrle32(scmd.address, &hdr->cmd_line_ptr);
+  } else {
+    /* Old-style command line protocol */
+    wrle16(0xA33F, (uint16_t *)(kernel+0x20));
+    wrle16(scmd.address - ssup.address, (uint16_t *)(kernel+0x22));
+  }
+  if (setup_ver >= 0x201) {
+    wrle16(scmd.address - ssup.address - 0x200, &hdr->heap_end_ptr);
+    hdr->loadflags |= CAN_USE_HEAP;
+  }
 
-  /* Segment 4 is the Linux kernel proper */
+  /* Segment: Linux kernel proper */
   skrn.next     = ninitrd ? &ird[0].seg : NULL;
-  skrn.address  = 0x100000;    /* XXX */
   skrn.length   = kernel_len - setup_len;
   skrn.sh_type  = SHT_PROGBITS;
   skrn.sh_flags = SHF_ALLOC;
   skrn.name     = "kernel";
   skrn.data     = kernel + setup_len;
 
-  /* Segment 5 is the initrd */
+  /* Additional segments: initrd */
   if (skrn.address < 0x100000)
     initrd_addr = 0x100000;
   else
@@ -186,16 +216,20 @@ int wrap_kernel(const char *kernel_file, const char *cmdline,
 
     initrd_addr += ird[i].seg.length;
   }
-  wrle32(initrd_len, &hdr->ramdisk_size);
+  if (setup_ver >= 0x200)
+    wrle32(initrd_len, &hdr->ramdisk_size);
 
   /* Set up the startup info */
-  wrle32(ninitrd ? ird[0].seg.address : 0, &info.rd_addr);
-  wrle32(initrd_len, &info.rd_len);
-  wrle32(rdle32(&hdr->initrd_addr_max), &info.rd_maxaddr); /* XXX */
-  wrle32(ssup.address, &info.setup_addr);
-  wrle32(scmd.address, &info.cmdline_addr);
+  wrle32(ninitrd ? ird[0].seg.address : 0, &info->rd_addr);
+  wrle32(initrd_len, &info->rd_len);
+  if (setup_ver >= 0x203)
+    wrle32(rdle32(&hdr->initrd_addr_max), &info->rd_maxaddr);
+  else
+    wrle32(0x37ffffff, &info->rd_maxaddr);
+  wrle32(ssup.address, &info->setup_addr);
+  wrle32(scmd.address, &info->cmdline_addr);
 
-  rv = output_elf(&sinf, srel.address, out);
+  rv = output_elf(&srel, srel.address + sizeof *info, out);
 
  err:
   if (ird) {
diff --git a/reloc.S b/reloc.S
index f7d1310..b70c66d 100644 (file)
--- a/reloc.S
+++ b/reloc.S
@@ -3,7 +3,7 @@
  * This could also be done with a binary-to-C converter.
  */
        
-               .section ".rodata","a"
+               .data
                .balign 4
                .globl  reloc_size
 reloc_size:
index dc63c62..c504bb0 100644 (file)
@@ -4,6 +4,7 @@ ENTRY(_start)
 SECTIONS
 {
        . = 0;                  /* Required for relocatabilty */
+       .startupinfo    : { *(.startupinfo) }
        .start          : { *(.start) }
        .text           : { *(.text) }
        .text16         : { *(.text16) }
index 5849c54..79655c1 100644 (file)
@@ -158,30 +158,36 @@ static int place_initrd(void)
        return probe_memory_88();
 }
 
+extern char _end[];
+/* This structure is hacked by the installer */
+static const struct startup_info startup_info
+__attribute__((section(".startupinfo"))) =
+{
+       .reloc_size = (uint32_t)&_end,
+};
+
 int main(void)
 {
-       extern struct startup_info _start[]; /* Cute hack, eh? */
-       struct startup_info *info = _start - 1;
-       struct setup_header *hdr = (void *)(info->setup_addr + 0x1f1);
+       struct setup_header *hdr = (void *)(startup_info.setup_addr + 0x1f1);
 
-       if (info->rd_len) {
-               initrd_len = info->rd_len;
-               max_addr   = info->rd_maxaddr;
+       if (startup_info.rd_len) {
+               initrd_len = startup_info.rd_len;
+               max_addr   = startup_info.rd_maxaddr;
 
                if (place_initrd())
                        return -1;
 
                /* Move the initrd into place */
                printf("Moving initrd: 0x%08x -> 0x%08x (0x%08x bytes)\n",
-                      info->rd_addr, initrd_addr, info->rd_len);
+                      startup_info.rd_addr, initrd_addr, startup_info.rd_len);
 
-               memmove((void *)initrd_addr, (void *)info->rd_addr,
-                       info->rd_len);
+               memmove((void *)initrd_addr, (void *)startup_info.rd_addr,
+                       startup_info.rd_len);
                
                hdr->ramdisk_image = initrd_addr;
        }
 
-       jump_to_kernel(info->setup_addr >> 4,
-                      info->cmdline_addr - info->setup_addr);
+       jump_to_kernel(startup_info.setup_addr >> 4,
+                      startup_info.cmdline_addr - startup_info.setup_addr);
        return -1;              /* Shouldn't return... */
 }
diff --git a/setup.h b/setup.h
index e203be8..baf008d 100644 (file)
--- a/setup.h
+++ b/setup.h
@@ -9,6 +9,7 @@ struct startup_info {
        uint32_t rd_maxaddr;
        uint32_t setup_addr;
        uint32_t cmdline_addr;
+       uint32_t reloc_size;
 };
 
 struct setup_header {