diff mbox

arm: add device tree support

Message ID 1330364305-410-1-git-send-email-peter.maydell@linaro.org
State New
Headers show

Commit Message

Peter Maydell Feb. 27, 2012, 5:38 p.m. UTC
From: Grant Likely <grant.likely@secretlab.ca>

If compiled with CONFIG_FDT, allow user to specify a device tree file using
the -dtb argument.  If the machine supports it then the dtb will be loaded
into memory and passed to the kernel on boot.

Signed-off-by: Jeremy Kerr <jeremy.kerr@canonical.com>
Signed-off-by: Grant Likely <grant.likely@secretlab.ca>
[Peter Maydell: Use machine opt rather than global to pass dtb filename]
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
---
v1->v2: fix potential NULL deref if qemu_opts_find() returns NULL

 Makefile.target |    1 +
 configure       |    1 +
 hw/arm-misc.h   |    1 +
 hw/arm_boot.c   |  102 +++++++++++++++++++++++++++++++++++++++++++++++++++---
 qemu-config.c   |    4 ++
 qemu-options.hx |    9 +++++
 vl.c            |    8 ++++
 7 files changed, 120 insertions(+), 6 deletions(-)

Comments

Anthony Liguori Feb. 27, 2012, 5:41 p.m. UTC | #1
On 02/27/2012 11:38 AM, Peter Maydell wrote:
> From: Grant Likely<grant.likely@secretlab.ca>
>
> If compiled with CONFIG_FDT, allow user to specify a device tree file using
> the -dtb argument.  If the machine supports it then the dtb will be loaded
> into memory and passed to the kernel on boot.
>
> Signed-off-by: Jeremy Kerr<jeremy.kerr@canonical.com>
> Signed-off-by: Grant Likely<grant.likely@secretlab.ca>
> [Peter Maydell: Use machine opt rather than global to pass dtb filename]
> Signed-off-by: Peter Maydell<peter.maydell@linaro.org>

Reviewed-by: Anthony Liguori <aliguori@us.ibm.com>

Thanks for doing the work to wrap this in QemuOpts.  This was a nice improvement.

Regards,

Anthony Liguori

> ---
> v1->v2: fix potential NULL deref if qemu_opts_find() returns NULL
>
>   Makefile.target |    1 +
>   configure       |    1 +
>   hw/arm-misc.h   |    1 +
>   hw/arm_boot.c   |  102 +++++++++++++++++++++++++++++++++++++++++++++++++++---
>   qemu-config.c   |    4 ++
>   qemu-options.hx |    9 +++++
>   vl.c            |    8 ++++
>   7 files changed, 120 insertions(+), 6 deletions(-)
>
> diff --git a/Makefile.target b/Makefile.target
> index 68a5641..6a56b3e 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -373,6 +373,7 @@ obj-arm-y += vexpress.o
>   obj-arm-y += strongarm.o
>   obj-arm-y += collie.o
>   obj-arm-y += pl041.o lm4549.o
> +obj-arm-$(CONFIG_FDT) += device_tree.o
>
>   obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o
>   obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o
> diff --git a/configure b/configure
> index f9d5330..7d509e8 100755
> --- a/configure
> +++ b/configure
> @@ -3472,6 +3472,7 @@ case "$target_arch2" in
>       gdb_xml_files="arm-core.xml arm-vfp.xml arm-vfp3.xml arm-neon.xml"
>       target_phys_bits=32
>       target_llong_alignment=4
> +    target_libs_softmmu="$fdt_libs"
>     ;;
>     cris)
>       target_nptl="yes"
> diff --git a/hw/arm-misc.h b/hw/arm-misc.h
> index 306013a..734bd82 100644
> --- a/hw/arm-misc.h
> +++ b/hw/arm-misc.h
> @@ -29,6 +29,7 @@ struct arm_boot_info {
>       const char *kernel_filename;
>       const char *kernel_cmdline;
>       const char *initrd_filename;
> +    const char *dtb_filename;
>       target_phys_addr_t loader_start;
>       /* multicore boards that use the default secondary core boot functions
>        * need to put the address of the secondary boot code, the boot reg,
> diff --git a/hw/arm_boot.c b/hw/arm_boot.c
> index 2ef25ca..fc66910 100644
> --- a/hw/arm_boot.c
> +++ b/hw/arm_boot.c
> @@ -7,11 +7,14 @@
>    * This code is licensed under the GPL.
>    */
>
> +#include "config.h"
>   #include "hw.h"
>   #include "arm-misc.h"
>   #include "sysemu.h"
> +#include "boards.h"
>   #include "loader.h"
>   #include "elf.h"
> +#include "device_tree.h"
>
>   #define KERNEL_ARGS_ADDR 0x100
>   #define KERNEL_LOAD_ADDR 0x00010000
> @@ -208,6 +211,67 @@ static void set_kernel_args_old(const struct arm_boot_info *info)
>       }
>   }
>
> +static int load_dtb(target_phys_addr_t addr, const struct arm_boot_info *binfo)
> +{
> +#ifdef CONFIG_FDT
> +    uint32_t mem_reg_property[] = { cpu_to_be32(binfo->loader_start),
> +                                    cpu_to_be32(binfo->ram_size) };
> +    void *fdt = NULL;
> +    char *filename;
> +    int size, rc;
> +
> +    filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename);
> +    if (!filename) {
> +        fprintf(stderr, "Couldn't open dtb file %s\n", binfo->dtb_filename);
> +        return -1;
> +    }
> +
> +    fdt = load_device_tree(filename,&size);
> +    if (!fdt) {
> +        fprintf(stderr, "Couldn't open dtb file %s\n", filename);
> +        g_free(filename);
> +        return -1;
> +    }
> +    g_free(filename);
> +
> +    rc = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property,
> +                               sizeof(mem_reg_property));
> +    if (rc<  0) {
> +        fprintf(stderr, "couldn't set /memory/reg\n");
> +    }
> +
> +    rc = qemu_devtree_setprop_string(fdt, "/chosen", "bootargs",
> +                                      binfo->kernel_cmdline);
> +    if (rc<  0) {
> +        fprintf(stderr, "couldn't set /chosen/bootargs\n");
> +    }
> +
> +    if (binfo->initrd_size) {
> +        rc = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-start",
> +                binfo->loader_start + INITRD_LOAD_ADDR);
> +        if (rc<  0) {
> +            fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n");
> +        }
> +
> +        rc = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-end",
> +                    binfo->loader_start + INITRD_LOAD_ADDR +
> +                    binfo->initrd_size);
> +        if (rc<  0) {
> +            fprintf(stderr, "couldn't set /chosen/linux,initrd-end\n");
> +        }
> +    }
> +
> +    cpu_physical_memory_write(addr, fdt, size);
> +
> +    return 0;
> +
> +#else
> +    fprintf(stderr, "Device tree requested, "
> +                "but qemu was compiled without fdt support\n");
> +    return -1;
> +#endif
> +}
> +
>   static void do_cpu_reset(void *opaque)
>   {
>       CPUState *env = opaque;
> @@ -222,10 +286,12 @@ static void do_cpu_reset(void *opaque)
>           } else {
>               if (env == first_cpu) {
>                   env->regs[15] = info->loader_start;
> -                if (old_param) {
> -                    set_kernel_args_old(info);
> -                } else {
> -                    set_kernel_args(info);
> +                if (!info->dtb_filename) {
> +                    if (old_param) {
> +                        set_kernel_args_old(info);
> +                    } else {
> +                        set_kernel_args(info);
> +                    }
>                   }
>               } else {
>                   info->secondary_cpu_reset_hook(env, info);
> @@ -243,6 +309,7 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
>       uint64_t elf_entry;
>       target_phys_addr_t entry;
>       int big_endian;
> +    QemuOpts *machine_opts;
>
>       /* Load the kernel.  */
>       if (!info->kernel_filename) {
> @@ -250,6 +317,13 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
>           exit(1);
>       }
>
> +    machine_opts = qemu_opts_find(qemu_find_opts("machine"), 0);
> +    if (machine_opts) {
> +        info->dtb_filename = qemu_opt_get(machine_opts, "dtb");
> +    } else {
> +        info->dtb_filename = NULL;
> +    }
> +
>       if (!info->secondary_cpu_reset_hook) {
>           info->secondary_cpu_reset_hook = default_reset_secondary;
>       }
> @@ -300,8 +374,25 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
>           } else {
>               initrd_size = 0;
>           }
> +        info->initrd_size = initrd_size;
> +
>           bootloader[4] = info->board_id;
> -        bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR;
> +
> +        /* for device tree boot, we pass the DTB directly in r2. Otherwise
> +         * we point to the kernel args.
> +         */
> +        if (info->dtb_filename) {
> +            /* Place the DTB after the initrd in memory */
> +            target_phys_addr_t dtb_start = TARGET_PAGE_ALIGN(info->loader_start
> +                                                             + INITRD_LOAD_ADDR
> +                                                             + initrd_size);
> +            if (load_dtb(dtb_start, info)) {
> +                exit(1);
> +            }
> +            bootloader[5] = dtb_start;
> +        } else {
> +            bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR;
> +        }
>           bootloader[6] = entry;
>           for (n = 0; n<  sizeof(bootloader) / 4; n++) {
>               bootloader[n] = tswap32(bootloader[n]);
> @@ -311,7 +402,6 @@ void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
>           if (info->nb_cpus>  1) {
>               info->write_secondary_boot(env, info);
>           }
> -        info->initrd_size = initrd_size;
>       }
>       info->is_linux = is_linux;
>
> diff --git a/qemu-config.c b/qemu-config.c
> index 7d9da78..be84a03 100644
> --- a/qemu-config.c
> +++ b/qemu-config.c
> @@ -578,6 +578,10 @@ static QemuOptsList qemu_machine_opts = {
>               .name = "append",
>               .type = QEMU_OPT_STRING,
>               .help = "Linux kernel command line",
> +        }, {
> +            .name = "dtb",
> +            .type = QEMU_OPT_STRING,
> +            .help = "Linux kernel device tree file",
>           },
>           { /* End of list */ }
>       },
> diff --git a/qemu-options.hx b/qemu-options.hx
> index b129996..e38799c 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -2037,6 +2037,15 @@ Use @var{file1} and @var{file2} as modules and pass arg=foo as parameter to the
>   first module.
>   ETEXI
>
> +DEF("dtb", HAS_ARG, QEMU_OPTION_dtb, \
> +    "-dtb    file    use 'file' as device tree image\n", QEMU_ARCH_ARM)
> +STEXI
> +@item -dtb @var{file}
> +@findex -dtb
> +Use @var{file} as a device tree binary (dtb) image and pass it to the kernel
> +on boot.
> +ETEXI
> +
>   STEXI
>   @end table
>   ETEXI
> diff --git a/vl.c b/vl.c
> index 1d4c350..2d3347d 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -2526,6 +2526,9 @@ int main(int argc, char **argv, char **envp)
>               case QEMU_OPTION_append:
>                   qemu_opts_set(qemu_find_opts("machine"), 0, "append", optarg);
>                   break;
> +            case QEMU_OPTION_dtb:
> +                qemu_opts_set(qemu_find_opts("machine"), 0, "dtb", optarg);
> +                break;
>               case QEMU_OPTION_cdrom:
>                   drive_add(IF_DEFAULT, 2, optarg, CDROM_OPTS);
>                   break;
> @@ -3345,6 +3348,11 @@ int main(int argc, char **argv, char **envp)
>           exit(1);
>       }
>
> +    if (!linux_boot&&  machine_opts&&  qemu_opt_get(machine_opts, "dtb")) {
> +        fprintf(stderr, "-dtb only allowed with -kernel option\n");
> +        exit(1);
> +    }
> +
>       os_set_line_buffering();
>
>       if (init_timer_alarm()<  0) {
diff mbox

Patch

diff --git a/Makefile.target b/Makefile.target
index 68a5641..6a56b3e 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -373,6 +373,7 @@  obj-arm-y += vexpress.o
 obj-arm-y += strongarm.o
 obj-arm-y += collie.o
 obj-arm-y += pl041.o lm4549.o
+obj-arm-$(CONFIG_FDT) += device_tree.o
 
 obj-sh4-y = shix.o r2d.o sh7750.o sh7750_regnames.o tc58128.o
 obj-sh4-y += sh_timer.o sh_serial.o sh_intc.o sh_pci.o sm501.o
diff --git a/configure b/configure
index f9d5330..7d509e8 100755
--- a/configure
+++ b/configure
@@ -3472,6 +3472,7 @@  case "$target_arch2" in
     gdb_xml_files="arm-core.xml arm-vfp.xml arm-vfp3.xml arm-neon.xml"
     target_phys_bits=32
     target_llong_alignment=4
+    target_libs_softmmu="$fdt_libs"
   ;;
   cris)
     target_nptl="yes"
diff --git a/hw/arm-misc.h b/hw/arm-misc.h
index 306013a..734bd82 100644
--- a/hw/arm-misc.h
+++ b/hw/arm-misc.h
@@ -29,6 +29,7 @@  struct arm_boot_info {
     const char *kernel_filename;
     const char *kernel_cmdline;
     const char *initrd_filename;
+    const char *dtb_filename;
     target_phys_addr_t loader_start;
     /* multicore boards that use the default secondary core boot functions
      * need to put the address of the secondary boot code, the boot reg,
diff --git a/hw/arm_boot.c b/hw/arm_boot.c
index 2ef25ca..fc66910 100644
--- a/hw/arm_boot.c
+++ b/hw/arm_boot.c
@@ -7,11 +7,14 @@ 
  * This code is licensed under the GPL.
  */
 
+#include "config.h"
 #include "hw.h"
 #include "arm-misc.h"
 #include "sysemu.h"
+#include "boards.h"
 #include "loader.h"
 #include "elf.h"
+#include "device_tree.h"
 
 #define KERNEL_ARGS_ADDR 0x100
 #define KERNEL_LOAD_ADDR 0x00010000
@@ -208,6 +211,67 @@  static void set_kernel_args_old(const struct arm_boot_info *info)
     }
 }
 
+static int load_dtb(target_phys_addr_t addr, const struct arm_boot_info *binfo)
+{
+#ifdef CONFIG_FDT
+    uint32_t mem_reg_property[] = { cpu_to_be32(binfo->loader_start),
+                                    cpu_to_be32(binfo->ram_size) };
+    void *fdt = NULL;
+    char *filename;
+    int size, rc;
+
+    filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, binfo->dtb_filename);
+    if (!filename) {
+        fprintf(stderr, "Couldn't open dtb file %s\n", binfo->dtb_filename);
+        return -1;
+    }
+
+    fdt = load_device_tree(filename, &size);
+    if (!fdt) {
+        fprintf(stderr, "Couldn't open dtb file %s\n", filename);
+        g_free(filename);
+        return -1;
+    }
+    g_free(filename);
+
+    rc = qemu_devtree_setprop(fdt, "/memory", "reg", mem_reg_property,
+                               sizeof(mem_reg_property));
+    if (rc < 0) {
+        fprintf(stderr, "couldn't set /memory/reg\n");
+    }
+
+    rc = qemu_devtree_setprop_string(fdt, "/chosen", "bootargs",
+                                      binfo->kernel_cmdline);
+    if (rc < 0) {
+        fprintf(stderr, "couldn't set /chosen/bootargs\n");
+    }
+
+    if (binfo->initrd_size) {
+        rc = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-start",
+                binfo->loader_start + INITRD_LOAD_ADDR);
+        if (rc < 0) {
+            fprintf(stderr, "couldn't set /chosen/linux,initrd-start\n");
+        }
+
+        rc = qemu_devtree_setprop_cell(fdt, "/chosen", "linux,initrd-end",
+                    binfo->loader_start + INITRD_LOAD_ADDR +
+                    binfo->initrd_size);
+        if (rc < 0) {
+            fprintf(stderr, "couldn't set /chosen/linux,initrd-end\n");
+        }
+    }
+
+    cpu_physical_memory_write(addr, fdt, size);
+
+    return 0;
+
+#else
+    fprintf(stderr, "Device tree requested, "
+                "but qemu was compiled without fdt support\n");
+    return -1;
+#endif
+}
+
 static void do_cpu_reset(void *opaque)
 {
     CPUState *env = opaque;
@@ -222,10 +286,12 @@  static void do_cpu_reset(void *opaque)
         } else {
             if (env == first_cpu) {
                 env->regs[15] = info->loader_start;
-                if (old_param) {
-                    set_kernel_args_old(info);
-                } else {
-                    set_kernel_args(info);
+                if (!info->dtb_filename) {
+                    if (old_param) {
+                        set_kernel_args_old(info);
+                    } else {
+                        set_kernel_args(info);
+                    }
                 }
             } else {
                 info->secondary_cpu_reset_hook(env, info);
@@ -243,6 +309,7 @@  void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
     uint64_t elf_entry;
     target_phys_addr_t entry;
     int big_endian;
+    QemuOpts *machine_opts;
 
     /* Load the kernel.  */
     if (!info->kernel_filename) {
@@ -250,6 +317,13 @@  void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
         exit(1);
     }
 
+    machine_opts = qemu_opts_find(qemu_find_opts("machine"), 0);
+    if (machine_opts) {
+        info->dtb_filename = qemu_opt_get(machine_opts, "dtb");
+    } else {
+        info->dtb_filename = NULL;
+    }
+
     if (!info->secondary_cpu_reset_hook) {
         info->secondary_cpu_reset_hook = default_reset_secondary;
     }
@@ -300,8 +374,25 @@  void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
         } else {
             initrd_size = 0;
         }
+        info->initrd_size = initrd_size;
+
         bootloader[4] = info->board_id;
-        bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR;
+
+        /* for device tree boot, we pass the DTB directly in r2. Otherwise
+         * we point to the kernel args.
+         */
+        if (info->dtb_filename) {
+            /* Place the DTB after the initrd in memory */
+            target_phys_addr_t dtb_start = TARGET_PAGE_ALIGN(info->loader_start
+                                                             + INITRD_LOAD_ADDR
+                                                             + initrd_size);
+            if (load_dtb(dtb_start, info)) {
+                exit(1);
+            }
+            bootloader[5] = dtb_start;
+        } else {
+            bootloader[5] = info->loader_start + KERNEL_ARGS_ADDR;
+        }
         bootloader[6] = entry;
         for (n = 0; n < sizeof(bootloader) / 4; n++) {
             bootloader[n] = tswap32(bootloader[n]);
@@ -311,7 +402,6 @@  void arm_load_kernel(CPUState *env, struct arm_boot_info *info)
         if (info->nb_cpus > 1) {
             info->write_secondary_boot(env, info);
         }
-        info->initrd_size = initrd_size;
     }
     info->is_linux = is_linux;
 
diff --git a/qemu-config.c b/qemu-config.c
index 7d9da78..be84a03 100644
--- a/qemu-config.c
+++ b/qemu-config.c
@@ -578,6 +578,10 @@  static QemuOptsList qemu_machine_opts = {
             .name = "append",
             .type = QEMU_OPT_STRING,
             .help = "Linux kernel command line",
+        }, {
+            .name = "dtb",
+            .type = QEMU_OPT_STRING,
+            .help = "Linux kernel device tree file",
         },
         { /* End of list */ }
     },
diff --git a/qemu-options.hx b/qemu-options.hx
index b129996..e38799c 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2037,6 +2037,15 @@  Use @var{file1} and @var{file2} as modules and pass arg=foo as parameter to the
 first module.
 ETEXI
 
+DEF("dtb", HAS_ARG, QEMU_OPTION_dtb, \
+    "-dtb    file    use 'file' as device tree image\n", QEMU_ARCH_ARM)
+STEXI
+@item -dtb @var{file}
+@findex -dtb
+Use @var{file} as a device tree binary (dtb) image and pass it to the kernel
+on boot.
+ETEXI
+
 STEXI
 @end table
 ETEXI
diff --git a/vl.c b/vl.c
index 1d4c350..2d3347d 100644
--- a/vl.c
+++ b/vl.c
@@ -2526,6 +2526,9 @@  int main(int argc, char **argv, char **envp)
             case QEMU_OPTION_append:
                 qemu_opts_set(qemu_find_opts("machine"), 0, "append", optarg);
                 break;
+            case QEMU_OPTION_dtb:
+                qemu_opts_set(qemu_find_opts("machine"), 0, "dtb", optarg);
+                break;
             case QEMU_OPTION_cdrom:
                 drive_add(IF_DEFAULT, 2, optarg, CDROM_OPTS);
                 break;
@@ -3345,6 +3348,11 @@  int main(int argc, char **argv, char **envp)
         exit(1);
     }
 
+    if (!linux_boot && machine_opts && qemu_opt_get(machine_opts, "dtb")) {
+        fprintf(stderr, "-dtb only allowed with -kernel option\n");
+        exit(1);
+    }
+
     os_set_line_buffering();
 
     if (init_timer_alarm() < 0) {