Skip to contentSkip to conversation

Linux boot API and usage in ARM boots

RFC In review Peter Crosthwaite 2 participants 8 messages Jun 30, 2015

lore
Share & tools
lore
#0 lore raw

cover Linux boot API and usage in ARM boots

Hi Peter and All,

QEMU continues to support booting Linux kernels directly without firmware, but Linux is becoming more and more reliant on firmware or bootloader setup. In cases where the firmware is very complicated, the firmware should just be run as the guest and Linux boot is defeatured. But in many cases there is only a handful of operations needed to get into a well defined linux-operable state.

So add an interface that devices can implement that allow definition of that state. QEMU Bootloaders can call the linux_init fn for these devices when it is identified that the boot image is Linux sans firmware.

Not complete. Need to solve the problem of conditionals. I.e. how to handle a GIC NS setup only in a NS boot. Opaque data might do it.

RFCing as Peter sent a related series for GIC NS setup.

P1 adds a recursive version of object_child_for_each which allows full QOM tree iterations. I have another use of this out of tree (but well out of scope of this series).

P2 is the new API. P3 adds it to GIC, but I have #if 0'd the functional code as it should be rebased to Peter's implementation of the GIC-side functionality. P4 adds boot.c support for Linux devs.

Regards,
Peter

Peter Crosthwaite (4):
qom: Add recursive version of object_child_for_each
hw: Introduce Linux Device API
intc: arm_gic: Implement Linux boot
arm: boot: Add support for Linux specific boot devs

 hw/Makefile.objs         |  1 +
 hw/arm/boot.c            | 18 ++++++++++++++++++
 hw/guest/Makefile.objs   |  1 +
 hw/guest/linux.c         | 14 ++++++++++++++
 hw/intc/arm_gic.c        | 21 +++++++++++++++++++++
 include/hw/guest/linux.h | 32 ++++++++++++++++++++++++++++++++
 include/qom/object.h     | 15 +++++++++++++++
 qom/object.c             | 25 ++++++++++++++++++++++---
 8 files changed, 124 insertions(+), 3 deletions(-)
 create mode 100644 hw/guest/Makefile.objs
 create mode 100644 hw/guest/linux.c
 create mode 100644 include/hw/guest/linux.h
Signature · 1 line
2.4.5.3.g6a5966f
#1 lore raw

RFC 1/4 qom: Add recursive version of object_child_for_each

Useful for iterating through an entire QOM subtree.

---
 include/qom/object.h | 15 +++++++++++++++
 qom/object.c         | 25 ++++++++++++++++++++++---
 2 files changed, 37 insertions(+), 3 deletions(-)
include/qom/object.h +15 -0
index 807978e..be7280c 100644
@@ -1494,6 +1494,21 @@ int object_child_foreach(Object *obj, int (*fn)(Object *child, void *opaque),
                          void *opaque);
 
 /**
+ * object_child_foreach_recursive:
+ * @obj: the object whose children will be navigated
+ * @fn: the iterator function to be called
+ * @opaque: an opaque value that will be passed to the iterator
+ *
+ * Call @fn passing each child of @obj and @opaque to it, until @fn returns
+ * non-zero. Calls recursively, all child nodes of @obj will also be passed
+ * all the way down to the leaf nodes of the tree. Depth first ordering.
+ *
+ * Returns: The last value returned by @fn, or 0 if there is no child.
+ */
+int object_child_foreach_recursive(Object *obj,
+                                   int (*fn)(Object *child, void *opaque),
+                                   void *opaque);
+/**
  * container_get:
  * @root: root of the #path, e.g., object_get_root()
  * @path: path to the container
qom/object.c +22 -3
index ee38431..081da83 100644
@@ -775,23 +775,42 @@ void object_class_foreach(void (*fn)(ObjectClass *klass, void *opaque),
     enumerating_types = false;
 }
 
-int object_child_foreach(Object *obj, int (*fn)(Object *child, void *opaque),
-                         void *opaque)
+static int do_object_child_foreach(Object *obj,
+                                   int (*fn)(Object *child, void *opaque),
+                                   void *opaque, bool recurse)
 {
     ObjectProperty *prop, *next;
     int ret = 0;
 
     QTAILQ_FOREACH_SAFE(prop, &obj->properties, node, next) {
         if (object_property_is_child(prop)) {
-            ret = fn(prop->opaque, opaque);
+            Object *child = prop->opaque;
+
+            ret = fn(child, opaque);
             if (ret != 0) {
                 break;
             }
+            if (recurse) {
+                do_object_child_foreach(child, fn, opaque, true);
+            }
         }
     }
     return ret;
 }
 
+int object_child_foreach(Object *obj, int (*fn)(Object *child, void *opaque),
+                         void *opaque)
+{
+    return do_object_child_foreach(obj, fn, opaque, false);
+}
+
+int object_child_foreach_recursive(Object *obj,
+                                   int (*fn)(Object *child, void *opaque),
+                                   void *opaque)
+{
+    return do_object_child_foreach(obj, fn, opaque, true);
+}
+
 static void object_class_get_list_tramp(ObjectClass *klass, void *opaque)
 {
     GSList **list = opaque;
Signature · 1 line
2.4.5.3.g6a5966f
#2 lore raw

RFC 2/4 hw: Introduce Linux Device API

In reply to Peter Crosthwaite

Introduce an interface for a device with some basic linux awareness. Allows devices to implement setup routines to emulate the device as when Linux handoff happens. Some devices needs this to emulate firmware or bootloader behaviour.

This allows use of -kernel -dtb -initrd style boots on platforms that have non-trivial firmware setup requirements.

---
 hw/Makefile.objs         |  1 +
 hw/guest/Makefile.objs   |  1 +
 hw/guest/linux.c         | 14 ++++++++++++++
 include/hw/guest/linux.h | 32 ++++++++++++++++++++++++++++++++
 4 files changed, 48 insertions(+)
 create mode 100644 hw/guest/Makefile.objs
 create mode 100644 hw/guest/linux.c
 create mode 100644 include/hw/guest/linux.h
hw/Makefile.objs +1 -0
index 73afa41..3b03d90 100644
@@ -7,6 +7,7 @@ devices-dirs-$(CONFIG_SOFTMMU) += char/
 devices-dirs-$(CONFIG_SOFTMMU) += cpu/
 devices-dirs-$(CONFIG_SOFTMMU) += display/
 devices-dirs-$(CONFIG_SOFTMMU) += dma/
+devices-dirs-$(CONFIG_SOFTMMU) += guest/
 devices-dirs-$(CONFIG_SOFTMMU) += gpio/
 devices-dirs-$(CONFIG_SOFTMMU) += i2c/
 devices-dirs-$(CONFIG_SOFTMMU) += ide/
hw/guest/Makefile.objs new file +1 -0
new file mode 100644
index 0000000..a413c89
@@ -0,0 +1 @@
+common-obj-y += linux.o
hw/guest/linux.c new file +14 -0
new file mode 100644
index 0000000..70d9652
@@ -0,0 +1,14 @@
+#include "hw/guest/linux.h"
+
+static const TypeInfo linux_device_info = {
+    .name          = TYPE_LINUX_DEVICE,
+    .parent        = TYPE_INTERFACE,
+    .class_size = sizeof(LinuxDeviceClass),
+};
+
+static void linux_register_types(void)
+{
+    type_register_static(&linux_device_info);
+}
+
+type_init(linux_register_types)
include/hw/guest/linux.h new file +32 -0
new file mode 100644
index 0000000..b481a33
@@ -0,0 +1,32 @@
+#ifndef GUEST_LINUX_H
+
+#include "qemu-common.h"
+#include "qom/object.h"
+
+#define TYPE_LINUX_DEVICE "linux,device"
+
+#define LINUX_DEVICE_CLASS(klass) \
+    OBJECT_CLASS_CHECK(LinuxDeviceClass, (klass), TYPE_LINUX_DEVICE)
+#define LINUX_DEVICE_GET_CLASS(obj) \
+    OBJECT_GET_CLASS(LinuxDeviceClass, (obj), TYPE_LINUX_DEVICE)
+#define LINUX_DEVICE(obj) \
+    INTERFACE_CHECK(LinuxDevice, (obj), TYPE_LINUX_DEVICE)
+
+typedef struct LinuxDevice {
+    /*< private >*/
+    Object parent_obj;
+} LinuxDevice;
+
+typedef struct LinuxDeviceClass {
+    /*< private > */
+    InterfaceClass parent_class;
+
+    /*< public >*/
+    /** linux_init - Init the device for a Linux boot. Setup the device in the
+     * correct post-firmware state for a Linux boot.
+     */
+    void (*linux_init)(LinuxDevice *obj);
+} LinuxDeviceClass;
+
+#define GUEST_LINUX_H
+#endif
Signature · 1 line
2.4.5.3.g6a5966f
#3 lore raw

RFC 3/4 intc: arm_gic: Implement Linux boot

In reply to Peter Crosthwaite

Change all the interrupt groups to 1 to enable interrupts from the NS world. This gives a Linux guest full control of the interrupts when booting directly with -kernel.

---
Needs to be rebased on Peter's work
---

---
 hw/intc/arm_gic.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
hw/intc/arm_gic.c +21 -0
index 454bfd7..9374145 100644
@@ -22,6 +22,8 @@
 #include "gic_internal.h"
 #include "qom/cpu.h"
 
+#include "hw/guest/linux.h"
+
 //#define DEBUG_GIC
 
 #ifdef DEBUG_GIC
@@ -1135,13 +1137,28 @@ static void arm_gic_realize(DeviceState *dev, Error **errp)
     }
 }
 
+static void arm_gic_linux_init(LinuxDevice *obj)
+{
+    /* FIXME: rebase - this is based on Xilinx tree code */
+#if 0
+    GICState *s = ARM_GIC(obj);
+    int i;
+
+    for (i = 0 ; i < s->num_irq; ++i) {
+        s->irq_state[i].group = 1;
+    }
+#endif
+}
+
 static void arm_gic_class_init(ObjectClass *klass, void *data)
 {
     DeviceClass *dc = DEVICE_CLASS(klass);
     ARMGICClass *agc = ARM_GIC_CLASS(klass);
+    LinuxDeviceClass *ldc = LINUX_DEVICE_CLASS(klass);
 
     agc->parent_realize = dc->realize;
     dc->realize = arm_gic_realize;
+    ldc->linux_init = arm_gic_linux_init;
 }
 
 static const TypeInfo arm_gic_info = {
@@ -1150,6 +1167,10 @@ static const TypeInfo arm_gic_info = {
     .instance_size = sizeof(GICState),
     .class_init = arm_gic_class_init,
     .class_size = sizeof(ARMGICClass),
+    .interfaces = (InterfaceInfo []) {
+        { TYPE_LINUX_DEVICE },
+        { },
+    }
 };
 
 static void arm_gic_register_types(void)
Signature · 1 line
2.4.5.3.g6a5966f
#4 lore raw

Re: [Qemu-devel] [RFC v0 3/4] intc: arm_gic: Implement Linux boot

Quoted 31 lines · Peter Crosthwaite

Change all the interrupt groups to 1 to enable interrupts from the NS world. This gives a Linux guest full control of the interrupts when booting directly with -kernel.

---
Needs to be rebased on Peter's work
---

---
 hw/intc/arm_gic.c | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)
hw/intc/arm_gic.c +4 -0
index 454bfd7..9374145 100644
@@ -22,6 +22,8 @@
 #include "gic_internal.h"
 #include "qom/cpu.h"
 
+#include "hw/guest/linux.h"
+
 //#define DEBUG_GIC
 
 #ifdef DEBUG_GIC
@@ -1135,13 +1137,28 @@ static void arm_gic_realize(DeviceState *dev, Error **errp)
     }
 }
 
+static void arm_gic_linux_init(LinuxDevice *obj)
+{

hw/intc/arm_gic.c static void arm_gic_realize(DeviceState *dev, Error **errp) · line 1151

Quoted 10 lines
hw/intc/arm_gic.c +10 -0
+    /* FIXME: rebase - this is based on Xilinx tree code */
+#if 0
+    GICState *s = ARM_GIC(obj);
+    int i;
+
+    for (i = 0 ; i < s->num_irq; ++i) {
+        s->irq_state[i].group = 1;
+    }
+#endif
+}

Implementing this as a hook like this means it will get called for any Linux boot, but we only want to call it if we're going to boot the kernel nonsecure (if we boot the kernel secure then it will expect its irqs to be in group 0 as usual).

You also need to change the cpu priority register.

thanks
-- PMM

#5 lore raw

RFC 4/4 arm: boot: Add support for Linux specific boot devs

In reply to Peter Crosthwaite

If booting Linux, call the Linux specific init routine for all devs that support it.

---
Doesn't solve the problem of conditional setup, e.g. GIC needs to only do NS setup on NS boot. I think this should be solved by passing the boot_info to the GIC as opaque data.

---
 hw/arm/boot.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
hw/arm/boot.c +18 -0
index 1e7fd28..2cf0dcb 100644
@@ -18,6 +18,8 @@
 #include "qemu/config-file.h"
 #include "exec/address-spaces.h"
 
+#include "hw/guest/linux.h"
+
 /* Kernel boot protocol is specified in the kernel docs
  * Documentation/arm/Booting and Documentation/arm64/booting.txt
  * They have different preferred image load offsets from system RAM base.
@@ -442,6 +444,19 @@ fail:
     return -1;
 }
 
+static int do_linux_dev_init(Object *obj, void *opaue)
+{
+    if (object_dynamic_cast(obj, TYPE_LINUX_DEVICE)) {
+        LinuxDevice *ld = LINUX_DEVICE(obj);
+        LinuxDeviceClass *ldc = LINUX_DEVICE_GET_CLASS(obj);
+
+        if (ldc->linux_init) {
+            ldc->linux_init(ld);
+        }
+    }
+    return 0;
+}
+
 static void do_cpu_reset(void *opaque)
 {
     ARMCPU *cpu = opaque;
@@ -504,8 +519,11 @@ static void do_cpu_reset(void *opaque)
             } else {
                 info->secondary_cpu_reset_hook(cpu, info);
             }
+            object_child_foreach_recursive(object_get_root(),
+                                           do_linux_dev_init, NULL);
         }
     }
+
 }
 
 /**
Signature · 1 line
2.4.5.3.g6a5966f
#6 lore raw

Re: [Qemu-devel] [RFC v0 4/4] arm: boot: Add support for Linux specific boot devs

Quoted 47 lines · Peter Crosthwaite

If booting Linux, call the Linux specific init routine for all devs that support it.

---
Doesn't solve the problem of conditional setup, e.g. GIC needs to only do NS setup on NS boot. I think this should be solved by passing the boot_info to the GIC as opaque data.

---
 hw/arm/boot.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
hw/arm/boot.c +15 -0
index 1e7fd28..2cf0dcb 100644
@@ -18,6 +18,8 @@
 #include "qemu/config-file.h"
 #include "exec/address-spaces.h"
 
+#include "hw/guest/linux.h"
+
 /* Kernel boot protocol is specified in the kernel docs
  * Documentation/arm/Booting and Documentation/arm64/booting.txt
  * They have different preferred image load offsets from system RAM base.
@@ -442,6 +444,19 @@ fail:
     return -1;
 }
 
+static int do_linux_dev_init(Object *obj, void *opaue)
+{
+    if (object_dynamic_cast(obj, TYPE_LINUX_DEVICE)) {
+        LinuxDevice *ld = LINUX_DEVICE(obj);
+        LinuxDeviceClass *ldc = LINUX_DEVICE_GET_CLASS(obj);
+
+        if (ldc->linux_init) {
+            ldc->linux_init(ld);
+        }
+    }
+    return 0;
+}
+
 static void do_cpu_reset(void *opaque)
 {
     ARMCPU *cpu = opaque;

hw/arm/boot.c static void do_cpu_reset(void *opaque) · line 526

Quoted 8 lines
hw/arm/boot.c +3 -0
@@ -504,8 +519,11 @@ static void do_cpu_reset(void *opaque)
             } else {
                 info->secondary_cpu_reset_hook(cpu, info);
             }
+            object_child_foreach_recursive(object_get_root(),
+                                           do_linux_dev_init, NULL);
         }
     }
+

This will call the device hooks once per CPU in the system, which doesn't seem right... Also, isn't this reintroducing a reset-order dependency, where we now rely on the CPU reset happening after any devices that implement this hook (otherwise the device reset will override any changes made by the linux_init hook)?

-- PMM

#7 lore raw

Re: [Qemu-devel] [RFC v0 4/4] arm: boot: Add support for Linux specific boot devs

Quoted 56 lines · Peter Maydell
Quoted 54 lines · Peter Crosthwaite

If booting Linux, call the Linux specific init routine for all devs that support it.

---
Doesn't solve the problem of conditional setup, e.g. GIC needs to only do NS setup on NS boot. I think this should be solved by passing the boot_info to the GIC as opaque data.

---
 hw/arm/boot.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)
hw/arm/boot.c +17 -0
index 1e7fd28..2cf0dcb 100644
@@ -18,6 +18,8 @@
 #include "qemu/config-file.h"
 #include "exec/address-spaces.h"
 
+#include "hw/guest/linux.h"
+
 /* Kernel boot protocol is specified in the kernel docs
  * Documentation/arm/Booting and Documentation/arm64/booting.txt
  * They have different preferred image load offsets from system RAM base.
@@ -442,6 +444,19 @@ fail:
     return -1;
 }
 
+static int do_linux_dev_init(Object *obj, void *opaue)
+{
+    if (object_dynamic_cast(obj, TYPE_LINUX_DEVICE)) {
+        LinuxDevice *ld = LINUX_DEVICE(obj);
+        LinuxDeviceClass *ldc = LINUX_DEVICE_GET_CLASS(obj);
+
+        if (ldc->linux_init) {
+            ldc->linux_init(ld);
+        }
+    }
+    return 0;
+}
+
 static void do_cpu_reset(void *opaque)
 {
     ARMCPU *cpu = opaque;
@@ -504,8 +519,11 @@ static void do_cpu_reset(void *opaque)
             } else {
                 info->secondary_cpu_reset_hook(cpu, info);
             }
+            object_child_foreach_recursive(object_get_root(),
+                                           do_linux_dev_init, NULL);
         }
     }
Quoted 9 lines

hw/arm/boot.c static void do_cpu_reset(void *opaque) · line 526

Quoted 1 line
hw/arm/boot.c +1 -0
+

This will call the device hooks once per CPU in the system, which doesn't seem right... Also, isn't this reintroducing a reset-order dependency, where we now rely on the CPU reset happening after any devices that implement this hook (otherwise the device reset will override any changes made by the linux_init hook)?

Yes. Although your patches may provide to solution, as in your patch, as GIC just captures a flag in your linux_init equivalent. This means this iterator is misplaced and should happen at _notify rather than reset. That could be the correct API semantic - must be called before reset.

Regards,
Peter

Quoted 2 lines

-- PMM