]> git.proxmox.com Git - grub2.git/blame - grub-core/loader/mips/linux.c
Rename Fuloong into Fuloong 2F. Add new ID for Fuloong2E.
[grub2.git] / grub-core / loader / mips / linux.c
CommitLineData
ea818634 1/* linux.c - boot Linux */
2/*
3 * GRUB -- GRand Unified Bootloader
ff989071 4 * Copyright (C) 2003,2004,2005,2007,2009,2010 Free Software Foundation, Inc.
ea818634 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>
25#include <grub/misc.h>
ea818634 26#include <grub/command.h>
27#include <grub/mips/relocator.h>
df3df23d 28#include <grub/memory.h>
47d5f3c1 29#include <grub/i18n.h>
dc16af03 30#include <grub/lib/cmdline.h>
0f355bc6 31
e745cf0c
VS
32GRUB_MOD_LICENSE ("GPLv3+");
33
0f355bc6 34/* For frequencies. */
d114e89c 35#include <grub/machine/time.h>
ea818634 36
54da1feb 37#ifdef GRUB_MACHINE_MIPS_LOONGSON
12ce749d 38#include <grub/pci.h>
74eea126
VS
39#include <grub/machine/kernel.h>
40
41const char loongson_machtypes[][60] =
42 {
43 [GRUB_ARCH_MACHINE_YEELOONG] = "machtype=lemote-yeeloong-2f-8.9inches",
14a2562c
VS
44 [GRUB_ARCH_MACHINE_FULOONG2F] = "machtype=lemote-fuloong-2f-unknown",
45 [GRUB_ARCH_MACHINE_FULOONG2E] = "machtype=lemote-fuloong-2e-unknown"
74eea126 46 };
b9b3839f
RM
47#endif
48
ea818634 49static grub_dl_t my_mod;
50
51static int loaded;
52
ea818634 53static grub_size_t linux_size;
54
7d8c9ec6 55static struct grub_relocator *relocator;
ea818634 56static grub_uint8_t *playground;
96c210da 57static grub_addr_t target_addr, entry_addr;
dc16af03
VS
58#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
59static char *params;
60#else
368a0c61 61static int linux_argc;
12ce749d 62static grub_off_t argv_off;
bee1aeb9 63#ifdef GRUB_MACHINE_MIPS_LOONGSON
12ce749d
VS
64static grub_off_t envp_off;
65#endif
96c210da 66static grub_off_t rd_addr_arg_off, rd_size_arg_off;
dc16af03 67#endif
96c210da 68static int initrd_loaded = 0;
ea818634 69
70static grub_err_t
71grub_linux_boot (void)
72{
ea818634 73 struct grub_relocator32_state state;
74
dc16af03
VS
75 grub_memset (&state, 0, sizeof (state));
76
ea818634 77 /* Boot the kernel. */
78 state.gpr[1] = entry_addr;
dc16af03
VS
79
80#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
81 {
82 grub_err_t err;
83 grub_relocator_chunk_t ch;
566a1917
VS
84 grub_uint32_t *memsize;
85 grub_uint32_t *magic;
86 char *str;
dc16af03
VS
87
88 err = grub_relocator_alloc_chunk_addr (relocator, &ch,
566a1917
VS
89 ((16 << 20) - 264),
90 grub_strlen (params) + 1 + 8);
dc16af03
VS
91 if (err)
92 return err;
566a1917
VS
93 memsize = get_virtual_current_address (ch);
94 magic = memsize + 1;
95 *memsize = grub_mmap_get_lower ();
96 *magic = 0x12345678;
97 str = (char *) (magic + 1);
98 grub_strcpy (str, params);
dc16af03
VS
99 }
100#endif
101
102#ifndef GRUB_MACHINE_MIPS_QEMU_MIPS
368a0c61 103 state.gpr[4] = linux_argc;
96c210da 104 state.gpr[5] = target_addr + argv_off;
bee1aeb9 105#ifdef GRUB_MACHINE_MIPS_LOONGSON
96c210da 106 state.gpr[6] = target_addr + envp_off;
12ce749d
VS
107#else
108 state.gpr[6] = 0;
109#endif
110 state.gpr[7] = 0;
dc16af03 111#endif
ea818634 112 state.jumpreg = 1;
7d8c9ec6 113 grub_relocator32_boot (relocator, state);
ea818634 114
115 return GRUB_ERR_NONE;
116}
117
118static grub_err_t
119grub_linux_unload (void)
120{
7d8c9ec6 121 grub_relocator_unload (relocator);
ea818634 122 grub_dl_unref (my_mod);
123
dc16af03
VS
124#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
125 grub_free (params);
126 params = 0;
127#endif
128
ea818634 129 loaded = 0;
130
7d8c9ec6 131 return GRUB_ERR_NONE;
ea818634 132}
133
134static grub_err_t
bb272a0f 135grub_linux_load32 (grub_elf_t elf, void **extra_mem, grub_size_t extra_size)
ea818634 136{
137 Elf32_Addr base;
bb272a0f 138 int extraoff;
7d8c9ec6 139 grub_err_t err;
ea818634 140
141 /* Linux's entry point incorrectly contains a virtual address. */
530a4814 142 entry_addr = elf->ehdr.ehdr32.e_entry;
ea818634 143
9c4cf53b 144 linux_size = grub_elf32_size (elf, &base, 0);
ea818634 145 if (linux_size == 0)
146 return grub_errno;
147 target_addr = base;
148 /* Pad it; the kernel scribbles over memory beyond its load address. */
149 linux_size += 0x100000;
bb272a0f 150 linux_size = ALIGN_UP (base + linux_size, 4) - base;
151 extraoff = linux_size;
152 linux_size += extra_size;
ea818634 153
7d8c9ec6
VS
154 relocator = grub_relocator_new ();
155 if (!relocator)
ea818634 156 return grub_errno;
157
530a4814
VS
158 {
159 grub_relocator_chunk_t ch;
160 err = grub_relocator_alloc_chunk_addr (relocator, &ch,
161 target_addr & 0x1fffffff,
162 linux_size);
163 if (err)
164 return err;
165 playground = get_virtual_current_address (ch);
166 }
7d8c9ec6 167
bb272a0f 168 *extra_mem = playground + extraoff;
ea818634 169
170 /* Now load the segments into the area we claimed. */
171 auto grub_err_t offset_phdr (Elf32_Phdr *phdr, grub_addr_t *addr, int *do_load);
172 grub_err_t offset_phdr (Elf32_Phdr *phdr, grub_addr_t *addr, int *do_load)
173 {
174 if (phdr->p_type != PT_LOAD)
175 {
176 *do_load = 0;
177 return 0;
178 }
179 *do_load = 1;
180
181 /* Linux's program headers incorrectly contain virtual addresses.
182 * Translate those to physical, and offset to the area we claimed. */
a45337b5 183 *addr = (grub_addr_t) (phdr->p_paddr - base + playground);
ea818634 184 return 0;
185 }
186 return grub_elf32_load (elf, offset_phdr, 0, 0);
187}
188
189static grub_err_t
bb272a0f 190grub_linux_load64 (grub_elf_t elf, void **extra_mem, grub_size_t extra_size)
ea818634 191{
192 Elf64_Addr base;
bb272a0f 193 int extraoff;
7d8c9ec6 194 grub_err_t err;
ea818634 195
196 /* Linux's entry point incorrectly contains a virtual address. */
530a4814 197 entry_addr = elf->ehdr.ehdr64.e_entry;
ea818634 198
9c4cf53b 199 linux_size = grub_elf64_size (elf, &base, 0);
ea818634 200 if (linux_size == 0)
201 return grub_errno;
202 target_addr = base;
203 /* Pad it; the kernel scribbles over memory beyond its load address. */
204 linux_size += 0x100000;
bb272a0f 205 linux_size = ALIGN_UP (base + linux_size, 4) - base;
206 extraoff = linux_size;
207 linux_size += extra_size;
ea818634 208
7d8c9ec6
VS
209 relocator = grub_relocator_new ();
210 if (!relocator)
ea818634 211 return grub_errno;
212
530a4814
VS
213 {
214 grub_relocator_chunk_t ch;
215 err = grub_relocator_alloc_chunk_addr (relocator, &ch,
216 target_addr & 0x1fffffff,
217 linux_size);
218 if (err)
219 return err;
220 playground = get_virtual_current_address (ch);
221 }
7d8c9ec6 222
bb272a0f 223 *extra_mem = playground + extraoff;
ea818634 224
225 /* Now load the segments into the area we claimed. */
226 auto grub_err_t offset_phdr (Elf64_Phdr *phdr, grub_addr_t *addr, int *do_load);
227 grub_err_t offset_phdr (Elf64_Phdr *phdr, grub_addr_t *addr, int *do_load)
228 {
229 if (phdr->p_type != PT_LOAD)
230 {
231 *do_load = 0;
232 return 0;
233 }
234 *do_load = 1;
235 /* Linux's program headers incorrectly contain virtual addresses.
236 * Translate those to physical, and offset to the area we claimed. */
a45337b5 237 *addr = (grub_addr_t) (phdr->p_paddr - base + playground);
ea818634 238 return 0;
239 }
240 return grub_elf64_load (elf, offset_phdr, 0, 0);
241}
242
243static grub_err_t
244grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
245 int argc, char *argv[])
246{
247 grub_elf_t elf = 0;
ea818634 248 int size;
3478d0aa 249 void *extra = NULL;
dc16af03
VS
250#ifndef GRUB_MACHINE_MIPS_QEMU_MIPS
251 int i;
12ce749d
VS
252 grub_uint32_t *linux_argv;
253 char *linux_args;
dc16af03 254#endif
bb272a0f 255 grub_err_t err;
bee1aeb9 256#ifdef GRUB_MACHINE_MIPS_LOONGSON
12ce749d
VS
257 char *linux_envs;
258 grub_uint32_t *linux_envp;
259#endif
ea818634 260
261 if (argc == 0)
bb272a0f 262 return grub_error (GRUB_ERR_BAD_ARGUMENT, "no kernel specified");
ea818634 263
264 elf = grub_elf_open (argv[0]);
265 if (! elf)
bb272a0f 266 return grub_errno;
ea818634 267
268 if (elf->ehdr.ehdr32.e_type != ET_EXEC)
269 {
bb272a0f 270 grub_elf_close (elf);
271 return grub_error (GRUB_ERR_UNKNOWN_OS,
67951a53 272 "this ELF file is not of the right type\n");
ea818634 273 }
274
275 /* Release the previously used memory. */
276 grub_loader_unset ();
bb272a0f 277 loaded = 0;
ea818634 278
dc16af03
VS
279#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
280 size = 0;
281#else
368a0c61
VS
282 /* For arguments. */
283 linux_argc = argc;
54da1feb 284#ifdef GRUB_MACHINE_MIPS_LOONGSON
b9b3839f
RM
285 linux_argc++;
286#endif
96c210da 287 /* Main arguments. */
52d67f54 288 size = (linux_argc) * sizeof (grub_uint32_t);
96c210da
VS
289 /* Initrd address and size. */
290 size += 2 * sizeof (grub_uint32_t);
291 /* NULL terminator. */
292 size += sizeof (grub_uint32_t);
293
52d67f54 294 /* First argument is always "a0". */
96c210da 295 size += ALIGN_UP (sizeof ("a0"), 4);
96c210da 296 /* Normal arguments. */
ea818634 297 for (i = 1; i < argc; i++)
bb272a0f 298 size += ALIGN_UP (grub_strlen (argv[i]) + 1, 4);
54da1feb 299#ifdef GRUB_MACHINE_MIPS_LOONGSON
74eea126 300 size += ALIGN_UP (sizeof (loongson_machtypes[0]), 4);
b9b3839f
RM
301#endif
302
96c210da
VS
303 /* rd arguments. */
304 size += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4);
305 size += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4);
ea818634 306
368a0c61 307 /* For the environment. */
0f355bc6
VS
308 size += sizeof (grub_uint32_t);
309 size += 4 * sizeof (grub_uint32_t);
310 size += ALIGN_UP (sizeof ("memsize=XXXXXXXXXXXXXXXXXXXX"), 4)
311 + ALIGN_UP (sizeof ("highmemsize=XXXXXXXXXXXXXXXXXXXX"), 4)
312 + ALIGN_UP (sizeof ("busclock=XXXXXXXXXX"), 4)
313 + ALIGN_UP (sizeof ("cpuclock=XXXXXXXXXX"), 4);
dc16af03 314#endif
368a0c61 315
ea818634 316 if (grub_elf_is_elf32 (elf))
bb272a0f 317 err = grub_linux_load32 (elf, &extra, size);
ea818634 318 else
319 if (grub_elf_is_elf64 (elf))
bb272a0f 320 err = grub_linux_load64 (elf, &extra, size);
ea818634 321 else
67951a53 322 err = grub_error (GRUB_ERR_BAD_FILE_TYPE, "unknown ELF class");
bb272a0f 323
324 grub_elf_close (elf);
325
326 if (err)
327 return err;
368a0c61 328
dc16af03
VS
329#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
330 /* Create kernel command line. */
331 size = grub_loader_cmdline_size(argc, argv);
332 params = grub_malloc (size + sizeof (LINUX_IMAGE));
333 if (! params)
334 {
335 grub_linux_unload ();
336 return grub_errno;
337 }
338
339 grub_memcpy (params, LINUX_IMAGE, sizeof (LINUX_IMAGE));
340 grub_create_loader_cmdline (argc, argv, params + sizeof (LINUX_IMAGE) - 1,
341 size);
342#else
bb272a0f 343 linux_argv = extra;
96c210da 344 argv_off = (grub_uint8_t *) linux_argv - (grub_uint8_t *) playground;
52d67f54 345 extra = linux_argv + (linux_argc + 1 + 2);
bb272a0f 346 linux_args = extra;
96c210da
VS
347
348 grub_memcpy (linux_args, "a0", sizeof ("a0"));
7c8f6c18 349 *linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground
350 + target_addr;
bb272a0f 351 linux_argv++;
96c210da
VS
352 linux_args += ALIGN_UP (sizeof ("a0"), 4);
353
54da1feb 354#ifdef GRUB_MACHINE_MIPS_LOONGSON
74eea126
VS
355 {
356 unsigned mtype = grub_arch_machine;
357 if (mtype >= ARRAY_SIZE (loongson_machtypes))
358 mtype = 0;
359 /* In Loongson platform, it is the responsibility of the bootloader/firmware
360 to supply the OS kernel with machine type information. */
361 grub_memcpy (linux_args, loongson_machtypes[mtype],
362 sizeof (loongson_machtypes[mtype]));
363 *linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground
364 + target_addr;
365 linux_argv++;
366 linux_args += ALIGN_UP (sizeof (loongson_machtypes[mtype]), 4);
367 }
b9b3839f
RM
368#endif
369
bb272a0f 370 for (i = 1; i < argc; i++)
ea818634 371 {
368a0c61 372 grub_memcpy (linux_args, argv[i], grub_strlen (argv[i]) + 1);
7c8f6c18 373 *linux_argv = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground
374 + target_addr;
bb272a0f 375 linux_argv++;
368a0c61 376 linux_args += ALIGN_UP (grub_strlen (argv[i]) + 1, 4);
ea818634 377 }
96c210da
VS
378
379 /* Reserve space for rd arguments. */
380 rd_addr_arg_off = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground;
381 linux_args += ALIGN_UP (sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), 4);
382 *linux_argv = 0;
383 linux_argv++;
384
385 rd_size_arg_off = (grub_uint8_t *) linux_args - (grub_uint8_t *) playground;
386 linux_args += ALIGN_UP (sizeof ("rd_size=0xXXXXXXXXXXXXXXXX"), 4);
387 *linux_argv = 0;
388 linux_argv++;
389
368a0c61 390 *linux_argv = 0;
96c210da 391
7c8f6c18 392 extra = linux_args;
368a0c61 393
bee1aeb9 394#ifdef GRUB_MACHINE_MIPS_LOONGSON
7c8f6c18 395 linux_envp = extra;
96c210da 396 envp_off = (grub_uint8_t *) linux_envp - (grub_uint8_t *) playground;
0f355bc6 397 linux_envs = (char *) (linux_envp + 5);
adb893f2 398 grub_snprintf (linux_envs, sizeof ("memsize=XXXXXXXXXXXXXXXXXXXX"),
2d49abe9
VS
399 "memsize=%lld",
400 (unsigned long long) grub_mmap_get_lower () >> 20);
0f355bc6
VS
401 linux_envp[0] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground
402 + target_addr;
403 linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4);
2d49abe9
VS
404 grub_snprintf (linux_envs, sizeof ("highmemsize=XXXXXXXXXXXXXXXXXXXX"),
405 "highmemsize=%lld",
406 (unsigned long long) grub_mmap_get_upper () >> 20);
0f355bc6
VS
407 linux_envp[1] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground
408 + target_addr;
409 linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4);
410
adb893f2 411 grub_snprintf (linux_envs, sizeof ("busclock=XXXXXXXXXX"),
2d49abe9 412 "busclock=%d", grub_arch_busclock);
0f355bc6
VS
413 linux_envp[2] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground
414 + target_addr;
415 linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4);
adb893f2 416 grub_snprintf (linux_envs, sizeof ("cpuclock=XXXXXXXXXX"),
2d49abe9 417 "cpuclock=%d", grub_arch_cpuclock);
0f355bc6
VS
418 linux_envp[3] = (grub_uint8_t *) linux_envs - (grub_uint8_t *) playground
419 + target_addr;
420 linux_envs += ALIGN_UP (grub_strlen (linux_envs) + 1, 4);
421
0f355bc6 422 linux_envp[4] = 0;
dc16af03 423#endif
12ce749d 424#endif
7c8f6c18 425
bb272a0f 426 grub_loader_set (grub_linux_boot, grub_linux_unload, 1);
96c210da 427 initrd_loaded = 0;
bb272a0f 428 loaded = 1;
429 grub_dl_ref (my_mod);
430
431 return GRUB_ERR_NONE;
ea818634 432}
433
434static grub_err_t
435grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
436 int argc, char *argv[])
437{
438 grub_file_t file = 0;
439 grub_ssize_t size;
7d8c9ec6
VS
440 void *initrd_src;
441 grub_addr_t initrd_dest;
442 grub_err_t err;
ea818634 443
444 if (argc == 0)
67951a53 445 return grub_error (GRUB_ERR_BAD_ARGUMENT, "no initrd specified");
ea818634 446
447 if (!loaded)
67951a53 448 return grub_error (GRUB_ERR_BAD_ARGUMENT, "you need to load Linux first.");
ea818634 449
96c210da 450 if (initrd_loaded)
67951a53 451 return grub_error (GRUB_ERR_BAD_ARGUMENT, "only one initrd can be loaded.");
96c210da 452
fc2ef117 453 grub_file_filter_disable_compression ();
ea818634 454 file = grub_file_open (argv[0]);
455 if (! file)
456 return grub_errno;
457
458 size = grub_file_size (file);
459
530a4814
VS
460 {
461 grub_relocator_chunk_t ch;
462
463 err = grub_relocator_alloc_chunk_align (relocator, &ch,
b01abe3e
VS
464 (target_addr & 0x1fffffff)
465 + linux_size + 0x10000,
466 (0x10000000 - size),
530a4814
VS
467 size, 0x10000,
468 GRUB_RELOCATOR_PREFERENCE_NONE);
469
470 if (err)
471 {
472 grub_file_close (file);
473 return err;
474 }
475 initrd_src = get_virtual_current_address (ch);
476 initrd_dest = get_physical_target_address (ch) | 0x80000000;
477 }
ea818634 478
7d8c9ec6 479 if (grub_file_read (file, initrd_src, size) != size)
ea818634 480 {
67951a53 481 grub_error (GRUB_ERR_FILE_READ_ERROR, "couldn't read file");
ea818634 482 grub_file_close (file);
483
484 return grub_errno;
485 }
486
dc16af03
VS
487#ifdef GRUB_MACHINE_MIPS_QEMU_MIPS
488 {
489 char *tmp;
490 tmp = grub_xasprintf ("%s rd_start=0x%" PRIxGRUB_ADDR
491 " rd_size=0x%" PRIxGRUB_ADDR, params,
492 initrd_dest, size);
493 if (!tmp)
494 {
495 grub_file_close (file);
496 return grub_errno;
497 }
498 grub_free (params);
499 params = tmp;
500 }
501#else
2d49abe9 502 grub_snprintf ((char *) playground + rd_addr_arg_off,
adb893f2 503 sizeof ("rd_start=0xXXXXXXXXXXXXXXXX"), "rd_start=0x%llx",
7d8c9ec6 504 (unsigned long long) initrd_dest);
96c210da
VS
505 ((grub_uint32_t *) (playground + argv_off))[linux_argc]
506 = target_addr + rd_addr_arg_off;
507 linux_argc++;
508
2d49abe9 509 grub_snprintf ((char *) playground + rd_size_arg_off,
adb893f2 510 sizeof ("rd_size=0xXXXXXXXXXXXXXXXXX"), "rd_size=0x%llx",
96c210da
VS
511 (unsigned long long) size);
512 ((grub_uint32_t *) (playground + argv_off))[linux_argc]
513 = target_addr + rd_size_arg_off;
514 linux_argc++;
dc16af03 515#endif
96c210da
VS
516
517 initrd_loaded = 1;
ea818634 518
519 grub_file_close (file);
520
521 return GRUB_ERR_NONE;
522}
523
524static grub_command_t cmd_linux, cmd_initrd;
525\f
526GRUB_MOD_INIT(linux)
527{
528 cmd_linux = grub_register_command ("linux", grub_cmd_linux,
ff989071 529 0, N_("Load Linux."));
ea818634 530 cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
ff989071 531 0, N_("Load initrd."));
ea818634 532 my_mod = mod;
533}
534
535GRUB_MOD_FINI(linux)
536{
537 grub_unregister_command (cmd_linux);
538 grub_unregister_command (cmd_initrd);
539}