]>
Commit | Line | Data |
---|---|---|
d8e1836c | 1 | /* linux.c - boot Linux */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
4 | * Copyright (C) 2003, 2004, 2005, 2007, 2009 Free Software Foundation, Inc. | |
5 | * | |
6 | * GRUB is free software: you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 3 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * GRUB is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
20 | #include <grub/elf.h> | |
21 | #include <grub/elfload.h> | |
22 | #include <grub/loader.h> | |
23 | #include <grub/dl.h> | |
24 | #include <grub/mm.h> | |
d8e1836c | 25 | #include <grub/misc.h> |
26 | #include <grub/ieee1275/ieee1275.h> | |
0552ff9f | 27 | #include <grub/command.h> |
809bbfeb | 28 | #include <grub/i18n.h> |
df3df23d | 29 | #include <grub/memory.h> |
25953e10 | 30 | #include <grub/lib/cmdline.h> |
92750e4c | 31 | #include <grub/linux.h> |
d8e1836c | 32 | |
e745cf0c VS |
33 | GRUB_MOD_LICENSE ("GPLv3+"); |
34 | ||
d8e1836c | 35 | static grub_dl_t my_mod; |
36 | ||
37 | static int loaded; | |
38 | ||
39 | /* /virtual-memory/translations property layout */ | |
40 | struct grub_ieee1275_translation { | |
41 | grub_uint64_t vaddr; | |
42 | grub_uint64_t size; | |
43 | grub_uint64_t data; | |
44 | }; | |
45 | ||
46 | static struct grub_ieee1275_translation *of_trans; | |
47 | static int of_num_trans; | |
48 | ||
49 | static grub_addr_t phys_base; | |
50 | static grub_addr_t grub_phys_start; | |
51 | static grub_addr_t grub_phys_end; | |
52 | ||
53 | static grub_addr_t initrd_addr; | |
54 | static grub_addr_t initrd_paddr; | |
55 | static grub_size_t initrd_size; | |
56 | ||
57 | static Elf64_Addr linux_entry; | |
58 | static grub_addr_t linux_addr; | |
59 | static grub_addr_t linux_paddr; | |
60 | static grub_size_t linux_size; | |
61 | ||
62 | static char *linux_args; | |
63 | ||
d8e1836c | 64 | struct linux_bootstr_info { |
65 | int len, valid; | |
66 | char buf[]; | |
67 | }; | |
68 | ||
69 | struct linux_hdrs { | |
70 | /* All HdrS versions support these fields. */ | |
71 | unsigned int start_insns[2]; | |
72 | char magic[4]; /* "HdrS" */ | |
73 | unsigned int linux_kernel_version; /* LINUX_VERSION_CODE */ | |
74 | unsigned short hdrs_version; | |
75 | unsigned short root_flags; | |
76 | unsigned short root_dev; | |
77 | unsigned short ram_flags; | |
78 | unsigned int __deprecated_ramdisk_image; | |
79 | unsigned int ramdisk_size; | |
80 | ||
81 | /* HdrS versions 0x0201 and higher only */ | |
82 | char *reboot_command; | |
83 | ||
84 | /* HdrS versions 0x0202 and higher only */ | |
85 | struct linux_bootstr_info *bootstr_info; | |
86 | ||
87 | /* HdrS versions 0x0301 and higher only */ | |
88 | unsigned long ramdisk_image; | |
89 | }; | |
90 | ||
91 | static grub_err_t | |
92 | grub_linux_boot (void) | |
93 | { | |
94 | struct linux_bootstr_info *bp; | |
d8e1836c | 95 | struct linux_hdrs *hp; |
96 | grub_addr_t addr; | |
97 | ||
98 | hp = (struct linux_hdrs *) linux_addr; | |
99 | ||
4241d2b1 | 100 | /* Any pointer we dereference in the kernel image must be relocated |
d8e1836c | 101 | to where we actually loaded the kernel. */ |
102 | addr = (grub_addr_t) hp->bootstr_info; | |
103 | addr += (linux_addr - linux_entry); | |
104 | bp = (struct linux_bootstr_info *) addr; | |
105 | ||
106 | /* Set the command line arguments, unless the kernel has been | |
107 | built with a fixed CONFIG_CMDLINE. */ | |
108 | if (!bp->valid) | |
109 | { | |
110 | int len = grub_strlen (linux_args) + 1; | |
111 | if (bp->len < len) | |
112 | len = bp->len; | |
113 | memcpy(bp->buf, linux_args, len); | |
114 | bp->buf[len-1] = '\0'; | |
115 | bp->valid = 1; | |
116 | } | |
117 | ||
118 | if (initrd_addr) | |
119 | { | |
120 | /* The kernel expects the physical address, adjusted relative | |
121 | to the lowest address advertised in "/memory"'s available | |
122 | property. | |
123 | ||
124 | The history of this is that back when the kernel only supported | |
125 | specifying a 32-bit ramdisk address, this was the way to still | |
126 | be able to specify the ramdisk physical address even if memory | |
127 | started at some place above 4GB. | |
128 | ||
129 | The magic 0x400000 is KERNBASE, I have no idea why SILO adds | |
130 | that term into the address, but it does and thus we have to do | |
131 | it too as this is what the kernel expects. */ | |
132 | hp->ramdisk_image = initrd_paddr - phys_base + 0x400000; | |
133 | hp->ramdisk_size = initrd_size; | |
134 | } | |
135 | ||
136 | grub_dprintf ("loader", "Entry point: 0x%lx\n", linux_addr); | |
137 | grub_dprintf ("loader", "Initrd at: 0x%lx, size 0x%lx\n", initrd_addr, | |
138 | initrd_size); | |
139 | grub_dprintf ("loader", "Boot arguments: %s\n", linux_args); | |
140 | grub_dprintf ("loader", "Jumping to Linux...\n"); | |
141 | ||
142 | /* Boot the kernel. */ | |
320dd174 VS |
143 | asm volatile ("sethi %hi(grub_ieee1275_entry_fn), %o1\n" |
144 | "ldx [%o1 + %lo(grub_ieee1275_entry_fn)], %o4\n" | |
145 | "sethi %hi(grub_ieee1275_original_stack), %o1\n" | |
146 | "ldx [%o1 + %lo(grub_ieee1275_original_stack)], %o6\n" | |
147 | "sethi %hi(linux_addr), %o1\n" | |
148 | "ldx [%o1 + %lo(linux_addr)], %o5\n" | |
149 | "mov %g0, %o0\n" | |
150 | "mov %g0, %o2\n" | |
151 | "mov %g0, %o3\n" | |
152 | "jmp %o5\n" | |
153 | "mov %g0, %o1\n"); | |
d8e1836c | 154 | |
155 | return GRUB_ERR_NONE; | |
156 | } | |
157 | ||
158 | static grub_err_t | |
159 | grub_linux_release_mem (void) | |
160 | { | |
161 | grub_free (linux_args); | |
162 | linux_args = 0; | |
163 | linux_addr = 0; | |
164 | initrd_addr = 0; | |
165 | ||
166 | return GRUB_ERR_NONE; | |
167 | } | |
168 | ||
169 | static grub_err_t | |
170 | grub_linux_unload (void) | |
171 | { | |
172 | grub_err_t err; | |
173 | ||
174 | err = grub_linux_release_mem (); | |
175 | grub_dl_unref (my_mod); | |
176 | ||
177 | loaded = 0; | |
178 | ||
179 | return err; | |
180 | } | |
181 | ||
182 | #define FOUR_MB (4 * 1024 * 1024) | |
183 | ||
ed12a003 CW |
184 | /* Context for alloc_phys. */ |
185 | struct alloc_phys_ctx | |
186 | { | |
187 | grub_addr_t size; | |
188 | grub_addr_t ret; | |
189 | }; | |
190 | ||
d0d4b8a0 CW |
191 | /* Helper for alloc_phys. */ |
192 | static int | |
193 | alloc_phys_choose (grub_uint64_t addr, grub_uint64_t len, | |
194 | grub_memory_type_t type, void *data) | |
d8e1836c | 195 | { |
ed12a003 | 196 | struct alloc_phys_ctx *ctx = data; |
d0d4b8a0 | 197 | grub_addr_t end = addr + len; |
d8e1836c | 198 | |
d0d4b8a0 CW |
199 | if (type != 1) |
200 | return 0; | |
d8e1836c | 201 | |
d0d4b8a0 | 202 | addr = ALIGN_UP (addr, FOUR_MB); |
ed12a003 | 203 | if (addr + ctx->size >= end) |
d0d4b8a0 | 204 | return 0; |
d8e1836c | 205 | |
d0d4b8a0 CW |
206 | if (addr >= grub_phys_start && addr < grub_phys_end) |
207 | { | |
208 | addr = ALIGN_UP (grub_phys_end, FOUR_MB); | |
ed12a003 | 209 | if (addr + ctx->size >= end) |
d0d4b8a0 CW |
210 | return 0; |
211 | } | |
ed12a003 CW |
212 | if ((addr + ctx->size) >= grub_phys_start |
213 | && (addr + ctx->size) < grub_phys_end) | |
d0d4b8a0 CW |
214 | { |
215 | addr = ALIGN_UP (grub_phys_end, FOUR_MB); | |
ed12a003 | 216 | if (addr + ctx->size >= end) |
d0d4b8a0 CW |
217 | return 0; |
218 | } | |
d8e1836c | 219 | |
d0d4b8a0 CW |
220 | if (loaded) |
221 | { | |
222 | grub_addr_t linux_end = ALIGN_UP (linux_paddr + linux_size, FOUR_MB); | |
223 | ||
224 | if (addr >= linux_paddr && addr < linux_end) | |
225 | { | |
226 | addr = linux_end; | |
ed12a003 | 227 | if (addr + ctx->size >= end) |
d0d4b8a0 CW |
228 | return 0; |
229 | } | |
ed12a003 CW |
230 | if ((addr + ctx->size) >= linux_paddr |
231 | && (addr + ctx->size) < linux_end) | |
d0d4b8a0 CW |
232 | { |
233 | addr = linux_end; | |
ed12a003 | 234 | if (addr + ctx->size >= end) |
d0d4b8a0 CW |
235 | return 0; |
236 | } | |
237 | } | |
238 | ||
ed12a003 | 239 | ctx->ret = addr; |
d0d4b8a0 CW |
240 | return 1; |
241 | } | |
242 | ||
243 | static grub_addr_t | |
244 | alloc_phys (grub_addr_t size) | |
245 | { | |
ed12a003 CW |
246 | struct alloc_phys_ctx ctx = { |
247 | .size = size, | |
248 | .ret = (grub_addr_t) -1 | |
249 | }; | |
d0d4b8a0 | 250 | |
ed12a003 | 251 | grub_machine_mmap_iterate (alloc_phys_choose, &ctx); |
d8e1836c | 252 | |
ed12a003 | 253 | return ctx.ret; |
d8e1836c | 254 | } |
255 | ||
256 | static grub_err_t | |
9c4b5c13 | 257 | grub_linux_load64 (grub_elf_t elf, const char *filename) |
d8e1836c | 258 | { |
259 | grub_addr_t off, paddr, base; | |
260 | int ret; | |
261 | ||
262 | linux_entry = elf->ehdr.ehdr64.e_entry; | |
263 | linux_addr = 0x40004000; | |
264 | off = 0x4000; | |
73bf57e2 | 265 | linux_size = grub_elf64_size (elf, 0, 0); |
d8e1836c | 266 | if (linux_size == 0) |
267 | return grub_errno; | |
268 | ||
269 | grub_dprintf ("loader", "Attempting to claim at 0x%lx, size 0x%lx.\n", | |
270 | linux_addr, linux_size); | |
271 | ||
272 | paddr = alloc_phys (linux_size + off); | |
273 | if (paddr == (grub_addr_t) -1) | |
274 | return grub_error (GRUB_ERR_OUT_OF_MEMORY, | |
61ba42be | 275 | "couldn't allocate physical memory"); |
904935c3 VS |
276 | ret = grub_ieee1275_map (paddr, linux_addr - off, |
277 | linux_size + off, IEEE1275_MAP_DEFAULT); | |
d8e1836c | 278 | if (ret) |
279 | return grub_error (GRUB_ERR_OUT_OF_MEMORY, | |
61ba42be | 280 | "couldn't map physical memory"); |
d8e1836c | 281 | |
809bbfeb | 282 | grub_dprintf ("loader", "Loading Linux at vaddr 0x%lx, paddr 0x%lx, size 0x%lx\n", |
d8e1836c | 283 | linux_addr, paddr, linux_size); |
284 | ||
285 | linux_paddr = paddr; | |
286 | ||
287 | base = linux_entry - off; | |
288 | ||
289 | /* Now load the segments into the area we claimed. */ | |
73bf57e2 | 290 | return grub_elf64_load (elf, filename, (void *) (linux_addr - off - base), GRUB_ELF_LOAD_FLAGS_NONE, 0, 0); |
d8e1836c | 291 | } |
292 | ||
0552ff9f | 293 | static grub_err_t |
294 | grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), | |
295 | int argc, char *argv[]) | |
d8e1836c | 296 | { |
297 | grub_file_t file = 0; | |
298 | grub_elf_t elf = 0; | |
d8e1836c | 299 | int size; |
d8e1836c | 300 | |
301 | grub_dl_ref (my_mod); | |
302 | ||
303 | if (argc == 0) | |
304 | { | |
9c4b5c13 | 305 | grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); |
d8e1836c | 306 | goto out; |
307 | } | |
308 | ||
fc2ef117 | 309 | file = grub_file_open (argv[0]); |
d8e1836c | 310 | if (!file) |
311 | goto out; | |
312 | ||
9c4b5c13 | 313 | elf = grub_elf_file (file, argv[0]); |
d8e1836c | 314 | if (! elf) |
315 | goto out; | |
316 | ||
317 | if (elf->ehdr.ehdr32.e_type != ET_EXEC) | |
318 | { | |
319 | grub_error (GRUB_ERR_UNKNOWN_OS, | |
9c4b5c13 | 320 | N_("this ELF file is not of the right type")); |
d8e1836c | 321 | goto out; |
322 | } | |
323 | ||
324 | /* Release the previously used memory. */ | |
325 | grub_loader_unset (); | |
326 | ||
327 | if (grub_elf_is_elf64 (elf)) | |
9c4b5c13 | 328 | grub_linux_load64 (elf, argv[0]); |
d8e1836c | 329 | else |
330 | { | |
67093bc0 | 331 | grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid arch-dependent ELF magic")); |
d8e1836c | 332 | goto out; |
333 | } | |
334 | ||
25953e10 | 335 | size = grub_loader_cmdline_size(argc, argv); |
d8e1836c | 336 | |
25953e10 | 337 | linux_args = grub_malloc (size + sizeof (LINUX_IMAGE)); |
d8e1836c | 338 | if (! linux_args) |
339 | goto out; | |
340 | ||
25953e10 SJ |
341 | /* Create kernel command line. */ |
342 | grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE)); | |
343 | grub_create_loader_cmdline (argc, argv, linux_args + sizeof (LINUX_IMAGE) - 1, | |
344 | size); | |
d8e1836c | 345 | |
346 | out: | |
347 | if (elf) | |
348 | grub_elf_close (elf); | |
349 | else if (file) | |
350 | grub_file_close (file); | |
351 | ||
352 | if (grub_errno != GRUB_ERR_NONE) | |
353 | { | |
354 | grub_linux_release_mem (); | |
355 | grub_dl_unref (my_mod); | |
356 | loaded = 0; | |
357 | } | |
358 | else | |
359 | { | |
360 | grub_loader_set (grub_linux_boot, grub_linux_unload, 1); | |
361 | initrd_addr = 0; | |
362 | loaded = 1; | |
363 | } | |
0552ff9f | 364 | |
365 | return grub_errno; | |
d8e1836c | 366 | } |
367 | ||
0552ff9f | 368 | static grub_err_t |
369 | grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), | |
370 | int argc, char *argv[]) | |
d8e1836c | 371 | { |
3c76ea0c | 372 | grub_size_t size = 0; |
d8e1836c | 373 | grub_addr_t paddr; |
374 | grub_addr_t addr; | |
375 | int ret; | |
9a67e1ac | 376 | struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 }; |
d8e1836c | 377 | |
378 | if (argc == 0) | |
379 | { | |
9c4b5c13 | 380 | grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); |
d8e1836c | 381 | goto fail; |
382 | } | |
383 | ||
384 | if (!loaded) | |
385 | { | |
9c4b5c13 | 386 | grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first")); |
d8e1836c | 387 | goto fail; |
388 | } | |
389 | ||
92750e4c | 390 | if (grub_initrd_init (argc, argv, &initrd_ctx)) |
d8e1836c | 391 | goto fail; |
392 | ||
92750e4c | 393 | size = grub_get_initrd_size (&initrd_ctx); |
3c76ea0c | 394 | |
d8e1836c | 395 | addr = 0x60000000; |
d8e1836c | 396 | |
397 | paddr = alloc_phys (size); | |
398 | if (paddr == (grub_addr_t) -1) | |
399 | { | |
400 | grub_error (GRUB_ERR_OUT_OF_MEMORY, | |
61ba42be | 401 | "couldn't allocate physical memory"); |
d8e1836c | 402 | goto fail; |
403 | } | |
904935c3 | 404 | ret = grub_ieee1275_map (paddr, addr, size, IEEE1275_MAP_DEFAULT); |
d8e1836c | 405 | if (ret) |
406 | { | |
407 | grub_error (GRUB_ERR_OUT_OF_MEMORY, | |
61ba42be | 408 | "couldn't map physical memory"); |
d8e1836c | 409 | goto fail; |
410 | } | |
411 | ||
412 | grub_dprintf ("loader", "Loading initrd at vaddr 0x%lx, paddr 0x%lx, size 0x%lx\n", | |
413 | addr, paddr, size); | |
414 | ||
92750e4c VS |
415 | if (grub_initrd_load (&initrd_ctx, argv, (void *) addr)) |
416 | goto fail; | |
d8e1836c | 417 | |
418 | initrd_addr = addr; | |
419 | initrd_paddr = paddr; | |
420 | initrd_size = size; | |
421 | ||
422 | fail: | |
92750e4c | 423 | grub_initrd_close (&initrd_ctx); |
0552ff9f | 424 | |
425 | return grub_errno; | |
d8e1836c | 426 | } |
427 | ||
d0d4b8a0 CW |
428 | /* Helper for determine_phys_base. */ |
429 | static int | |
430 | get_physbase (grub_uint64_t addr, grub_uint64_t len __attribute__ ((unused)), | |
e7b66a28 | 431 | grub_memory_type_t type, void *data __attribute__ ((unused))) |
d8e1836c | 432 | { |
d0d4b8a0 | 433 | if (type != 1) |
d8e1836c | 434 | return 0; |
d0d4b8a0 CW |
435 | if (addr < phys_base) |
436 | phys_base = addr; | |
437 | return 0; | |
438 | } | |
d8e1836c | 439 | |
d0d4b8a0 CW |
440 | static void |
441 | determine_phys_base (void) | |
442 | { | |
d8e1836c | 443 | phys_base = ~(grub_uint64_t) 0; |
d0d4b8a0 | 444 | grub_machine_mmap_iterate (get_physbase, NULL); |
d8e1836c | 445 | } |
446 | ||
447 | static void | |
448 | fetch_translations (void) | |
449 | { | |
450 | grub_ieee1275_phandle_t node; | |
451 | grub_ssize_t actual; | |
452 | int i; | |
453 | ||
454 | if (grub_ieee1275_finddevice ("/virtual-memory", &node)) | |
455 | { | |
456 | grub_printf ("Cannot find /virtual-memory node.\n"); | |
457 | return; | |
458 | } | |
459 | ||
460 | if (grub_ieee1275_get_property_length (node, "translations", &actual)) | |
461 | { | |
462 | grub_printf ("Cannot find /virtual-memory/translations size.\n"); | |
463 | return; | |
464 | } | |
465 | ||
466 | of_trans = grub_malloc (actual); | |
467 | if (!of_trans) | |
468 | { | |
469 | grub_printf ("Cannot allocate translations buffer.\n"); | |
470 | return; | |
471 | } | |
472 | ||
473 | if (grub_ieee1275_get_property (node, "translations", of_trans, actual, &actual)) | |
474 | { | |
4241d2b1 | 475 | grub_printf ("Cannot fetch /virtual-memory/translations property.\n"); |
d8e1836c | 476 | return; |
477 | } | |
478 | ||
479 | of_num_trans = actual / sizeof(struct grub_ieee1275_translation); | |
480 | ||
481 | for (i = 0; i < of_num_trans; i++) | |
482 | { | |
483 | struct grub_ieee1275_translation *p = &of_trans[i]; | |
484 | ||
485 | if (p->vaddr == 0x2000) | |
486 | { | |
487 | grub_addr_t phys, tte = p->data; | |
488 | ||
489 | phys = tte & ~(0xff00000000001fffULL); | |
490 | ||
491 | grub_phys_start = phys; | |
492 | grub_phys_end = grub_phys_start + p->size; | |
493 | grub_dprintf ("loader", "Grub lives at phys_start[%lx] phys_end[%lx]\n", | |
494 | (unsigned long) grub_phys_start, | |
495 | (unsigned long) grub_phys_end); | |
496 | break; | |
497 | } | |
498 | } | |
499 | } | |
500 | ||
501 | \f | |
0552ff9f | 502 | static grub_command_t cmd_linux, cmd_initrd; |
503 | ||
d8e1836c | 504 | GRUB_MOD_INIT(linux) |
505 | { | |
506 | determine_phys_base (); | |
507 | fetch_translations (); | |
508 | ||
0552ff9f | 509 | cmd_linux = grub_register_command ("linux", grub_cmd_linux, |
809bbfeb | 510 | 0, N_("Load Linux.")); |
0552ff9f | 511 | cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd, |
809bbfeb | 512 | 0, N_("Load initrd.")); |
d8e1836c | 513 | my_mod = mod; |
514 | } | |
515 | ||
516 | GRUB_MOD_FINI(linux) | |
517 | { | |
0552ff9f | 518 | grub_unregister_command (cmd_linux); |
519 | grub_unregister_command (cmd_initrd); | |
d8e1836c | 520 | } |