Cleaner memory map; avoid < 64K; handle older kernels
[wraplinux.git] / linux.c
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) {