diff --git a/ChangeLog b/ChangeLog index cf73d8cc4..fe49dd51f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,7 @@ +2013-12-22 Vladimir Serbinenko + + Fix ARM Linux Loader on non-FDT platforms. + 2013-12-21 Vladimir Serbinenko * configure.ac: Choose link format based on host_os on emu. diff --git a/grub-core/lib/fdt.c b/grub-core/lib/fdt.c index 9f34dc77b..651e9d3cc 100644 --- a/grub-core/lib/fdt.c +++ b/grub-core/lib/fdt.c @@ -265,10 +265,9 @@ static grub_uint32_t *find_prop (void *fdt, unsigned int nodeoffset, the size allocated for the FDT; if this function is called before the other functions in this file and returns success, the other functions are guaranteed not to access memory locations outside the allocated memory. */ -int grub_fdt_check_header (void *fdt, unsigned int size) +int grub_fdt_check_header_nosize (void *fdt) { if (((grub_addr_t) fdt & 0x7) || (grub_fdt_get_magic (fdt) != FDT_MAGIC) - || (grub_fdt_get_totalsize (fdt) > size) || (grub_fdt_get_version (fdt) < FDT_SUPPORTED_VERSION) || (grub_fdt_get_last_comp_version (fdt) > FDT_SUPPORTED_VERSION) || (grub_fdt_get_off_dt_struct (fdt) & 0x00000003) @@ -284,6 +283,15 @@ int grub_fdt_check_header (void *fdt, unsigned int size) return 0; } +int grub_fdt_check_header (void *fdt, unsigned int size) +{ + if (size < sizeof (grub_fdt_header_t) + || (grub_fdt_get_totalsize (fdt) > size) + || grub_fdt_check_header_nosize (fdt) == -1) + return -1; + return 0; +} + /* Find a direct sub-node of a given parent node. */ int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset, const char *name) diff --git a/grub-core/loader/arm/linux.c b/grub-core/loader/arm/linux.c index a0dae380e..1fc1d3a40 100644 --- a/grub-core/loader/arm/linux.c +++ b/grub-core/loader/arm/linux.c @@ -44,15 +44,97 @@ static char *linux_args; static grub_uint32_t machine_type; static void *fdt_addr; +typedef void (*kernel_entry_t) (int, unsigned long, void *); + #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) +static grub_size_t +get_atag_size (grub_uint32_t *atag) +{ + grub_uint32_t *atag0 = atag; + while (atag[0] && atag[1]) + atag += atag[0]; + return atag - atag0; +} + +/* + * 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_atag (void) +{ + grub_uint32_t *atag_orig = (grub_uint32_t *) fdt_addr; + grub_uint32_t *tmp_atag, *from, *to; + grub_size_t tmp_size; + grub_size_t arg_size = grub_strlen (linux_args); + + /* some place for cmdline, initrd and terminator. */ + tmp_size = get_atag_size (atag_orig) + 20 + (arg_size) / 4; + tmp_atag = grub_malloc (tmp_size * sizeof (grub_uint32_t)); + if (!tmp_atag) + return grub_errno; + + for (from = atag_orig, to = tmp_atag; from[0] && from[1]; + from += from[0]) + switch (from[1]) + { + case 0x54410004: + case 0x54410005: + case 0x54420005: + case 0x54420009: + break; + default: + grub_memcpy (to, from, sizeof (grub_uint32_t) * from[0]); + to += from[0]; + break; + } + + grub_dprintf ("linux", "linux_args: '%s'\n", linux_args); + + /* Generate and set command line */ + to[0] = 3 + arg_size / 4; + to[1] = 0x54410009; + grub_memcpy (to + 2, linux_args, arg_size); + grub_memset ((char *) to + 8 + arg_size, 0, + 4 - (arg_size & 3)); + to += 3 + arg_size / 4; + + 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); + + to[0] = 4; + to[1] = 0x54420005; + to[2] = initrd_start; + to[3] = initrd_end - initrd_start; + to += 4; + } + + to[0] = 0; + to[1] = 0; + to += 2; + + /* Copy updated FDT to its launch location */ + grub_memcpy (atag_orig, tmp_atag, sizeof (grub_uint32_t) * (to - tmp_atag)); + grub_free (tmp_atag); + + grub_dprintf ("loader", "ATAG updated for Linux boot\n"); + + return GRUB_ERR_NONE; +} + /* * linux_prepare_fdt(): * Prepares a loaded FDT for being passed to Linux. @@ -128,9 +210,19 @@ static grub_err_t linux_boot (void) { kernel_entry_t linuxmain; - grub_err_t err; + int fdt_valid, atag_valid; - if (!fdt_addr && machine_type == ARM_FDT_MACHINE_TYPE) + fdt_valid = (fdt_addr && grub_fdt_check_header_nosize (fdt_addr) == 0); + atag_valid = ((((grub_uint16_t *) fdt_addr)[3] & ~3) == 0x5440 + && *((grub_uint32_t *) fdt_addr)); + grub_dprintf ("loader", "atag: %p, %x, %x, %s, %s\n", + fdt_addr, + ((grub_uint16_t *) fdt_addr)[3], + *((grub_uint32_t *) fdt_addr), + (char *) fdt_addr, + (char *) fdt_addr + 1); + + if (!fdt_valid && machine_type == GRUB_ARM_MACHINE_TYPE_FDT) return grub_error (GRUB_ERR_FILE_NOT_FOUND, N_("device tree must be supplied (see `devicetree' command)")); @@ -138,10 +230,24 @@ linux_boot (void) grub_dprintf ("loader", "Kernel at: 0x%x\n", linux_addr); - err = linux_prepare_fdt (); - if (err) - return err; - grub_dprintf ("loader", "FDT @ 0x%p\n", fdt_addr); + if (fdt_valid) + { + grub_err_t err; + + err = linux_prepare_fdt (); + if (err) + return err; + grub_dprintf ("loader", "FDT @ 0x%p\n", fdt_addr); + } + else if (atag_valid) + { + grub_err_t err; + + err = linux_prepare_atag (); + if (err) + return err; + grub_dprintf ("loader", "ATAG @ 0x%p\n", fdt_addr); + } grub_dprintf ("loader", "Jumping to Linux...\n"); @@ -154,14 +260,17 @@ linux_boot (void) linuxmain = (kernel_entry_t) linux_addr; #ifdef GRUB_MACHINE_EFI - err = grub_efi_prepare_platform(); - if (err != GRUB_ERR_NONE) - return err; + { + grub_err_t err; + err = grub_efi_prepare_platform(); + if (err != GRUB_ERR_NONE) + return err; + } #endif linuxmain (0, machine_type, fdt_addr); - return err; + return grub_error (GRUB_ERR_BAD_OS, "Linux call returned"); } /* @@ -173,8 +282,6 @@ linux_load (const char *filename, grub_file_t file) int size; size = grub_file_size (file); - if (size == 0) - return grub_error (GRUB_ERR_BAD_OS, "empty kernel"); #ifdef GRUB_MACHINE_EFI linux_addr = (grub_addr_t) grub_efi_allocate_loader_memory (LINUX_PHYS_OFFSET, size); @@ -194,11 +301,16 @@ linux_load (const char *filename, grub_file_t file) return grub_errno; } - if (*(grub_uint32_t *) (linux_addr + LINUX_ZIMAGE_OFFSET) - != LINUX_ZIMAGE_MAGIC) - { - return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid zImage")); - } + if (size > LINUX_ZIMAGE_OFFSET + 4 + && *(grub_uint32_t *) (linux_addr + LINUX_ZIMAGE_OFFSET) + == LINUX_ZIMAGE_MAGIC) + ; + else if (size > 0x8000 && *(grub_uint32_t *) (linux_addr) == 0xea000006 + && machine_type == GRUB_ARM_MACHINE_TYPE_RASPBERRY_PI) + grub_memmove ((void *) linux_addr, (void *) (linux_addr + 0x8000), + size - 0x8000); + else + return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid zImage")); linux_size = size; @@ -281,9 +393,10 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)), size = grub_get_initrd_size (&initrd_ctx); - if (initrd_start) - grub_free ((void *) initrd_start); #ifdef GRUB_MACHINE_EFI + if (initrd_start) + grub_efi_free_pages (initrd_start, + (initrd_end - initrd_start + 0xfff) >> 12); initrd_start = (grub_addr_t) grub_efi_allocate_loader_memory (LINUX_INITRD_PHYS_OFFSET, size); if (!initrd_start) @@ -367,7 +480,7 @@ grub_cmd_devicetree (grub_command_t cmd __attribute__ ((unused)), * We've successfully loaded an FDT, so any machine type passed * from firmware is now obsolete. */ - machine_type = ARM_FDT_MACHINE_TYPE; + machine_type = GRUB_ARM_MACHINE_TYPE_FDT; out: grub_file_close (dtb); @@ -387,8 +500,8 @@ GRUB_MOD_INIT (linux) /* TRANSLATORS: DTB stands for device tree blob. */ 0, N_("Load DTB file.")); my_mod = mod; - fdt_addr = (void *) firmware_get_boot_data (); - machine_type = firmware_get_machine_type (); + fdt_addr = (void *) grub_arm_firmware_get_boot_data (); + machine_type = grub_arm_firmware_get_machine_type (); } GRUB_MOD_FINI (linux) diff --git a/include/grub/arm/linux.h b/include/grub/arm/linux.h index 1f62d76ac..29ab96660 100644 --- a/include/grub/arm/linux.h +++ b/include/grub/arm/linux.h @@ -23,15 +23,19 @@ #define LINUX_ZIMAGE_OFFSET 0x24 #define LINUX_ZIMAGE_MAGIC 0x016f2818 -#define ARM_FDT_MACHINE_TYPE 0xFFFFFFFF +enum + { + GRUB_ARM_MACHINE_TYPE_RASPBERRY_PI = 3138, + GRUB_ARM_MACHINE_TYPE_FDT = 0xFFFFFFFF + }; #if defined GRUB_MACHINE_UBOOT # include # define LINUX_ADDRESS (start_of_ram + 0x8000) # define LINUX_INITRD_ADDRESS (start_of_ram + 0x02000000) # define LINUX_FDT_ADDRESS (LINUX_INITRD_ADDRESS - 0x10000) -# define firmware_get_boot_data grub_uboot_get_boot_data -# define firmware_get_machine_type grub_uboot_get_machine_type +# define grub_arm_firmware_get_boot_data grub_uboot_get_boot_data +# define grub_arm_firmware_get_machine_type grub_uboot_get_machine_type #elif defined GRUB_MACHINE_EFI # include # include @@ -41,19 +45,17 @@ # define LINUX_INITRD_PHYS_OFFSET (LINUX_PHYS_OFFSET + 0x02000000) # define LINUX_FDT_PHYS_OFFSET (LINUX_INITRD_PHYS_OFFSET - 0x10000) static inline grub_addr_t -firmware_get_boot_data (void) +grub_arm_firmware_get_boot_data (void) { return 0; } static inline grub_uint32_t -firmware_get_machine_type (void) +grub_arm_firmware_get_machine_type (void) { - return ARM_FDT_MACHINE_TYPE; + return GRUB_ARM_MACHINE_TYPE_FDT; } #endif #define FDT_ADDITIONAL_ENTRIES_SIZE 0x300 -typedef void (*kernel_entry_t) (int, unsigned long, void *); - #endif /* ! GRUB_LINUX_CPU_HEADER */ diff --git a/include/grub/fdt.h b/include/grub/fdt.h index 2ad0536b6..a2721926b 100644 --- a/include/grub/fdt.h +++ b/include/grub/fdt.h @@ -83,6 +83,7 @@ typedef struct { grub_fdt_set_header(fdt, size_dt_struct, value) int grub_fdt_check_header (void *fdt, unsigned int size); +int grub_fdt_check_header_nosize (void *fdt); int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset, const char *name); int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset,