[PATCH 13/27] hw/core/fdt_generic_util: implement fdt_get_irq/_info API

Ruslan Ruslichenko posted 27 patches 1 week, 4 days ago
Maintainers: Peter Maydell <peter.maydell@linaro.org>, Alistair Francis <alistair@alistair23.me>, "Edgar E. Iglesias" <edgar.iglesias@gmail.com>, Eduardo Habkost <eduardo@habkost.net>, Marcel Apfelbaum <marcel.apfelbaum@gmail.com>, "Philippe Mathieu-Daudé" <philmd@linaro.org>, Yanan Wang <wangyanan55@huawei.com>, Zhao Liu <zhao1.liu@intel.com>, Paolo Bonzini <pbonzini@redhat.com>, "Daniel P. Berrangé" <berrange@redhat.com>, David Gibson <david@gibson.dropbear.id.au>, Peter Xu <peterx@redhat.com>
[PATCH 13/27] hw/core/fdt_generic_util: implement fdt_get_irq/_info API
Posted by Ruslan Ruslichenko 1 week, 4 days ago
From: Ruslan Ruslichenko <Ruslan_Ruslichenko@epam.com>

Both interfaces may be used by devices to get IRQ information
based device tree entry.
Both methods will identify interrupt parent for a device and
retrieve irq entry.

Signed-off-by: Ruslan Ruslichenko <Ruslan_Ruslichenko@epam.com>
---
 hw/core/fdt_generic_util.c         | 261 +++++++++++++++++++++++++++++
 include/hw/core/fdt_generic_util.h |  18 ++
 2 files changed, 279 insertions(+)

diff --git a/hw/core/fdt_generic_util.c b/hw/core/fdt_generic_util.c
index b22c612e0f..d57d9387a1 100644
--- a/hw/core/fdt_generic_util.c
+++ b/hw/core/fdt_generic_util.c
@@ -71,6 +71,11 @@ static int fdt_generic_num_cpus;
 
 static int simple_bus_fdt_init(char *bus_node_path, FDTMachineInfo *fdti);
 
+static void fdt_get_irq_info_from_intc(FDTMachineInfo *fdti, qemu_irq *ret,
+                                       char *intc_node_path,
+                                       uint32_t *cells, uint32_t num_cells,
+                                       uint32_t max, Error **errp);
+
 FDTMachineInfo *fdt_generic_create_machine(void *fdt, qemu_irq *cpu_irq)
 {
     char node_path[DT_PATH_LENGTH];
@@ -239,6 +244,262 @@ static int simple_bus_fdt_init(char *node_path, FDTMachineInfo *fdti)
     return 0;
 }
 
+static void fdt_get_irq_info_from_intc(FDTMachineInfo *fdti, qemu_irq *ret,
+                                       char *intc_node_path,
+                                       uint32_t *cells, uint32_t num_cells,
+                                       uint32_t max, Error **errp)
+{
+    FDTGenericIntcClass *intc_fdt_class;
+    DeviceState *intc;
+
+    while (!fdt_init_has_opaque(fdti, intc_node_path)) {
+        fdt_init_yield(fdti);
+    }
+    intc = DEVICE(fdt_init_get_opaque(fdti, intc_node_path));
+
+    if (!intc) {
+        goto fail;
+    }
+
+    while (!intc->realized) {
+        fdt_init_yield(fdti);
+    }
+
+    intc_fdt_class = FDT_GENERIC_INTC_GET_CLASS(intc);
+    if (!intc_fdt_class) {
+        goto fail;
+    }
+
+    intc_fdt_class->get_irq(FDT_GENERIC_INTC(intc), ret, cells, num_cells,
+                            max, errp);
+
+    return;
+fail:
+    error_setg(errp, "%s", __func__);
+}
+
+static uint32_t imap_cache[32 * 1024];
+static bool imap_cached;
+
+qemu_irq *fdt_get_irq_info(FDTMachineInfo *fdti, char *node_path, int irq_idx,
+                          char *info, bool *map_mode) {
+    void *fdt = fdti->fdt;
+    uint32_t intc_phandle, intc_cells, cells[32];
+    char intc_node_path[DT_PATH_LENGTH];
+    qemu_irq *ret = NULL;
+    int i;
+    Error *errp = NULL;
+
+    intc_phandle = qemu_fdt_getprop_cell(fdt, node_path, "interrupt-parent",
+                                         0, true, &errp);
+    if (errp) {
+        errp = NULL;
+        intc_cells = qemu_fdt_getprop_cell(fdt, node_path,
+                                           "#interrupt-cells", 0, true, &errp);
+        *map_mode = true;
+    } else {
+        if (qemu_devtree_get_node_by_phandle(fdt, intc_node_path,
+                                             intc_phandle)) {
+            goto fail;
+        }
+
+        /* Check if the device is using interrupt-maps */
+        qemu_fdt_getprop_cell(fdt, node_path, "interrupt-map-mask", 0,
+                              false, &errp);
+        if (!errp) {
+            errp = NULL;
+            intc_cells = qemu_fdt_getprop_cell(fdt, node_path,
+                                               "#interrupt-cells", 0,
+                                               true, &errp);
+            *map_mode = true;
+        } else {
+            errp = NULL;
+            intc_cells = qemu_fdt_getprop_cell(fdt, intc_node_path,
+                                               "#interrupt-cells", 0,
+                                               true, &errp);
+            *map_mode = false;
+        }
+    }
+
+    if (errp) {
+        goto fail;
+    }
+
+    DB_PRINT_NP(2, "%s intc_phandle: %d\n", node_path, intc_phandle);
+
+    for (i = 0; i < intc_cells; ++i) {
+        cells[i] = qemu_fdt_getprop_cell(fdt, node_path, "interrupts",
+                                        intc_cells * irq_idx + i, false, &errp);
+        if (errp) {
+            goto fail;
+        }
+    }
+
+    if (*map_mode) {
+        int k;
+        ret = g_new0(qemu_irq, 1);
+        int num_matches = 0;
+        int len;
+        g_autofree uint32_t *imap_mask = g_new(uint32_t, intc_cells);
+        const uint32_t *prop;
+        uint32_t *imap;
+        bool use_parent = false;
+
+        for (k = 0; k < intc_cells; ++k) {
+            imap_mask[k] = qemu_fdt_getprop_cell(fdt, node_path,
+                                                 "interrupt-map-mask", k + 2,
+                                                 true, &errp);
+            if (errp) {
+                goto fail;
+            }
+        }
+
+        /* Check if the device has an interrupt-map property */
+        prop = qemu_fdt_getprop(fdt, node_path, "interrupt-map", &len,
+                                  use_parent, &errp);
+        imap = g_memdup2(prop, len);
+        if (!imap || errp) {
+            /*
+             * If the device doesn't have an interrupt-map, try again with
+             * inheritance. This will return the parents interrupt-map
+             */
+            use_parent = true;
+            errp = NULL;
+
+            prop = qemu_fdt_getprop(fdt, node_path, "interrupt-map",
+                                      &len, use_parent, &errp);
+            if (!imap_cached) {
+                memcpy(imap_cache, prop, len);
+                imap_cached = true;
+            }
+            imap = imap_cache;
+
+            if (errp) {
+                goto fail;
+            }
+        }
+
+        len /= sizeof(uint32_t);
+
+        i = 0;
+        assert(imap);
+        while (i < len) {
+            if (!use_parent) {
+                /*
+                 * Only re-sync the interrupt-map when the device has it's
+                 * own map, to save time.
+                 */
+                prop = qemu_fdt_getprop(fdt, node_path, "interrupt-map", &len,
+                                          use_parent, &errp);
+                if (imap != imap_cache) {
+                    g_free(imap);
+                }
+                imap = g_memdup2(prop, len);
+                if (errp) {
+                    goto fail;
+                }
+
+                len /= sizeof(uint32_t);
+            }
+
+            bool match = true;
+            uint32_t new_intc_cells, new_cells[32];
+            i++; i++; /* FIXME: do address cells properly */
+            for (k = 0; k < intc_cells; ++k) {
+                uint32_t  map_val = be32_to_cpu(imap[i++]);
+                if ((cells[k] ^ map_val) & imap_mask[k]) {
+                    match = false;
+                }
+            }
+            /*
+             * when caching, we hackishly store the number of cells for
+             * the parent in the MSB. +1, so zero MSB means non cachd
+             * and the full lookup is needed.
+             */
+            intc_phandle = be32_to_cpu(imap[i++]);
+            if (intc_phandle & (0xffu << 24)) {
+                new_intc_cells = (intc_phandle >> 24) - 1;
+            } else {
+                if (qemu_devtree_get_node_by_phandle(fdt, intc_node_path,
+                                                     intc_phandle)) {
+                    goto fail;
+                }
+                new_intc_cells = qemu_fdt_getprop_cell(fdt, intc_node_path,
+                                                       "#interrupt-cells", 0,
+                                                       false, &errp);
+                imap[i - 1] = cpu_to_be32(intc_phandle |
+                                            (new_intc_cells + 1) << 24);
+                if (errp) {
+                    goto fail;
+                }
+            }
+            for (k = 0; k < new_intc_cells; ++k) {
+                new_cells[k] = be32_to_cpu(imap[i++]);
+            }
+            if (match) {
+                num_matches++;
+                ret = g_renew(qemu_irq, ret, num_matches + 1);
+                if (intc_phandle & (0xffu << 24)) {
+                    if (qemu_devtree_get_node_by_phandle(fdt, intc_node_path,
+                                                         intc_phandle &
+                                                         ((1 << 24) - 1))) {
+                        goto fail;
+                    }
+                }
+
+                DB_PRINT_NP(2, "Getting IRQ information: %s -> 0x%x (%s)\n",
+                            node_path, intc_phandle, intc_node_path);
+
+                memset(&ret[num_matches], 0, sizeof(*ret));
+                fdt_get_irq_info_from_intc(fdti, &ret[num_matches - 1],
+                                           intc_node_path, new_cells,
+                                           new_intc_cells, 1, NULL);
+                if (info) {
+                    sprintf(info, "%s", intc_node_path);
+                    info += strlen(info) + 1;
+                }
+            }
+        }
+
+        if (imap != imap_cache) {
+            g_free(imap);
+        }
+        return ret;
+    }
+
+    DB_PRINT_NP(2, "Getting IRQ information: %s -> %s\n",
+                node_path, intc_node_path);
+
+    ret = g_new0(qemu_irq, fdt_generic_num_cpus + 2);
+    fdt_get_irq_info_from_intc(fdti, ret, intc_node_path, cells, intc_cells,
+                               fdt_generic_num_cpus, &errp);
+
+    if (errp) {
+        goto fail;
+    }
+
+    /* FIXME: Phase out this info bussiness */
+    if (info) {
+        sprintf(info, "%s", intc_node_path);
+    }
+
+    return ret;
+
+fail:
+    if (errp) {
+        sprintf(info, "%s", error_get_pretty(errp));
+    } else {
+        sprintf(info, "(none)");
+    }
+    return NULL;
+}
+
+qemu_irq *fdt_get_irq(FDTMachineInfo *fdti, char *node_path, int irq_idx,
+                      bool *map_mode)
+{
+    return fdt_get_irq_info(fdti, node_path, irq_idx, NULL, map_mode);
+}
+
 /* FIXME: figure out a real solution to this */
 
 #define DIGIT(a) ((a) >= '0' && (a) <= '9')
diff --git a/include/hw/core/fdt_generic_util.h b/include/hw/core/fdt_generic_util.h
index c5aaa58dd6..dfc9fa3632 100644
--- a/include/hw/core/fdt_generic_util.h
+++ b/include/hw/core/fdt_generic_util.h
@@ -16,6 +16,24 @@
 
 FDTMachineInfo *fdt_generic_create_machine(void *fdt, qemu_irq *cpu_irq);
 
+/*
+ * get an irq for a device. The interrupt parent of a device is idenitified
+ * and the specified irq (by the interrupts device-tree property) is retrieved
+ */
+
+qemu_irq *fdt_get_irq(FDTMachineInfo *fdti, char *node_path, int irq_idx,
+                      bool *map_mode);
+
+/*
+ * same as above, but poulates err with non-zero if something goes wrong, and
+ * populates info with a human readable string giving some basic information
+ * about the interrupt connection found (or not found). Both arguments are
+ * optional (i.e. can be NULL)
+ */
+
+qemu_irq *fdt_get_irq_info(FDTMachineInfo *fdti, char *node_path, int irq_idx,
+                           char *info, bool *map_mode);
+
 #define TYPE_FDT_GENERIC_INTC "fdt-generic-intc"
 
 #define FDT_GENERIC_INTC_CLASS(klass) \
-- 
2.43.0