Patchwork [RFC,V1,03/14] fdt_generic: First revision

login
register
mail settings
Submitter Peter A. G. Crosthwaite
Date Aug. 25, 2011, 6:41 a.m.
Message ID <1314254480-22438-4-git-send-email-peter.crosthwaite@petalogix.com>
Download mbox | patch
Permalink /patch/111470/
State New
Headers show

Comments

Peter A. G. Crosthwaite - Aug. 25, 2011, 6:41 a.m.
First revision of fdt generic infrastructure. These modules allow
for fdt generic machine models, which create machines to match a device
tree specification.

Signed-off-by: Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>
---
 Makefile.objs         |    3 +
 hw/fdt_generic.c      |  199 +++++++++++++++++++++++++++++++++++++++++++++++++
 hw/fdt_generic.h      |   92 +++++++++++++++++++++++
 hw/fdt_generic_util.c |  196 ++++++++++++++++++++++++++++++++++++++++++++++++
 hw/fdt_generic_util.h |   43 +++++++++++
 5 files changed, 533 insertions(+), 0 deletions(-)
 create mode 100644 hw/fdt_generic.c
 create mode 100644 hw/fdt_generic.h
 create mode 100644 hw/fdt_generic_util.c
 create mode 100644 hw/fdt_generic_util.h

Patch

diff --git a/Makefile.objs b/Makefile.objs
index d1f3e5d..9fd18ff 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -220,6 +220,9 @@  hw-obj-$(CONFIG_SMARTCARD) += usb-ccid.o ccid-card-passthru.o
 hw-obj-$(CONFIG_SMARTCARD_NSS) += ccid-card-emulated.o
 hw-obj-$(CONFIG_USB_REDIR) += usb-redir.o
 
+hw-obj-$(CONFIG_FDT) += fdt_generic.o
+hw-obj-$(CONFIG_FDT) += fdt_generic_util.o
+
 # PPC devices
 hw-obj-$(CONFIG_OPENPIC) += openpic.o
 hw-obj-$(CONFIG_PREP_PCI) += prep_pci.o
diff --git a/hw/fdt_generic.c b/hw/fdt_generic.c
new file mode 100644
index 0000000..cfcaaf6
--- /dev/null
+++ b/hw/fdt_generic.c
@@ -0,0 +1,199 @@ 
+/*
+ * Tables of FDT device models and their init functions. Keyed by compatibility
+ * strings, device instance names.
+ *
+ * Copyright (c) 2010 PetaLogix Qld Pty Ltd.
+ * Copyright (c) 2010 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "fdt_generic.h"
+
+#define FDT_GENERIC_MAX_PATTERN_LEN 1024
+
+typedef struct TableListNode {
+    void *next;
+    char key[FDT_GENERIC_MAX_PATTERN_LEN];
+    FDTInitFn fdt_init;
+    void *opaque;
+} TableListNode;
+
+/* add a node to the table specified by *head_p */
+
+static void add_to_table(
+        FDTInitFn fdt_init,
+        const char *key,
+        void *opaque,
+        TableListNode **head_p)
+{
+    TableListNode *nn = malloc(sizeof(*nn));
+    nn->next = (void*)(*head_p);
+    strcpy(nn->key, key);
+    nn->fdt_init = fdt_init;
+    nn->opaque = opaque;
+    *head_p = nn;
+}
+
+/* FIXME: add return codes that differentiate between not found and error */
+
+/* search a table for a key string and call the fdt init function if found.
+ * Returns 0 if a match if found, 1 otherwise
+ */
+
+static int fdt_init_search_table(
+        char *node_path,
+        FDTMachineInfo *fdti,
+        const char *key, /* string to match */
+        TableListNode **head) /* head of the list to search */
+{
+    TableListNode *c = *head;
+    if (c == NULL) {
+        return 1;
+    } else if (!strcmp(key, c->key)) {
+        return c->fdt_init(node_path, fdti, c->opaque);
+    }
+    return fdt_init_search_table(node_path, fdti, key,
+        (TableListNode**)(&(*head)->next));
+}
+
+TableListNode *compat_list_head = NULL;
+
+void add_to_compat_table(FDTInitFn fdt_init, const char *compat, void *opaque)
+{
+    add_to_table(fdt_init, compat, opaque, &compat_list_head);
+}
+
+int fdt_init_compat(char *node_path, FDTMachineInfo *fdti, const char *compat)
+{
+    return fdt_init_search_table(node_path, fdti, compat, &compat_list_head);
+}
+
+TableListNode *inst_bind_list_head = NULL;
+
+void add_to_inst_bind_table(FDTInitFn fdt_init, const char *name, void *opaque)
+{
+    add_to_table(fdt_init, name, opaque, &inst_bind_list_head);
+}
+
+int fdt_init_inst_bind(char *node_path, FDTMachineInfo *fdti,
+        const char *name)
+{
+    return fdt_init_search_table(node_path, fdti, name, &inst_bind_list_head);
+}
+
+TableListNode *force_list_head = NULL;
+
+void add_to_force_table(FDTInitFn fdt_init, const char *name, void *opaque)
+{
+    add_to_table(fdt_init, name, opaque, &force_list_head);
+}
+
+int fdt_force_bind_all(FDTMachineInfo *fdti)
+{
+    int ret = 0;
+    while (force_list_head != NULL) {
+        TableListNode *to_delete = force_list_head;
+        ret |= force_list_head->fdt_init(NULL, fdti, force_list_head->opaque);
+        force_list_head = force_list_head->next;
+        free(to_delete);
+    }
+    return ret;
+}
+
+static void dump_table(TableListNode *head)
+{
+    if (head == NULL) {
+        return;
+    }
+    printf("key : %s, opaque data %p\n", head->key, head->opaque);
+    dump_table(head->next);
+}
+
+void dump_compat_table(void)
+{
+    printf("FDT COMPATIBILITY TABLE:\n");
+    dump_table(compat_list_head);
+}
+
+void dump_inst_bind_table(void)
+{
+    printf("FDT INSTANCE BINDING TABLE:\n");
+    dump_table(inst_bind_list_head);
+}
+
+void fdt_init_yield(FDTMachineInfo *fdti)
+{
+    qemu_co_queue_wait(fdti->cq);
+}
+
+void fdt_init_set_opaque(FDTMachineInfo *fdti, char *node_path, void *opaque)
+{
+    FDTDevOpaque *dp;
+    for (dp = fdti->dev_opaques;
+        dp->node_path && strcmp(dp->node_path, node_path);
+        dp++);
+    if (!dp->node_path) {
+        dp->node_path = strdup(node_path);
+    }
+    dp->opaque = opaque;
+}
+
+int fdt_init_has_opaque(FDTMachineInfo *fdti, char *node_path)
+{
+    FDTDevOpaque *dp;
+    for (dp = fdti->dev_opaques; dp->node_path; dp++) {
+        if (!strcmp(dp->node_path, node_path)) {
+            return 1;
+         }
+    }
+    return 0;
+}
+
+void *fdt_init_get_opaque(FDTMachineInfo *fdti, char *node_path)
+{
+    FDTDevOpaque *dp;
+    for (dp = fdti->dev_opaques; dp->node_path; dp++) {
+        if (!strcmp(dp->node_path, node_path)) {
+            return dp->opaque;
+        }
+    }
+    return NULL;
+}
+
+FDTMachineInfo *fdt_init_new_fdti(void *fdt)
+{
+    FDTMachineInfo *fdti = g_malloc0(sizeof(*fdti));
+    fdti->fdt = fdt;
+    fdti->cq = g_malloc0(sizeof(*(fdti->cq)));
+    qemu_co_queue_init(fdti->cq);
+    fdti->dev_opaques = g_malloc0( sizeof(*(fdti->dev_opaques)) *
+        (devtree_get_num_nodes(fdt) + 1) );
+    return fdti;
+}
+
+void fdt_init_destroy_fdti(FDTMachineInfo *fdti)
+{
+    FDTDevOpaque *dp;
+    for (dp = fdti->dev_opaques; dp->node_path; dp++) {
+        g_free(dp->node_path);
+    }
+    g_free(fdti->dev_opaques);
+    g_free(fdti);
+}
diff --git a/hw/fdt_generic.h b/hw/fdt_generic.h
new file mode 100644
index 0000000..977b627
--- /dev/null
+++ b/hw/fdt_generic.h
@@ -0,0 +1,92 @@ 
+/*
+ * Tables of FDT device models and their init functions. Keyed by compatibility
+ * strings, device instance names.
+ */
+
+#ifndef FDT_GENERIC_H
+#define FDT_GENERIC_H
+
+#include "qemu-common.h"
+#include "sysbus.h"
+#include "device_tree.h"
+#include "qemu-coroutine.h"
+
+typedef struct FDTDevOpaque {
+    char *node_path;
+    void *opaque;
+} FDTDevOpaque;
+
+typedef struct FDTMachineInfo {
+    /* the fdt blob */
+    void *fdt;
+    /* irq descriptors for top level int controller */
+    qemu_irq *irq_base;
+    /* per-device specific opaques */
+    FDTDevOpaque *dev_opaques;
+    /* recheck coroutine queue */
+    CoQueue *cq;
+} FDTMachineInfo;
+
+/* create a new FDTMachineInfo. The client is responsible for setting irq_base.
+ * the mutex fdt_mutex is locked on return. Client must call
+ * fdt_init_destroy_fdti to cleanup
+ */
+
+FDTMachineInfo *fdt_init_new_fdti(void *fdt);
+void fdt_init_destroy_fdti(FDTMachineInfo *fdti);
+
+typedef int (*FDTInitFn)(char *, FDTMachineInfo *, void *);
+
+/* associate a FDTInitFn with a FDT compatibility */
+
+void add_to_compat_table(FDTInitFn, const char *, void *);
+
+/* try and find a device model for a particular compatibility. If found,
+ * the FDTInitFn associated with the compat will be called and 0 will
+ * be returned. Returns non-zero on not found or error
+ */
+
+int fdt_init_compat(char *, FDTMachineInfo *, const char *);
+
+/* same as above, but associates with a FDT node name (rather than compat) */
+
+void add_to_inst_bind_table(FDTInitFn, const char *, void *);
+int fdt_init_inst_bind(char *, FDTMachineInfo *, const char *);
+
+/* Register an FDTInitFn that should always be called upon machine creation */
+
+void add_to_force_table(FDTInitFn, const char *, void *);
+int fdt_force_bind_all(FDTMachineInfo *);
+
+void dump_compat_table(void);
+void dump_inst_bind_table(void);
+
+/* Called from FDTInitFn's to inform the framework that a dependency is unresolved
+ * and the calling context needs to wait for another device to instantiate
+ * first. The calling thread will suspend until a change in state in the
+ * argument fdt machine is detected
+ */
+
+void fdt_init_yield(FDTMachineInfo *);
+
+/* set, check and get per device opaques. Keyed by fdt node_paths */
+
+void fdt_init_set_opaque(FDTMachineInfo *fdti, char *node_path, void *opaque);
+int fdt_init_has_opaque(FDTMachineInfo *fdti, char *node_path);
+void *fdt_init_get_opaque(FDTMachineInfo *fdti, char *node_path);
+
+/* statically register a FDTInitFn as being associate with a compatibility */
+
+#define fdt_register_compatibility_opaque(function, compat, n, opaque) \
+static void __attribute__((constructor)) \
+function ## n ## _register_imp( void ) { \
+    add_to_compat_table(function, compat, opaque); \
+}
+
+#define fdt_register_compatibility_n(function, compat, n) \
+fdt_register_compatibility_opaque(function, compat, n, NULL)
+
+#define fdt_register_compatibility(function, compat) \
+fdt_register_compatibility_n(function, compat, 0)
+
+#endif /* FDT_GENERIC_H */
diff --git a/hw/fdt_generic_util.c b/hw/fdt_generic_util.c
new file mode 100644
index 0000000..bd78fc4
--- /dev/null
+++ b/hw/fdt_generic_util.c
@@ -0,0 +1,196 @@ 
+/*
+ * Utility functions for fdt generic framework
+ *
+ * Copyright (c) 2009 Edgar E. Iglesias.
+ * Copyright (c) 2009 Michal Simek.
+ * Copyright (c) 2011 PetaLogix Qld Pty Ltd.
+ * Copyright (c) 2011 Peter A. G. Crosthwaite <peter.crosthwaite@petalogix.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "fdt_generic_util.h"
+#include "net.h"
+
+FDTMachineInfo *fdt_generic_create_machine(void *fdt, qemu_irq *cpu_irq)
+{
+    char node_path[DT_PATH_LENGTH];
+
+    FDTMachineInfo *fdti = fdt_init_new_fdti(fdt);
+
+    fdti->irq_base = cpu_irq;
+
+    /* bind any force bound instances */
+    fdt_force_bind_all(fdti);
+
+    /* parse the device tree branch under the simple-bus node */
+    if (!qemu_devtree_node_by_compatible(fdt, node_path, "simple-bus")) {
+        simple_bus_fdt_init(node_path, fdti, NULL);
+        while (qemu_co_queue_enter_next(fdti->cq));
+    }
+    else
+        fprintf(stderr, "FDT: ERROR: no system bus found in device tree\n");
+
+    printf("FDT: Device tree scan complete\n");
+    FDTMachineInfo *ret = g_malloc0(sizeof(*ret));
+    return fdti;
+}
+
+const char *fdt_generic_chosen_kcmdline(void *fdt, const char *append)
+{
+    char *p;
+    char chosen [DT_PATH_LENGTH];
+    if (!(
+        qemu_devtree_get_node_by_name(fdt, chosen, "chosen") &&
+        qemu_devtree_get_node_by_name(fdt, chosen, "chosen@0") )) {
+        int len = strlen(append), plen;
+
+        p = qemu_devtree_getprop_string(fdt, chosen,"bootargs", 0, NULL, 0);
+        if (p) {
+            plen = strlen(p);
+            len += plen;
+
+            p = memcpy(malloc(len + 2), p, plen);
+            p[plen] = ' ';
+            memcpy(p + plen + 1, append, len - plen + 1);
+            return p;
+        } else
+            return strdup(append);
+    }
+    return NULL;
+}
+
+struct FDTInitNodeArgs {
+    char *node_path;
+    FDTMachineInfo *fdti;
+};
+
+static void fdt_init_node(void *args) {
+
+    struct FDTInitNodeArgs *a = args;
+    char *node_path = a->node_path;
+    FDTMachineInfo *fdti = a->fdti;
+    g_free(a);
+
+    char *all_compats, *compat, *node_name, *next_compat;
+    int compat_len;
+
+    /* try instance binding first */
+    if (!(node_name = qemu_devtree_get_node_name(fdti->fdt, node_path))) {
+        printf("FDT: ERROR: nameless node: %s\n", node_path);
+    }
+    if (!fdt_init_inst_bind(node_path, fdti, node_name)) {
+        goto exit;
+    }
+
+    /* fallback to compatibility binding */
+    all_compats = qemu_devtree_getprop_string(fdti->fdt, node_path,
+        "compatible", 0, &compat_len, 0);
+    if (!all_compats) {
+        printf("FDT: ERROR: no compatibility found for node %s%s\n",node_path,
+            node_name);
+        return;
+    }
+    compat = all_compats;
+
+try_next_compat:
+    if (compat_len == 0) goto invalidate;
+    if (!fdt_init_compat(node_path, fdti, compat)) {
+        goto exit;
+    }
+    next_compat = rawmemchr(compat, '\0');
+    compat_len -= (next_compat + 1 - compat);
+    if (compat_len > 0) {
+        *next_compat = ' ';
+    }
+    compat = next_compat+1;
+    goto try_next_compat;
+invalidate:
+    printf("FDT: Unsupported peripheral invalidated %s compatibilities %s\n",
+        node_name, all_compats);
+    qemu_devtree_setprop_string(fdti->fdt, node_path, "compatible",
+        "invalidated");
+exit:
+    if (!fdt_init_has_opaque(fdti, node_path))
+        fdt_init_set_opaque(fdti, node_path, NULL);
+    g_free(node_path);
+    return;
+}
+
+int simple_bus_fdt_init(char *bus_node_path, FDTMachineInfo *fdti, void *unused)
+{
+    int i;
+    int num_children = qemu_devtree_get_num_children(fdti->fdt, bus_node_path, 1);
+    char **children = qemu_devtree_get_children(fdti->fdt, bus_node_path, 1);
+
+    for (i = 0; i < num_children;i++) {
+        struct FDTInitNodeArgs *init_args = g_malloc0(sizeof(*init_args));
+        init_args->node_path = children[i];
+        init_args->fdti = fdti;
+        qemu_coroutine_enter(qemu_coroutine_create(fdt_init_node), init_args);
+    }
+
+    g_free(children);
+    return 0;
+}
+
+qemu_irq fdt_get_irq_info(FDTMachineInfo *fdti, char *node_path, int irq_idx,
+        int *err, char *info) {
+    void *fdt = fdti->fdt;
+    int errl;
+    if (!err)
+        err = &errl;
+    int intc_phandle = qemu_devtree_getprop(fdt, err, node_path,
+        "interrupt-parent", 0, 1);
+    if (*err)
+        goto fail;
+    char intc_node_path[DT_PATH_LENGTH];
+    if (qemu_devtree_get_node_by_phandle(fdt, intc_node_path, intc_phandle))
+        goto fail;
+    int intc_cells = qemu_devtree_getprop(fdt, err, intc_node_path,
+        "#interrupt-cells", 0, 0);
+    if (*err)
+        goto fail;
+    int intc_idx = qemu_devtree_getprop(fdt, err, node_path, "interrupts",
+        intc_cells *irq_idx, 0);
+    if (*err)
+        goto fail;
+    while (!fdt_init_has_opaque(fdti, intc_node_path))
+        fdt_init_yield(fdti);
+    qemu_irq *irqs = fdt_init_get_opaque(fdti, intc_node_path);
+    if (!irqs)
+        goto fail;
+    if (info) {
+        char *node_name = qemu_devtree_get_node_name(fdt, intc_node_path);
+        sprintf(info, "%d (%s)", intc_idx, node_name);
+        g_free((void*)node_name);
+    }
+    return irqs[intc_idx];
+fail:
+    *err = 1;
+    if (info)
+        sprintf(info, "(none)");
+    return NULL;
+}
+
+qemu_irq fdt_get_irq(FDTMachineInfo *fdti, char *node_path, int irq_idx) {
+    return fdt_get_irq_info(fdti, node_path, irq_idx, NULL, NULL);
+}
+
+fdt_register_compatibility(simple_bus_fdt_init, "simple-bus");
diff --git a/hw/fdt_generic_util.h b/hw/fdt_generic_util.h
new file mode 100644
index 0000000..161cf7f
--- /dev/null
+++ b/hw/fdt_generic_util.h
@@ -0,0 +1,43 @@ 
+#ifndef FDT_GENERIC_UTIL_H
+#define FDT_GENERIC_UTIL_H
+
+#include "qemu-common.h"
+#include "fdt_generic.h"
+
+/* create a fdt_generic machine. the top level cpu irqs are required for
+ * systems instantiating interrupt devices. The client is responsible for
+ * destroying the returned FDTMachineInfo (using fdt_init_destroy_fdti)
+ */
+
+FDTMachineInfo *fdt_generic_create_machine(void *fdt, qemu_irq *cpu_irq);
+
+/* search a device tree for the 'chosen' node and return the boot args. An
+ * optional argument append can specify a string to cat onto the end of the
+ * returned bootargs
+ */
+
+const char *fdt_generic_chosen_kcmdline(void *fdt, const char *append);
+
+/* fdt init a simple bus. Search the bus for child nodes and instantiate or
+ * invalidate devices as appropriate. Conformant to FDTInitFn prototype, i.e.
+ * a bus may fdt_register_compatibilty this as its instantiator.
+ */
+
+int simple_bus_fdt_init(char *node_path, FDTMachineInfo *fdti, void *priv);
+
+/* 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);
+
+/* 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,
+    int * err, char * info);
+
+#endif /* FDT_GENERIC_UTIL_H */