summaryrefslogtreecommitdiffstats
path: root/reloc/reloc_linux.c
diff options
context:
space:
mode:
authorH. Peter Anvin <hpa@zytor.com>2008-01-03 21:54:24 -0800
committerH. Peter Anvin <hpa@zytor.com>2008-01-03 21:54:24 -0800
commitb3e7732cf39938575a287cd55907216c318d940c (patch)
tree4aaf0a8f22dcf2cc5fbb46cbdab6afcad2c99daa /reloc/reloc_linux.c
downloadwraplinux-b3e7732cf39938575a287cd55907216c318d940c.tar.gz
wraplinux-b3e7732cf39938575a287cd55907216c318d940c.tar.xz
wraplinux-b3e7732cf39938575a287cd55907216c318d940c.zip
Initial commit: functional for newer bzImage kernels
Diffstat (limited to 'reloc/reloc_linux.c')
-rw-r--r--reloc/reloc_linux.c187
1 files changed, 187 insertions, 0 deletions
diff --git a/reloc/reloc_linux.c b/reloc/reloc_linux.c
new file mode 100644
index 0000000..5849c54
--- /dev/null
+++ b/reloc/reloc_linux.c
@@ -0,0 +1,187 @@
+/* ----------------------------------------------------------------------- *
+ *
+ * Copyright 2008 H. Peter Anvin - All Rights Reserved
+ *
+ * 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.
+ *
+ * ----------------------------------------------------------------------- */
+
+/*
+ * reloc_linux.c
+ *
+ * This is the initial program run before the Linux kernel.
+ * It serves to hoist the initrd as high as permitted.
+ *
+ * This runs in protected mode, but in low memory, and should be kept
+ * as small as possible.
+ */
+
+#include "reloc.h"
+#include "setup.h"
+#include "conio.h"
+
+static uint32_t initrd_len, initrd_addr;
+static uint64_t max_addr;
+
+static int initrd_fit(uint32_t base, uint32_t end)
+{
+ uint64_t ibase;
+
+ printf("Fit: base = %08x, end = %08x\n", base, end);
+
+ if (end <= base)
+ return -1; /* Invalid */
+
+ if (base > max_addr)
+ return -1; /* Not accessible */
+
+ if (end > max_addr)
+ end = max_addr;
+
+ if (end < initrd_len)
+ return -1;
+
+ ibase = (end - initrd_len) & ~0xfff;
+ if (ibase < base)
+ return -1;
+
+ if (initrd_addr < ibase) {
+ initrd_addr = ibase; /* This is a better one... */
+ printf("Best: initrd_addr = %08x\n", initrd_addr);
+ }
+
+ return 0;
+}
+
+static int probe_memory_e820(void)
+{
+ com32sys_t regs;
+ struct e820_info {
+ uint64_t base;
+ uint64_t len;
+ uint32_t type;
+ } buf;
+ uint64_t base, end;
+ int rv = -1;
+ uint32_t copied;
+
+ memset(&regs, 0, sizeof regs);
+
+ do {
+ regs.eax.l = 0x0000e820;
+ regs.ecx.l = sizeof buf;
+ regs.edx.l = 0x534d4150;
+ regs.edi.w[0] = OFFS(&buf);
+ regs.es = SEG(&buf);
+
+ intcall(0x15, &regs, &regs);
+ copied = (regs.eflags.l & EFLAGS_CF)
+ ? 0 : regs.ecx.l;
+
+ if ( regs.eax.l != 0x534d4150 || copied < 20 )
+ break;
+
+ if (buf.type != 1)
+ continue; /* Not memory */
+
+ rv &= initrd_fit(buf.base, buf.base+buf.len);
+ } while (regs.ebx.l);
+
+ return rv;
+}
+
+static int probe_memory_e801(void)
+{
+ com32sys_t regs;
+ uint64_t base, end;
+
+ memset(&regs, 0, sizeof regs);
+ regs.eax.w[0] = 0xe801;
+ intcall(0x15, &regs, &regs);
+
+ if (regs.eflags.l & EFLAGS_CF)
+ return -1; /* No e801 */
+
+ if (regs.eax.w[0] < 15*1024)
+ end = (uint64_t)(regs.eax.w[0] << 10) + 0x100000;
+ else
+ end = (uint64_t)(regs.ebx.w[0] << 16) + 0x1000000;
+
+ return initrd_fit(0x100000, end);
+}
+
+static int probe_memory_88(void)
+{
+ com32sys_t regs;
+
+ memset(&regs, 0, sizeof regs);
+ regs.eax.b[1] = 0x88;
+ intcall(0x15, &regs, &regs);
+
+ if (regs.eflags.l & EFLAGS_CF)
+ return -1;
+
+ return initrd_fit(0x100000, (regs.eax.w[0] << 10)+0x100000);
+}
+
+static int place_initrd(void)
+{
+ int rv;
+
+ rv = probe_memory_e820();
+ if (!rv)
+ return 0;
+
+ rv = probe_memory_e801();
+ if (!rv)
+ return 0;
+
+ return probe_memory_88();
+}
+
+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);
+
+ if (info->rd_len) {
+ initrd_len = info->rd_len;
+ max_addr = 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);
+
+ memmove((void *)initrd_addr, (void *)info->rd_addr,
+ info->rd_len);
+
+ hdr->ramdisk_image = initrd_addr;
+ }
+
+ jump_to_kernel(info->setup_addr >> 4,
+ info->cmdline_addr - info->setup_addr);
+ return -1; /* Shouldn't return... */
+}