symbian-qemu-0.9.1-12/qemu-symbian-svp/devtree.c
changeset 1 2fb8b9db1c86
child 36 a587897e3bb2
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/symbian-qemu-0.9.1-12/qemu-symbian-svp/devtree.c	Fri Jul 31 15:01:17 2009 +0100
@@ -0,0 +1,725 @@
+/*
+ *  Dynamic device configuration and creation.
+ *
+ *  Copyright (c) 2008 CodeSourcery
+ *
+ * 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ */
+
+/* FIXME: check all malloc/strdup exit coeds.  Or better still have
+   malloc/strdup abort.  */
+
+#include "qemu-common.h"
+#include "sysemu.h"
+#include "devtree.h"
+#include "hw/boards.h"
+#include "libfdt/libfdt.h"
+
+#define BADF(fmt, args...) \
+do { fprintf(stderr, "error: " fmt , ##args); exit(1);} while (0)
+
+/* Assume no device will ever need more than 4 register windows.  */
+#define MAX_DEV_REGS 4
+
+enum QEMUDeicePropetyType {
+    QDEV_PROP_INT,
+    QDEV_PROP_STRING
+};
+
+typedef struct QEMUDeviceProperty {
+    const char *name;
+    enum QEMUDeicePropetyType type;
+    union {
+        int i;
+        char *string;
+    } value;
+    struct QEMUDeviceProperty *next;
+} QEMUDeviceProperty;
+
+struct QEMUDeviceClass {
+    struct QEMUDeviceClass *next;
+    const char *name;
+    void *opaque;
+    QEMUDeviceProperty *properties;
+    int num_irqs;
+    int num_regs;
+    CPUReadMemoryFunc **mem_read[MAX_DEV_REGS];
+    CPUWriteMemoryFunc **mem_write[MAX_DEV_REGS];
+    target_phys_addr_t mem_size[MAX_DEV_REGS];
+    QDEVCreateFn create;
+    SaveStateHandler *save_state;
+    LoadStateHandler *load_state;
+    int savevm_version;
+    unsigned has_chardev:1;
+};
+
+struct QEMUDevice {
+    QEMUDevice *next;
+    QEMUDeviceClass *dc;
+    QEMUDeviceProperty *properties;
+    qemu_irq **irqp;
+    qemu_irq *irq;
+    CharDriverState *chardev;
+    qemu_irq *irq_sink;
+    void *mem_opaque[MAX_DEV_REGS];
+    void *opaque;
+    int irq_sink_count;
+    const void *dt;
+    int node_offset;
+    uint32_t phandle;
+};
+
+const void *machine_devtree;
+int machine_devtree_size;
+
+devtree_ram_region *devtree_ram_map;
+int devtree_ram_map_size;
+
+/* Device (class) registration.  */
+
+QEMUDeviceClass *cpu_device_class;
+
+static QEMUDeviceClass *all_dc;
+
+QEMUDeviceClass *qdev_new(const char *name, QDEVCreateFn create, int nirq)
+{
+    QEMUDeviceClass *dc = qemu_mallocz(sizeof(*dc));
+
+    dc->num_irqs = nirq;
+    dc->create = create;
+    dc->name = qemu_strdup(name);
+
+    dc->next = all_dc;
+    all_dc = dc;
+
+    return dc;
+}
+
+void qdev_add_chardev(QEMUDeviceClass *dc)
+{
+    if (dc->has_chardev) {
+        BADF("Device class %s already has a chardev\n", dc->name);
+    }
+    dc->has_chardev = 1;
+}
+
+void qdev_add_property_string(QEMUDeviceClass *dc, const char *name,
+                              const char *def)
+{
+    QEMUDeviceProperty *p = qemu_mallocz(sizeof(*p));
+
+    p->name = qemu_strdup(name);
+    p->type = QDEV_PROP_STRING;
+    if (def)
+        p->value.string = qemu_strdup(def);
+    p->next = dc->properties;
+    dc->properties = p;
+}
+
+void qdev_add_property_int(QEMUDeviceClass *dc, const char *name, int def)
+{
+    QEMUDeviceProperty *p = qemu_mallocz(sizeof(*p));
+
+    p->name = qemu_strdup(name);
+    p->type = QDEV_PROP_INT;
+    p->value.i = def;
+    p->next = dc->properties;
+    dc->properties = p;
+}
+
+void qdev_add_registers(QEMUDeviceClass *dc, CPUReadMemoryFunc **mem_read,
+                        CPUWriteMemoryFunc **mem_write,
+                        target_phys_addr_t mem_size)
+{
+    if (dc->num_regs == MAX_DEV_REGS) {
+        BADF("too many regs");
+        return;
+    }
+
+    dc->mem_read[dc->num_regs] = mem_read;
+    dc->mem_write[dc->num_regs] = mem_write;
+    dc->mem_size[dc->num_regs] = mem_size;
+    dc->num_regs++;
+}
+
+void qdev_add_class_opaque(QEMUDeviceClass *dc, void *opaque)
+{
+    dc->opaque = opaque;
+}
+
+void qdev_add_savevm(QEMUDeviceClass *dc, int ver,
+                     SaveStateHandler *save_state,
+                     LoadStateHandler *load_state)
+{
+    dc->savevm_version = ver;
+    dc->save_state = save_state;
+    dc->load_state = load_state;
+}
+
+static QEMUDeviceProperty *qdev_copy_properties(QEMUDeviceProperty *src)
+{
+    QEMUDeviceProperty *first;
+    QEMUDeviceProperty **p;
+    QEMUDeviceProperty *dest;
+
+    first = NULL;
+    p = &first;
+    while (src) {
+        dest = qemu_mallocz(sizeof(*dest));
+        dest->name = src->name;
+        dest->type = src->type;
+        switch (src->type) {
+        case QDEV_PROP_INT:
+            dest->value.i = src->value.i;
+            break;
+        case QDEV_PROP_STRING:
+            if (src->value.string)
+                dest->value.string = qemu_strdup(src->value.string);
+            break;
+        }
+        src = src->next;
+        *p = dest;
+        p = &dest->next;
+    }
+    return first;
+}
+
+
+/* Device manipulation.  */
+
+static QEMUDevice *first_device;
+
+static QEMUDevice *qdev_create(QEMUDeviceClass *dc, const void *dt,
+                               int node_offset)
+{
+    QEMUDevice *dev = qemu_mallocz(sizeof(*dc));
+
+    dev->dc = dc;
+    dev->properties = qdev_copy_properties(dc->properties);
+    if (dc->num_irqs) {
+        dev->irqp = qemu_mallocz(dc->num_irqs * sizeof(qemu_irq *));
+        dev->irq = qemu_mallocz(dc->num_irqs * sizeof(qemu_irq));
+    }
+    dev->node_offset = node_offset;
+    dev->dt = dt;
+    dev->phandle = fdt_get_phandle(dt, node_offset);
+
+    dev->next = first_device;
+    first_device = dev;
+
+    return dev;
+}
+
+/* IRQs are not created/linked until all devices have been created.
+   This function take a pointer to a qemu_irq object, which will be
+   populated later.  */
+/* FIXME: Should we just have qdev_irq_{raise,lower}?  */
+void qdev_get_irq(QEMUDevice *dev, int n, qemu_irq *p)
+{
+    if (n >= dev->dc->num_irqs)
+        BADF("Bad IRQ %d (%d)\n", n, dev->dc->num_irqs);
+    dev->irqp[n] = p;
+}
+
+CharDriverState *qdev_get_chardev(QEMUDevice *dev)
+{
+    return dev->chardev;
+}
+
+void qdev_create_interrupts(QEMUDevice *dev, qemu_irq_handler handler, 
+                            void *opaque, int n)
+{
+    dev->irq_sink = qemu_allocate_irqs(handler, opaque, n);
+    dev->irq_sink_count = n;
+}
+
+int qdev_get_property_int(QEMUDevice *dev, const char *name)
+{
+    QEMUDeviceProperty *p;
+
+    for (p = dev->properties; p; p = p->next) {
+        if (strcmp(name, p->name) == 0) {
+            if (p->type != QDEV_PROP_INT)
+                abort();
+            return p->value.i;
+        }
+    }
+    abort();
+}
+
+const char *qdev_get_property_string(QEMUDevice *dev, const char *name)
+{
+    QEMUDeviceProperty *p;
+
+    for (p = dev->properties; p; p = p->next) {
+        if (strcmp(name, p->name) == 0) {
+            if (p->type != QDEV_PROP_STRING)
+                abort();
+            return p->value.string;
+        }
+    }
+    abort();
+}
+
+const char *qdev_get_name(QEMUDevice *dev)
+{
+    return fdt_get_name(dev->dt, dev->node_offset, NULL);
+}
+
+void *qdev_get_class_opaque(QEMUDevice *dev)
+{
+    return dev->dc->opaque;
+}
+
+void qdev_set_opaque(QEMUDevice *dev, void *opaque)
+{
+    dev->opaque = opaque;
+}
+
+void qdev_set_region_opaque(QEMUDevice *dev, int n, void *opaque)
+{
+    dev->mem_opaque[n] = opaque;
+}
+
+void qdev_set_irq_level(QEMUDevice *dev, int n, int level)
+{
+    if (n < 0 || n > dev->dc->num_irqs)
+        return;
+
+    qemu_set_irq(dev->irq[n], level);
+}
+
+/* FDT handling.  */
+
+static void invalid_devtree(QEMUDevice *dev, const char *msg)
+{
+    fprintf(stderr, "devtree: %s: %s\n", dev->dc->name, msg);
+    exit(1);
+}
+
+static const char *fdt_getprop_string(const void *dt, int node,
+                                      const char * name)
+{
+    const char *p;
+    int len;
+
+    p = fdt_getprop(dt, node, name, &len);
+    if (!p || len == 0)
+        return NULL;
+    /* Check string is properly terminated.  If the wrong kind of property
+       is used then this may not be true.  */
+    if (p[len - 1] != 0)
+        return NULL;
+    return p;
+}
+
+static void find_properties(QEMUDevice *dev)
+{
+    const struct fdt_property *p;
+    QEMUDeviceProperty *dp;
+    int len;
+
+    for (dp = dev->properties; dp; dp = dp->next) {
+        p = fdt_get_property(dev->dt, dev->node_offset, dp->name, &len);
+        if (!p)
+            continue;
+        switch (dp->type) {
+        case QDEV_PROP_INT:
+            if (len != 4) {
+                invalid_devtree(dev, "Bad integer property");
+                break;
+            }
+            dp->value.i = fdt32_to_cpu(*(uint32_t *)p->data);
+            break;
+        case QDEV_PROP_STRING:
+            if (len == 0 || p->data[len - 1]) {
+                invalid_devtree(dev, "Bad string property");
+                break;
+            }
+            if (dp->value.string)
+                qemu_free(dp->value.string);
+            dp->value.string = qemu_strdup((const char *)p->data);
+            break;
+        }
+    }
+}
+
+/* We currently assume a fixed address/size.  Enforce that here.  */
+static void check_cells(const void *dt, int node, int address, int size)
+{
+    const struct fdt_property *p;
+    int parent;
+    int len;
+    int n;
+
+    parent = fdt_parent_offset(dt, node);
+    if (node < 0) {
+        fprintf(stderr, "missing parent node for %s\n",
+                fdt_get_name(dt, node, NULL));
+        exit(1);
+    }
+    p = fdt_get_property(dt, parent, "#address-cells", &len);
+    if (!p || len != 4) {
+        fprintf(stderr,
+                "Invalid or missing #address-cells for %s\n",
+                fdt_get_name(dt, node, NULL));
+        exit(1);
+    }
+    n = fdt32_to_cpu(*(uint32_t *)p->data);
+    if (n != address) {
+        fprintf(stderr,
+                "Incorrect #address-cells for %s (expected %d got %d)\n",
+                fdt_get_name(dt, node, NULL), address, n);
+        exit(1);
+    }
+    p = fdt_get_property(dt, parent, "#size-cells", &len);
+    if (!p || len != 4) {
+        fprintf(stderr,
+                "Invalid or missing #size-cells for %s\n",
+                fdt_get_name(dt, node, NULL));
+        exit(1);
+    }
+    n = fdt32_to_cpu(*(uint32_t *)p->data);
+    if (n != size) {
+        fprintf(stderr,
+                "Incorrect #size-cells for %s (expected %d got %d)\n",
+                fdt_get_name(dt, node, NULL), size, n);
+        exit(1);
+    }
+}
+
+static void create_from_node(QEMUDeviceClass *dc, const void *dt, int node)
+{
+    QEMUDevice *d;
+    const char *propstr;
+    int i;
+
+    d = qdev_create(dc, dt, node);
+    if (dc->has_chardev) {
+        int n;
+        propstr = fdt_getprop_string(dt, node, "chardev");
+        if (propstr) {
+            i = sscanf(propstr, "serial%d", &n);
+            if (i == 1 && n >= 0 && n < MAX_SERIAL_PORTS)
+                d->chardev = serial_hds[n];
+        }
+    }
+    find_properties(d);
+    d->dc->create(d);
+    if (dc->savevm_version) {
+        register_savevm(dc->name, -1, dc->savevm_version,
+                        dc->save_state, dc->load_state, d->opaque);
+    }
+    if (dc->num_regs) {
+        const struct fdt_property *p;
+        uint32_t base;
+        uint32_t *data;
+        void *opaque;
+        int iomemtype;
+        int len;
+
+        check_cells(dt, node, 1, 0);
+        p = fdt_get_property(dt, node, "reg", &len);
+        if (!p || len != dc->num_regs * 4) {
+            invalid_devtree(d, "Missing reg");
+            return;
+        }
+        data = (uint32_t *)p->data;
+        for (i = 0; i < dc->num_regs; i++) {
+            base = fdt32_to_cpu(*data);
+            data++;
+            opaque = d->mem_opaque[i];
+            if (!opaque)
+                opaque = d->opaque;
+            iomemtype = cpu_register_io_memory(0, dc->mem_read[i],
+                                               dc->mem_write[i], opaque);
+            cpu_register_physical_memory(base, dc->mem_size[i], iomemtype);
+        }
+    }
+}
+
+static void scan_devtree(const void *dt)
+{
+    QEMUDeviceClass *dc;
+    int node;
+
+    for (dc = all_dc; dc; dc = dc->next) {
+        node = -1;
+        while (1) {
+            node = fdt_node_offset_by_compatible(dt, node, dc->name);
+            if (node < 0)
+                break;
+            create_from_node(dc, dt, node);
+        }
+    }
+}
+
+/* Create CPU devices.  These are devices so that they can have interrupts.  */
+static void create_cpus(const void *dt)
+{
+    int node = -1;
+
+    while (1) {
+        node = fdt_node_offset_by_prop_value(dt, node, "device_type",
+                                             "cpu", 4);
+        if (node < 0)
+            break;
+        create_from_node(cpu_device_class, dt, node);
+    }
+}
+
+/* Add RAM.  */
+static void create_ram(const void *dt)
+{
+    int node = -1;
+    const struct fdt_property *p;
+    int len;
+    uint32_t base;
+    uint32_t size;
+    uint32_t *data;
+    ram_addr_t offset;
+
+    while (1) {
+        node = fdt_node_offset_by_prop_value(dt, node, "device_type",
+                                             "memory", 7);
+        if (node < 0)
+            break;
+
+
+        check_cells(dt, node, 1, 1);
+        p = fdt_get_property(dt, node, "reg", &len);
+        if (!p || (len % 8) != 0) {
+            fprintf(stderr, "bad memory section %s\n",
+                    fdt_get_name(dt, node, NULL));
+            exit(1);
+        }
+        data = (uint32_t *)p->data;
+        while (len) {
+            base = fdt32_to_cpu(data[0]);
+            size = fdt32_to_cpu(data[1]);
+            data += 2;
+            len -= 8;
+            /* Ignore zero size regions.  */
+            if (size == 0)
+                continue;
+            offset = qemu_ram_alloc(size);
+            cpu_register_physical_memory(base, size, offset | IO_MEM_RAM);
+
+            devtree_ram_map_size++;
+            devtree_ram_map = qemu_realloc(devtree_ram_map,
+                devtree_ram_map_size * sizeof(devtree_ram_region));
+            devtree_ram_map[devtree_ram_map_size - 1].base = base;
+            devtree_ram_map[devtree_ram_map_size - 1].size = size;
+        }
+    }
+    /* FIXME: Merge and sort memory map entries.  */
+    /* Technically there's no reason we have to have RAM.  However in
+       practice it indicates a busted machine description.  */
+    if (!devtree_ram_map) {
+        fprintf(stderr, "No memory regions found\n");
+        exit(1);
+    }
+}
+
+static QEMUDevice *find_device_by_phandle(uint32_t phandle)
+{
+    QEMUDevice *dev;
+    for (dev = first_device; dev; dev = dev->next) {
+        if (dev->phandle == phandle)
+            return dev;
+    }
+    return NULL;
+}
+
+/* We currently assume #interrupt-cells is 1.  */
+static void check_interrupt_cells(QEMUDevice *dev)
+{
+    const struct fdt_property *p;
+    int len;
+
+    p = fdt_get_property(dev->dt, dev->node_offset, "#interrupt-cells", &len);
+    /* Allow a missing value.  Useful for devices that are pointed to by
+       a qemu,interrupts property.  */
+    if (!p)
+        return;
+    if (len != 4) {
+        invalid_devtree(dev, "Invalid #interrupt-cells");
+    }
+    if (fdt32_to_cpu(*(uint32_t *)p->data) != 1) {
+        invalid_devtree(dev, "#interrupt-cells must be 1");
+    }
+}
+
+static QEMUDevice *find_interrupt_parent(QEMUDevice *dev)
+{
+    const struct fdt_property *p;
+    QEMUDevice *parent;
+    uint32_t phandle;
+    int len;
+
+    p = fdt_get_property(dev->dt, dev->node_offset, "interrupt-parent", &len);
+    if (!p)
+        return NULL;
+    if (len != 4) {
+        invalid_devtree(dev, "bad/missing interrupt-parent");
+        return NULL;
+    }
+    phandle = fdt32_to_cpu(*(uint32_t *)p->data);
+
+    parent = find_device_by_phandle(phandle);
+    if (!parent) {
+        invalid_devtree(dev, "interrupt-parent not found");
+    }
+    check_interrupt_cells(parent);
+    return parent;
+}
+
+static void fixup_irqs(void)
+{
+    QEMUDevice *dev;
+    QEMUDevice *parent;
+    const struct fdt_property *prop;
+    int len;
+    int i;
+    qemu_irq parent_irq;
+    int is_qemu_irq = 0;
+    uint32_t *data;
+
+    for (dev = first_device; dev; dev = dev->next) {
+        if (dev->dc->num_irqs) {
+            parent = find_interrupt_parent(dev);
+            if (!parent) {
+                prop = fdt_get_property(dev->dt, dev->node_offset,
+                                        "qemu,interrupts", &len);
+                if (!prop) {
+                    invalid_devtree(dev, "missing interrupt-parent");
+                    continue;
+                }
+                if (len != dev->dc->num_irqs * 8) {
+                    invalid_devtree(dev, "bad interrupts");
+                    continue;
+                }
+                is_qemu_irq = 1;
+            } else {
+                prop = fdt_get_property(dev->dt, dev->node_offset,
+                                        "interrupts", &len);
+                if (!prop || len != dev->dc->num_irqs * 4) {
+                    invalid_devtree(dev, "bad/missing interrupts");
+                    continue;
+                }
+                is_qemu_irq = 0;
+            }
+            data = (uint32_t *)prop->data;
+            /* FIXME: Need to handle interrupt remapping.  */
+            for (i = 0; i < dev->dc->num_irqs; i++) {
+                uint32_t parent_irq_num;
+                if (is_qemu_irq) {
+                    parent = find_device_by_phandle(fdt32_to_cpu(*data));
+                    data++;
+                    if (!parent) {
+                        invalid_devtree(dev, "bad qemu,interrupts");
+                    }
+                    check_interrupt_cells(parent);
+                }
+                parent_irq_num = fdt32_to_cpu(*data);
+                data++;
+                if (parent_irq_num >= parent->irq_sink_count) {
+                    invalid_devtree(dev, "bad interrupt number");
+                    continue;
+                }
+                parent_irq = parent->irq_sink[parent_irq_num];
+                dev->irq[i] = parent_irq;
+                if (dev->irqp[i])
+                    *(dev->irqp[i]) = parent_irq;
+            }
+        }
+    }
+}
+
+static void parse_devtree(const char *filename)
+{
+    FILE *f;
+    void *dt;
+
+    f = fopen(filename, "rb");
+    if (!f)
+        goto err;
+    fseek(f, 0, SEEK_END);
+    machine_devtree_size = ftell(f);
+    fseek(f, 0, SEEK_SET);
+    dt = qemu_malloc(machine_devtree_size);
+    if (!dt)
+        goto err_close;
+    machine_devtree = dt;
+    if (fread(dt, machine_devtree_size, 1, f) != 1)
+        goto err_close;
+    if (fdt_check_header(dt))
+        goto err_close;
+
+    create_cpus(dt);
+
+    create_ram(dt);
+
+    scan_devtree(dt);
+
+    fixup_irqs();
+
+    fclose(f);
+    return;
+
+err_close:
+    fclose(f);
+err:
+    fprintf(stderr, "Failed to load device tree\n");
+    exit(1);
+}
+
+int devtree_get_config_int(const char *name, int def)
+{
+    const struct fdt_property *p;
+    int len;
+    int node;
+
+    node = fdt_path_offset(machine_devtree, "/chosen");
+    if (node < 0)
+        return def;
+    p = fdt_get_property(machine_devtree, node, name, &len);
+    if (!p)
+        return def;
+    if (len != 4) {
+        fprintf(stderr, "Expected integer for /chosen/%s\n", name);
+        exit(1);
+    }
+    return fdt32_to_cpu(*(uint32_t *)p->data);
+}
+
+static void devtree_machine_init(ram_addr_t ram_size, int vga_ram_size,
+                            const char *boot_device, DisplayState *ds,
+                            const char *kernel_filename, const char *kernel_cmdline,
+                            const char *initrd_filename, const char *cpu_model)
+{
+    cpu_device_register();
+    register_devices();
+    parse_devtree(devtree_machine.name);
+    /* FIXME: Get these values from device tree.  */
+    cpu_bootstrap(kernel_filename, kernel_cmdline, initrd_filename);
+}
+
+QEMUMachine devtree_machine = {
+    .name = "",
+    .desc = "Device tree",
+    .init = devtree_machine_init,
+    .max_cpus = 1,
+};