From: Ruslan Ruslichenko <[email protected]>

The patch implements IRQ wiring logic for fdt created
devices.

The wiring performed in two stages. As devices are
created, their IRQs saved in the list.
After all devices processed, fdt_init_all_irqs() goes
through all IRQs and does actual wiring. For IRQs
which have multiple source, shared IRQ handler
allocated.

For interrupt controllers with auto_parent callback
implemented and their fdt node has 'interrupt and
'interrupts-extended' properties empty,
the auto_parent() will be called to connect them
to their interrupt-parent, for example to CPUs.

Signed-off-by: Ruslan Ruslichenko <[email protected]>
---
 hw/core/fdt_generic_util.c    | 154 ++++++++++++++++++++++++++++++++++
 include/hw/core/fdt_generic.h |  12 +++
 2 files changed, 166 insertions(+)

diff --git a/hw/core/fdt_generic_util.c b/hw/core/fdt_generic_util.c
index 838ed9a85c..63390e2f17 100644
--- a/hw/core/fdt_generic_util.c
+++ b/hw/core/fdt_generic_util.c
@@ -75,6 +75,88 @@ static void fdt_get_irq_info_from_intc(FDTMachineInfo *fdti, 
qemu_irq *ret,
                                        uint32_t *cells, uint32_t num_cells,
                                        uint32_t max, Error **errp);
 
+typedef struct QEMUIRQSharedState {
+    qemu_irq sink;
+    int num;
+    bool (*merge_fn)(bool *, int);
+/* FIXME: remove artificial limit */
+#define MAX_IRQ_SHARED_INPUTS 256
+    bool inputs[MAX_IRQ_SHARED_INPUTS];
+} QEMUIRQSharedState;
+
+static bool qemu_irq_shared_or_handler(bool *inputs, int n)
+{
+    int i;
+
+    assert(n < MAX_IRQ_SHARED_INPUTS);
+
+    for (i = 0; i < n; ++i) {
+        if (inputs[i]) {
+            return true;
+        }
+    }
+    return false;
+}
+
+static void qemu_irq_shared_handler(void *opaque, int n, int level)
+{
+    QEMUIRQSharedState *s = opaque;
+
+    assert(n < MAX_IRQ_SHARED_INPUTS);
+    s->inputs[n] = level;
+    qemu_set_irq(s->sink, s->merge_fn(s->inputs, s->num));
+}
+
+static void fdt_init_all_irqs(FDTMachineInfo *fdti)
+{
+    while (fdti->irqs) {
+        FDTIRQConnection *first = fdti->irqs;
+        qemu_irq sink = first->irq;
+        bool (*merge_fn)(bool *, int) = first->merge_fn;
+        int num_sources = 0;
+        FDTIRQConnection *irq;
+
+        for (irq = first; irq; irq = irq->next) {
+            if (irq->irq == sink) { /* Same sink */
+                num_sources++;
+            }
+        }
+        if (num_sources > 1) {
+            QEMUIRQSharedState *s = g_malloc0(sizeof *s);
+            s->sink = sink;
+            s->merge_fn = merge_fn;
+            qemu_irq *sources = qemu_allocate_irqs(qemu_irq_shared_handler, s,
+                                                   num_sources);
+            for (irq = first; irq; irq = irq->next) {
+                if (irq->irq == sink) {
+                    char *shared_irq_name = g_strdup_printf("shared-irq-%p",
+                                                            *sources);
+
+                    if (irq->merge_fn != merge_fn) {
+                        fprintf(stderr, "ERROR: inconsistent IRQ merge fns\n");
+                        exit(1);
+                    }
+
+                    object_property_add_child(OBJECT(irq->dev), 
shared_irq_name,
+                                              OBJECT(*sources));
+                    g_free(shared_irq_name);
+                    irq->irq = *(sources++);
+                    s->num++;
+                }
+            }
+        }
+        DB_PRINT(0, "%s: connected to %s irq line %d (%s)\n",
+                 first->sink_info ? first->sink_info : "",
+                 object_get_canonical_path(OBJECT(first->dev)),
+                 first->i, first->name ? first->name : "");
+
+        qdev_connect_gpio_out_named(DEVICE(first->dev), first->name, first->i,
+                                    first->irq);
+        fdti->irqs = first->next;
+        g_free(first);
+    }
+}
+
 FDTMachineInfo *fdt_generic_create_machine(void *fdt, qemu_irq *cpu_irq)
 {
     char node_path[DT_PATH_LENGTH];
@@ -90,6 +172,7 @@ FDTMachineInfo *fdt_generic_create_machine(void *fdt, 
qemu_irq *cpu_irq)
         while (qemu_co_enter_next(fdti->cq, NULL)) {
             ;
         }
+        fdt_init_all_irqs(fdti);
         memory_region_transaction_commit();
     } else {
         fprintf(stderr, "FDT: ERROR: cannot get root node from device tree 
%s\n"
@@ -958,6 +1041,75 @@ exit_reg_parse:
     return;
 }
 
+static void fdt_parse_node_irq_prop(FDTMachineInfo *fdti, char *node_path,
+                            Object *dev)
+{
+    int is_intc;
+    int i, j;
+
+    if (!object_dynamic_cast(dev, TYPE_SYS_BUS_DEVICE)) {
+        return;
+    }
+
+    {
+        int len;
+        fdt_get_property(fdti->fdt, fdt_path_offset(fdti->fdt, node_path),
+                                "interrupt-controller", &len);
+        is_intc = len >= 0;
+        DB_PRINT_NP(is_intc ? 0 : 1, "is interrupt controller: %c\n",
+                    is_intc ? 'y' : 'n');
+    }
+    /* connect irq */
+    j = 0;
+    for (i = 0;; i++) {
+        char irq_info[6 * 1024];
+        char *irq_info_p = irq_info;
+        bool map_mode;
+        int len = -1;
+        qemu_irq *irqs = fdt_get_irq_info(fdti, node_path, i, irq_info,
+                                            &map_mode);
+        /* INTCs inferr their top level, if no IRQ connection specified */
+        fdt_get_property(fdti->fdt, fdt_path_offset(fdti->fdt, node_path),
+                            "interrupts-extended", &len);
+        if (!irqs && is_intc && i == 0 && len <= 0) {
+            FDTGenericIntc *id = (FDTGenericIntc *)object_dynamic_cast(
+                                    dev, TYPE_FDT_GENERIC_INTC);
+            FDTGenericIntcClass *idc = FDT_GENERIC_INTC_GET_CLASS(id);
+            if (id && idc->auto_parent) {
+                Error *err = NULL;
+                idc->auto_parent(id, &err);
+            } else {
+                irqs = fdti->irq_base;
+            }
+        }
+        if (!irqs) {
+            break;
+        }
+        while (*irqs) {
+            FDTIRQConnection *irq = g_new0(FDTIRQConnection, 1);
+            *irq = (FDTIRQConnection) {
+                .dev = DEVICE(dev),
+                .name = SYSBUS_DEVICE_GPIO_IRQ,
+                .merge_fn = qemu_irq_shared_or_handler,
+                .i = j,
+                .irq = *irqs,
+                .sink_info = g_strdup(irq_info_p),
+                .next = fdti->irqs
+            };
+            if (!map_mode) {
+                j++;
+            } else {
+                irq_info_p += strlen(irq_info_p) + 1;
+            }
+            fdti->irqs = irq;
+            irqs++;
+        }
+        if (map_mode) {
+            j++;
+        }
+    }
+}
+
 static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *compat)
 {
     Object *dev, *parent;
@@ -1101,6 +1253,8 @@ static int fdt_init_qdev(char *node_path, FDTMachineInfo 
*fdti, char *compat)
 
     fdt_parse_node_reg_prop(fdti, node_path, dev);
 
+    fdt_parse_node_irq_prop(fdti, node_path, dev);
+
     g_free(dev_type);
     g_free(props);
 
diff --git a/include/hw/core/fdt_generic.h b/include/hw/core/fdt_generic.h
index 46b7dc084b..ad9e249156 100644
--- a/include/hw/core/fdt_generic.h
+++ b/include/hw/core/fdt_generic.h
@@ -24,6 +24,16 @@ typedef struct FDTCPUCluster {
     bool user;
 } FDTCPUCluster;
 
+typedef struct FDTIRQConnection {
+    DeviceState *dev;
+    const char *name;
+    int i;
+    bool (*merge_fn)(bool *, int);
+    qemu_irq irq;
+    char *sink_info; /* Debug only */
+    void *next;
+} FDTIRQConnection;
+
 typedef struct FDTMachineInfo {
     /* the fdt blob */
     void *fdt;
@@ -33,6 +43,8 @@ typedef struct FDTMachineInfo {
     FDTDevOpaque *dev_opaques;
     /* recheck coroutine queue */
     CoQueue *cq;
+    /* list of all IRQ connections */
+    FDTIRQConnection *irqs;
     /* list of all CPU clusters */
     FDTCPUCluster *clusters;
 } FDTMachineInfo;
-- 
2.43.0


Reply via email to