diff mbox

[v3,5/7] sysbus: Add new platform bus helper device

Message ID 1411572143-40345-6-git-send-email-agraf@suse.de
State New
Headers show

Commit Message

Alexander Graf Sept. 24, 2014, 3:22 p.m. UTC
We need to support spawning of sysbus devices dynamically via the command line.
The easiest way to represent these dynamically spawned devices in the guest's
memory and IRQ layout is by preallocating some space for dynamic sysbus devices.

This is what the "platform bus" device does. It is a sysbus device that exports
a configurably sized MMIO region and a configurable number of IRQ lines. When
this device encounters sysbus devices that have been dynamically created and not
manually wired up, it dynamically connects them to its own pool of resources.

The machine model can then loop through all of these devices and create a guest
configuration (device tree) to make them visible to the guest.

Signed-off-by: Alexander Graf <agraf@suse.de>
---
 hw/core/Makefile.objs     |   1 +
 hw/core/platform-bus.c    | 253 ++++++++++++++++++++++++++++++++++++++++++++++
 include/hw/platform-bus.h |  57 +++++++++++
 3 files changed, 311 insertions(+)
 create mode 100644 hw/core/platform-bus.c
 create mode 100644 include/hw/platform-bus.h

Comments

Paolo Bonzini Sept. 26, 2014, 12:05 p.m. UTC | #1
Il 24/09/2014 17:22, Alexander Graf ha scritto:
> +    if (!memory_region_is_mapped(sbdev_mr)) {
> +        /* Region is not mapped? */
> +        return -1;
> +    }
> +
> +    parent_mr = object_property_get_link(OBJECT(sbdev_mr), "container", NULL);
> +
> +    assert(parent_mr);
> +    if (parent_mr != pbus_mr_obj) {
> +        /* MMIO region is not mapped on platform bus */
> +        return -1;
> +    }
> +
> +    return object_property_get_int(OBJECT(sbdev_mr), "addr", NULL);

I think this should try going through the parent recursively until
reaching NULL (which would fail) or pbus_mr_obj.

Paolo
Alexander Graf Sept. 26, 2014, 12:26 p.m. UTC | #2
On 09/26/2014 02:05 PM, Paolo Bonzini wrote:
> Il 24/09/2014 17:22, Alexander Graf ha scritto:
>> +    if (!memory_region_is_mapped(sbdev_mr)) {
>> +        /* Region is not mapped? */
>> +        return -1;
>> +    }
>> +
>> +    parent_mr = object_property_get_link(OBJECT(sbdev_mr), "container", NULL);
>> +
>> +    assert(parent_mr);
>> +    if (parent_mr != pbus_mr_obj) {
>> +        /* MMIO region is not mapped on platform bus */
>> +        return -1;
>> +    }
>> +
>> +    return object_property_get_int(OBJECT(sbdev_mr), "addr", NULL);
> I think this should try going through the parent recursively until
> reaching NULL (which would fail) or pbus_mr_obj.

Are you sure? Imagine one sysbus device includes another. We only want 
to look at the region the lowest sysbus device exposes, no?


Alex
Paolo Bonzini Sept. 26, 2014, 12:44 p.m. UTC | #3
Il 26/09/2014 14:26, Alexander Graf ha scritto:
> 
> Are you sure? Imagine one sysbus device includes another. We only want
> to look at the region the lowest sysbus device exposes, no?

IIUC this function is used to build the device tree.  Say you have 2
consecutive memory regions and the device tree requires separate "reg"
entries for them.  But because they are consecutive (or perhaps because
you have a PCI version of the same device that sticks them in a single
BAR) you use a single MMIO area at the sysbus level.

In that case, you will use platform_bus_get_mmio_addr on the two inner
regions, not the outer one.

BTW, I think you will never have one sysbus device including another.
The contained device would be busless (similar to the "naked" 8250
device in hw/char/serial.c, except perhaps QOMified).

Paolo
Alexander Graf Sept. 29, 2014, 8:24 a.m. UTC | #4
On 26.09.14 14:44, Paolo Bonzini wrote:
> Il 26/09/2014 14:26, Alexander Graf ha scritto:
>>
>> Are you sure? Imagine one sysbus device includes another. We only want
>> to look at the region the lowest sysbus device exposes, no?
> 
> IIUC this function is used to build the device tree.

Yes, it's used to figure out the map of "start of region x of my device"
to "offset y in the platform bus mmio space".

> Say you have 2
> consecutive memory regions and the device tree requires separate "reg"
> entries for them.  But because they are consecutive (or perhaps because
> you have a PCI version of the same device that sticks them in a single
> BAR) you use a single MMIO area at the sysbus level.
> 
> In that case, you will use platform_bus_get_mmio_addr on the two inner
> regions, not the outer one.

In that case, you will use platform_bus_get_mmio_addr on the outer
region because that's what the device model exposes. The parameter to
this function that tells us which region we want is the "mmio region
number" that sysbus exposes.

If in device tree there are 2 reg properties, the device tree assembling
code has to do the conversion from sysbus granularity to device tree
granularity :).

> 
> BTW, I think you will never have one sysbus device including another.
> The contained device would be busless (similar to the "naked" 8250
> device in hw/char/serial.c, except perhaps QOMified).

Yeah, I agree :).


Alex
Paolo Bonzini Sept. 29, 2014, 8:30 a.m. UTC | #5
Il 29/09/2014 10:24, Alexander Graf ha scritto:
> 
> 
> On 26.09.14 14:44, Paolo Bonzini wrote:
>> Il 26/09/2014 14:26, Alexander Graf ha scritto:
>>>
>>> Are you sure? Imagine one sysbus device includes another. We only want
>>> to look at the region the lowest sysbus device exposes, no?
>>
>> IIUC this function is used to build the device tree.
> 
> Yes, it's used to figure out the map of "start of region x of my device"
> to "offset y in the platform bus mmio space".
> 
>> Say you have 2
>> consecutive memory regions and the device tree requires separate "reg"
>> entries for them.  But because they are consecutive (or perhaps because
>> you have a PCI version of the same device that sticks them in a single
>> BAR) you use a single MMIO area at the sysbus level.
>>
>> In that case, you will use platform_bus_get_mmio_addr on the two inner
>> regions, not the outer one.
> 
> In that case, you will use platform_bus_get_mmio_addr on the outer
> region because that's what the device model exposes. The parameter to
> this function that tells us which region we want is the "mmio region
> number" that sysbus exposes.
> 
> If in device tree there are 2 reg properties, the device tree assembling
> code has to do the conversion from sysbus granularity to device tree
> granularity :).

Thanks for the clarification.  Series

Reviewed-by: Paolo Bonzini <pbonzini@redhat.com>
diff mbox

Patch

diff --git a/hw/core/Makefile.objs b/hw/core/Makefile.objs
index 17845df..9dce1bc 100644
--- a/hw/core/Makefile.objs
+++ b/hw/core/Makefile.objs
@@ -14,3 +14,4 @@  common-obj-$(CONFIG_SOFTMMU) += machine.o
 common-obj-$(CONFIG_SOFTMMU) += null-machine.o
 common-obj-$(CONFIG_SOFTMMU) += loader.o
 common-obj-$(CONFIG_SOFTMMU) += qdev-properties-system.o
+common-obj-$(CONFIG_SOFTMMU) += platform-bus.o
diff --git a/hw/core/platform-bus.c b/hw/core/platform-bus.c
new file mode 100644
index 0000000..0f052b3
--- /dev/null
+++ b/hw/core/platform-bus.c
@@ -0,0 +1,253 @@ 
+/*
+ *  Platform Bus device to support dynamic Sysbus devices
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/platform-bus.h"
+#include "monitor/monitor.h"
+#include "exec/address-spaces.h"
+#include "sysemu/sysemu.h"
+
+
+/*
+ * Returns the PlatformBus IRQ number for a SysBusDevice irq number or -1 if
+ * the IRQ is not mapped on this Platform bus.
+ */
+int platform_bus_get_irqn(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+                          int n)
+{
+    qemu_irq sbirq = sysbus_get_connected_irq(sbdev, n);
+    int i;
+
+    for (i = 0; i < pbus->num_irqs; i++) {
+        if (pbus->irqs[i] == sbirq) {
+            return i;
+        }
+    }
+
+    /* IRQ not mapped on platform bus */
+    return -1;
+}
+
+/*
+ * Returns the PlatformBus MMIO region offset for Region n of a SysBusDevice or
+ * -1 if the region is not mapped on this Platform bus.
+ */
+hwaddr platform_bus_get_mmio_addr(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+                                  int n)
+{
+    MemoryRegion *pbus_mr = &pbus->mmio;
+    MemoryRegion *sbdev_mr = sysbus_mmio_get_region(sbdev, n);
+    Object *pbus_mr_obj = OBJECT(pbus_mr);
+    Object *parent_mr;
+
+    if (!memory_region_is_mapped(sbdev_mr)) {
+        /* Region is not mapped? */
+        return -1;
+    }
+
+    parent_mr = object_property_get_link(OBJECT(sbdev_mr), "container", NULL);
+
+    assert(parent_mr);
+    if (parent_mr != pbus_mr_obj) {
+        /* MMIO region is not mapped on platform bus */
+        return -1;
+    }
+
+    return object_property_get_int(OBJECT(sbdev_mr), "addr", NULL);
+}
+
+static int platform_bus_count_irqs(SysBusDevice *sbdev, void *opaque)
+{
+    PlatformBusDevice *pbus = opaque;
+    qemu_irq sbirq;
+    int n, i;
+
+    for (n = 0; ; n++) {
+        if (!sysbus_has_irq(sbdev, n)) {
+            break;
+        }
+
+        sbirq = sysbus_get_connected_irq(sbdev, n);
+        for (i = 0; i < pbus->num_irqs; i++) {
+            if (pbus->irqs[i] == sbirq) {
+                bitmap_set(pbus->used_irqs, i, 1);
+                break;
+            }
+        }
+    }
+
+    return 0;
+}
+
+/*
+ * Loop through all sysbus devices and look for unassigned IRQ lines as well as
+ * unassociated MMIO regions. Connect them to the platform bus if available.
+ */
+static void plaform_bus_refresh_irqs(PlatformBusDevice *pbus)
+{
+    bitmap_zero(pbus->used_irqs, pbus->num_irqs);
+    foreach_dynamic_sysbus_device(platform_bus_count_irqs, pbus);
+    pbus->done_gathering = true;
+}
+
+static int platform_bus_map_irq(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+                                int n)
+{
+    int max_irqs = pbus->num_irqs;
+    int irqn;
+
+    if (sysbus_is_irq_connected(sbdev, n)) {
+        /* IRQ is already mapped, nothing to do */
+        return 0;
+    }
+
+    irqn = find_first_zero_bit(pbus->used_irqs, max_irqs);
+    if (irqn >= max_irqs) {
+        hw_error("Platform Bus: Can not fit IRQ line");
+        return -1;
+    }
+
+    set_bit(irqn, pbus->used_irqs);
+    sysbus_connect_irq(sbdev, n, pbus->irqs[irqn]);
+
+    return 0;
+}
+
+static int platform_bus_map_mmio(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+                                 int n)
+{
+    MemoryRegion *sbdev_mr = sysbus_mmio_get_region(sbdev, n);
+    uint64_t size = memory_region_size(sbdev_mr);
+    uint64_t alignment = (1ULL << (63 - clz64(size + size - 1)));
+    uint64_t off;
+    bool found_region = false;
+
+    if (memory_region_is_mapped(sbdev_mr)) {
+        /* Region is already mapped, nothing to do */
+        return 0;
+    }
+
+    /*
+     * Look for empty space in the MMIO space that is naturally aligned with
+     * the target device's memory region
+     */
+    for (off = 0; off < pbus->mmio_size; off += alignment) {
+        if (!memory_region_find(&pbus->mmio, off, size).mr) {
+            found_region = true;
+            break;
+        }
+    }
+
+    if (!found_region) {
+        hw_error("Platform Bus: Can not fit MMIO region of size %"PRIx64, size);
+    }
+
+    /* Map the device's region into our Platform Bus MMIO space */
+    memory_region_add_subregion(&pbus->mmio, off, sbdev_mr);
+
+    return 0;
+}
+
+/*
+ * For each sysbus device, look for unassigned IRQ lines as well as
+ * unassociated MMIO regions. Connect them to the platform bus if available.
+ */
+static int link_sysbus_device(SysBusDevice *sbdev, void *opaque)
+{
+    PlatformBusDevice *pbus = opaque;
+    int i;
+
+    for (i = 0; sysbus_has_irq(sbdev, i); i++) {
+        platform_bus_map_irq(pbus, sbdev, i);
+    }
+
+    for (i = 0; sysbus_has_mmio(sbdev, i); i++) {
+        platform_bus_map_mmio(pbus, sbdev, i);
+    }
+
+    return 0;
+}
+
+static void platform_bus_init_notify(Notifier *notifier, void *data)
+{
+    PlatformBusDevice *pb = container_of(notifier, PlatformBusDevice, notifier);
+
+    /*
+     * Generate a bitmap of used IRQ lines, as the user might have specified
+     * them on the command line.
+     */
+    plaform_bus_refresh_irqs(pb);
+
+    foreach_dynamic_sysbus_device(link_sysbus_device, pb);
+}
+
+static void platform_bus_realize(DeviceState *dev, Error **errp)
+{
+    PlatformBusDevice *pbus;
+    SysBusDevice *d;
+    int i;
+
+    d = SYS_BUS_DEVICE(dev);
+    pbus = PLATFORM_BUS_DEVICE(dev);
+
+    memory_region_init(&pbus->mmio, NULL, "platform bus", pbus->mmio_size);
+    sysbus_init_mmio(d, &pbus->mmio);
+
+    pbus->used_irqs = bitmap_new(pbus->num_irqs);
+    pbus->irqs = g_new0(qemu_irq, pbus->num_irqs);
+    for (i = 0; i < pbus->num_irqs; i++) {
+        sysbus_init_irq(d, &pbus->irqs[i]);
+    }
+
+    /*
+     * Register notifier that allows us to gather dangling devices once the
+     * machine is completely assembled
+     */
+    pbus->notifier.notify = platform_bus_init_notify;
+    qemu_add_machine_init_done_notifier(&pbus->notifier);
+}
+
+static Property platform_bus_properties[] = {
+    DEFINE_PROP_UINT32("num_irqs", PlatformBusDevice, num_irqs, 0),
+    DEFINE_PROP_UINT32("mmio_size", PlatformBusDevice, mmio_size, 0),
+    DEFINE_PROP_END_OF_LIST()
+};
+
+static void platform_bus_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->realize = platform_bus_realize;
+    dc->props = platform_bus_properties;
+}
+
+static const TypeInfo platform_bus_info = {
+    .name          = TYPE_PLATFORM_BUS_DEVICE,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(PlatformBusDevice),
+    .class_init    = platform_bus_class_init,
+};
+
+static void platform_bus_register_types(void)
+{
+    type_register_static(&platform_bus_info);
+}
+
+type_init(platform_bus_register_types)
diff --git a/include/hw/platform-bus.h b/include/hw/platform-bus.h
new file mode 100644
index 0000000..bd42b83
--- /dev/null
+++ b/include/hw/platform-bus.h
@@ -0,0 +1,57 @@ 
+#ifndef HW_PLATFORM_BUS_H
+#define HW_PLATFORM_BUS_H 1
+
+/*
+ *  Platform Bus device to support dynamic Sysbus devices
+ *
+ * Copyright (C) 2014 Freescale Semiconductor, Inc. All rights reserved.
+ *
+ * Author: Alexander Graf, <agraf@suse.de>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "hw/sysbus.h"
+
+typedef struct PlatformBusDevice PlatformBusDevice;
+
+#define TYPE_PLATFORM_BUS_DEVICE "platform-bus-device"
+#define PLATFORM_BUS_DEVICE(obj) \
+     OBJECT_CHECK(PlatformBusDevice, (obj), TYPE_PLATFORM_BUS_DEVICE)
+#define PLATFORM_BUS_DEVICE_CLASS(klass) \
+     OBJECT_CLASS_CHECK(PlatformBusDeviceClass, (klass), TYPE_PLATFORM_BUS_DEVICE)
+#define PLATFORM_BUS_DEVICE_GET_CLASS(obj) \
+     OBJECT_GET_CLASS(PlatformBusDeviceClass, (obj), TYPE_PLATFORM_BUS_DEVICE)
+
+struct PlatformBusDevice {
+    /*< private >*/
+    SysBusDevice parent_obj;
+    Notifier notifier;
+    bool done_gathering;
+
+    /*< public >*/
+    uint32_t mmio_size;
+    MemoryRegion mmio;
+
+    uint32_t num_irqs;
+    qemu_irq *irqs;
+    unsigned long *used_irqs;
+};
+
+int platform_bus_get_irqn(PlatformBusDevice *platform_bus, SysBusDevice *sbdev,
+                          int n);
+hwaddr platform_bus_get_mmio_addr(PlatformBusDevice *pbus, SysBusDevice *sbdev,
+                                  int n);
+
+#endif /* !HW_PLATFORM_BUS_H */