Cleaner memory map; avoid < 64K; handle older kernels
[wraplinux.git] / reloc / reloc_linux.c
1 /* ----------------------------------------------------------------------- *
2  *   
3  *   Copyright 2008 H. Peter Anvin - All Rights Reserved
4  *
5  *   Permission is hereby granted, free of charge, to any person
6  *   obtaining a copy of this software and associated documentation
7  *   files (the "Software"), to deal in the Software without
8  *   restriction, including without limitation the rights to use,
9  *   copy, modify, merge, publish, distribute, sublicense, and/or
10  *   sell copies of the Software, and to permit persons to whom
11  *   the Software is furnished to do so, subject to the following
12  *   conditions:
13  *   
14  *   The above copyright notice and this permission notice shall
15  *   be included in all copies or substantial portions of the Software.
16  *   
17  *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18  *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
19  *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20  *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
21  *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
22  *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
23  *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
24  *   OTHER DEALINGS IN THE SOFTWARE.
25  *
26  * ----------------------------------------------------------------------- */
27
28 /*
29  * reloc_linux.c
30  *
31  * This is the initial program run before the Linux kernel.
32  * It serves to hoist the initrd as high as permitted.
33  *
34  * This runs in protected mode, but in low memory, and should be kept
35  * as small as possible.
36  */
37
38 #include "reloc.h"
39 #include "setup.h"
40 #include "conio.h"
41
42 static uint32_t initrd_len, initrd_addr;
43 static uint64_t max_addr;
44
45 static int initrd_fit(uint32_t base, uint32_t end)
46 {
47         uint64_t ibase;
48
49         printf("Fit: base = %08x, end = %08x\n", base, end);
50
51         if (end <= base)
52                 return -1;      /* Invalid */
53
54         if (base > max_addr)
55                 return -1;      /* Not accessible */
56
57         if (end > max_addr)
58                 end = max_addr;
59
60         if (end < initrd_len)
61                 return -1;
62
63         ibase = (end - initrd_len) & ~0xfff;
64         if (ibase < base)
65                 return -1;
66
67         if (initrd_addr < ibase) {
68                 initrd_addr = ibase; /* This is a better one... */
69                 printf("Best: initrd_addr = %08x\n", initrd_addr);
70         }
71
72         return 0;
73 }
74
75 static int probe_memory_e820(void)
76 {
77         com32sys_t regs;
78         struct e820_info {
79                 uint64_t base;
80                 uint64_t len;
81                 uint32_t type;
82         } buf;
83         uint64_t base, end;
84         int rv = -1;
85         uint32_t copied;
86
87         memset(&regs, 0, sizeof regs);
88
89         do {
90                 regs.eax.l = 0x0000e820;
91                 regs.ecx.l = sizeof buf;
92                 regs.edx.l = 0x534d4150;
93                 regs.edi.w[0] = OFFS(&buf);
94                 regs.es = SEG(&buf);
95
96                 intcall(0x15, &regs, &regs);
97                 copied = (regs.eflags.l & EFLAGS_CF)
98                         ? 0 : regs.ecx.l;
99                 
100                 if ( regs.eax.l != 0x534d4150 || copied < 20 )
101                         break;
102
103                 if (buf.type != 1)
104                         continue; /* Not memory */
105
106                 rv &= initrd_fit(buf.base, buf.base+buf.len);
107         } while (regs.ebx.l);
108
109         return rv;
110 }
111
112 static int probe_memory_e801(void)
113 {
114         com32sys_t regs;
115         uint64_t base, end;
116
117         memset(&regs, 0, sizeof regs);
118         regs.eax.w[0] = 0xe801;
119         intcall(0x15, &regs, &regs);
120
121         if (regs.eflags.l & EFLAGS_CF)
122                 return -1;      /* No e801 */
123
124         if (regs.eax.w[0] < 15*1024)
125                 end = (uint64_t)(regs.eax.w[0] << 10) + 0x100000;
126         else
127                 end = (uint64_t)(regs.ebx.w[0] << 16) + 0x1000000;
128
129         return initrd_fit(0x100000, end);
130 }
131
132 static int probe_memory_88(void)
133 {
134         com32sys_t regs;
135
136         memset(&regs, 0, sizeof regs);
137         regs.eax.b[1] = 0x88;
138         intcall(0x15, &regs, &regs);
139
140         if (regs.eflags.l & EFLAGS_CF)
141                 return -1;
142
143         return initrd_fit(0x100000, (regs.eax.w[0] << 10)+0x100000);
144 }
145
146 static int place_initrd(void)
147 {
148         int rv;
149
150         rv = probe_memory_e820();
151         if (!rv)
152                 return 0;
153
154         rv = probe_memory_e801();
155         if (!rv)
156                 return 0;
157
158         return probe_memory_88();
159 }
160
161 extern char _end[];
162 /* This structure is hacked by the installer */
163 static const struct startup_info startup_info
164 __attribute__((section(".startupinfo"))) =
165 {
166         .reloc_size = (uint32_t)&_end,
167 };
168
169 int main(void)
170 {
171         struct setup_header *hdr = (void *)(startup_info.setup_addr + 0x1f1);
172
173         if (startup_info.rd_len) {
174                 initrd_len = startup_info.rd_len;
175                 max_addr   = startup_info.rd_maxaddr;
176
177                 if (place_initrd())
178                         return -1;
179
180                 /* Move the initrd into place */
181                 printf("Moving initrd: 0x%08x -> 0x%08x (0x%08x bytes)\n",
182                        startup_info.rd_addr, initrd_addr, startup_info.rd_len);
183
184                 memmove((void *)initrd_addr, (void *)startup_info.rd_addr,
185                         startup_info.rd_len);
186                 
187                 hdr->ramdisk_image = initrd_addr;
188         }
189
190         jump_to_kernel(startup_info.setup_addr >> 4,
191                        startup_info.cmdline_addr - startup_info.setup_addr);
192         return -1;              /* Shouldn't return... */
193 }