123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522 |
- /* linux.c - boot Linux */
- /*
- * GRUB -- GRand Unified Bootloader
- * Copyright (C) 2003, 2004, 2005, 2007, 2009 Free Software Foundation, Inc.
- *
- * GRUB is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * GRUB is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
- */
- #include <grub/elf.h>
- #include <grub/elfload.h>
- #include <grub/loader.h>
- #include <grub/dl.h>
- #include <grub/mm.h>
- #include <grub/misc.h>
- #include <grub/ieee1275/ieee1275.h>
- #include <grub/command.h>
- #include <grub/i18n.h>
- #include <grub/memory.h>
- #include <grub/lib/cmdline.h>
- #include <grub/linux.h>
- GRUB_MOD_LICENSE ("GPLv3+");
- static grub_dl_t my_mod;
- static int loaded;
- /* /virtual-memory/translations property layout */
- struct grub_ieee1275_translation {
- grub_uint64_t vaddr;
- grub_uint64_t size;
- grub_uint64_t data;
- };
- static struct grub_ieee1275_translation *of_trans;
- static int of_num_trans;
- static grub_addr_t phys_base;
- static grub_addr_t grub_phys_start;
- static grub_addr_t grub_phys_end;
- static grub_addr_t initrd_addr;
- static grub_addr_t initrd_paddr;
- static grub_size_t initrd_size;
- static Elf64_Addr linux_entry;
- static grub_addr_t linux_addr;
- static grub_addr_t linux_paddr;
- static grub_size_t linux_size;
- static char *linux_args;
- struct linux_bootstr_info {
- int len, valid;
- char buf[];
- };
- struct linux_hdrs {
- /* All HdrS versions support these fields. */
- unsigned int start_insns[2];
- char magic[4]; /* "HdrS" */
- unsigned int linux_kernel_version; /* LINUX_VERSION_CODE */
- unsigned short hdrs_version;
- unsigned short root_flags;
- unsigned short root_dev;
- unsigned short ram_flags;
- unsigned int __deprecated_ramdisk_image;
- unsigned int ramdisk_size;
- /* HdrS versions 0x0201 and higher only */
- char *reboot_command;
- /* HdrS versions 0x0202 and higher only */
- struct linux_bootstr_info *bootstr_info;
- /* HdrS versions 0x0301 and higher only */
- unsigned long ramdisk_image;
- };
- static grub_err_t
- grub_linux_boot (void)
- {
- struct linux_bootstr_info *bp;
- struct linux_hdrs *hp;
- grub_addr_t addr;
- hp = (struct linux_hdrs *) linux_addr;
- /* Any pointer we dereference in the kernel image must be relocated
- to where we actually loaded the kernel. */
- addr = (grub_addr_t) hp->bootstr_info;
- addr += (linux_addr - linux_entry);
- bp = (struct linux_bootstr_info *) addr;
- /* Set the command line arguments, unless the kernel has been
- built with a fixed CONFIG_CMDLINE. */
- if (!bp->valid)
- {
- int len = grub_strlen (linux_args) + 1;
- if (bp->len < len)
- len = bp->len;
- grub_memcpy(bp->buf, linux_args, len);
- bp->buf[len-1] = '\0';
- bp->valid = 1;
- }
- if (initrd_addr)
- {
- /* The kernel expects the physical address, adjusted relative
- to the lowest address advertised in "/memory"'s available
- property.
- The history of this is that back when the kernel only supported
- specifying a 32-bit ramdisk address, this was the way to still
- be able to specify the ramdisk physical address even if memory
- started at some place above 4GB.
- The magic 0x400000 is KERNBASE, I have no idea why SILO adds
- that term into the address, but it does and thus we have to do
- it too as this is what the kernel expects. */
- hp->ramdisk_image = initrd_paddr - phys_base + 0x400000;
- hp->ramdisk_size = initrd_size;
- }
- grub_dprintf ("loader", "Entry point: 0x%lx\n", linux_addr);
- grub_dprintf ("loader", "Initrd at: 0x%lx, size 0x%lx\n", initrd_addr,
- initrd_size);
- grub_dprintf ("loader", "Boot arguments: %s\n", linux_args);
- grub_dprintf ("loader", "Jumping to Linux...\n");
- /* Boot the kernel. */
- asm volatile ("ldx %0, %%o4\n"
- "ldx %1, %%o6\n"
- "ldx %2, %%o5\n"
- "mov %%g0, %%o0\n"
- "mov %%g0, %%o2\n"
- "mov %%g0, %%o3\n"
- "jmp %%o5\n"
- "mov %%g0, %%o1\n": :
- "m"(grub_ieee1275_entry_fn),
- "m"(grub_ieee1275_original_stack),
- "m"(linux_addr));
- return GRUB_ERR_NONE;
- }
- static grub_err_t
- grub_linux_release_mem (void)
- {
- grub_free (linux_args);
- linux_args = 0;
- linux_addr = 0;
- initrd_addr = 0;
- return GRUB_ERR_NONE;
- }
- static grub_err_t
- grub_linux_unload (void)
- {
- grub_err_t err;
- err = grub_linux_release_mem ();
- grub_dl_unref (my_mod);
- loaded = 0;
- return err;
- }
- #define FOUR_MB (4 * 1024 * 1024)
- /* Context for alloc_phys. */
- struct alloc_phys_ctx
- {
- grub_addr_t size;
- grub_addr_t ret;
- };
- /* Helper for alloc_phys. */
- static int
- alloc_phys_choose (grub_uint64_t addr, grub_uint64_t len,
- grub_memory_type_t type, void *data)
- {
- struct alloc_phys_ctx *ctx = data;
- grub_addr_t end = addr + len;
- if (type != GRUB_MEMORY_AVAILABLE)
- return 0;
- addr = ALIGN_UP (addr, FOUR_MB);
- if (addr + ctx->size >= end)
- return 0;
- /* OBP available region contains grub. Start at grub_phys_end. */
- /* grub_phys_start does not start at the beginning of the memory region */
- if ((grub_phys_start >= addr && grub_phys_end < end) ||
- (addr > grub_phys_start && addr < grub_phys_end))
- {
- addr = ALIGN_UP (grub_phys_end, FOUR_MB);
- if (addr + ctx->size >= end)
- return 0;
- }
- grub_dprintf("loader",
- "addr = 0x%lx grub_phys_start = 0x%lx grub_phys_end = 0x%lx\n",
- addr, grub_phys_start, grub_phys_end);
- if (loaded)
- {
- grub_addr_t linux_end = ALIGN_UP (linux_paddr + linux_size, FOUR_MB);
- if (addr >= linux_paddr && addr < linux_end)
- {
- addr = linux_end;
- if (addr + ctx->size >= end)
- return 0;
- }
- if ((addr + ctx->size) >= linux_paddr
- && (addr + ctx->size) < linux_end)
- {
- addr = linux_end;
- if (addr + ctx->size >= end)
- return 0;
- }
- }
- ctx->ret = addr;
- return 1;
- }
- static grub_addr_t
- alloc_phys (grub_addr_t size)
- {
- struct alloc_phys_ctx ctx = {
- .size = size,
- .ret = (grub_addr_t) -1
- };
- grub_machine_mmap_iterate (alloc_phys_choose, &ctx);
- return ctx.ret;
- }
- static grub_err_t
- grub_linux_load64 (grub_elf_t elf, const char *filename)
- {
- grub_addr_t off, paddr, base;
- int ret;
- linux_entry = elf->ehdr.ehdr64.e_entry;
- linux_addr = 0x40004000;
- off = 0x4000;
- linux_size = grub_elf64_size (elf, 0, 0);
- if (linux_size == 0)
- return grub_errno;
- grub_dprintf ("loader", "Attempting to claim at 0x%lx, size 0x%lx.\n",
- linux_addr, linux_size);
- paddr = alloc_phys (linux_size + off);
- if (paddr == (grub_addr_t) -1)
- return grub_error (GRUB_ERR_OUT_OF_MEMORY,
- "couldn't allocate physical memory");
- ret = grub_ieee1275_map (paddr, linux_addr - off,
- linux_size + off, IEEE1275_MAP_DEFAULT);
- if (ret)
- return grub_error (GRUB_ERR_OUT_OF_MEMORY,
- "couldn't map physical memory");
- grub_dprintf ("loader", "Loading Linux at vaddr 0x%lx, paddr 0x%lx, size 0x%lx\n",
- linux_addr, paddr, linux_size);
- linux_paddr = paddr;
- base = linux_entry - off;
- /* Now load the segments into the area we claimed. */
- return grub_elf64_load (elf, filename, (void *) (linux_addr - off - base), GRUB_ELF_LOAD_FLAGS_NONE, 0, 0);
- }
- static grub_err_t
- grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)),
- int argc, char *argv[])
- {
- grub_file_t file = 0;
- grub_elf_t elf = 0;
- int size;
- grub_dl_ref (my_mod);
- if (argc == 0)
- {
- grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
- goto out;
- }
- file = grub_file_open (argv[0], GRUB_FILE_TYPE_LINUX_KERNEL);
- if (!file)
- goto out;
- elf = grub_elf_file (file, argv[0]);
- if (! elf)
- goto out;
- if (elf->ehdr.ehdr32.e_type != ET_EXEC)
- {
- grub_error (GRUB_ERR_UNKNOWN_OS,
- N_("this ELF file is not of the right type"));
- goto out;
- }
- /* Release the previously used memory. */
- grub_loader_unset ();
- if (grub_elf_is_elf64 (elf))
- grub_linux_load64 (elf, argv[0]);
- else
- {
- grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid arch-dependent ELF magic"));
- goto out;
- }
- size = grub_loader_cmdline_size(argc, argv);
- linux_args = grub_malloc (size + sizeof (LINUX_IMAGE));
- if (! linux_args)
- goto out;
- /* Create kernel command line. */
- grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE));
- if (grub_create_loader_cmdline (argc, argv, linux_args + sizeof (LINUX_IMAGE) - 1,
- size, GRUB_VERIFY_KERNEL_CMDLINE))
- goto out;
- out:
- if (elf)
- grub_elf_close (elf);
- else if (file)
- grub_file_close (file);
- if (grub_errno != GRUB_ERR_NONE)
- {
- grub_linux_release_mem ();
- grub_dl_unref (my_mod);
- loaded = 0;
- }
- else
- {
- grub_loader_set (grub_linux_boot, grub_linux_unload, 1);
- initrd_addr = 0;
- loaded = 1;
- }
- return grub_errno;
- }
- static grub_err_t
- grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
- int argc, char *argv[])
- {
- grub_size_t size = 0;
- grub_addr_t paddr;
- grub_addr_t addr;
- int ret;
- struct grub_linux_initrd_context initrd_ctx = { 0, 0, 0 };
- if (argc == 0)
- {
- grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected"));
- goto fail;
- }
- if (!loaded)
- {
- grub_error (GRUB_ERR_BAD_ARGUMENT, N_("you need to load the kernel first"));
- goto fail;
- }
- if (grub_initrd_init (argc, argv, &initrd_ctx))
- goto fail;
- size = grub_get_initrd_size (&initrd_ctx);
- addr = 0x60000000;
- paddr = alloc_phys (size);
- if (paddr == (grub_addr_t) -1)
- {
- grub_error (GRUB_ERR_OUT_OF_MEMORY,
- "couldn't allocate physical memory");
- goto fail;
- }
- ret = grub_ieee1275_map (paddr, addr, size, IEEE1275_MAP_DEFAULT);
- if (ret)
- {
- grub_error (GRUB_ERR_OUT_OF_MEMORY,
- "couldn't map physical memory");
- goto fail;
- }
- grub_dprintf ("loader", "Loading initrd at vaddr 0x%lx, paddr 0x%lx, size 0x%lx\n",
- addr, paddr, size);
- if (grub_initrd_load (&initrd_ctx, (void *) addr))
- goto fail;
- initrd_addr = addr;
- initrd_paddr = paddr;
- initrd_size = size;
- fail:
- grub_initrd_close (&initrd_ctx);
- return grub_errno;
- }
- /* Helper for determine_phys_base. */
- static int
- get_physbase (grub_uint64_t addr, grub_uint64_t len __attribute__ ((unused)),
- grub_memory_type_t type, void *data __attribute__ ((unused)))
- {
- if (type != GRUB_MEMORY_AVAILABLE)
- return 0;
- if (addr < phys_base)
- phys_base = addr;
- return 0;
- }
- static void
- determine_phys_base (void)
- {
- phys_base = ~(grub_uint64_t) 0;
- grub_machine_mmap_iterate (get_physbase, NULL);
- }
- static void
- fetch_translations (void)
- {
- grub_ieee1275_phandle_t node;
- grub_ssize_t actual;
- int i;
- if (grub_ieee1275_finddevice ("/virtual-memory", &node))
- {
- grub_printf ("Cannot find /virtual-memory node.\n");
- return;
- }
- if (grub_ieee1275_get_property_length (node, "translations", &actual))
- {
- grub_printf ("Cannot find /virtual-memory/translations size.\n");
- return;
- }
- of_trans = grub_malloc (actual);
- if (!of_trans)
- {
- grub_printf ("Cannot allocate translations buffer.\n");
- return;
- }
- if (grub_ieee1275_get_property (node, "translations", of_trans, actual, &actual))
- {
- grub_printf ("Cannot fetch /virtual-memory/translations property.\n");
- return;
- }
- of_num_trans = actual / sizeof(struct grub_ieee1275_translation);
- for (i = 0; i < of_num_trans; i++)
- {
- struct grub_ieee1275_translation *p = &of_trans[i];
- if (p->vaddr == 0x2000)
- {
- grub_addr_t phys, tte = p->data;
- phys = tte & ~(0xff00000000001fffULL);
- grub_phys_start = phys;
- grub_phys_end = grub_phys_start + p->size;
- grub_dprintf ("loader", "Grub lives at phys_start[%lx] phys_end[%lx]\n",
- (unsigned long) grub_phys_start,
- (unsigned long) grub_phys_end);
- break;
- }
- }
- }
- static grub_command_t cmd_linux, cmd_initrd;
- GRUB_MOD_INIT(linux)
- {
- determine_phys_base ();
- fetch_translations ();
- cmd_linux = grub_register_command ("linux", grub_cmd_linux,
- 0, N_("Load Linux."));
- cmd_initrd = grub_register_command ("initrd", grub_cmd_initrd,
- 0, N_("Load initrd."));
- my_mod = mod;
- }
- GRUB_MOD_FINI(linux)
- {
- grub_unregister_command (cmd_linux);
- grub_unregister_command (cmd_initrd);
- }
|