From: Ruslan Ruslichenko <[email protected]> This feature allows QEMU to dynamically instantiate hardware model, instead of relying on static configs.
Key changes: - Added a new global option '-hw-dtb' to the QEMU command-line parser. - Implemented logic in the fdt-generic to parse this DTB and dynamically instantiate devices, memory regions, and interrupts based on the node definitions. Signed-off-by: Ruslan Ruslichenko <[email protected]> --- blockdev.c | 12 + hw/arm/boot.c | 8 +- hw/arm/raspi4b.c | 4 +- hw/arm/vexpress.c | 4 +- hw/arm/xlnx-zcu102.c | 3 +- hw/core/fdt_generic.c | 283 +++++ hw/core/fdt_generic_devices.c | 130 +++ hw/core/fdt_generic_util.c | 1883 ++++++++++++++++++++++++++++++ hw/core/machine.c | 19 + hw/core/meson.build | 3 + include/hw/boards.h | 1 + include/hw/fdt_generic.h | 126 ++ include/hw/fdt_generic_devices.h | 22 + include/hw/fdt_generic_util.h | 283 +++++ include/hw/qdev-properties.h | 1 + include/net/net.h | 4 + include/qemu/log.h | 3 + include/system/blockdev.h | 2 + include/system/device_tree.h | 101 +- net/net.c | 4 +- qemu-options.hx | 9 + system/device_tree.c | 377 +++++- system/globals.c | 2 + system/vl.c | 3 + 24 files changed, 3248 insertions(+), 39 deletions(-) create mode 100644 hw/core/fdt_generic.c create mode 100644 hw/core/fdt_generic_devices.c create mode 100644 hw/core/fdt_generic_util.c create mode 100644 include/hw/fdt_generic.h create mode 100644 include/hw/fdt_generic_devices.h create mode 100644 include/hw/fdt_generic_util.h diff --git a/blockdev.c b/blockdev.c index dbd1d4d3e8..fb97951149 100644 --- a/blockdev.c +++ b/blockdev.c @@ -306,6 +306,18 @@ int drive_get_max_bus(BlockInterfaceType type) return max_bus; } +/* Xilinx: keep for fdt_generic */ +/* Get a block device. This should only be used for single-drive devices + * (e.g. SD/Floppy/MTD). Multi-disk devices (scsi/ide) should use the + * appropriate bus. + */ +DriveInfo *drive_get_next(BlockInterfaceType type) +{ + static int next_block_unit[IF_COUNT]; + + return drive_get(type, 0, next_block_unit[type]++); +} + static void bdrv_format_print(void *opaque, const char *name) { qemu_printf(" %s", name); diff --git a/hw/arm/boot.c b/hw/arm/boot.c index b91660208f..a2125efc7f 100644 --- a/hw/arm/boot.c +++ b/hw/arm/boot.c @@ -509,10 +509,10 @@ int arm_load_dtb(hwaddr addr, const struct arm_boot_info *binfo, return 0; } - acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells", - NULL, &error_fatal); - scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells", - NULL, &error_fatal); + acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells", 0, + false, &error_fatal); + scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells", 0, + false, &error_fatal); if (acells == 0 || scells == 0) { fprintf(stderr, "dtb file invalid (#address-cells or #size-cells 0)\n"); goto fail; diff --git a/hw/arm/raspi4b.c b/hw/arm/raspi4b.c index 0422ae0f00..c06dc88d1b 100644 --- a/hw/arm/raspi4b.c +++ b/hw/arm/raspi4b.c @@ -42,9 +42,9 @@ static void raspi_add_memory_node(void *fdt, hwaddr mem_base, hwaddr mem_len) uint32_t acells, scells; char *nodename = g_strdup_printf("/memory@%" PRIx64, mem_base); - acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells", + acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells", 0, NULL, &error_fatal); - scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells", + scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells", 0, NULL, &error_fatal); /* validated by arm_load_dtb */ g_assert(acells && scells); diff --git a/hw/arm/vexpress.c b/hw/arm/vexpress.c index 3492e03a65..e5cb884a50 100644 --- a/hw/arm/vexpress.c +++ b/hw/arm/vexpress.c @@ -486,9 +486,9 @@ static void vexpress_modify_dtb(const struct arm_boot_info *info, void *fdt) const VEDBoardInfo *daughterboard = (const VEDBoardInfo *)info; acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells", - NULL, &error_fatal); + 0, 0, &error_fatal); scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells", - NULL, &error_fatal); + 0, 0, &error_fatal); intc = find_int_controller(fdt); if (!intc) { /* Not fatal, we just won't provide virtio. This will diff --git a/hw/arm/xlnx-zcu102.c b/hw/arm/xlnx-zcu102.c index 06a3d7dfe7..64b77d041a 100644 --- a/hw/arm/xlnx-zcu102.c +++ b/hw/arm/xlnx-zcu102.c @@ -88,7 +88,8 @@ static void zcu102_modify_dtb(const struct arm_boot_info *binfo, void *fdt) &error_fatal); for (i = 0; node_path && node_path[i]; i++) { - r = qemu_fdt_getprop(fdt, node_path[i], "method", &prop_len, NULL); + r = qemu_fdt_getprop(fdt, node_path[i], "method", &prop_len, + false, NULL); method_is_hvc = r && !strcmp("hvc", r); /* Allow HVC based firmware if EL2 is enabled. */ diff --git a/hw/core/fdt_generic.c b/hw/core/fdt_generic.c new file mode 100644 index 0000000000..fbf8c802fc --- /dev/null +++ b/hw/core/fdt_generic.c @@ -0,0 +1,283 @@ +/* + * 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 <[email protected]>. + * + * 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 "qemu/osdep.h" +#include "hw/fdt_generic.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "qemu/coroutine.h" +#include "qemu/log.h" +#include "hw/cpu/cluster.h" +#include "system/reset.h" + +#ifndef FDT_GENERIC_ERR_DEBUG +#define FDT_GENERIC_ERR_DEBUG 0 +#endif +#define DB_PRINT(lvl, ...) do { \ + if (FDT_GENERIC_ERR_DEBUG > (lvl)) { \ + qemu_log_mask(LOG_FDT, ": %s: ", __func__); \ + qemu_log_mask(LOG_FDT, ## __VA_ARGS__); \ + } \ +} while (0) + +#define FDT_GENERIC_MAX_PATTERN_LEN 1024 + +typedef struct TableListNode { + struct TableListNode *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 = *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 is 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 *iter; + + for (iter = *head; iter != NULL; iter = iter->next) { + if (!strcmp(key, iter->key)) { + if (iter->fdt_init) { + return iter->fdt_init(node_path, fdti, iter->opaque); + } + return 0; + } + } + + return 1; +} + +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); +} + +static void dump_table(TableListNode *head) +{ + TableListNode *iter; + + for (iter = head; iter != NULL; iter = iter->next) { + printf("key : %s, opaque data %p\n", head->key, head->opaque); + } +} + +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(1, "Yield #%d\n", this_yield); + qemu_co_queue_wait(fdti->cq, NULL); + DB_PRINT(1, "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; +} + +static int get_next_cpu_cluster_id(void) +{ + static int i; + + return i++; +} + +void fdt_init_register_user_cpu_cluster(FDTMachineInfo *fdti, Object *cluster) +{ + int i = get_next_cpu_cluster_id(); + DeviceState *dev = DEVICE(cluster); + FDTCPUCluster *cl; + + qdev_prop_set_uint32(dev, "cluster-id", i); + + cl = g_new0(FDTCPUCluster, 1); + cl->cpu_cluster = cluster; + cl->next = fdti->clusters; + cl->user = true; + + fdti->clusters = cl; + + DB_PRINT(0, "%s: Registered user-defined cpu cluster with id %d\n", + object_get_canonical_path(cluster), i); +} + +static void *fdt_init_add_cpu_cluster(FDTMachineInfo *fdti, char *compat) +{ + FDTCPUCluster *cl = g_malloc0(sizeof(*cl)); + int i = get_next_cpu_cluster_id(); + char *name = g_strdup_printf("cluster%d", i); + Object *obj; + + obj = object_new(TYPE_CPU_CLUSTER); + object_property_add_child(object_get_root(), name, OBJECT(obj)); + qdev_prop_set_uint32(DEVICE(obj), "cluster-id", i); + + cl->cpu_type = g_strdup(compat); + cl->cpu_cluster = obj; + cl->next = fdti->clusters; + + fdti->clusters = cl; + + g_free(name); + + return obj; +} + +void *fdt_init_get_cpu_cluster(FDTMachineInfo *fdti, Object *parent, char *compat) +{ + FDTCPUCluster *cl = fdti->clusters; + + if (object_dynamic_cast(parent, TYPE_CPU_CLUSTER)) { + /* The direct parent of this CPU is a CPU cluster. Use it. */ + return parent; + } + + while (cl) { + if (!cl->user && !strcmp(compat, cl->cpu_type)) { + return cl->cpu_cluster; + } + cl = cl->next; + } + + /* No cluster found so create and return a new one */ + return fdt_init_add_cpu_cluster(fdti, compat); +} + +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) +{ + FDTCPUCluster *cl = fdti->clusters; + FDTDevOpaque *dp; + + while (cl) { + FDTCPUCluster *tmp = cl; + cl = cl->next; + g_free(tmp->cpu_type); + g_free(tmp); + } + 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/core/fdt_generic_devices.c b/hw/core/fdt_generic_devices.c new file mode 100644 index 0000000000..b0ca2ee06b --- /dev/null +++ b/hw/core/fdt_generic_devices.c @@ -0,0 +1,130 @@ +#include "qemu/osdep.h" +#include "hw/fdt_generic_util.h" +#include "hw/fdt_generic_devices.h" +#include "qom/object.h" +#include "system/blockdev.h" +#include "system/memory.h" +#include "system/address-spaces.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "chardev/char.h" +#include "qemu/coroutine.h" + +#include "hw/char/serial.h" +#include "hw/block/flash.h" +#include "hw/qdev-core.h" + +/* FIXME: This file should go away. When these devices are properly QOMified + * then these FDT creations should happen automatically without need for these + * explict shim functions + */ + +/* Piggy back fdt_generic_util.c ERR_DEBUG symbol as these two are really the + * same feature + */ + +#ifndef FDT_GENERIC_UTIL_ERR_DEBUG +#define FDT_GENERIC_UTIL_ERR_DEBUG 0 +#endif +#define DB_PRINT(lvl, ...) do { \ + if (FDT_GENERIC_UTIL_ERR_DEBUG > (lvl)) { \ + qemu_log_mask(lvl, ": %s: ", __func__); \ + qemu_log_mask(lvl, ## __VA_ARGS__); \ + } \ +} while (0); + +#define DB_PRINT_NP(lvl, ...) do { \ + if (FDT_GENERIC_UTIL_ERR_DEBUG > (lvl)) { \ + qemu_log_mask(lvl, "%s", node_path); \ + DB_PRINT((lvl), ## __VA_ARGS__); \ + } \ +} while (0); + +int fdt_generic_num_cpus; + +static int i2c_bus_fdt_init(char *node_path, FDTMachineInfo *fdti, void *priv) +{ + Object *parent; + DeviceState *dev; + char parent_node_path[DT_PATH_LENGTH]; + char *node_name = qemu_devtree_get_node_name(fdti->fdt, node_path); + + DB_PRINT_NP(1, "\n"); + /* FIXME: share this code with fdt_generic_util.c/fdt_init_qdev() */ + if (qemu_devtree_getparent(fdti->fdt, parent_node_path, node_path)) { + abort(); + } + while (!fdt_init_has_opaque(fdti, parent_node_path)) { + fdt_init_yield(fdti); + } + parent = fdt_init_get_opaque(fdti, parent_node_path); + dev = (DeviceState *)object_dynamic_cast(parent, TYPE_DEVICE); + if (parent && dev) { + while (!dev->realized) { + fdt_init_yield(fdti); + } + DB_PRINT_NP(0, "parenting i2c bus to %s bus %s\n", parent_node_path, + node_name); + fdt_init_set_opaque(fdti, node_path, + qdev_get_child_bus(dev, node_name)); + } else { + DB_PRINT_NP(0, "orphaning i2c bus\n"); + } + return 0; +} + +static int sysmem_fdt_init(char *node_path, FDTMachineInfo *fdti, + void *priv) +{ + fdt_init_set_opaque(fdti, node_path, OBJECT(get_system_memory())); + return 0; +} + +fdt_register_compatibility(sysmem_fdt_init, "compatible:qemu:system-memory"); + +static const void *null; + +fdt_register_compatibility_n(null, "compatible:marvell,88e1111", 1); +fdt_register_compatibility_n(null, "compatible:arm,pl310-cache", 2); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-cortexa9-1.00.a", 3); +fdt_register_compatibility_n(null, "compatible:xlnx,zynq_remoteproc", 4); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-smcc-1.00.a", 5); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-smc", 6); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-nand-1.00.a", 7); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-ram-1.00.a", 8); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-ocm", 9); +fdt_register_compatibility_n(null, "compatible:marvell,88e1118r", 10); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-clkc", 11); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-ddrc", 12); +fdt_register_compatibility_n(null, "compatible:xlnx,ps7-scuc-1.00.a", 13); +fdt_register_compatibility_n(null, "compatible:fixed-clock", 14); +fdt_register_compatibility_n(null, "compatible:xlnx,pinctrl-zynq", 15); +fdt_register_compatibility_n(null, "compatible:ulpi-phy", 16); +fdt_register_compatibility_n(null, "compatible:xlnx,zynq-efuse", 17); +fdt_register_compatibility_n(null, "compatible:qemu:memory-region-spec", 18); +fdt_register_compatibility_n(null, "compatible:shared-dma-pool", 19); + +fdt_register_instance_n(i2c_bus_fdt_init, "i2c@0", 0); +fdt_register_instance_n(i2c_bus_fdt_init, "i2c@1", 1); +fdt_register_instance_n(i2c_bus_fdt_init, "i2c@2", 2); +fdt_register_instance_n(i2c_bus_fdt_init, "i2c@3", 3); +fdt_register_instance_n(i2c_bus_fdt_init, "i2c@4", 4); +fdt_register_instance_n(i2c_bus_fdt_init, "i2c@5", 5); +fdt_register_instance_n(i2c_bus_fdt_init, "i2c@6", 6); +fdt_register_instance_n(i2c_bus_fdt_init, "i2c@7", 7); + +static const TypeInfo fdt_qom_aliases [] = { + { .name = "qemu-memory-region", .parent = "memory-region" }, + { .name = "simple-bus", .parent = "memory-region" }, +}; + +static void fdt_generic_register_types(void) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(fdt_qom_aliases); ++i) { + type_register_static(&fdt_qom_aliases[i]); + } +} + +type_init(fdt_generic_register_types) diff --git a/hw/core/fdt_generic_util.c b/hw/core/fdt_generic_util.c new file mode 100644 index 0000000000..8af3cabe51 --- /dev/null +++ b/hw/core/fdt_generic_util.c @@ -0,0 +1,1883 @@ +/* + * 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 <[email protected]>. + * + * 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 "qemu/osdep.h" + #include "hw/fdt_generic_util.h" + #include "hw/fdt_generic_devices.h" + #include "net/net.h" + #include "system/memory.h" + #include "system/address-spaces.h" + #include "hw/sysbus.h" + #include "qapi/error.h" + #include "qemu/error-report.h" + #include "system/system.h" + #include "system/reset.h" + #include "qemu/cutils.h" + #include "system/blockdev.h" + #include "system/block-backend.h" + #include "chardev/char.h" + #include "qemu/log.h" + #include "qemu/config-file.h" + #include "block/block.h" + #include "hw/ssi/ssi.h" + #include "hw/nvram/xlnx-bbram.h" + #include "hw/nvram/xlnx-efuse.h" + #include "hw/boards.h" + #include "qemu/option.h" + #include "hw/qdev-properties.h" + #include "hw/cpu/cluster.h" + + #ifndef FDT_GENERIC_UTIL_ERR_DEBUG + #define FDT_GENERIC_UTIL_ERR_DEBUG 3 + #endif + #define DB_PRINT(lvl, ...) do { \ + if (FDT_GENERIC_UTIL_ERR_DEBUG > (lvl)) { \ + qemu_log_mask(LOG_FDT, ": %s: ", __func__); \ + qemu_log_mask(LOG_FDT, ## __VA_ARGS__); \ + } \ + } while (0); + + #define DB_PRINT_NP(lvl, ...) do { \ + if (FDT_GENERIC_UTIL_ERR_DEBUG > (lvl)) { \ + qemu_log_mask(LOG_FDT, "%s", node_path); \ + DB_PRINT((lvl), ## __VA_ARGS__); \ + } \ + } while (0); + + /* FIXME: wrap direct calls into libfdt */ + + #include <libfdt.h> + #include <stdlib.h> + + int fdt_serial_ports; + + 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); + + + 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 bool qemu_irq_shared_and_handler(bool *inputs, int n) + { + int i; + + assert(n < MAX_IRQ_SHARED_INPUTS); + + for (i = 0; i < n; ++i) { + if (!inputs[i]) { + return false; + } + } + return true; + } + + 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); + } + } + + static void fdt_init_cpu_clusters(FDTMachineInfo *fdti) + { + FDTCPUCluster *cl = fdti->clusters; + + while (cl) { + qdev_realize(DEVICE(cl->cpu_cluster), NULL, &error_fatal); + cl = cl->next; + } + } + + 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; + + fdt_serial_ports = 0; + + /* parse the device tree */ + if (!qemu_devtree_get_root_node(fdt, node_path)) { + memory_region_transaction_begin(); + fdt_init_set_opaque(fdti, node_path, NULL); + simple_bus_fdt_init(node_path, fdti); + while (qemu_co_enter_next(fdti->cq, NULL)); + fdt_init_cpu_clusters(fdti); + fdt_init_all_irqs(fdti); + memory_region_transaction_commit(); + } else { + fprintf(stderr, "FDT: ERROR: cannot get root node from device tree %s\n" + , node_path); + } + + /* FIXME: Populate these from DTS and create CPU clusters. */ + //current_machine->smp.cores = fdt_generic_num_cpus; + //current_machine->smp.cpus = fdt_generic_num_cpus; + //current_machine->smp.max_cpus = fdt_generic_num_cpus; + + bdrv_drain_all(); + DB_PRINT(0, "FDT: Device tree scan complete\n"); + return fdti; + } + + struct FDTInitNodeArgs { + char *node_path; + char *parent_path; + FDTMachineInfo *fdti; + }; + + static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *compat); + + static int check_compat(const char *prefix, const char *compat, + char *node_path, FDTMachineInfo *fdti) + { + char *compat_prefixed = g_strconcat(prefix, compat, NULL); + const int done = !fdt_init_compat(node_path, fdti, compat_prefixed); + g_free(compat_prefixed); + return done; + } + + static void fdt_init_node(void *args) + { + struct FDTInitNodeArgs *a = args; + char *node_path = a->node_path; + FDTMachineInfo *fdti = a->fdti; + g_free(a); + + simple_bus_fdt_init(node_path, fdti); + + char *all_compats = NULL, *node_name; + char *device_type = NULL; + int compat_len; + + DB_PRINT_NP(1, "enter\n"); + + /* try instance binding first */ + node_name = qemu_devtree_get_node_name(fdti->fdt, node_path); + DB_PRINT_NP(1, "node with name: %s\n", node_name ? node_name : "(none)"); + if (!node_name) { + printf("FDT: ERROR: nameless node: %s\n", node_path); + } + if (!fdt_init_inst_bind(node_path, fdti, node_name)) { + DB_PRINT_NP(0, "instance bind successful\n"); + goto exit; + } + + /* fallback to compatibility binding */ + all_compats = qemu_fdt_getprop(fdti->fdt, node_path, "compatible", + &compat_len, false, NULL); + if (all_compats) { + char *compat = all_compats; + char * const end = &all_compats[compat_len - 1]; /* points to nul */ + + while (compat < end) { + if (check_compat("compatible:", compat, node_path, fdti)) { + goto exit; + } + + if (!fdt_init_qdev(node_path, fdti, compat)) { + check_compat("postinit:", compat, node_path, fdti); + goto exit; + } + + /* Scan forward to the end of the current compat. */ + while (compat < end && *compat) { + ++compat; + } + + /* Replace nul with space for the debug printf below. */ + if (compat < end) { + *compat++ = ' '; + } + }; + } else { + DB_PRINT_NP(0, "no compatibility found\n"); + } + + /* Try to create the device using device_type property + * Not every device tree node has compatible property, so + * try with device_type. + */ + device_type = qemu_fdt_getprop(fdti->fdt, node_path, + "device_type", NULL, false, NULL); + if (device_type) { + if (check_compat("device_type:", device_type, node_path, fdti)) { + goto exit; + } + + if (!fdt_init_qdev(node_path, fdti, device_type)) { + goto exit; + } + } + + if (all_compats) { + DB_PRINT_NP(0, "FDT: Unsupported peripheral invalidated - " + "compatibilities %s\n", all_compats); + qemu_fdt_setprop_string(fdti->fdt, node_path, "compatible", + "invalidated"); + } + exit: + + DB_PRINT_NP(1, "exit\n"); + + if (!fdt_init_has_opaque(fdti, node_path)) { + fdt_init_set_opaque(fdti, node_path, NULL); + } + g_free(node_path); + g_free(all_compats); + g_free(device_type); + return; + } + + static int simple_bus_fdt_init(char *node_path, FDTMachineInfo *fdti) + { + int i; + int num_children = qemu_devtree_get_num_children(fdti->fdt, node_path, + 1); + char **children; + + if (num_children == 0) { + return 0; + } + children = qemu_devtree_get_children(fdti->fdt, node_path, 1); + + DB_PRINT_NP(num_children ? 0 : 1, "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; + } + + static qemu_irq fdt_get_gpio(FDTMachineInfo *fdti, char *node_path, + int* cur_cell, qemu_irq input, + const FDTGenericGPIOSet *gpio_set, + const char *debug_success, bool *end) { + void *fdt = fdti->fdt; + uint32_t parent_phandle, parent_cells = 0, cells[32]; + char parent_node_path[DT_PATH_LENGTH]; + DeviceState *parent; + int i; + Error *errp = NULL; + const char *reason = NULL; + bool free_reason = false; + const char *propname = gpio_set->names->propname; + const char *cells_propname = gpio_set->names->cells_propname; + + cells[0] = 0; + + parent_phandle = qemu_fdt_getprop_cell(fdt, node_path, propname, + (*cur_cell)++, false, &errp); + if (errp) { + reason = g_strdup_printf("Cant get phandle from \"%s\" property\n", + propname); + *end = true; + free_reason = true; + goto fail_silent; + } + if (qemu_devtree_get_node_by_phandle(fdt, parent_node_path, + parent_phandle)) { + *end = true; + reason = "cant get node from phandle\n"; + goto fail; + } + parent_cells = qemu_fdt_getprop_cell(fdt, parent_node_path, + cells_propname, 0, false, &errp); + if (errp) { + *end = true; + reason = g_strdup_printf("cant get the property \"%s\" from the " \ + "parent \"%s\"\n", + cells_propname, parent_node_path); + free_reason = true; + goto fail; + } + + for (i = 0; i < parent_cells; ++i) { + cells[i] = qemu_fdt_getprop_cell(fdt, node_path, propname, + (*cur_cell)++, false, &errp); + if (errp) { + *end = true; + reason = "cant get cell value"; + goto fail; + } + } + + while (!fdt_init_has_opaque(fdti, parent_node_path)) { + fdt_init_yield(fdti); + } + parent = DEVICE(fdt_init_get_opaque(fdti, parent_node_path)); + + if (!parent) { + reason = "parent is not a device"; + goto fail_silent; + } + + while (!parent->realized) { + fdt_init_yield(fdti); + } + + { + const FDTGenericGPIOConnection *fgg_con = NULL; + uint16_t range, idx; + const char *gpio_name = NULL; + qemu_irq ret; + + if (object_dynamic_cast(OBJECT(parent), TYPE_FDT_GENERIC_GPIO)) { + const FDTGenericGPIOSet *set; + FDTGenericGPIOClass *parent_fggc = + FDT_GENERIC_GPIO_GET_CLASS(parent); + + for (set = parent_fggc->controller_gpios; set && set->names; + set++) { + if (!strcmp(gpio_set->names->cells_propname, + set->names->cells_propname)) { + fgg_con = set->gpios; + break; + } + } + } + + /* FIXME: cells[0] is not always the fdt indexing match system */ + idx = cells[0] & ~(1ul << 31); + if (fgg_con) { + range = fgg_con->range ? fgg_con->range : 1; + while (!(idx >= fgg_con->fdt_index + && idx < (fgg_con->fdt_index + range)) + && fgg_con->name) { + fgg_con++; + range = fgg_con->range ? fgg_con->range : 1; + } + + idx -= fgg_con->fdt_index; + gpio_name = fgg_con->name; + } + + if (input) { + FDTIRQConnection *irq = g_new0(FDTIRQConnection, 1); + bool (*merge_fn)(bool *, int) = qemu_irq_shared_or_handler; + + /* FIXME: I am kind of stealing here. Use the msb of the first + * cell to indicate the merge function. This needs to be discussed + * with device-tree community on how this should be done properly. + */ + if (cells[0] & (1 << 31)) { + merge_fn = qemu_irq_shared_and_handler; + } + + DB_PRINT_NP(1, "%s GPIO output %s[%d] on %s\n", debug_success, + gpio_name ? gpio_name : "unnamed", idx, + parent_node_path); + *irq = (FDTIRQConnection) { + .dev = parent, + .name = gpio_name, + .merge_fn = merge_fn, + .i = idx, + .irq = input, + .sink_info = NULL, /* FIMXE */ + .next = fdti->irqs + }; + fdti->irqs = irq; + } + + if (!strcmp(propname, "interrupts-extended") && + object_dynamic_cast(OBJECT(parent), TYPE_FDT_GENERIC_INTC) && + parent_cells > 1) { + qemu_irq *irqs = g_new0(qemu_irq, fdt_generic_num_cpus); + // int i; + + fdt_get_irq_info_from_intc(fdti, irqs, parent_node_path, cells, + parent_cells, fdt_generic_num_cpus, &errp); + if (errp) { + reason = "failed to create gpio connection"; + goto fail; + } + + ret = NULL; + for (i = 0; i < fdt_generic_num_cpus; i++) { + if (irqs[i]) { + ret = irqs[i]; + break; + } + } + g_free(irqs); + } else { + ret = qdev_get_gpio_in_named(parent, gpio_name, idx); + } + + if (ret) { + DB_PRINT_NP(1, "wiring GPIO input %s on %s ...\n", + fgg_con ? fgg_con->name : "unnamed", parent_node_path); + } + return ret; + } + fail: + if (reason) { + fprintf(stderr, "%s Failed: %s\n", node_path, reason); + } + + fail_silent: + if (free_reason) { + g_free((void *)reason); + } + return NULL; + } + + 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 = false; + + 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); + uint32_t *imap_p; + 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 */ + imap = qemu_fdt_getprop(fdt, node_path, "interrupt-map", &len, + use_parent, &errp); + + 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; + + imap_p = qemu_fdt_getprop(fdt, node_path, "interrupt-map", + &len, use_parent, &errp); + if (!imap_cached) { + memcpy(imap_cache, imap_p, 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. + */ + imap = qemu_fdt_getprop(fdt, node_path, "interrupt-map", &len, + use_parent, &errp); + + 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; + } + } + } + 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') + #define LOWER_CASE(a) ((a) >= 'a' && (a) <= 'z') + + static void trim_version(char *x) + { + long result; + + for (;;) { + x = strchr(x, '-'); + if (!x) { + return; + } + if (DIGIT(x[1])) { + /* Try to trim Xilinx version suffix */ + const char *p; + + qemu_strtol(x + 1, &p, 0, &result); + + if ( *p == '.') { + *x = 0; + return; + } else if ( *p == 0) { + return; + } + } else if (x[1] == 'r' && x[3] == 'p') { + /* Try to trim ARM version suffix */ + if (DIGIT(x[2]) && DIGIT(x[4])) { + *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 inline const char *trim_vendor(const char *s) + { + /* FIXME: be more intelligent */ + const char *ret = memchr(s, ',', strlen(s)); + return ret ? ret + 1 : s; + } + + /* + * Try to attach by matching drive created by '-blockdev node-name=LABEL' + * iff the FDT node contains property 'blockdev-node-name=LABEL'. + * + * Return false unless the given node_path has the property. + * + * Presence of the property also disables the node from ever attached + * to any drive created by the legacy '-drive' QEMU option. + * + * For more on '-blockdev', see: + * http://events17.linuxfoundation.org/sites/events/files/slides/talk_11.pdf + */ + static bool fdt_attach_blockdev(FDTMachineInfo *fdti, + char *node_path, Object *dev) + { + static const char propname[] = "blockdev-node-name"; + char *label; + + /* Inspect FDT node for blockdev-only binding */ + label = qemu_fdt_getprop(fdti->fdt, node_path, propname, + NULL, false, NULL); + + /* Skip legacy node */ + if (!label) { + return false; + } + + /* + * Missing matching bdev is not an error: attachment is optional. + * + * error_setg() aborts, never returns: 'goto ret' is just sanity. + */ + if (!label[0]) { + error_setg(&error_abort, "FDT-node '%s': property '%s' = <empty>", + node_path, propname); + goto ret; + } + + if (!bdrv_find_node(label)) { + goto ret; + } + + object_property_set_str(OBJECT(dev), "drive", label, NULL); + ret: + g_free(label); + return true; + } + + static void fdt_attach_blockdev_noname(FDTMachineInfo *fdti, + char *node_path, Object *dev) + { + char *blockdev_name = NULL; + + blockdev_name = qemu_fdt_getprop_string(fdti->fdt, node_path, + "blockdev-node-name", 0, false, NULL); + if (!blockdev_name) { + blockdev_name = qemu_devtree_get_node_name(fdti->fdt, + node_path); + substitute_char(blockdev_name, '@', '-'); + qemu_fdt_setprop_string(fdti->fdt, node_path, + "blockdev-node-name", + blockdev_name); + } + g_free(blockdev_name); + fdt_attach_blockdev(fdti, node_path, dev); + } + + static void fdt_attach_drive(FDTMachineInfo *fdti, char *node_path, + Object *dev, BlockInterfaceType drive_type) + { + DriveInfo *dinfo = NULL; + uint32_t *di_val = NULL; + int di_len = 0; + + /* Do nothing if the device is not a block front-end */ + if (!object_property_find(OBJECT(dev), "drive")) { + return; + } + + /* Try non-legacy */ + if (fdt_attach_blockdev(fdti, node_path, dev)) { + return; + } + + /* + * Try legacy with explicit 'drive-index' binding, or next-unit + * as fallback binding. + */ + di_val = qemu_fdt_getprop(fdti->fdt, node_path, "drive-index", + &di_len, false, NULL); + + if (di_val && (di_len == sizeof(*di_val))) { + dinfo = drive_get_by_index(drive_type, be32_to_cpu(*di_val)); + } else { + dinfo = drive_get_next(drive_type); + } + + if (dinfo) { + qdev_prop_set_drive(DEVICE(dev), "drive", + blk_by_legacy_dinfo(dinfo)); + } + + return; + } + + static void fdt_attach_indexed_drive(FDTMachineInfo *fdti, char *node_path, + Object *dev, uint32_t di_default, + BlockInterfaceType drive_type) + { + static const char prop[] = "drive-index"; + uint32_t *di_val = NULL; + int di_len = 0; + + di_val = qemu_fdt_getprop(fdti->fdt, node_path, prop, &di_len, false, NULL); + if (!di_val || (di_len != sizeof(*di_val))) { + uint32_t di = cpu_to_be32(di_default); + int r; + + r = qemu_fdt_setprop(fdti->fdt, node_path, prop, &di, sizeof(di)); + if (r < 0) { + error_setg(&error_abort, + "Couldn't set fdt property %s.%s: %s", + node_path, prop, fdt_strerror(r)); + } + } + + fdt_attach_drive(fdti, node_path, dev, drive_type); + } + + static Object *fdt_create_from_compat(const char *compat, char **dev_type) + { + Object *ret = NULL; + char *c = g_strdup(compat); + + /* Try to create the object */ + ret = object_new(c); + + if (!ret) { + /* Trim the version off the end and try again */ + trim_version(c); + ret = object_new(c); + + if (!ret) { + /* Replace commas with full stops */ + substitute_char(c, ',', '.'); + ret = object_new(c); + } + } + + if (!ret) { + /* Restart with the orginal string and now replace commas with full stops + * and try again. This means that versions are still included. + */ + g_free(c); + c = g_strdup(compat); + substitute_char(c, ',', '.'); + ret = object_new(c); + } + + if (dev_type) { + *dev_type = c; + } else { + g_free(c); + } + + if (!ret) { + const char *no_vendor = trim_vendor(compat); + + if (no_vendor != compat) { + return fdt_create_from_compat(no_vendor, dev_type); + } + } + return ret; + } + + /*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(); + } + } + + /* FIXME: use structs instead of parallel arrays */ + + static const char *fdt_generic_reg_size_prop_names[] = { + "#address-cells", + "#size-cells", + "#bus-cells", + "#priority-cells", + }; + + static const int fdt_generic_reg_cells_defaults[] = { + 1, + 1, + 0, + 0, + }; + + /* + * Error handler for device creation failure. + * + * We look for qemu-fdt-abort-on-error properties up the tree. + * If we find one, we abort with the provided error message. + */ + static void fdt_dev_error(FDTMachineInfo *fdti, char *node_path, char *compat) + { + char *abort_on_error; + char *warn_on_error; + + warn_on_error = qemu_fdt_getprop(fdti->fdt, node_path, + "qemu-fdt-warn-on-error", 0, + true, NULL); + abort_on_error = qemu_fdt_getprop(fdti->fdt, node_path, + "qemu-fdt-abort-on-error", 0, + true, NULL); + if (warn_on_error) { + if (strncmp("device_type", compat, strlen("device_type"))) { + warn_report("%s: %s", compat, warn_on_error); + } + } + + if (abort_on_error) { + error_report("Failed to create %s", compat); + error_setg(&error_fatal, "%s", abort_on_error); + } + } + + static void fdt_init_qdev_link_prop(Object *obj, ObjectProperty *p, + FDTMachineInfo *fdti, + const char *node_path, + const QEMUDevtreeProp *prop) + { + int len = prop->len; + void *val = prop->value; + const char *propname = prop->name; + + Object *linked_dev, *proxy; + char target_node_path[DT_PATH_LENGTH]; + g_autofree char *propname_target = g_strconcat(propname, "-target", NULL); + Error *errp = NULL; + + 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); + + proxy = linked_dev ? object_property_get_link(linked_dev, + propname_target, + &errp) : NULL; + if (!errp && proxy) { + DB_PRINT_NP(0, "detected proxy object for %s connection\n", propname); + linked_dev = proxy; + } + + if (!linked_dev) { + return; + } + + errp = NULL; + object_property_set_link(obj, propname, linked_dev, &errp); + if (errp) { + /* Unable to set the property, maybe it is a memory alias? */ + MemoryRegion *alias_mr; + int offset = len / 2; + int region = 0; + + if (len > 4) { + region = get_int_be(val + offset, len - offset); + } + + alias_mr = sysbus_mmio_get_region(SYS_BUS_DEVICE(linked_dev), region); + + object_property_set_link(obj, propname, OBJECT(alias_mr), &error_abort); + + } + + DB_PRINT_NP(0, "set link %s\n", propname); + } + + static void fdt_init_qdev_scalar_prop(Object *obj, ObjectProperty *p, + FDTMachineInfo *fdti, + const char *node_path, + const QEMUDevtreeProp *prop) + { + const char *propname = trim_vendor(prop->name); + void *val = prop->value; + int len = prop->len; + + /* FIXME: handle generically using accessors and stuff */ + if (!strncmp(p->type, "link", 4)) { + fdt_init_qdev_link_prop(obj, p, fdti, node_path, prop); + return; + } + + if (!strcmp(p->type, "uint8") || !strcmp(p->type, "uint16") || + !strcmp(p->type, "uint32") || !strcmp(p->type, "uint64") || + !strcmp(p->type, "int8") || !strcmp(p->type, "int16") || + !strcmp(p->type, "int32") || !strcmp(p->type, "int64")) { + object_property_set_int(obj, propname, + get_int_be(val, len), &error_abort); + DB_PRINT_NP(0, "set property %s to 0x%llx\n", propname, + (unsigned long long)get_int_be(val, len)); + return; + } + + if (!strcmp(p->type, "boolean") || !strcmp(p->type, "bool")) { + object_property_set_bool(obj, propname, + !!get_int_be(val, len), &error_abort); + DB_PRINT_NP(0, "set property %s to %s\n", propname, + get_int_be(val, len) ? "true" : "false"); + return; + } + + if (!strcmp(p->type, "string") || !strcmp(p->type, "str")) { + object_property_set_str(obj, propname, + (const char *)val, &error_abort); + DB_PRINT_NP(0, "set property %s to %s\n", propname, (const char *)val); + return; + } + + DB_PRINT_NP(0, "WARNING: property is of unknown type\n"); + } + + static size_t fdt_array_elem_len(FDTMachineInfo *fdti, + const char *node_path, + const char *propname) + { + g_autofree char *elem_cells_propname = NULL; + Error *err = NULL; + uint32_t elem_cells; + + /* + * Default element size to 1 uint32_t cell, unless it is explicitly + * given in the same FDT node (not inherited). + */ + elem_cells_propname = g_strconcat("#", propname, "-cells", NULL); + elem_cells = qemu_fdt_getprop_cell(fdti->fdt, node_path, + elem_cells_propname, 0, false, &err); + + return (err ? 1 : elem_cells) * 4; + } + +static ObjectProperty *fdt_array_elem_prop(Object *obj, + const char *propname, int k) +{ + g_autofree char *elem_name = g_strdup_printf("%s[%d]", propname, k); + + return object_property_find(obj, elem_name); +} + +static char *fdt_array_elem_type(ObjectProperty *e) +{ + size_t n = strlen(e->type); + + if (strncmp(e->type, "link", 4)) { + return NULL; + } + if (n > 6) { + return g_strndup(&e->type[5], (n - 6)); + } + + return g_strdup(TYPE_OBJECT); +} + +static ObjectProperty *fdt_array_link_elem_prop(Object *obj, ObjectProperty *e, + const char *elem_link_type) + { + /* + * Starting QEMU 8.2.0, the array-scheme has changed to be a single + * property of list. See: + * https://mail.gnu.org/archive/html/qemu-devel/2023-09/msg01832.html + * + * Thus, fdt_init_qdev_array_prop() below will have to be changed + * substantially. + * + * So for now, use a temporary hack to work around DEFINE_PROP_ARRAY() not + * creating the proper elements of type "link" (see set_prop_arraylen()). + */ + g_autofree char *elem_name = g_strdup(e->name); + void *elem_ptr = object_field_prop_ptr(obj, e->opaque); + + object_property_del(obj, elem_name); + e = object_property_add_link(obj, elem_name, + elem_link_type, elem_ptr, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); + + return e; + } + + static void fdt_init_qdev_array_prop(Object *obj, + FDTMachineInfo *fdti, + const char *node_path, + QEMUDevtreeProp *prop) + { + const char *propname = trim_vendor(prop->name); + int nr = prop->len; + Error *local_err = NULL; + char *len_name; + uint32_t elem_len; + g_autofree char *elem_link_type = NULL; + ObjectProperty *e; + + if (!prop->value || !nr) { + return; + } + + elem_len = fdt_array_elem_len(fdti, node_path, propname); + if (nr % elem_len) { + return; + } + + nr /= elem_len; + + /* + * Fail gracefully on setting the 'len-' property, due to: + * 1. The property does not exist, or + * 2. The property is not integer type, or + * 3. The property has been set, e.g., by the '-global' cmd option + */ + len_name = g_strconcat(PROP_ARRAY_LEN_PREFIX, propname, NULL); + object_property_set_int(obj, len_name, nr, &local_err); + g_free(len_name); + + if (local_err) { + error_free(local_err); + return; + } + + e = fdt_array_elem_prop(obj, propname, 0); + if (!e) { + return; + } + + elem_link_type = fdt_array_elem_type(e); + + while (nr--) { + QEMUDevtreeProp q; + + e = fdt_array_elem_prop(obj, propname, nr); + if (!e) { + continue; + } + + q = (QEMUDevtreeProp){.name = e->name, + .value = prop->value + nr * elem_len, + .len = elem_len, + }; + + if (elem_link_type) { + e = fdt_array_link_elem_prop(obj, e, elem_link_type); + q.name = e->name; + fdt_init_qdev_link_prop(obj, e, fdti, node_path, &q); + } else { + fdt_init_qdev_scalar_prop(obj, e, fdti, node_path, &q); + } + } + } + + static void fdt_prop_override(char *node_path, + QEMUDevtreeProp *props, + QEMUDevtreeProp *prop, + const char *prefix, + const char *propname) + { + char *pfxPropname = g_strdup_printf("%s-%s", prefix, propname); + QEMUDevtreeProp *pp; + + pp = qemu_devtree_prop_search(props, pfxPropname); + if (pp) { + g_free(prop->value); + prop->len = pp->len; + prop->value = g_memdup(pp->value, pp->len); + DB_PRINT_NP(1, "Found %s property match: %s\n", + prefix, pfxPropname); + } + g_free(pfxPropname); + } + + static bool ready_to_realize(DeviceState *dev) + { + FDTGenericHelperClass *fghc; + + if (!object_dynamic_cast(OBJECT(dev), TYPE_FDT_GENERIC_HELPER)) { + return true; + } + + fghc = FDT_GENERIC_HELPER_GET_CLASS(dev); + + if (!fghc->ready_to_realize) { + return true; + } + + return fghc->ready_to_realize(dev); + } + + static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *compat) + { + Object *dev, *parent; + char *dev_type = NULL; + bool is_direct_linux; + int is_intc; + Error *errp = NULL; + int i, j; + QEMUDevtreeProp *prop, *props; + char parent_node_path[DT_PATH_LENGTH]; + const FDTGenericGPIOSet *gpio_set = NULL; + /* Allocate a large number and assert if something goes over */ + FDTGenericGPIOSet tmp_gpio_set[64]; + FDTGenericGPIOClass *fggc = NULL; + + if (!compat) { + return 1; + } + dev = fdt_create_from_compat(compat, &dev_type); + if (!dev) { + DB_PRINT_NP(1, "no match found for %s\n", compat); + fdt_dev_error(fdti, node_path, compat); + return 1; + } + DB_PRINT_NP(1, "matched compat %s\n", compat); + + /* Are we doing a direct Linux boot? */ + is_direct_linux = object_property_get_bool(OBJECT(qdev_get_machine()), + "linux", NULL); + + /* Do this super early so fdt_generic_num_cpus is correct ASAP */ + if (object_dynamic_cast(dev, TYPE_CPU)) { + fdt_generic_num_cpus++; + DB_PRINT_NP(0, "is a CPU - total so far %d\n", fdt_generic_num_cpus); + } + + if (qemu_devtree_getparent(fdti->fdt, parent_node_path, node_path)) { + abort(); + } + while (!fdt_init_has_opaque(fdti, parent_node_path)) { + fdt_init_yield(fdti); + } + + parent = fdt_init_get_opaque(fdti, parent_node_path); + + if (object_dynamic_cast(dev, TYPE_CPU)) { + parent = fdt_init_get_cpu_cluster(fdti, parent, compat); + } + + if (dev->parent) { + DB_PRINT_NP(0, "Node already parented - skipping node\n"); + } else if (parent) { + DB_PRINT_NP(1, "parenting node\n"); + object_property_add_child(OBJECT(parent), + qemu_devtree_get_node_name(fdti->fdt, node_path), + OBJECT(dev)); + if (object_dynamic_cast(dev, TYPE_DEVICE)) { + Object *parent_bus = parent; + unsigned int depth = 0; + + DB_PRINT_NP(1, "bus parenting node\n"); + /* Look for an FDT ancestor that is a Bus. */ + while (parent_bus && !object_dynamic_cast(parent_bus, TYPE_BUS)) { + /* + * Assert against insanely deep hierarchies which are an + * indication of loops. + */ + assert(depth < 4096); + + parent_bus = parent_bus->parent; + depth++; + } + + if (!parent_bus + && object_dynamic_cast(OBJECT(dev), TYPE_SYS_BUS_DEVICE)) { + /* + * Didn't find any bus. Use the default sysbus one. + * This allows ad-hoc busses belonging to sysbus devices to be + * visible to -device bus=x. + */ + parent_bus = OBJECT(sysbus_get_default()); + } + + if (parent_bus) { + qdev_set_parent_bus(DEVICE(dev), BUS(parent_bus), + &error_abort); + } + } + } else { + DB_PRINT_NP(1, "orphaning node\n"); + if (object_dynamic_cast(OBJECT(dev), TYPE_SYS_BUS_DEVICE)) { + qdev_set_parent_bus(DEVICE(dev), BUS(sysbus_get_default()), + &error_abort); + } + + /* FIXME: Make this go away (centrally) */ + object_property_add_child( + object_get_root(), + qemu_devtree_get_node_name(fdti->fdt, node_path), + OBJECT(dev)); + } + fdt_init_set_opaque(fdti, node_path, dev); + + /* Call FDT Generic hooks for overriding prop default values. */ + if (object_dynamic_cast(dev, TYPE_FDT_GENERIC_PROPS)) { + FDTGenericPropsClass *k = FDT_GENERIC_PROPS_GET_CLASS(dev); + + assert(k->set_props); + k->set_props(OBJECT(dev), &error_fatal); + } + + props = qemu_devtree_get_props(fdti->fdt, node_path); + for (prop = props; prop->name; prop++) { + const char *propname = trim_vendor(prop->name); + int len; + void *val; + ObjectProperty *p = NULL; + #ifdef _WIN32 + fdt_prop_override(node_path, props, prop, "windows", propname); + #endif + if (is_direct_linux) { + /* + * We use a short lnx name because device-tree props have a max + * length of 30 characters. We already break that limit if using + * direct-linux-start-powered-off, for example. + */ + fdt_prop_override(node_path, props, prop, "direct-lnx", propname); + } + + val = prop->value; + len = prop->len; + + p = object_property_find(OBJECT(dev), propname); + if (p) { + DB_PRINT_NP(1, "matched property: %s of type %s, len %d\n", + propname, p->type, prop->len); + } + if (!p) { + fdt_init_qdev_array_prop(dev, fdti, node_path, prop); + continue; + } + + if (!strcmp(propname, "type")) { + continue; + } + + /* Special case for chardevs. It's an ordered list of strings. */ + if (!strcmp(propname, "chardev") && !strcmp(p->type, "str")) { + const char *chardev = val; + const char *chardevs_end = chardev + len; + + assert(errp == NULL); + while (chardev < chardevs_end) { + object_property_set_str(OBJECT(dev), propname, (const char *)chardev, &errp); + if (!errp) { + DB_PRINT_NP(0, "set property %s to %s\n", propname, + chardev); + break; + } + chardev += strlen(chardev) + 1; + errp = NULL; + } + assert(errp == NULL); + continue; + } + + fdt_init_qdev_scalar_prop(OBJECT(dev), p, fdti, node_path, prop); + } + + if (object_dynamic_cast(dev, TYPE_DEVICE)) { + DeviceClass *dc = DEVICE_GET_CLASS(dev); + /* connect nic if appropriate */ + static int nics; + const char *short_name = qemu_devtree_get_node_name(fdti->fdt, node_path); + + if (object_property_find(OBJECT(dev), "mac") && + object_property_find(OBJECT(dev), "netdev")) { + qdev_set_nic_properties(DEVICE(dev), &nd_table[nics]); + } + if (nd_table[nics].instantiated) { + DB_PRINT_NP(0, "NIC instantiated: %s\n", dev_type); + nics++; + } + + /* We also need to externally connect drives. Let's try to do that + * here. + */ + if (object_property_find(OBJECT(dev), "drive")) { + uint32_t *use_blkdev = NULL; + use_blkdev = qemu_fdt_getprop(fdti->fdt, node_path, + "use-blockdev", NULL, + false, NULL); + if (use_blkdev && *use_blkdev) { + fdt_attach_blockdev_noname(fdti, node_path, dev); + } else { + /* + * Remove these after we fully convert to blockdev based + * drive binding. + */ + if (object_dynamic_cast(dev, TYPE_XLNX_BBRAM)) { + /* default drive index: 0 for Versal, 2 for ZU+ */ + uint32_t di; + + di = object_property_get_uint(dev, "crc-zpads", + &error_abort) == 0 + ? 0 : 2; + fdt_attach_indexed_drive(fdti, node_path, dev, + di, IF_PFLASH); + } + if (object_dynamic_cast(dev, TYPE_XLNX_EFUSE)) { + /* default drive index: 1 for Versal, 3 for ZU+ */ + uint32_t di; + + di = object_property_get_uint(dev, "efuse-size", + &error_abort) > 2048 + ? 1 : 3; + fdt_attach_indexed_drive(fdti, node_path, dev, + di, IF_PFLASH); + } + if (object_dynamic_cast(dev, TYPE_SSI_PERIPHERAL)) { + fdt_attach_drive(fdti, node_path, dev, IF_MTD); + } + } + g_free(use_blkdev); + } + + /* Regular TYPE_DEVICE houskeeping */ + DB_PRINT_NP(0, "Short naming node: %s\n", short_name); + (DEVICE(dev))->id = g_strdup(short_name); + + if (object_dynamic_cast(dev, TYPE_CPU_CLUSTER)) { + /* + * CPU clusters must be realized at the end to make sure all child + * CPUs are parented. + */ + fdt_init_register_user_cpu_cluster(fdti, OBJECT(dev)); + } else { + while (!ready_to_realize(DEVICE(dev))) { + fdt_init_yield(fdti); + } + + object_property_set_bool(OBJECT(dev), "realized", true, &error_fatal); + qemu_register_reset((void (*)(void *))dc->legacy_reset, dev); + } + } + + if (object_dynamic_cast(dev, TYPE_SYS_BUS_DEVICE) || + object_dynamic_cast(dev, TYPE_FDT_GENERIC_MMAP)) { + FDTGenericRegPropInfo reg = {0}; + char parent_path[DT_PATH_LENGTH]; + int cell_idx = 0; + bool extended = true; + + qemu_fdt_getprop_cell(fdti->fdt, node_path, "reg-extended", 0, false, + &errp); + if (errp) { + error_free(errp); + errp = NULL; + extended = false; + qemu_devtree_getparent(fdti->fdt, parent_path, node_path); + } + + for (reg.n = 0;; reg.n++) { + char ph_parent[DT_PATH_LENGTH]; + const char *pnp = parent_path; + + reg.parents = g_renew(Object *, reg.parents, reg.n + 1); + reg.parents[reg.n] = parent; + + if (extended) { + int p_ph = qemu_fdt_getprop_cell(fdti->fdt, node_path, + "reg-extended", cell_idx++, + false, &errp); + if (errp) { + error_free(errp); + errp = NULL; + goto exit_reg_parse; + } + if (qemu_devtree_get_node_by_phandle(fdti->fdt, ph_parent, + p_ph)) { + goto exit_reg_parse; + } + while (!fdt_init_has_opaque(fdti, ph_parent)) { + fdt_init_yield(fdti); + } + reg.parents[reg.n] = fdt_init_get_opaque(fdti, ph_parent); + pnp = ph_parent; + } + + for (i = 0; i < FDT_GENERIC_REG_TUPLE_LENGTH; ++i) { + const char *size_prop_name = fdt_generic_reg_size_prop_names[i]; + int nc = qemu_fdt_getprop_cell(fdti->fdt, pnp, size_prop_name, + 0, true, &errp); + + if (errp) { + int size_default = fdt_generic_reg_cells_defaults[i]; + + DB_PRINT_NP(0, "WARNING: no %s for %s container, assuming " + "default of %d\n", size_prop_name, pnp, + size_default); + nc = size_default; + error_free(errp); + errp = NULL; + } + + reg.x[i] = g_renew(uint64_t, reg.x[i], reg.n + 1); + reg.x[i][reg.n] = nc ? + qemu_fdt_getprop_sized_cell(fdti->fdt, node_path, + extended ? "reg-extended" + : "reg", + cell_idx, nc, &errp) + : 0; + cell_idx += nc; + if (errp) { + goto exit_reg_parse; + } + } + } + exit_reg_parse: + + if (object_dynamic_cast(dev, TYPE_FDT_GENERIC_MMAP)) { + FDTGenericMMapClass *fmc = FDT_GENERIC_MMAP_GET_CLASS(dev); + if (fmc->parse_reg) { + while (fmc->parse_reg(FDT_GENERIC_MMAP(dev), reg, + &error_abort)) { + fdt_init_yield(fdti); + } + } + } + } + + if (object_dynamic_cast(dev, TYPE_SYS_BUS_DEVICE)) { + { + 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) { + /* + * Hack alert! auto-parenting the interrupt + * controller before the first CPU has been + * realized leads to a segmentation fault in + * xilinx_intc_fdt_auto_parent. + */ + while (!DEVICE(first_cpu)) { + fdt_init_yield(fdti); + } + 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++; + } + } + } + + if (object_dynamic_cast(dev, TYPE_FDT_GENERIC_GPIO)) { + fggc = FDT_GENERIC_GPIO_GET_CLASS(dev); + gpio_set = fggc->client_gpios; + + /* + * Add default GPIOs to the client GPIOs so the device has access to + * reset, power, and halt control. + */ + if (gpio_set) { + size_t gpio_cnt = 0; + const FDTGenericGPIOSet *p_gpio; + + for (p_gpio = gpio_set; p_gpio->names; p_gpio++) { + assert(gpio_cnt < ARRAY_SIZE(tmp_gpio_set)); + tmp_gpio_set[gpio_cnt] = *p_gpio; + gpio_cnt++; + } + + for (p_gpio = default_gpio_sets; p_gpio->names; p_gpio++) { + assert(gpio_cnt < ARRAY_SIZE(tmp_gpio_set)); + tmp_gpio_set[gpio_cnt] = *p_gpio; + gpio_cnt++; + } + + gpio_set = tmp_gpio_set; + } + } + + if (!gpio_set) { + gpio_set = default_gpio_sets; + } + + for (; object_dynamic_cast(dev, TYPE_DEVICE) && gpio_set->names; + gpio_set++) { + bool end = false; + int cur_cell = 0; + + for (i = 0; !end; i++) { + char *debug_success; + const FDTGenericGPIOConnection *c = gpio_set->gpios; + const char *gpio_name = NULL; + uint16_t named_idx = 0; + qemu_irq input, output; + memset(&input, 0, sizeof(input)); + + if (c) { + uint16_t range = c->range ? c->range : 1; + while (((c->fdt_index > i) || ((c->fdt_index + range) <= i)) + && c->name) { + c++; + range = c->range ? c->range : 1; + } + named_idx = i - c->fdt_index; + gpio_name = c->name; + } + if (!gpio_name) { + const char *names_propname = gpio_set->names->names_propname; + gpio_name = qemu_fdt_getprop_string(fdti->fdt, node_path, + names_propname, i, false, + NULL); + } + if (!gpio_name) { + input = qdev_get_gpio_in(DEVICE(dev), i); + } else { + input = qdev_get_gpio_in_named(DEVICE(dev), gpio_name, + named_idx); + } + debug_success = g_strdup_printf("Wiring GPIO input %s[%" PRId16 "] " + "to", gpio_name, named_idx); + output = fdt_get_gpio(fdti, node_path, &cur_cell, input, gpio_set, + debug_success, &end); + g_free(debug_success); + if (output) { + FDTIRQConnection *irq = g_new0(FDTIRQConnection, 1); + *irq = (FDTIRQConnection) { + .dev = DEVICE(dev), + .name = gpio_name, + .merge_fn = qemu_irq_shared_or_handler, + .i = named_idx, + .irq = output, + .sink_info = NULL, /*FIXME */ + .next = fdti->irqs + }; + fdti->irqs = irq; + DB_PRINT_NP(1, "... GPIO output %s[%" PRId16 "]\n", gpio_name, + named_idx); + } + } + } + + if (dev_type) { + g_free(dev_type); + } + + return 0; + } + + static const TypeInfo fdt_generic_intc_info = { + .name = TYPE_FDT_GENERIC_INTC, + .parent = TYPE_INTERFACE, + .class_size = sizeof(FDTGenericIntcClass), + }; + + static const TypeInfo fdt_generic_mmap_info = { + .name = TYPE_FDT_GENERIC_MMAP, + .parent = TYPE_INTERFACE, + .class_size = sizeof(FDTGenericMMapClass), + }; + + static const TypeInfo fdt_generic_gpio_info = { + .name = TYPE_FDT_GENERIC_GPIO, + .parent = TYPE_INTERFACE, + .class_size = sizeof(FDTGenericGPIOClass), + }; + + static const TypeInfo fdt_generic_props_info = { + .name = TYPE_FDT_GENERIC_PROPS, + .parent = TYPE_INTERFACE, + .class_size = sizeof(FDTGenericPropsClass), + }; + + static const TypeInfo fdt_generic_helper_info = { + .name = TYPE_FDT_GENERIC_HELPER, + .parent = TYPE_INTERFACE, + .class_size = sizeof(FDTGenericHelperClass), + }; + + static void fdt_generic_intc_register_types(void) + { + type_register_static(&fdt_generic_intc_info); + type_register_static(&fdt_generic_mmap_info); + type_register_static(&fdt_generic_gpio_info); + type_register_static(&fdt_generic_props_info); + type_register_static(&fdt_generic_helper_info); + } + + type_init(fdt_generic_intc_register_types) + \ No newline at end of file diff --git a/hw/core/machine.c b/hw/core/machine.c index 27372bb01e..361da5978f 100644 --- a/hw/core/machine.c +++ b/hw/core/machine.c @@ -375,6 +375,20 @@ static void machine_set_dtb(Object *obj, const char *value, Error **errp) ms->dtb = g_strdup(value); } +static char *machine_get_hw_dtb(Object *obj, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + return g_strdup(ms->hw_dtb); +} + +static void machine_set_hw_dtb(Object *obj, const char *value, Error **errp) +{ + MachineState *ms = MACHINE(obj); + + ms->hw_dtb = g_strdup(value); +} + static char *machine_get_dumpdtb(Object *obj, Error **errp) { MachineState *ms = MACHINE(obj); @@ -1284,6 +1298,11 @@ static void machine_initfn(Object *obj) ms->ram_size = mc->default_ram_size; ms->maxram_size = mc->default_ram_size; + object_property_add_str(obj, "hw-dtb", + machine_get_hw_dtb, machine_set_hw_dtb); + object_property_set_description(obj, "hw-dtb", + "A device tree used to describe the hardware to QEMU."); + if (mc->nvdimm_supported) { ms->nvdimms_state = g_new0(NVDIMMState, 1); object_property_add_bool(obj, "nvdimm", diff --git a/hw/core/meson.build b/hw/core/meson.build index b5a545a0ed..cea3b4660e 100644 --- a/hw/core/meson.build +++ b/hw/core/meson.build @@ -28,6 +28,9 @@ system_ss.add(when: 'CONFIG_EIF', if_true: [files('eif.c'), zlib, libcbor, gnutl system_ss.add(files( 'cpu-system.c', + 'fdt_generic.c', + 'fdt_generic_devices.c', + 'fdt_generic_util.c', 'fw-path-provider.c', 'gpio.c', 'hotplug.c', diff --git a/include/hw/boards.h b/include/hw/boards.h index a48ed4f86a..68711be386 100644 --- a/include/hw/boards.h +++ b/include/hw/boards.h @@ -402,6 +402,7 @@ struct MachineState { void *fdt; char *dtb; + char *hw_dtb; char *dumpdtb; int phandle_start; char *dt_compatible; diff --git a/include/hw/fdt_generic.h b/include/hw/fdt_generic.h new file mode 100644 index 0000000000..2376d459cd --- /dev/null +++ b/include/hw/fdt_generic.h @@ -0,0 +1,126 @@ +/* + * 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/help-texts.h" +#include "hw/irq.h" +#include "system/device_tree.h" +#include "qemu/coroutine.h" + +/* This is the number of serial ports we have connected */ +extern int fdt_serial_ports; + +typedef struct FDTDevOpaque { + char *node_path; + void *opaque; +} FDTDevOpaque; + +typedef struct FDTCPUCluster { + char *cpu_type; + void *cpu_cluster; + void *next; + 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; + /* irq descriptors for top level int controller */ + qemu_irq *irq_base; + /* per-device specific opaques */ + FDTDevOpaque *dev_opaques; + /* recheck coroutine queue */ + CoQueue *cq; + /* list of all IRQ connections */ + FDTIRQConnection *irqs; + /* list of all CPU clusters */ + FDTCPUCluster *clusters; +} 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 *); + +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); + +void *fdt_init_get_cpu_cluster(FDTMachineInfo *fdti, Object *parent, char *compat); +void fdt_init_register_user_cpu_cluster(FDTMachineInfo *fdti, Object *cluster); + +/* 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) + +#define fdt_register_instance_opaque(function, inst, n, opaque) \ +static void __attribute__((constructor)) \ +function ## n ## _register_imp(void) { \ + add_to_inst_bind_table(function, inst, opaque); \ +} + +#define fdt_register_instance_n(function, inst, n) \ +fdt_register_instance_opaque(function, inst, n, NULL) + +#define fdt_register_instance(function, inst) \ +fdt_register_instance_n(function, inst, 0) + +#endif /* FDT_GENERIC_H */ diff --git a/include/hw/fdt_generic_devices.h b/include/hw/fdt_generic_devices.h new file mode 100644 index 0000000000..f5eada89b5 --- /dev/null +++ b/include/hw/fdt_generic_devices.h @@ -0,0 +1,22 @@ +#ifndef FDT_GENERIC_DEVICES_H +#define FDT_GENERIC_DEVICES_H + +#include "fdt_generic.h" +#include "exec/cpu-common.h" + +#if !defined(CONFIG_USER_ONLY) + +/* XXX: Hack to find the last range in a memory node. */ +typedef struct FDTMemoryInfo { + unsigned int nr_regions; + ram_addr_t last_base; + ram_addr_t last_size; +} FDTMemoryInfo; + +#endif + +int pflash_cfi01_fdt_init(char *node_path, FDTMachineInfo *fdti, void *opaque); + +extern int fdt_generic_num_cpus; + +#endif /* FDT_GENERIC_DEVICES_H */ diff --git a/include/hw/fdt_generic_util.h b/include/hw/fdt_generic_util.h new file mode 100644 index 0000000000..231513c43f --- /dev/null +++ b/include/hw/fdt_generic_util.h @@ -0,0 +1,283 @@ +#ifndef FDT_GENERIC_UTIL_H +#define FDT_GENERIC_UTIL_H + +#include "qemu/help-texts.h" +#include "fdt_generic.h" +#include "system/memory.h" +#include "qom/object.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); + +/* 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) \ + OBJECT_CLASS_CHECK(FDTGenericIntcClass, (klass), TYPE_FDT_GENERIC_INTC) +#define FDT_GENERIC_INTC_GET_CLASS(obj) \ + OBJECT_GET_CLASS(FDTGenericIntcClass, (obj), TYPE_FDT_GENERIC_INTC) +#define FDT_GENERIC_INTC(obj) \ + INTERFACE_CHECK(FDTGenericIntc, (obj), TYPE_FDT_GENERIC_INTC) + +typedef struct FDTGenericIntc { + /*< private >*/ + Object parent_obj; +} FDTGenericIntc; + +typedef struct FDTGenericIntcClass { + /*< private >*/ + InterfaceClass parent_class; + + /*< public >*/ + /** + * get irq - Based on the FDT generic interrupt binding for this device + * grab the irq(s) for the given interrupt cells description. In some device + * tree bindings (E.G. ARM GIC with its PPI) a single interrupt cell-tuple + * can describe more than one connection. So populates an array with all + * relevant IRQs. + * + * @obj - interrupt controller to get irqs input for ("interrupt-parent") + * @irqs - array to populate with irqs (must be >= @max length + * @cells - interrupt cells values. Must be >= ncells length + * @ncells - number of cells in @cells + * @max - maximum number of irqs to return + * @errp - Error condition + * + * @returns the number of interrupts populated in irqs. Undefined on error + * (use errp for error checking). If it is valid for the interrupt + * controller binding to specify no (or a disabled) connections it may + * return 0 as a non-error. + */ + + int (*get_irq)(FDTGenericIntc *obj, qemu_irq *irqs, uint32_t *cells, + int ncells, int max, Error **errp); + + /** + * auto_parent. An interrupt controller often infers its own interrupt + * parent (usually a CPU or CPU cluster. This function allows an interrupt + * controller to implement its own auto-connections. Is called if an + * interrupt controller itself (detected via "interrupt-controller") has no + * "interrupt-parent" node. + * + * @obj - Interrupt controller top attempt autoconnection + * @errp - Error condition + * + * FIXME: More arguments need to be added for partial descriptions + */ + + void (*auto_parent)(FDTGenericIntc *obj, Error **errp); + +} FDTGenericIntcClass; + +#define TYPE_FDT_GENERIC_MMAP "fdt-generic-mmap" + +#define FDT_GENERIC_MMAP_CLASS(klass) \ + OBJECT_CLASS_CHECK(FDTGenericMMapClass, (klass), TYPE_FDT_GENERIC_MMAP) +#define FDT_GENERIC_MMAP_GET_CLASS(obj) \ + OBJECT_GET_CLASS(FDTGenericMMapClass, (obj), TYPE_FDT_GENERIC_MMAP) +#define FDT_GENERIC_MMAP(obj) \ + INTERFACE_CHECK(FDTGenericMMap, (obj), TYPE_FDT_GENERIC_MMAP) + +typedef struct FDTGenericMMap { + /*< private >*/ + Object parent_obj; +} FDTGenericMMap; + +/* The number of "things" in the tuple. Not to be confused with the cell length + * of the tuple (which is variable based on content + */ + +#define FDT_GENERIC_REG_TUPLE_LENGTH 4 + +typedef struct FDTGenericRegPropInfo { + int n; + union { + struct { + uint64_t *a; + uint64_t *s; + uint64_t *b; + uint64_t *p; + }; + uint64_t *x[FDT_GENERIC_REG_TUPLE_LENGTH]; + }; + Object **parents; +} FDTGenericRegPropInfo; + +typedef struct FDTGenericMMapClass { + /*< private >*/ + InterfaceClass parent_class; + + /*< public >*/ + bool (*parse_reg)(FDTGenericMMap *obj, FDTGenericRegPropInfo info, + Error **errp); +} FDTGenericMMapClass; + +#define TYPE_FDT_GENERIC_GPIO "fdt-generic-gpio" + +#define FDT_GENERIC_GPIO_CLASS(klass) \ + OBJECT_CLASS_CHECK(FDTGenericGPIOClass, (klass), TYPE_FDT_GENERIC_GPIO) +#define FDT_GENERIC_GPIO_GET_CLASS(obj) \ + OBJECT_GET_CLASS(FDTGenericGPIOClass, (obj), TYPE_FDT_GENERIC_GPIO) +#define FDT_GENERIC_GPIO(obj) \ + INTERFACE_CHECK(FDTGenericGPIO, (obj), TYPE_FDT_GENERIC_GPIO) + +typedef struct FDTGenericGPIO { + /*< private >*/ + Object parent_obj; +} FDTGenericGPIO; + +typedef struct FDTGenericGPIOConnection { + const char *name; + uint16_t fdt_index; + uint16_t range; +} FDTGenericGPIOConnection; + +typedef struct FDTGenericGPIONameSet { + const char *propname; + const char *cells_propname; + const char *names_propname; +} FDTGenericGPIONameSet; + +typedef struct FDTGenericGPIOSet { + const FDTGenericGPIONameSet *names; + const FDTGenericGPIOConnection *gpios; +} FDTGenericGPIOSet; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_power_gpio = { + .propname = "power-gpios", + .cells_propname = "#gpio-cells", + .names_propname = "power-gpio-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_reset_gpio = { + .propname = "reset-gpios", + .cells_propname = "#gpio-cells", + .names_propname = "reset-gpio-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_resets = { + .propname = "resets", + .cells_propname = "#reset-cells", + .names_propname = "reset-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_gpio = { + .propname = "gpios", + .cells_propname = "#gpio-cells", + .names_propname = "gpio-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_clock = { + .propname = "clocks", + .cells_propname = "#clock-cells", + .names_propname = "clock-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_interrupts = { + .propname = "interrupts-extended", + .cells_propname = "#interrupt-cells", + .names_propname = "interrupt-names", +}; + +static const FDTGenericGPIOSet default_gpio_sets [] = { + { .names = &fdt_generic_gpio_name_set_gpio }, + { + .names = &fdt_generic_gpio_name_set_reset_gpio, + .gpios = (FDTGenericGPIOConnection[]) { + { .name = "rst_cntrl", .fdt_index = 0, .range = 6 }, + }, + }, + { + .names = &fdt_generic_gpio_name_set_resets, + .gpios = (FDTGenericGPIOConnection[]) { + { .name = "rst_cntrl", .fdt_index = 0, .range = 6 }, + }, + }, + { + .names = &fdt_generic_gpio_name_set_power_gpio, + .gpios = (FDTGenericGPIOConnection[]) { + { .name = "pwr_cntrl", .fdt_index = 0, .range = 1 }, + }, + }, + { .names = &fdt_generic_gpio_name_set_clock }, + { .names = &fdt_generic_gpio_name_set_interrupts }, + { }, +}; + +typedef struct FDTGenericGPIOClass { + /*< private >*/ + InterfaceClass parent_class; + + /*< public >*/ + /** + * Unfortunately, FDT GPIOs aren't named. This allows a device to define + * a mapping between a QEMU named GPIO and the FDT GPIO lists. Client GPIOs + * name the GPIOs in the fdt 'gpios' property. E.G. An entry in this list + * with .name = "foo' and fdt_index = 0 would associated the first element + * in the gpios list with named gpio 'foo' on the device. + * + * controller_gpios is the same but for for gpio controllers. E.g. with the + * example above, gpio "foo" would bt the first gpio defined for the device. + */ + + const FDTGenericGPIOSet *controller_gpios; + const FDTGenericGPIOSet *client_gpios; +} FDTGenericGPIOClass; + +#define TYPE_FDT_GENERIC_PROPS "fdt-generic-props" + +#define FDT_GENERIC_PROPS_CLASS(klass) \ + OBJECT_CLASS_CHECK(FDTGenericPropsClass, (klass), \ + TYPE_FDT_GENERIC_PROPS) +#define FDT_GENERIC_PROPS_GET_CLASS(obj) \ + OBJECT_GET_CLASS(FDTGenericPropsClass, (obj), \ + TYPE_FDT_GENERIC_PROPS) + +typedef struct FDTGenericPropsClass { + /*< private >*/ + InterfaceClass parent_class; + + /*< public >*/ + void (*set_props)(Object *obj, Error **errp); +} FDTGenericPropsClass; + + +#define TYPE_FDT_GENERIC_HELPER "fdt-generic-helper" + +#define FDT_GENERIC_HELPER_CLASS(klass) \ + OBJECT_CLASS_CHECK(FDTGenericHelperClass, (klass), \ + TYPE_FDT_GENERIC_HELPER) +#define FDT_GENERIC_HELPER_GET_CLASS(obj) \ + OBJECT_GET_CLASS(FDTGenericHelperClass, (obj), \ + TYPE_FDT_GENERIC_HELPER) + +typedef struct FDTGenericHelperClass { + /*< private >*/ + InterfaceClass parent_class; + + /*< public >*/ + + /* Return true if the device is ready to be realized */ + bool (*ready_to_realize)(DeviceState *dev); +} FDTGenericHelperClass; + +#endif /* FDT_GENERIC_UTIL_H */ diff --git a/include/hw/qdev-properties.h b/include/hw/qdev-properties.h index 60b8133009..8553abdda1 100644 --- a/include/hw/qdev-properties.h +++ b/include/hw/qdev-properties.h @@ -133,6 +133,7 @@ extern const PropertyInfo qdev_prop_link; .bitmask = (_bitmask), \ .set_default = false) +#define PROP_ARRAY_LEN_PREFIX "len-" /** * DEFINE_PROP_ARRAY: * @_name: name of the array diff --git a/include/net/net.h b/include/net/net.h index 72b476ee1d..3908d8bcb4 100644 --- a/include/net/net.h +++ b/include/net/net.h @@ -309,6 +309,10 @@ struct NICInfo { int nvectors; }; +extern int nb_nics; +extern NICInfo nd_table[MAX_NICS]; +extern const char *host_net_devices[]; + /* from net.c */ extern NetClientStateList net_clients; bool netdev_is_modern(const char *optstr); diff --git a/include/qemu/log.h b/include/qemu/log.h index 7effba4da4..a0c3b12657 100644 --- a/include/qemu/log.h +++ b/include/qemu/log.h @@ -39,6 +39,9 @@ bool qemu_log_separate(void); #define LOG_TB_OP_PLUGIN (1u << 22) #define LOG_INVALID_MEM (1u << 23) +/* device entries */ +#define LOG_FDT (1 << 24) + /* Lock/unlock output. */ FILE *qemu_log_trylock(void) G_GNUC_WARN_UNUSED_RESULT; diff --git a/include/system/blockdev.h b/include/system/blockdev.h index 3211b16513..8d07f3a911 100644 --- a/include/system/blockdev.h +++ b/include/system/blockdev.h @@ -55,6 +55,8 @@ DriveInfo *drive_get(BlockInterfaceType type, int bus, int unit); void drive_check_orphaned(void); DriveInfo *drive_get_by_index(BlockInterfaceType type, int index); int drive_get_max_bus(BlockInterfaceType type); +/* Xilinx: keep for fdt_generic */ +DriveInfo *drive_get_next(BlockInterfaceType type); QemuOpts *drive_add(BlockInterfaceType type, int index, const char *file, const char *optstr); diff --git a/include/system/device_tree.h b/include/system/device_tree.h index 49d8482ed4..c6ad90f981 100644 --- a/include/system/device_tree.h +++ b/include/system/device_tree.h @@ -90,6 +90,13 @@ int qemu_fdt_setprop_string_array(void *fdt, const char *node_path, int qemu_fdt_setprop_phandle(void *fdt, const char *node_path, const char *property, const char *target_node_path); + +uint64_t qemu_fdt_getprop_sized_cell(void *fdt, const char *node_path, + const char *property, int offset, + int size, Error **errp); +char *qemu_fdt_getprop_string(void *fdt, const char*node_path, + const char *property, int cell, + bool inherit, Error **errp); /** * qemu_fdt_getprop: retrieve the value of a given property * @fdt: pointer to the device tree blob @@ -100,24 +107,25 @@ int qemu_fdt_setprop_phandle(void *fdt, const char *node_path, * * returns a pointer to the property on success and NULL on failure */ -const void *qemu_fdt_getprop(void *fdt, const char *node_path, - const char *property, int *lenp, - Error **errp); +void *qemu_fdt_getprop(void *fdt, const char *node_path, + const char *property, int *lenp, + bool inherit, Error **errp); /** - * qemu_fdt_getprop_cell: retrieve the value of a given 4 byte property - * @fdt: pointer to the device tree blob - * @node_path: node path - * @property: name of the property to find - * @lenp: fdt error if any or -EINVAL if the property size is different from - * 4 bytes, or 4 (expected length of the property) upon success. - * @errp: handle to an error object - * - * returns the property value on success - */ +* qemu_fdt_getprop_cell: retrieve the value of a given 4 byte property +* @fdt: pointer to the device tree blob +* @node_path: node path +* @property: name of the property to find +* @lenp: fdt error if any or -EINVAL if the property size is different from +* 4 bytes, or 4 (expected length of the property) upon success. +* @errp: handle to an error object +* +* returns the property value on success +*/ uint32_t qemu_fdt_getprop_cell(void *fdt, const char *node_path, - const char *property, int *lenp, - Error **errp); + const char *property, int offset, + bool inherit, Error **errp); uint32_t qemu_fdt_get_phandle(void *fdt, const char *path); +uint32_t qemu_fdt_check_phandle(void *fdt, const char *path); uint32_t qemu_fdt_alloc_phandle(void *fdt); int qemu_fdt_nop_node(void *fdt, const char *node_path); int qemu_fdt_add_subnode(void *fdt, const char *name); @@ -192,6 +200,69 @@ int qemu_fdt_setprop_sized_cells_from_array(void *fdt, qdt_tmp); \ }) +typedef struct QEMUDevtreeProp { + char *name; + int len; + void *value; +} QEMUDevtreeProp; + +/* node queries */ + +char *qemu_devtree_get_node_name(void *fdt, const char *node_path); +int qemu_devtree_get_node_depth(void *fdt, const char *node_path); +int qemu_devtree_get_num_children(void *fdt, const char *node_path, int depth); +char **qemu_devtree_get_children(void *fdt, const char *node_path, int depth); +int qemu_devtree_num_props(void *fdt, const char *node_path); +QEMUDevtreeProp *qemu_devtree_get_props(void *fdt, const char *node_path); +QEMUDevtreeProp *qemu_devtree_prop_search(QEMUDevtreeProp *props, + const char *name); + +/* node getters */ + +int qemu_devtree_node_by_compatible(void *fdt, char *node_path, + const char *compats); +int qemu_devtree_get_node_by_name(void *fdt, char *node_path, + const char *cmpname); +int qemu_devtree_get_node_by_phandle(void *fdt, char *node_path, int phandle); +int qemu_devtree_getparent(void *fdt, char *node_path, + const char *current); +int qemu_devtree_get_root_node(void *fdt, char *node_path); + +/* qemu_devtree_get_child_by_name: Check for the matching node name under + * structural block of parent node and returns node path. + * args: + * fdt: flatend device tree fp + * parent_path : path of the parent, whose child to be searched + * cmpname : node name of child + * return: + * Node path of the child + * Note: + * Returned string memory should be deallocated by g_free() + */ +char *qemu_devtree_get_child_by_name(void *fdt, char *parent_path, + const char *cmpname); + +/* qemu_devtree_get_n_nodes_by_name: Same as qemu_devtree_get_node_by_name but + * returns all the possible node paths matching the node name. + * args: + * fdt: flatend device tree + * array: pointer to hold array of strings + * cmpname: node name to search for + * return: + * Returns number of matching nodes found + * Note: + * Array of strings should be released after usage. Each of the individual + * strings in the array and the array itself should be released. + */ +int qemu_devtree_get_n_nodes_by_name(void *fdt, char ***array, + const char *cmpname); + +/* misc */ + +int devtree_get_num_nodes(void *fdt); +void devtree_info_dump(void *fdt); + +#define DT_PATH_LENGTH 1024 /** * qemu_fdt_randomize_seeds: diff --git a/net/net.c b/net/net.c index 8aefdb3424..51d96f793a 100644 --- a/net/net.c +++ b/net/net.c @@ -78,8 +78,8 @@ static NetdevQueue nd_queue = QSIMPLEQ_HEAD_INITIALIZER(nd_queue); static GHashTable *nic_model_help; -static int nb_nics; -static NICInfo nd_table[MAX_NICS]; +//static int nb_nics; +//static NICInfo nd_table[MAX_NICS]; /***********************************************************/ /* network device redirectors */ diff --git a/qemu-options.hx b/qemu-options.hx index fca2b7bc74..10567c5c45 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -4490,6 +4490,15 @@ SRST(initrd) ERST +DEF("hw-dtb", HAS_ARG, QEMU_OPTION_hw_dtb, \ + "-hw-dtb file use 'file' as device tree image\n", QEMU_ARCH_ALL) +SRST +``-hw-dtb file`` + Use <file> as a device tree binary (dtb) image used to create the + emulated machine. This dtb will not be passed to the kernel, use -dtb + for that. +ERST + DEF("dtb", HAS_ARG, QEMU_OPTION_dtb, \ "-dtb file use 'file' as device tree image\n", QEMU_ARCH_ALL) SRST diff --git a/system/device_tree.c b/system/device_tree.c index 7850b90fa7..7882a0b964 100644 --- a/system/device_tree.c +++ b/system/device_tree.c @@ -30,6 +30,7 @@ #include "qapi/qapi-commands-machine.h" #include "qobject/qdict.h" #include "monitor/hmp.h" +#include "qemu/log.h" #include <libfdt.h> @@ -428,8 +429,9 @@ int qemu_fdt_setprop_string_array(void *fdt, const char *node_path, return ret; } -const void *qemu_fdt_getprop(void *fdt, const char *node_path, - const char *property, int *lenp, Error **errp) +void *qemu_fdt_getprop(void *fdt, const char *node_path, + const char *property, int *lenp, + bool inherit, Error **errp) { int len; const void *r; @@ -439,31 +441,109 @@ const void *qemu_fdt_getprop(void *fdt, const char *node_path, } r = fdt_getprop(fdt, findnode_nofail(fdt, node_path), property, lenp); if (!r) { + char parent[DT_PATH_LENGTH]; + if (inherit && !qemu_devtree_getparent(fdt, parent, node_path)) { + return qemu_fdt_getprop(fdt, parent, property, lenp, true, errp); + } error_setg(errp, "%s: Couldn't get %s/%s: %s", __func__, node_path, property, fdt_strerror(*lenp)); + return NULL; } - return r; + return g_memdup(r, *lenp); } -uint32_t qemu_fdt_getprop_cell(void *fdt, const char *node_path, - const char *property, int *lenp, Error **errp) +char *qemu_fdt_getprop_string(void *fdt, const char*node_path, + const char *property, int cell, + bool inherit, Error **errp) { int len; - const uint32_t *p; + void *prop; + Error *err= NULL; - if (!lenp) { - lenp = &len; + if (!errp) { + errp = &err; + } + + prop = qemu_fdt_getprop(fdt, node_path, property, &len, inherit, errp); + if (*errp) { + return NULL; + } + while (cell) { + void *term = memchr(prop, '\0', len); + size_t diff; + + if (!term) { + error_setg(errp, "%s: Couldn't get %s/%s: %s", __func__, + node_path, property, fdt_strerror(len)); + return NULL; + } + diff = term - prop + 1; + len -= diff; + assert(len >= 0); + prop += diff; + cell--; + } + + if (!len) { + return NULL; + } + + if (!*(char *)prop) { + error_setg(errp, "%s: Couldn't get %s/%s: %s", __func__, + node_path, property, fdt_strerror(len)); + return NULL; } - p = qemu_fdt_getprop(fdt, node_path, property, lenp, errp); - if (!p) { + return prop; +} + +uint32_t qemu_fdt_getprop_cell(void *fdt, const char *node_path, + const char *property, int offset, + bool inherit, Error **errp) +{ + int len; + uint32_t ret; + uint32_t *p = qemu_fdt_getprop(fdt, node_path, property, &len, + inherit, errp); + if (errp && *errp) { return 0; - } else if (*lenp != 4) { + } + if (len < (offset+1)*4) { error_setg(errp, "%s: %s/%s not 4 bytes long (not a cell?)", __func__, node_path, property); - *lenp = -EINVAL; return 0; } - return be32_to_cpu(*p); + ret = be32_to_cpu(p[offset]); + g_free(p); + return ret; +} + +uint64_t qemu_fdt_getprop_sized_cell(void *fdt, const char *node_path, + const char *property, int offset, + int size, Error **errp) +{ + uint64_t ret = 0; + for (;size ;size--) { + ret <<= 32; + ret |= qemu_fdt_getprop_cell(fdt, node_path, property, offset++, false, + errp); + if (errp && *errp) { + return 0; + } + } + return ret; +} + +uint32_t qemu_fdt_check_phandle(void *fdt, const char *path) +{ + uint32_t r; + + r = fdt_get_phandle(fdt, findnode_nofail(fdt, path)); + if (r == 0) { + qemu_log("%s: Couldn't find phandle for %s: %s", __func__, + path, fdt_strerror(r)); + } + + return r; } uint32_t qemu_fdt_get_phandle(void *fdt, const char *path) @@ -633,6 +713,277 @@ out: return ret; } +char *qemu_devtree_get_node_name(void *fdt, const char *node_path) +{ + const char *ret = fdt_get_name(fdt, fdt_path_offset(fdt, node_path), NULL); + return ret ? strdup(ret) : NULL; +} + +int qemu_devtree_get_node_depth(void *fdt, const char *node_path) +{ + return fdt_node_depth(fdt, fdt_path_offset(fdt, node_path)); +} + + +int qemu_devtree_num_props(void *fdt, const char *node_path) +{ + int offset = fdt_path_offset(fdt, node_path); + int ret = 0; + + for (offset = fdt_first_property_offset(fdt, offset); + offset != -FDT_ERR_NOTFOUND; + offset = fdt_next_property_offset(fdt, offset)) { + ret++; + } + return ret; +} + +QEMUDevtreeProp *qemu_devtree_prop_search(QEMUDevtreeProp *props, + const char *name) +{ + while (props->name) { + if (!strcmp(props->name, name)) { + return props; + } + props++; + } + return NULL; +} + +QEMUDevtreeProp *qemu_devtree_get_props(void *fdt, const char *node_path) +{ + QEMUDevtreeProp *ret = g_new0(QEMUDevtreeProp, + qemu_devtree_num_props(fdt, node_path) + 1); + int offset = fdt_path_offset(fdt, node_path); + int i = 0; + + for (offset = fdt_first_property_offset(fdt, offset); + offset != -FDT_ERR_NOTFOUND; + offset = fdt_next_property_offset(fdt, offset)) { + const char *propname; + const void *val = fdt_getprop_by_offset(fdt, offset, &propname, + &ret[i].len); + + ret[i].name = g_strdup(propname); + ret[i].value = g_memdup(val, ret[i].len); + i++; + } + return ret; +} + +static void qemu_devtree_children_info(void *fdt, const char *node_path, + int depth, int *num, char **returned_paths) { + int offset = fdt_path_offset(fdt, node_path); + int root_depth = fdt_node_depth(fdt, offset); + int cur_depth = root_depth; + + if (num) { + *num = 0; + } + for (;;) { + offset = fdt_next_node(fdt, offset, &cur_depth); + if (cur_depth <= root_depth) { + break; + } + if (cur_depth <= root_depth + depth || depth == 0) { + if (returned_paths) { + returned_paths[*num] = g_malloc0(DT_PATH_LENGTH); + fdt_get_path(fdt, offset, returned_paths[*num], DT_PATH_LENGTH); + } + if (num) { + (*num)++; + } + } + } +} + +char **qemu_devtree_get_children(void *fdt, const char *node_path, int depth) +{ + int num_children = qemu_devtree_get_num_children(fdt, node_path, depth); + char **ret = g_malloc0(sizeof(*ret) * num_children); + + qemu_devtree_children_info(fdt, node_path, depth, &num_children, ret); + return ret; +} + +int qemu_devtree_get_num_children(void *fdt, const char *node_path, int depth) +{ + int ret; + + qemu_devtree_children_info(fdt, node_path, depth, &ret, NULL); + return ret; +} + +int qemu_devtree_node_by_compatible(void *fdt, char *node_path, + const char *compats) +{ + int offset = fdt_node_offset_by_compatible(fdt, 0, compats); + return offset > 0 ? + fdt_get_path(fdt, offset, node_path, DT_PATH_LENGTH) : 1; +} + +int qemu_devtree_get_node_by_name(void *fdt, char *node_path, + const char *cmpname) { + int offset = 0; + char *name = NULL; + + do { + char *at; + + offset = fdt_next_node(fdt, offset, NULL); + name = (void *)fdt_get_name(fdt, offset, NULL); + if (!name) { + continue; + } + at = memchr(name, '@', strlen(name)); + if (!strncmp(name, cmpname, at ? at - name : strlen(name) )) { + break; + } + } while (offset > 0); + return offset > 0 ? + fdt_get_path(fdt, offset, node_path, DT_PATH_LENGTH) : 1; +} + +int qemu_devtree_get_n_nodes_by_name(void *fdt, char ***array, + const char *cmpname) +{ + int offset = 0; + char *name = NULL; + uint32_t n = 0; + char node_p[DT_PATH_LENGTH]; + char **node_path = NULL; + + do { + char *at; + + offset = fdt_next_node(fdt, offset, NULL); + name = (void *)fdt_get_name(fdt, offset, NULL); + + if (!name) { + continue; + } + + at = memchr(name, '@', strlen(name)); + if (!strncmp(name, cmpname, at ? at - name : strlen(name))) { + if (fdt_get_path(fdt, offset, node_p, DT_PATH_LENGTH) >= 0) { + if (node_path == NULL) { + node_path = (char **) g_new(char *, 1); + } else { + node_path = (char **) g_renew(char *, *node_path, n); + } + node_path[n] = g_strdup(node_p); + n++; + } + } + } while (offset > 0); + + *array = node_path; + return n; +} + +char *qemu_devtree_get_child_by_name(void *fdt, char *parent_path, + const char *cmpname) +{ + int offset = 0; + int parent_offset; + int namelen = strlen(cmpname); + char child_path[DT_PATH_LENGTH]; + + parent_offset = fdt_path_offset(fdt, parent_path); + + if (parent_offset > 0) { + offset = fdt_subnode_offset_namelen(fdt, parent_offset, + cmpname, namelen); + if (fdt_get_path(fdt, offset, child_path, DT_PATH_LENGTH) == 0) { + return g_strdup(child_path); + } + } + + return NULL; +} + +int qemu_devtree_get_node_by_phandle(void *fdt, char *node_path, int phandle) +{ + return fdt_get_path(fdt, fdt_node_offset_by_phandle(fdt, phandle), + node_path, DT_PATH_LENGTH); +} + +int qemu_devtree_getparent(void *fdt, char *node_path, const char *current) +{ + int offset = fdt_path_offset(fdt, current); + int parent_offset = fdt_supernode_atdepth_offset(fdt, offset, + fdt_node_depth(fdt, offset) - 1, NULL); + + return parent_offset >= 0 ? + fdt_get_path(fdt, parent_offset, node_path, DT_PATH_LENGTH) : 1; +} + +int qemu_devtree_get_root_node(void *fdt, char *node_path) +{ + return fdt_get_path(fdt, 0, node_path, DT_PATH_LENGTH); +} + +static void devtree_scan(void *fdt, int *num_nodes, int info_dump) +{ + int depth = 0, offset = 0; + + if (num_nodes) { + *num_nodes = 0; + } + for (;;) { + offset = fdt_next_node(fdt, offset, &depth); + if (num_nodes) { + (*num_nodes)++; + } + if (offset <= 0 || depth <= 0) { + break; + } + + if (info_dump) { + char node_path[DT_PATH_LENGTH]; + char *all_compats = NULL; + int compat_len; + Error *errp = NULL; + + if (fdt_get_path(fdt, offset, node_path, DT_PATH_LENGTH)) { + sprintf(node_path, "(none)"); + } else { + all_compats = qemu_fdt_getprop(fdt, node_path, "compatible", + &compat_len, false, &errp); + } + if (!errp) { + char *i = all_compats; + for (;;) { + char *j = memchr(i, '\0', DT_PATH_LENGTH); + compat_len -= ((j+1)-i); + if (!compat_len) { + break; + } + *j = ' '; + i = j+1; + } + } + printf("OFFSET: %d, DEPTH: %d, PATH: %s, COMPATS: %s\n", + offset, depth, node_path, + all_compats ? all_compats : "(none)"); + } + } +} + +void devtree_info_dump(void *fdt) +{ + devtree_scan(fdt, NULL, 1); +} + +int devtree_get_num_nodes(void *fdt) +{ + int ret; + + devtree_scan(fdt, &ret, 0); + return ret; +} + + void qmp_dumpdtb(const char *filename, Error **errp) { ERRP_GUARD(); diff --git a/system/globals.c b/system/globals.c index 98f9876d5d..32869e65ed 100644 --- a/system/globals.c +++ b/system/globals.c @@ -46,6 +46,8 @@ int display_opengl; const char* keyboard_layout; MlockState mlock_state; bool enable_cpu_pm; +int nb_nics; +NICInfo nd_table[MAX_NICS]; int autostart = 1; int vga_interface_type = VGA_NONE; bool vga_interface_created; diff --git a/system/vl.c b/system/vl.c index 5091fe52d9..c18306e914 100644 --- a/system/vl.c +++ b/system/vl.c @@ -3021,6 +3021,9 @@ void qemu_init(int argc, char **argv) case QEMU_OPTION_dtb: qdict_put_str(machine_opts_dict, "dtb", optarg); break; + case QEMU_OPTION_hw_dtb: + qdict_put_str(machine_opts_dict, "hw-dtb", optarg); + break; case QEMU_OPTION_cdrom: drive_add(IF_DEFAULT, 2, optarg, CDROM_OPTS); break; -- 2.43.0
