| /* 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]); |
| 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, argv, (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); |
| } |