diff mbox

[v2,7/9] PPC: e500: Support dynamically spawned sysbus devices

Message ID 1404324093-19225-8-git-send-email-agraf@suse.de
State New
Headers show

Commit Message

Alexander Graf July 2, 2014, 6:01 p.m. UTC
For e500 our approach to supporting dynamically spawned sysbus devices is to
create a simple bus from the guest's point of view within which we map those
devices dynamically.

We allocate memory regions always within the "platform" hole in address
space and map IRQs to predetermined IRQ lines that are reserved for platform
device usage.

This maps really nicely into device tree logic, so we can just tell the
guest about our virtual simple bus in device tree as well.

Signed-off-by: Alexander Graf <agraf@suse.de>

---

v1 -> v2:

  - access sysbus properties via qom
  - move platform bus definitions to params
  - move platform bus to 36bit address space
  - make naming more consistent
  - remove device_type from platform bus dt node
  - remove id field in dt generation
---
 hw/ppc/e500.c     | 251 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hw/ppc/e500.h     |   5 ++
 hw/ppc/e500plat.c |   6 ++
 3 files changed, 262 insertions(+)

Comments

Peter Maydell July 30, 2014, 2:55 p.m. UTC | #1
On 2 July 2014 19:01, Alexander Graf <agraf@suse.de> wrote:
> For e500 our approach to supporting dynamically spawned sysbus devices is to
> create a simple bus from the guest's point of view within which we map those
> devices dynamically.
>
> +    /* Connect sysbus device to virtual platform bus */
> +    for (i = 0; i < sbdev->num_irq; i++) {
> +        if (!sbdev->irqp[i]) {
> +            /* This IRQ is an incoming IRQ, we can't wire those here */

I don't understand this comment -- sysbus IRQs are all outbound
by definition. Lines going in to a device are GPIOs. (Ideally we
should get rid of "sysbus IRQs" completely and just use named
inbound GPIOs for everything.)

Also, sbdev->irqp[] is private to the sysbus implementation, so
you shouldn't be fishing about in it in the platform code.

thanks
-- PMM
Peter Crosthwaite July 31, 2014, 4:07 a.m. UTC | #2
On Thu, Jul 31, 2014 at 12:55 AM, Peter Maydell
<peter.maydell@linaro.org> wrote:
> On 2 July 2014 19:01, Alexander Graf <agraf@suse.de> wrote:
>> For e500 our approach to supporting dynamically spawned sysbus devices is to
>> create a simple bus from the guest's point of view within which we map those
>> devices dynamically.
>>
>> +    /* Connect sysbus device to virtual platform bus */
>> +    for (i = 0; i < sbdev->num_irq; i++) {
>> +        if (!sbdev->irqp[i]) {
>> +            /* This IRQ is an incoming IRQ, we can't wire those here */
>
> I don't understand this comment -- sysbus IRQs are all outbound
> by definition. Lines going in to a device are GPIOs. (Ideally we
> should get rid of "sysbus IRQs" completely and just use named
> inbound GPIOs for everything.)
>

I have some patches in this area. Will post shortly cc you and Alex.

> Also, sbdev->irqp[] is private to the sysbus implementation, so
> you shouldn't be fishing about in it in the platform code.
>

And this is in the crosshairs of that work:

@@ -47,8 +49,6 @@ struct SysBusDevice {
     /*< public >*/

     int num_irq;
-    qemu_irq irqs[QDEV_MAX_IRQ];
-    qemu_irq *irqp[QDEV_MAX_IRQ];


Regards,
Peter

> thanks
> -- PMM
>
diff mbox

Patch

diff --git a/hw/ppc/e500.c b/hw/ppc/e500.c
index bb2e75f..f7fe41c 100644
--- a/hw/ppc/e500.c
+++ b/hw/ppc/e500.c
@@ -36,6 +36,7 @@ 
 #include "exec/address-spaces.h"
 #include "qemu/host-utils.h"
 #include "hw/pci-host/ppce500.h"
+#include "qemu/error-report.h"
 
 #define EPAPR_MAGIC                (0x45504150)
 #define BINARY_DEVICE_TREE_FILE    "mpc8544ds.dtb"
@@ -47,6 +48,8 @@ 
 
 #define RAM_SIZES_ALIGN            (64UL << 20)
 
+#define E500_PLATFORM_BUS_PAGE_SHIFT 12
+
 /* TODO: parameterize */
 #define MPC8544_CCSRBAR_BASE       0xE0000000ULL
 #define MPC8544_CCSRBAR_SIZE       0x00100000ULL
@@ -122,6 +125,76 @@  static void dt_serial_create(void *fdt, unsigned long long offset,
     }
 }
 
+typedef struct PlatformDevtreeData {
+    void *fdt;
+    const char *mpic;
+    int irq_start;
+    const char *node;
+} PlatformDevtreeData;
+
+static int sysbus_device_create_devtree(Object *obj, void *opaque)
+{
+    PlatformDevtreeData *data = opaque;
+    Object *dev;
+    SysBusDevice *sbdev;
+    bool matched = false;
+
+    dev = object_dynamic_cast(obj, TYPE_SYS_BUS_DEVICE);
+    sbdev = (SysBusDevice *)dev;
+
+    if (!sbdev) {
+        /* Container, traverse it for children */
+        return object_child_foreach(obj, sysbus_device_create_devtree, data);
+    }
+
+    if (!matched) {
+        error_report("Device %s is not supported by this machine yet.",
+                     qdev_fw_name(DEVICE(dev)));
+        exit(1);
+    }
+
+    return 0;
+}
+
+static void platform_bus_create_devtree(PPCE500Params *params, void *fdt,
+                                        const char *mpic)
+{
+    gchar *node = g_strdup_printf("/platform@%"PRIx64, params->platform_bus_base);
+    const char platcomp[] = "qemu,platform\0simple-bus";
+    PlatformDevtreeData data;
+    Object *container;
+    uint64_t addr = params->platform_bus_base;
+    uint64_t size = params->platform_bus_size;
+    int irq_start = params->platform_bus_first_irq;
+
+    /* Create a /platform node that we can put all devices into */
+
+    qemu_fdt_add_subnode(fdt, node);
+    qemu_fdt_setprop(fdt, node, "compatible", platcomp, sizeof(platcomp));
+
+    /* Our platform bus region is less than 32bit big, so 1 cell is enough for
+       address and size */
+    qemu_fdt_setprop_cells(fdt, node, "#size-cells", 1);
+    qemu_fdt_setprop_cells(fdt, node, "#address-cells", 1);
+    qemu_fdt_setprop_cells(fdt, node, "ranges", 0, addr >> 32, addr, size);
+
+    qemu_fdt_setprop_phandle(fdt, node, "interrupt-parent", mpic);
+
+    /* Loop through all devices and create nodes for known ones */
+
+    data.fdt = fdt;
+    data.mpic = mpic;
+    data.irq_start = irq_start;
+    data.node = node;
+
+    container = container_get(qdev_get_machine(), "/peripheral");
+    sysbus_device_create_devtree(container, &data);
+    container = container_get(qdev_get_machine(), "/peripheral-anon");
+    sysbus_device_create_devtree(container, &data);
+
+    g_free(node);
+}
+
 static int ppce500_load_device_tree(MachineState *machine,
                                     PPCE500Params *params,
                                     hwaddr addr,
@@ -379,6 +452,10 @@  static int ppce500_load_device_tree(MachineState *machine,
     qemu_fdt_setprop_cell(fdt, pci, "#address-cells", 3);
     qemu_fdt_setprop_string(fdt, "/aliases", "pci0", pci);
 
+    if (params->has_platform_bus) {
+        platform_bus_create_devtree(params, fdt, mpic);
+    }
+
     params->fixup_devtree(params, fdt);
 
     if (toplevel_compat) {
@@ -618,6 +695,169 @@  static qemu_irq *ppce500_init_mpic(PPCE500Params *params, MemoryRegion *ccsr,
     return mpic;
 }
 
+typedef struct PlatformBusNotifier {
+    Notifier notifier;
+    MemoryRegion *address_space_mem;
+    qemu_irq *mpic;
+    PPCE500Params params;
+} PlatformBusNotifier;
+
+typedef struct PlatformBusInitData {
+    unsigned long *used_irqs;
+    unsigned long *used_mem;
+    MemoryRegion *mem;
+    qemu_irq *irqs;
+    int device_count;
+    PPCE500Params *params;
+} PlatformBusInitData;
+
+static int platform_bus_map_irq(PPCE500Params *params, SysBusDevice *sbdev,
+                                int n, unsigned long *used_irqs,
+                                qemu_irq *platform_irqs)
+{
+    int max_irqs = params->platform_bus_num_irqs;
+    char *prop = g_strdup_printf("irq[%d]", n);
+    int irqn = object_property_get_int(OBJECT(sbdev), prop, NULL);
+
+    if (irqn == SYSBUS_DYNAMIC) {
+        /* Find the first available IRQ */
+        irqn = find_first_zero_bit(used_irqs, max_irqs);
+    }
+
+    if ((irqn >= max_irqs) || test_and_set_bit(irqn, used_irqs)) {
+        hw_error("e500: IRQ %d is already allocated or no free IRQ left", irqn);
+    }
+
+    sysbus_connect_irq(sbdev, n, platform_irqs[irqn]);
+    object_property_set_int(OBJECT(sbdev), irqn, prop, NULL);
+
+    g_free(prop);
+    return 0;
+}
+
+static int platform_bus_map_mmio(PPCE500Params *params, SysBusDevice *sbdev,
+                                 int n, unsigned long *used_mem,
+                                 MemoryRegion *pmem)
+{
+    MemoryRegion *device_mem = sbdev->mmio[n].memory;
+    uint64_t size = memory_region_size(device_mem);
+    uint64_t page_size = (1 << E500_PLATFORM_BUS_PAGE_SHIFT);
+    uint64_t page_mask = page_size - 1;
+    uint64_t size_pages = (size + page_mask) >> E500_PLATFORM_BUS_PAGE_SHIFT;
+    uint64_t max_size = params->platform_bus_size;
+    uint64_t max_pages = max_size >> E500_PLATFORM_BUS_PAGE_SHIFT;
+    char *prop = g_strdup_printf("mmio[%d]", n);
+    hwaddr addr = object_property_get_int(OBJECT(sbdev), prop, NULL);
+    int page;
+    int i;
+
+    page = addr >> E500_PLATFORM_BUS_PAGE_SHIFT;
+    if (addr == SYSBUS_DYNAMIC) {
+        uint64_t size_pages_align;
+
+        /* Align the region to at least its own size granularity */
+        if (is_power_of_2(size_pages)) {
+            size_pages_align = size_pages;
+        } else {
+            size_pages_align = pow2floor(size_pages) << 1;
+        }
+
+        /* Find the first available region that fits */
+        page = bitmap_find_next_zero_area(used_mem, max_pages, 0, size_pages,
+                                          size_pages_align);
+
+        addr = (uint64_t)page << E500_PLATFORM_BUS_PAGE_SHIFT;
+    }
+
+    if (page >= max_pages || test_bit(page, used_mem) ||
+        (find_next_bit(used_mem, max_pages, page) < size_pages)) {
+        hw_error("e500: Memory [%"PRIx64":%"PRIx64" is already allocated or "
+                 "no slot left", addr, size);
+    }
+
+    for (i = page; i < (page + size_pages); i++) {
+        set_bit(i, used_mem);
+    }
+
+    memory_region_add_subregion(pmem, addr, device_mem);
+    sbdev->mmio[n].addr = addr;
+    object_property_set_int(OBJECT(sbdev), addr, prop, NULL);
+
+    g_free(prop);
+    return 0;
+}
+
+static int sysbus_device_check(Object *obj, void *opaque)
+{
+    PlatformBusInitData *init = opaque;
+    Object *dev;
+    SysBusDevice *sbdev;
+    int i;
+
+    dev = object_dynamic_cast(obj, TYPE_SYS_BUS_DEVICE);
+    sbdev = (SysBusDevice *)dev;
+
+    if (!sbdev) {
+        /* Container, traverse it for children */
+        return object_child_foreach(obj, sysbus_device_check, opaque);
+    }
+
+    /* Connect sysbus device to virtual platform bus */
+    for (i = 0; i < sbdev->num_irq; i++) {
+        if (!sbdev->irqp[i]) {
+            /* This IRQ is an incoming IRQ, we can't wire those here */
+            continue;
+        }
+        platform_bus_map_irq(init->params, sbdev, i, init->used_irqs, init->irqs);
+    }
+
+    for (i = 0; i < sbdev->num_mmio; i++) {
+        platform_bus_map_mmio(init->params, sbdev, i, init->used_mem, init->mem);
+    }
+
+    return 0;
+}
+
+static void platform_bus_init(PPCE500Params *params,
+                              MemoryRegion *address_space_mem,
+                              qemu_irq *mpic)
+{
+    uint64_t max_size = params->platform_bus_size;
+    uint64_t max_pages = max_size >> E500_PLATFORM_BUS_PAGE_SHIFT;
+    DECLARE_BITMAP(used_irqs, params->platform_bus_num_irqs);
+    DECLARE_BITMAP(used_mem, max_pages);
+    MemoryRegion *platform_region = g_new(MemoryRegion, 1);
+    Object *container;
+    PlatformBusInitData init = {
+        .used_irqs = used_irqs,
+        .used_mem = used_mem,
+        .mem = platform_region,
+        .irqs = &mpic[params->platform_bus_first_irq],
+        .params = params,
+    };
+
+    memory_region_init(platform_region, NULL, "platform devices",
+                       params->platform_bus_size);
+
+    bitmap_clear(used_irqs, 0, params->platform_bus_num_irqs);
+    bitmap_clear(used_mem, 0, max_pages);
+
+    /* Loop through all sysbus devices that were spawened outside the machine */
+    container = container_get(qdev_get_machine(), "/peripheral");
+    sysbus_device_check(container, &init);
+    container = container_get(qdev_get_machine(), "/peripheral-anon");
+    sysbus_device_check(container, &init);
+
+    memory_region_add_subregion(address_space_mem, params->platform_bus_base,
+                                platform_region);
+}
+
+static void platform_bus_init_notify(Notifier *notifier, void *data)
+{
+    PlatformBusNotifier *pn = (PlatformBusNotifier *)notifier;
+    platform_bus_init(&pn->params, pn->address_space_mem, pn->mpic);
+}
+
 void ppce500_init(MachineState *machine, PPCE500Params *params)
 {
     MemoryRegion *address_space_mem = get_system_memory();
@@ -770,6 +1010,17 @@  void ppce500_init(MachineState *machine, PPCE500Params *params)
         cur_base = (32 * 1024 * 1024);
     }
 
+    /* Platform Devices */
+    if (params->has_platform_bus) {
+        PlatformBusNotifier *notifier = g_new(PlatformBusNotifier, 1);
+
+        notifier->notifier.notify = platform_bus_init_notify;
+        notifier->address_space_mem = address_space_mem;
+        notifier->mpic = mpic;
+        notifier->params = *params;
+        qemu_add_machine_init_done_notifier(&notifier->notifier);
+    }
+
     /* Load kernel. */
     if (machine->kernel_filename) {
         kernel_base = cur_base;
diff --git a/hw/ppc/e500.h b/hw/ppc/e500.h
index 08b25fa..f1b2766 100644
--- a/hw/ppc/e500.h
+++ b/hw/ppc/e500.h
@@ -11,6 +11,11 @@  typedef struct PPCE500Params {
     void (*fixup_devtree)(struct PPCE500Params *params, void *fdt);
 
     int mpic_version;
+    bool has_platform_bus;
+    hwaddr platform_bus_base;
+    hwaddr platform_bus_size;
+    int platform_bus_first_irq;
+    int platform_bus_num_irqs;
 } PPCE500Params;
 
 void ppce500_init(MachineState *machine, PPCE500Params *params);
diff --git a/hw/ppc/e500plat.c b/hw/ppc/e500plat.c
index 27df31d..befe1d1 100644
--- a/hw/ppc/e500plat.c
+++ b/hw/ppc/e500plat.c
@@ -35,6 +35,11 @@  static void e500plat_init(MachineState *machine)
         .pci_nr_slots = PCI_SLOT_MAX - 1,
         .fixup_devtree = e500plat_fixup_devtree,
         .mpic_version = OPENPIC_MODEL_FSL_MPIC_42,
+        .has_platform_bus = true,
+        .platform_bus_base = 0xf00000000ULL,
+        .platform_bus_size = (128ULL * 1024 * 1024),
+        .platform_bus_first_irq = 5,
+        .platform_bus_num_irqs = 10,
     };
 
     /* Older KVM versions don't support EPR which breaks guests when we announce
@@ -51,6 +56,7 @@  static QEMUMachine e500plat_machine = {
     .desc = "generic paravirt e500 platform",
     .init = e500plat_init,
     .max_cpus = 32,
+    .has_dynamic_sysbus = true,
 };
 
 static void e500plat_machine_init(void)