diff mbox

[RFC,v0,07/10] fdt_generic: First revision

Message ID 949d83d4c80e0ba8629e7dd930d2955759e64757.1347871922.git.peter.crosthwaite@petalogix.com
State New
Headers show

Commit Message

Peter A. G. Crosthwaite Sept. 17, 2012, 9:02 a.m. UTC
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>
---
 default-configs/microblaze-softmmu.mak   |    1 +
 default-configs/microblazeel-softmmu.mak |    1 +
 hw/fdt_generic.c                         |  215 +++++++++++++++
 hw/fdt_generic.h                         |   92 +++++++
 hw/fdt_generic_util.c                    |  438 ++++++++++++++++++++++++++++++
 hw/fdt_generic_util.h                    |   36 +++
 hw/microblaze/Makefile.objs              |    3 +
 7 files changed, 786 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

Comments

Paolo Bonzini Sept. 17, 2012, 9:48 a.m. UTC | #1
Il 17/09/2012 11:02, Peter A. G. Crosthwaite ha scritto:
> First revision of fdt generic infrastructure. These modules allow
> for fdt generic machine models, which create machines to match a device
> tree specification.

Ok, so here is the famous coroutine-based FDT generation.  :)

It does look pretty, to be fair.

However, as things stand there is nothing (I think) that really requires
coroutines.  A simple 2-pass initialization would do, with links and
irqs connected on the second pass.  I'm wondering how your patch fares
in terms of error handling for example; can you end up with
partially-uninitialized devices or infinite loops?  If so, why not?

It would be nice if you included some simple qtest test cases.  They
would generate a few machines with different device trees and check that
the output of "info qtree" matches somewhat the device tree.  Rough
checks should not be hard to implement, and would provide some guidance
to people who want to hack the code.

Paolo
Peter A. G. Crosthwaite Sept. 17, 2012, 10:10 a.m. UTC | #2
On Mon, Sep 17, 2012 at 7:48 PM, Paolo Bonzini <pbonzini@redhat.com> wrote:
> Il 17/09/2012 11:02, Peter A. G. Crosthwaite ha scritto:
>> First revision of fdt generic infrastructure. These modules allow
>> for fdt generic machine models, which create machines to match a device
>> tree specification.
>
> Ok, so here is the famous coroutine-based FDT generation.  :)
>
> It does look pretty, to be fair.
>

Thanks :)

> However, as things stand there is nothing (I think) that really requires
> coroutines.  A simple 2-pass initialization would do, with links and
> irqs connected on the second pass.

I'm just trying to be a little more general. For more complex setups
we may in the future want to resolve dependencies at different phases
of the machine init process. The ability to yield arbitrarily solves
that problem universally rather than have to manage a discrete number
of passes. That and two pass is brutally ugly in code (and
in-efficient) as you need to tag passes.

I'm wondering how your patch fares
> in terms of error handling for example; can you end up with
> partially-uninitialized devices or infinite loops?  If so, why not?
>

Deadlock looping shouldnt happen if thats what your worried about.
Each device is instantiated before its able to yield (waiting on
another) and all devices are inited before you reach that
all-threads-are-yielded point. Essentially your "first-pass" as you
suggested above is complete before before every thread is yielded,
which makes deadlock impossible with current usages.

Paritally initied shouldnt happen - as that can only happen if
coroutines are left yielded. The machine init does not return until
all coros have exited.

Regards,
Peter

> It would be nice if you included some simple qtest test cases.  They
> would generate a few machines with different device trees and check that
> the output of "info qtree" matches somewhat the device tree.  Rough
> checks should not be hard to implement, and would provide some guidance
> to people who want to hack the code.
>
> Paolo
Paolo Bonzini Sept. 17, 2012, 12:55 p.m. UTC | #3
> > However, as things stand there is nothing (I think) that really requires
> > coroutines.  A simple 2-pass initialization would do, with links and
> > irqs connected on the second pass.
> 
> I'm just trying to be a little more general. For more complex setups
> we may in the future want to resolve dependencies at different phases
> of the machine init process.

Do you have any examples of things that are more complicated than
"build everything" followed by "resolve everything"?

> That and two pass is brutally ugly in code (and
> in-efficient) as you need to tag passes.

Not sure I follow, but I can't of course promise aesthetics without
having written any code.

> I'm wondering how your patch fares
> > in terms of error handling for example; can you end up with
> > partially-uninitialized devices or infinite loops?  If so, why not?
> 
> Deadlock looping shouldnt happen if thats what your worried about.
> Each device is instantiated before its able to yield (waiting on
> another) and all devices are inited before you reach that
> all-threads-are-yielded point. Essentially your "first-pass" as you
> suggested above is complete before before every thread is yielded,
> which makes deadlock impossible with current usages.

Ok, so something checks the syntax before initialization runs.

Pao
diff mbox

Patch

diff --git a/default-configs/microblaze-softmmu.mak b/default-configs/microblaze-softmmu.mak
index 64c9485..c3bd740 100644
--- a/default-configs/microblaze-softmmu.mak
+++ b/default-configs/microblaze-softmmu.mak
@@ -5,3 +5,4 @@  CONFIG_PFLASH_CFI01=y
 CONFIG_SERIAL=y
 CONFIG_XILINX=y
 CONFIG_XILINX_AXI=y
+CONFIG_FDT_GENERIC=y
diff --git a/default-configs/microblazeel-softmmu.mak b/default-configs/microblazeel-softmmu.mak
index a962276..dda2c51 100644
--- a/default-configs/microblazeel-softmmu.mak
+++ b/default-configs/microblazeel-softmmu.mak
@@ -5,3 +5,4 @@  CONFIG_PFLASH_CFI01=y
 CONFIG_SERIAL=y
 CONFIG_XILINX=y
 CONFIG_XILINX_AXI=y
+CONFIG_FDT_GENERIC=y
diff --git a/hw/fdt_generic.c b/hw/fdt_generic.c
new file mode 100644
index 0000000..3950107
--- /dev/null
+++ b/hw/fdt_generic.c
@@ -0,0 +1,215 @@ 
+/*
+ * 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_ERR_DEBUG
+
+#ifdef FDT_GENERIC_ERR_DEBUG
+#define DB_PRINT(...) do { \
+    fprintf(stderr,  ": %s: ", __func__); \
+    fprintf(stderr, ## __VA_ARGS__); \
+    } while (0);
+#else
+    #define DB_PRINT(...)
+#endif
+
+#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;
+
+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;
+
+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;
+
+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)
+{
+    static int yield_index;
+    int this_yield = yield_index++;
+
+    DB_PRINT("yield #%d %p\n", this_yield, fdti->cq);
+    qemu_co_queue_wait(fdti->cq);
+    DB_PRINT("unyield #%d\n", this_yield);
+}
+
+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..41992e3
--- /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..3c04957
--- /dev/null
+++ b/hw/fdt_generic_util.c
@@ -0,0 +1,438 @@ 
+/*
+ * 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.
+ */
+
+#define FDT_GENERIC_UTIL_ERR_DEBUG
+
+#ifdef FDT_GENERIC_UTIL_ERR_DEBUG
+#define DB_PRINT(...) do { \
+    fprintf(stderr,  ": %s: ", __func__); \
+    fprintf(stderr, ## __VA_ARGS__); \
+    } while (0);
+#else
+    #define DB_PRINT(...)
+#endif
+
+#include "fdt_generic_util.h"
+#include "net.h"
+
+/* FIXME: wrap direct calls into libfdt */
+
+#include <libfdt.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 */
+    if (!qemu_devtree_get_root_node(fdt, node_path)) {
+        simple_bus_fdt_init(node_path, fdti, NULL);
+        while (qemu_co_queue_enter_next(fdti->cq));
+    } else {
+        fprintf(stderr, "FDT: ERROR: cannot get root node from device tree %s\n"
+            , node_path);
+    }
+
+    printf("FDT: Device tree scan complete\n");
+    FDTMachineInfo *ret = g_malloc0(sizeof(*ret));
+    return fdti;
+}
+
+struct FDTInitNodeArgs {
+    char *node_path;
+    FDTMachineInfo *fdti;
+};
+
+static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *compat);
+
+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 = NULL, *compat, *node_name, *next_compat;
+    int compat_len;
+
+    static int entry_index;
+    int this_entry = entry_index++;
+    DB_PRINT("enter %d %s\n", this_entry, node_path);
+
+    /* try instance binding first */
+    node_name = qemu_devtree_get_node_name(fdti->fdt, node_path);
+    if (!node_name) {
+        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(fdti->fdt, node_path,
+        "compatible", &compat_len, false, NULL);
+    if (!all_compats) {
+        printf("FDT: ERROR: no compatibility found for node %s%s\n", node_path,
+            node_name);
+        DB_PRINT("exit %d\n", this_entry);
+        return;
+    }
+    compat = all_compats;
+
+try_next_compat:
+    if (compat_len == 0) {
+        goto invalidate;
+    }
+    if (!fdt_init_compat(node_path, fdti, compat)) {
+        goto exit;
+    }
+    if (!fdt_init_qdev(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:
+
+    DB_PRINT("exit %d\n", this_entry);
+
+    if (!fdt_init_has_opaque(fdti, node_path)) {
+        fdt_init_set_opaque(fdti, node_path, NULL);
+    }
+    g_free(node_path);
+    g_free(all_compats);
+    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);
+
+    DB_PRINT("num child devices: %d\n", num_children);
+
+    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 intc_phandle, intc_cells, idx, errl;
+    char intc_node_path[DT_PATH_LENGTH];
+    Error *errp = NULL;
+    DeviceState *intc;
+
+    if (!err) {
+        err = &errl;
+    }
+    intc_phandle = qemu_devtree_getprop_cell(fdt, node_path, "interrupt-parent",
+                                                                0, true, &errp);
+    if (errp) {
+        goto fail;
+    }
+
+    if (qemu_devtree_get_node_by_phandle(fdt, intc_node_path, intc_phandle)) {
+        goto fail;
+    }
+    intc_cells = qemu_devtree_getprop_cell(fdt, intc_node_path,
+                                           "#interrupt-cells", 0, false, &errp);
+    if (errp) {
+        goto fail;
+    }
+    idx = qemu_devtree_getprop_cell(fdt, node_path, "interrupts",
+                                        intc_cells * irq_idx, false, &errp);
+    if (errp) {
+        goto fail;
+    }
+
+    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;
+    }
+    if (info) {
+        char *node_name = qemu_devtree_get_node_name(fdt, intc_node_path);
+        sprintf(info, "%d (%s)", idx, node_name);
+        g_free((void *)node_name);
+    }
+    return qdev_get_gpio_in(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);
+}
+
+/* FIXME: figure out a real solution to this */
+
+#define DIGIT(a) ((a) >= '0' && (a) <= '9')
+#define LOWER_CASE(a) ((a) >= 'a' && (a) <= 'z')
+
+static void trim_xilinx_version(char *x)
+{
+    for (;;) {
+        x = strchr(x, '-');
+        if (!x || strlen(x) < 7) {
+            return;
+        }
+        if (DIGIT(x[1]) &&
+                x[2] == '.' &&
+                DIGIT(x[3]) &&
+                DIGIT(x[4]) &&
+                x[5] == '.' &&
+                LOWER_CASE(x[6])) {
+            *x = '\0';
+            return;
+        }
+        x++;
+    }
+}
+
+static void substitute_char(char *s, char a, char b)
+{
+    for (;;) {
+        s = strchr(s, a);
+        if (!s) {
+            return;
+        }
+        *s = b;
+        s++;
+    }
+}
+
+static DeviceState *fdt_create_qdev_from_compat(char *compat, char **dev_type)
+{
+    DeviceState *ret = NULL;
+
+    char *c = g_strdup(compat);
+    ret = qdev_try_create(NULL, c);
+    if (!ret) {
+        /* QEMU substitutes "."s for ","s in device names, so try with that
+         * substitutution
+         */
+        substitute_char(c, ',', '.');
+        ret = qdev_try_create(NULL, c);
+    }
+    if (!ret) {
+        /* try again with the xilinx version string trimmed */
+        trim_xilinx_version(c);
+        ret = qdev_try_create(NULL, c);
+    }
+
+    if (dev_type) {
+        *dev_type = c;
+    } else {
+        g_free(c);
+    }
+    return ret;
+}
+
+static inline const char *trim_vendor(const char *s)
+{
+    /* FIXME: be more intelligent */
+    const char *ret = memchr(s, ',', sizeof(s));
+    return ret ? ret + 1 : s;
+}
+
+/*FIXME: roll into device tree functionality */
+
+static inline uint64_t get_int_be(const void *p, int len)
+{
+    switch (len) {
+    case 1:
+        return *((uint8_t *)p);
+    case 2:
+        return be16_to_cpu(*((uint16_t *)p));
+    case 4:
+        return be32_to_cpu(*((uint32_t *)p));
+    case 8:
+        return be32_to_cpu(*((uint64_t *)p));
+    default:
+        fprintf(stderr, "unsupported integer length\n");
+        abort();
+    }
+}
+
+static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *compat)
+{
+    int err;
+    qemu_irq irq;
+    target_phys_addr_t base;
+    int offset;
+    DeviceState *dev;
+    char *dev_type = NULL;
+    int is_intc;
+    Error *errp = NULL;
+    int i;
+
+    dev = fdt_create_qdev_from_compat(compat, &dev_type);
+    if (!dev) {
+        DB_PRINT("no match found for %s\n", compat);
+        return 1;
+    }
+    /* FIXME: attach to the sysbus instead */
+    object_property_add_child(container_get(qdev_get_machine(), "/unattached"),
+                              qemu_devtree_get_node_name(fdti->fdt, node_path),
+                              OBJECT(dev), NULL);
+
+    fdt_init_set_opaque(fdti, node_path, dev);
+
+    /* connect nic if appropriate */
+    static int nics;
+    qdev_set_nic_properties(dev, &nd_table[nics]);
+    if (nd_table[nics].instantiated) {
+        DB_PRINT("NIC instantiated: %s\n", dev_type);
+        nics++;
+    }
+
+    offset = fdt_path_offset(fdti->fdt, node_path);
+    for (offset = fdt_first_property_offset(fdti->fdt, offset);
+            offset != -FDT_ERR_NOTFOUND;
+            offset = fdt_next_property_offset(fdti->fdt, offset)) {
+        const char *propname;
+        int len;
+        const void *val = fdt_getprop_by_offset(fdti->fdt, offset,
+                                                    &propname, &len);
+
+        propname = trim_vendor(propname);
+        ObjectProperty *p = object_property_find(OBJECT(dev), propname, NULL);
+        if (p) {
+            DB_PRINT("matched property: %s of type %s, len %d\n",
+                                            propname, p->type, len);
+        }
+        if (!p) {
+            continue;
+        }
+
+        /* FIXME: handle generically using accessors and stuff */
+        if (!strcmp(p->type, "uint8") || !strcmp(p->type, "uint16") ||
+                !strcmp(p->type, "uint32") || !strcmp(p->type, "uint64")) {
+            object_property_set_int(OBJECT(dev), get_int_be(val, len), propname,
+                                                                &errp);
+            assert_no_error(errp);
+            DB_PRINT("set property %s to %#llx\n", propname,
+                                            get_int_be(val, len));
+        } else if (!strcmp(p->type, "bool")) {
+            object_property_set_bool(OBJECT(dev), !!get_int_be(val, len),
+                        propname, &errp);
+            assert_no_error(errp);
+            DB_PRINT("set property %s to %#llx\n", propname,
+                                            get_int_be(val, len));
+        } else if (!strncmp(p->type, "link", 4)) {
+            char target_node_path[DT_PATH_LENGTH];
+            DeviceState *linked_dev;
+
+            if (qemu_devtree_get_node_by_phandle(fdti->fdt, target_node_path,
+                                                get_int_be(val, len))) {
+                abort();
+            }
+            while (!fdt_init_has_opaque(fdti, target_node_path)) {
+                fdt_init_yield(fdti);
+            }
+            linked_dev = fdt_init_get_opaque(fdti, target_node_path);
+            object_property_set_link(OBJECT(dev), OBJECT(linked_dev), propname,
+                                        errp);
+            assert_no_error(errp);
+        }
+    }
+
+    qdev_init_nofail(dev);
+    /* map slave attachment */
+    base = qemu_devtree_getprop_cell(fdti->fdt, node_path, "reg", 0, false,
+                                                                    &errp);
+    assert_no_error(errp);
+    sysbus_mmio_map(sysbus_from_qdev(dev), 0, base);
+
+    {
+        int len;
+        fdt_get_property(fdti->fdt, fdt_path_offset(fdti->fdt, node_path),
+                                "interrupt-controller", &len);
+        is_intc = len >= 0;
+        DB_PRINT("is interrupt controller: %c\n", is_intc ? 'y' : 'n');
+    }
+    /* connect irq */
+    for (i = 0; ; ++i) {
+        char irq_info[1024];
+        irq = fdt_get_irq_info(fdti, node_path, i, &err, irq_info);
+        /* INTCs inferr their top level, if no IRQ connection specified */
+        if (err && is_intc) {
+            irq = fdti->irq_base[0];
+            sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq);
+            fprintf(stderr, "FDT: (%s) connected top level irq %s\n", dev_type,
+                        irq_info);
+            break;
+        }
+        if (!err) {
+            sysbus_connect_irq(sysbus_from_qdev(dev), i, irq);
+            fprintf(stderr, "FDT: (%s) connected irq %s\n", dev_type, irq_info);
+        } else {
+            break;
+        }
+    }
+
+    if (dev_type) {
+        g_free(dev_type);
+    }
+
+    return 0;
+}
+
+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..3906721
--- /dev/null
+++ b/hw/fdt_generic_util.h
@@ -0,0 +1,36 @@ 
+#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);
+
+/* 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 */
diff --git a/hw/microblaze/Makefile.objs b/hw/microblaze/Makefile.objs
index 274d2c5..23f9cbb 100644
--- a/hw/microblaze/Makefile.objs
+++ b/hw/microblaze/Makefile.objs
@@ -6,4 +6,7 @@  obj-y += microblaze_pic_cpu.o
 obj-y += xilinx_ethlite.o
 obj-$(CONFIG_FDT) += ../device_tree.o
 
+obj-$(CONFIG_FDT_GENERIC) += fdt_generic.o
+obj-$(CONFIG_FDT_GENERIC) += fdt_generic_util.o
+
 obj-y := $(addprefix ../,$(obj-y))