Fix ARM Linux Loader on non-FDT platforms.

This commit is contained in:
Vladimir Serbinenko 2013-12-22 00:30:19 +01:00
parent bf082198e2
commit 0d8b81f89a
5 changed files with 162 additions and 34 deletions

View file

@ -1,3 +1,7 @@
2013-12-22 Vladimir Serbinenko <phcoder@gmail.com>
Fix ARM Linux Loader on non-FDT platforms.
2013-12-21 Vladimir Serbinenko <phcoder@gmail.com> 2013-12-21 Vladimir Serbinenko <phcoder@gmail.com>
* configure.ac: Choose link format based on host_os on emu. * configure.ac: Choose link format based on host_os on emu.

View file

@ -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 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 functions in this file and returns success, the other functions are
guaranteed not to access memory locations outside the allocated memory. */ 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) 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_version (fdt) < FDT_SUPPORTED_VERSION)
|| (grub_fdt_get_last_comp_version (fdt) > FDT_SUPPORTED_VERSION) || (grub_fdt_get_last_comp_version (fdt) > FDT_SUPPORTED_VERSION)
|| (grub_fdt_get_off_dt_struct (fdt) & 0x00000003) || (grub_fdt_get_off_dt_struct (fdt) & 0x00000003)
@ -284,6 +283,15 @@ int grub_fdt_check_header (void *fdt, unsigned int size)
return 0; 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. */ /* Find a direct sub-node of a given parent node. */
int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset, int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset,
const char *name) const char *name)

View file

@ -44,15 +44,97 @@ static char *linux_args;
static grub_uint32_t machine_type; static grub_uint32_t machine_type;
static void *fdt_addr; static void *fdt_addr;
typedef void (*kernel_entry_t) (int, unsigned long, void *);
#define LINUX_ZIMAGE_OFFSET 0x24 #define LINUX_ZIMAGE_OFFSET 0x24
#define LINUX_ZIMAGE_MAGIC 0x016f2818 #define LINUX_ZIMAGE_MAGIC 0x016f2818
#define ARM_FDT_MACHINE_TYPE 0xFFFFFFFF
#define LINUX_PHYS_OFFSET (0x00008000) #define LINUX_PHYS_OFFSET (0x00008000)
#define LINUX_INITRD_PHYS_OFFSET (LINUX_PHYS_OFFSET + 0x02000000) #define LINUX_INITRD_PHYS_OFFSET (LINUX_PHYS_OFFSET + 0x02000000)
#define LINUX_FDT_PHYS_OFFSET (LINUX_INITRD_PHYS_OFFSET - 0x10000) #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(): * linux_prepare_fdt():
* Prepares a loaded FDT for being passed to Linux. * Prepares a loaded FDT for being passed to Linux.
@ -128,9 +210,19 @@ static grub_err_t
linux_boot (void) linux_boot (void)
{ {
kernel_entry_t linuxmain; 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, return grub_error (GRUB_ERR_FILE_NOT_FOUND,
N_("device tree must be supplied (see `devicetree' command)")); 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); grub_dprintf ("loader", "Kernel at: 0x%x\n", linux_addr);
err = linux_prepare_fdt (); if (fdt_valid)
if (err) {
return err; grub_err_t err;
grub_dprintf ("loader", "FDT @ 0x%p\n", fdt_addr);
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"); grub_dprintf ("loader", "Jumping to Linux...\n");
@ -154,14 +260,17 @@ linux_boot (void)
linuxmain = (kernel_entry_t) linux_addr; linuxmain = (kernel_entry_t) linux_addr;
#ifdef GRUB_MACHINE_EFI #ifdef GRUB_MACHINE_EFI
err = grub_efi_prepare_platform(); {
if (err != GRUB_ERR_NONE) grub_err_t err;
return err; err = grub_efi_prepare_platform();
if (err != GRUB_ERR_NONE)
return err;
}
#endif #endif
linuxmain (0, machine_type, fdt_addr); 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; int size;
size = grub_file_size (file); size = grub_file_size (file);
if (size == 0)
return grub_error (GRUB_ERR_BAD_OS, "empty kernel");
#ifdef GRUB_MACHINE_EFI #ifdef GRUB_MACHINE_EFI
linux_addr = (grub_addr_t) grub_efi_allocate_loader_memory (LINUX_PHYS_OFFSET, size); 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; return grub_errno;
} }
if (*(grub_uint32_t *) (linux_addr + LINUX_ZIMAGE_OFFSET) if (size > LINUX_ZIMAGE_OFFSET + 4
!= LINUX_ZIMAGE_MAGIC) && *(grub_uint32_t *) (linux_addr + LINUX_ZIMAGE_OFFSET)
{ == LINUX_ZIMAGE_MAGIC)
return grub_error (GRUB_ERR_BAD_FILE_TYPE, N_("invalid zImage")); ;
} 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; linux_size = size;
@ -281,9 +393,10 @@ grub_cmd_initrd (grub_command_t cmd __attribute__ ((unused)),
size = grub_get_initrd_size (&initrd_ctx); size = grub_get_initrd_size (&initrd_ctx);
if (initrd_start)
grub_free ((void *) initrd_start);
#ifdef GRUB_MACHINE_EFI #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); initrd_start = (grub_addr_t) grub_efi_allocate_loader_memory (LINUX_INITRD_PHYS_OFFSET, size);
if (!initrd_start) 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 * We've successfully loaded an FDT, so any machine type passed
* from firmware is now obsolete. * from firmware is now obsolete.
*/ */
machine_type = ARM_FDT_MACHINE_TYPE; machine_type = GRUB_ARM_MACHINE_TYPE_FDT;
out: out:
grub_file_close (dtb); grub_file_close (dtb);
@ -387,8 +500,8 @@ GRUB_MOD_INIT (linux)
/* TRANSLATORS: DTB stands for device tree blob. */ /* TRANSLATORS: DTB stands for device tree blob. */
0, N_("Load DTB file.")); 0, N_("Load DTB file."));
my_mod = mod; my_mod = mod;
fdt_addr = (void *) firmware_get_boot_data (); fdt_addr = (void *) grub_arm_firmware_get_boot_data ();
machine_type = firmware_get_machine_type (); machine_type = grub_arm_firmware_get_machine_type ();
} }
GRUB_MOD_FINI (linux) GRUB_MOD_FINI (linux)

View file

@ -23,15 +23,19 @@
#define LINUX_ZIMAGE_OFFSET 0x24 #define LINUX_ZIMAGE_OFFSET 0x24
#define LINUX_ZIMAGE_MAGIC 0x016f2818 #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 #if defined GRUB_MACHINE_UBOOT
# include <grub/uboot/uboot.h> # include <grub/uboot/uboot.h>
# define LINUX_ADDRESS (start_of_ram + 0x8000) # define LINUX_ADDRESS (start_of_ram + 0x8000)
# define LINUX_INITRD_ADDRESS (start_of_ram + 0x02000000) # define LINUX_INITRD_ADDRESS (start_of_ram + 0x02000000)
# define LINUX_FDT_ADDRESS (LINUX_INITRD_ADDRESS - 0x10000) # define LINUX_FDT_ADDRESS (LINUX_INITRD_ADDRESS - 0x10000)
# define firmware_get_boot_data grub_uboot_get_boot_data # define grub_arm_firmware_get_boot_data grub_uboot_get_boot_data
# define firmware_get_machine_type grub_uboot_get_machine_type # define grub_arm_firmware_get_machine_type grub_uboot_get_machine_type
#elif defined GRUB_MACHINE_EFI #elif defined GRUB_MACHINE_EFI
# include <grub/efi/efi.h> # include <grub/efi/efi.h>
# include <grub/machine/loader.h> # include <grub/machine/loader.h>
@ -41,19 +45,17 @@
# define LINUX_INITRD_PHYS_OFFSET (LINUX_PHYS_OFFSET + 0x02000000) # define LINUX_INITRD_PHYS_OFFSET (LINUX_PHYS_OFFSET + 0x02000000)
# define LINUX_FDT_PHYS_OFFSET (LINUX_INITRD_PHYS_OFFSET - 0x10000) # define LINUX_FDT_PHYS_OFFSET (LINUX_INITRD_PHYS_OFFSET - 0x10000)
static inline grub_addr_t static inline grub_addr_t
firmware_get_boot_data (void) grub_arm_firmware_get_boot_data (void)
{ {
return 0; return 0;
} }
static inline grub_uint32_t 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 #endif
#define FDT_ADDITIONAL_ENTRIES_SIZE 0x300 #define FDT_ADDITIONAL_ENTRIES_SIZE 0x300
typedef void (*kernel_entry_t) (int, unsigned long, void *);
#endif /* ! GRUB_LINUX_CPU_HEADER */ #endif /* ! GRUB_LINUX_CPU_HEADER */

View file

@ -83,6 +83,7 @@ typedef struct {
grub_fdt_set_header(fdt, size_dt_struct, value) grub_fdt_set_header(fdt, size_dt_struct, value)
int grub_fdt_check_header (void *fdt, unsigned int size); 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, int grub_fdt_find_subnode (const void *fdt, unsigned int parentoffset,
const char *name); const char *name);
int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset, int grub_fdt_add_subnode (void *fdt, unsigned int parentoffset,