/* linux.c - boot Linux */ /* * GRUB -- GRand Unified Bootloader * Copyright (C) 2013 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 . */ #include #include #include #include #include #include #include #include #include #include GRUB_MOD_LICENSE ("GPLv3+"); static grub_dl_t my_mod; static grub_addr_t initrd_start, initrd_end; static grub_addr_t linux_addr; static grub_size_t linux_size; static char *linux_args; static void *fdt_addr; #define LINUX_ZIMAGE_OFFSET 0x24 #define LINUX_ZIMAGE_MAGIC 0x016f2818 #define ARM_FDT_MACHINE_TYPE 0xFFFFFFFF #define LINUX_PHYS_OFFSET (0x00008000) #define LINUX_INITRD_PHYS_OFFSET (LINUX_PHYS_OFFSET + 0x02000000) #define LINUX_FDT_PHYS_OFFSET (LINUX_INITRD_PHYS_OFFSET - 0x10000) typedef void (*kernel_entry_t) (int, unsigned long, void *); /* * linux_prepare_fdt(): * Prepares a loaded FDT for being passed to Linux. * Merges in command line parameters and sets up initrd addresses. */ static grub_err_t linux_prepare_fdt (void) { #define FDT_ADDITIONAL_ENTRIES_SIZE (0x100 + grub_strlen (linux_args)) int node; int retval; int tmp_size; void *tmp_fdt; tmp_size = grub_fdt_get_totalsize (fdt_addr) + FDT_ADDITIONAL_ENTRIES_SIZE; tmp_fdt = grub_malloc (tmp_size); if (!tmp_fdt) return GRUB_ERR_OUT_OF_MEMORY; grub_memcpy (tmp_fdt, fdt_addr, grub_fdt_get_totalsize (fdt_addr)); grub_fdt_set_totalsize (tmp_fdt, tmp_size); /* Find or create '/chosen' node */ node = grub_fdt_find_subnode (tmp_fdt, 0, "chosen"); if (node < 0) { grub_printf ("No 'chosen' node in FDT - creating.\n"); node = grub_fdt_add_subnode (tmp_fdt, 0, "chosen"); if (node < 0) goto failure; } grub_printf ("linux_args: '%s'\n", linux_args); /* Generate and set command line */ retval = grub_fdt_set_prop (tmp_fdt, node, "bootargs", linux_args, grub_strlen (linux_args) + 1); if (retval) goto failure; if (initrd_start && initrd_end) { /* * We're using physical addresses, so even if we have LPAE, we're * restricted to a 32-bit address space. */ grub_dprintf ("loader", "Initrd @ 0x%08x-0x%08x\n", initrd_start, initrd_end); retval = grub_fdt_set_prop32 (tmp_fdt, node, "linux,initrd-start", initrd_start); if (retval) goto failure; retval = grub_fdt_set_prop32 (tmp_fdt, node, "linux,initrd-end", initrd_end); if (retval) goto failure; } /* Copy updated FDT to its launch location */ grub_memcpy (fdt_addr, tmp_fdt, tmp_size); grub_free (tmp_fdt); grub_dprintf ("loader", "FDT updated for Linux boot\n"); return GRUB_ERR_NONE; failure: grub_free (tmp_fdt); return GRUB_ERR_BAD_ARGUMENT; } static grub_err_t linux_boot (void) { kernel_entry_t linuxmain; grub_err_t err; if (!fdt_addr) return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("device tree must be supplied")); grub_arch_sync_caches ((void *) linux_addr, linux_size); grub_dprintf ("loader", "Kernel at: 0x%x\n", linux_addr); err = linux_prepare_fdt (); if (err != GRUB_ERR_NONE) { return grub_error (err, "unable to prepare FDT"); } grub_dprintf ("loader", "FDT @ 0x%p\n", fdt_addr); grub_dprintf ("loader", "Jumping to Linux...\n"); /* Boot the kernel. * Arguments to kernel: * r0 - 0 * r1 - machine type * r2 - address of DTB */ linuxmain = (kernel_entry_t) linux_addr; err = grub_efi_prepare_platform(); if (err != GRUB_ERR_NONE) return err; linuxmain (0, ARM_FDT_MACHINE_TYPE, fdt_addr); return err; } /* * Only support zImage, so no relocations necessary */ static grub_err_t linux_load (grub_file_t file) { int size; grub_err_t err; size = grub_file_size (file); if (size == 0) return GRUB_ERR_FILE_READ_ERROR; linux_addr = (grub_addr_t) grub_efi_allocate_loader_memory (LINUX_PHYS_OFFSET, size); if (!linux_addr) return GRUB_ERR_OUT_OF_MEMORY; grub_dprintf ("loader", "Loading Linux to 0x%08x\n", (grub_addr_t) linux_addr); if (grub_file_read (file, (void *) linux_addr, size) != size) { grub_printf ("Kernel read failed!\n"); err = GRUB_ERR_FILE_READ_ERROR; goto error; } if (*(grub_uint32_t *) (linux_addr + LINUX_ZIMAGE_OFFSET) != LINUX_ZIMAGE_MAGIC) { err = grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid zImage")); goto error; } linux_size = size; return GRUB_ERR_NONE; error: grub_free ((void *) linux_addr); return err; } static grub_err_t linux_unload (void) { grub_dl_unref (my_mod); if (linux_args) { grub_free (linux_args); linux_args = NULL; } grub_free ((void *) linux_addr); return GRUB_ERR_NONE; } static grub_err_t grub_cmd_linux (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { int size; grub_err_t retval; grub_file_t file; grub_dl_ref (my_mod); if (argc == 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); file = grub_file_open (argv[0]); if (!file) goto fail; retval = linux_load (file); grub_file_close (file); if (retval != GRUB_ERR_NONE) { grub_errno = retval; goto fail; } grub_loader_set (linux_boot, linux_unload, 0); size = grub_loader_cmdline_size (argc, argv); linux_args = grub_malloc (size + sizeof (LINUX_IMAGE)); if (!linux_args) { grub_loader_unset(); goto fail; } /* Create kernel command line. */ grub_memcpy (linux_args, LINUX_IMAGE, sizeof (LINUX_IMAGE)); grub_create_loader_cmdline (argc, argv, linux_args + sizeof (LINUX_IMAGE) - 1, size); return GRUB_ERR_NONE; fail: grub_dl_unref (my_mod); return grub_errno; } static grub_err_t grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_file_t file; int size; if (argc == 0) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); file = grub_file_open (argv[0]); if (!file) return grub_errno; size = grub_file_size (file); if (size == 0) goto fail; if (initrd_start) grub_free ((void *) initrd_start); initrd_start = (grub_addr_t) grub_efi_allocate_loader_memory ( LINUX_INITRD_PHYS_OFFSET, size); if (!initrd_start) { grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("memory allocation failed")); goto fail; } grub_dprintf ("loader", "Loading initrd to 0x%08x\n", (grub_addr_t) initrd_start); if (grub_file_read (file, (void *) initrd_start, size) != size) { grub_free ((void *) initrd_start); initrd_start = 0; goto fail; } initrd_end = initrd_start + size; return GRUB_ERR_NONE; fail: grub_file_close (file); return grub_errno; } static grub_err_t load_dtb (grub_file_t dtb, int size) { if ((grub_file_read (dtb, fdt_addr, size) != size) || (grub_fdt_check_header (fdt_addr, size) != 0)) return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid device tree")); grub_fdt_set_totalsize (fdt_addr, size); return GRUB_ERR_NONE; } static grub_err_t grub_cmd_devicetree (grub_command_t cmd __attribute__ ((unused)), int argc, char *argv[]) { grub_file_t dtb; int size; if (argc != 1) return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("filename expected")); dtb = grub_file_open (argv[0]); if (!dtb) return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("failed to open file")); size = grub_file_size (dtb); if (size == 0) goto out; if (fdt_addr) grub_free (fdt_addr); fdt_addr = grub_efi_allocate_loader_memory (LINUX_FDT_PHYS_OFFSET, size); if (!fdt_addr) { grub_error (GRUB_ERR_OUT_OF_MEMORY, N_("memory allocation failed")); goto out; } grub_dprintf ("loader", "Loading device tree to 0x%08x\n", (grub_addr_t) fdt_addr); load_dtb (dtb, size); if (grub_errno != GRUB_ERR_NONE) { grub_free (fdt_addr); fdt_addr = NULL; } out: grub_file_close (dtb); return grub_errno; } static grub_command_t cmd_linux, cmd_initrd, cmd_devicetree; GRUB_MOD_INIT (linux) { 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.")); cmd_devicetree = grub_register_command ("devicetree", grub_cmd_devicetree, 0, N_("Load DTB file.")); my_mod = mod; } GRUB_MOD_FINI (linux) { grub_unregister_command (cmd_linux); grub_unregister_command (cmd_initrd); grub_unregister_command (cmd_devicetree); }