From patchwork Mon Sep 17 09:02:59 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: "Peter A. G. Crosthwaite" X-Patchwork-Id: 184336 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [208.118.235.17]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (Client did not present a certificate) by ozlabs.org (Postfix) with ESMTPS id 3EC422C0091 for ; Mon, 17 Sep 2012 19:14:43 +1000 (EST) Received: from localhost ([::1]:38741 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TDXPd-0007H4-72 for incoming@patchwork.ozlabs.org; Mon, 17 Sep 2012 05:14:41 -0400 Received: from eggs.gnu.org ([208.118.235.92]:35758) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TDXFe-0003cg-VG for qemu-devel@nongnu.org; Mon, 17 Sep 2012 05:04:25 -0400 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1TDXFX-0001eO-DA for qemu-devel@nongnu.org; Mon, 17 Sep 2012 05:04:22 -0400 Received: from mail-iy0-f173.google.com ([209.85.210.173]:53565) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1TDXFX-0001a3-5g for qemu-devel@nongnu.org; Mon, 17 Sep 2012 05:04:15 -0400 Received: by mail-iy0-f173.google.com with SMTP id x26so4933478iak.4 for ; Mon, 17 Sep 2012 02:04:15 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=20120113; h=from:to:cc:subject:date:message-id:x-mailer:in-reply-to:references :in-reply-to:references:x-gm-message-state; bh=xzli+q2q/fbYzqjrUdXYRfyhlRdO+rAa3K4zYlS9cNs=; b=jlLDUIWF8Mpei4tXQkvZS1jcEGdNKCnZQLFuPV0TY5myIH6jKOFXqdc64Kkz/CHD5N jWZ862N4WIdb4b8EcSPnmFeUxMy5cDeZ4UuJ3H40e10lKnRXOZksKKtmR7I4RIcm8rJ1 q4GfJIuhLPOCW0okAAQ8hR5oxMQQxKHlftvR/Aei5jepiZgf1KmwUtUcSO4N5U+exiCG KUA210vq4NROALDVOfulKsuE0ZckS1e+phuyTOk2menT7+KcYrJBqvm8eLsbFcCAdnD/ 2lh23uzp07PqfUMcmTilI7UA8yYIKwIfNMz/IPxrhOdPipwPoJ/AR2DiTbyhnwddXObO f8Sg== Received: by 10.50.12.138 with SMTP id y10mr5890126igb.53.1347872654873; Mon, 17 Sep 2012 02:04:14 -0700 (PDT) Received: from localhost ([124.148.20.9]) by mx.google.com with ESMTPS id bp8sm16565789igb.12.2012.09.17.02.04.11 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 17 Sep 2012 02:04:13 -0700 (PDT) From: "Peter A. G. Crosthwaite" To: qemu-devel@nongnu.org, edgar.iglesias@gmail.com Date: Mon, 17 Sep 2012 19:02:59 +1000 Message-Id: <949d83d4c80e0ba8629e7dd930d2955759e64757.1347871922.git.peter.crosthwaite@petalogix.com> X-Mailer: git-send-email 1.7.0.4 In-Reply-To: References: In-Reply-To: References: X-Gm-Message-State: ALoCoQmyRdFZ0G0xa8SXCu5pqCCXJl5OuwPHrxA7v8RPBnKFSECyBMiPLJ885+aWy8Q0UzP2Ks6z X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 209.85.210.173 Cc: peter.crosthwaite@petalogix.com, crwulff@gmail.com Subject: [Qemu-devel] [RFC v0 07/10] fdt_generic: First revision X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org First revision of fdt generic infrastructure. These modules allow for fdt generic machine models, which create machines to match a device tree specification. Signed-off-by: Peter A. G. Crosthwaite --- default-configs/microblaze-softmmu.mak | 1 + default-configs/microblazeel-softmmu.mak | 1 + hw/fdt_generic.c | 215 +++++++++++++++ hw/fdt_generic.h | 92 +++++++ hw/fdt_generic_util.c | 438 ++++++++++++++++++++++++++++++ hw/fdt_generic_util.h | 36 +++ hw/microblaze/Makefile.objs | 3 + 7 files changed, 786 insertions(+), 0 deletions(-) create mode 100644 hw/fdt_generic.c create mode 100644 hw/fdt_generic.h create mode 100644 hw/fdt_generic_util.c create mode 100644 hw/fdt_generic_util.h diff --git a/default-configs/microblaze-softmmu.mak b/default-configs/microblaze-softmmu.mak index 64c9485..c3bd740 100644 --- a/default-configs/microblaze-softmmu.mak +++ b/default-configs/microblaze-softmmu.mak @@ -5,3 +5,4 @@ CONFIG_PFLASH_CFI01=y CONFIG_SERIAL=y CONFIG_XILINX=y CONFIG_XILINX_AXI=y +CONFIG_FDT_GENERIC=y diff --git a/default-configs/microblazeel-softmmu.mak b/default-configs/microblazeel-softmmu.mak index a962276..dda2c51 100644 --- a/default-configs/microblazeel-softmmu.mak +++ b/default-configs/microblazeel-softmmu.mak @@ -5,3 +5,4 @@ CONFIG_PFLASH_CFI01=y CONFIG_SERIAL=y CONFIG_XILINX=y CONFIG_XILINX_AXI=y +CONFIG_FDT_GENERIC=y diff --git a/hw/fdt_generic.c b/hw/fdt_generic.c new file mode 100644 index 0000000..3950107 --- /dev/null +++ b/hw/fdt_generic.c @@ -0,0 +1,215 @@ +/* + * Tables of FDT device models and their init functions. Keyed by compatibility + * strings, device instance names. + * + * Copyright (c) 2010 PetaLogix Qld Pty Ltd. + * Copyright (c) 2010 Peter A. G. Crosthwaite . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "fdt_generic.h" + +#define FDT_GENERIC_ERR_DEBUG + +#ifdef FDT_GENERIC_ERR_DEBUG +#define DB_PRINT(...) do { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } while (0); +#else + #define DB_PRINT(...) +#endif + +#define FDT_GENERIC_MAX_PATTERN_LEN 1024 + +typedef struct TableListNode { + void *next; + char key[FDT_GENERIC_MAX_PATTERN_LEN]; + FDTInitFn fdt_init; + void *opaque; +} TableListNode; + +/* add a node to the table specified by *head_p */ + +static void add_to_table( + FDTInitFn fdt_init, + const char *key, + void *opaque, + TableListNode **head_p) +{ + TableListNode *nn = malloc(sizeof(*nn)); + nn->next = (void *)(*head_p); + strcpy(nn->key, key); + nn->fdt_init = fdt_init; + nn->opaque = opaque; + *head_p = nn; +} + +/* FIXME: add return codes that differentiate between not found and error */ + +/* search a table for a key string and call the fdt init function if found. + * Returns 0 if a match if found, 1 otherwise + */ + +static int fdt_init_search_table( + char *node_path, + FDTMachineInfo *fdti, + const char *key, /* string to match */ + TableListNode **head) /* head of the list to search */ +{ + TableListNode *c = *head; + if (c == NULL) { + return 1; + } else if (!strcmp(key, c->key)) { + return c->fdt_init(node_path, fdti, c->opaque); + } + return fdt_init_search_table(node_path, fdti, key, + (TableListNode **)(&(*head)->next)); +} + +TableListNode *compat_list_head; + +void add_to_compat_table(FDTInitFn fdt_init, const char *compat, void *opaque) +{ + add_to_table(fdt_init, compat, opaque, &compat_list_head); +} + +int fdt_init_compat(char *node_path, FDTMachineInfo *fdti, const char *compat) +{ + return fdt_init_search_table(node_path, fdti, compat, &compat_list_head); +} + +TableListNode *inst_bind_list_head; + +void add_to_inst_bind_table(FDTInitFn fdt_init, const char *name, void *opaque) +{ + add_to_table(fdt_init, name, opaque, &inst_bind_list_head); +} + +int fdt_init_inst_bind(char *node_path, FDTMachineInfo *fdti, + const char *name) +{ + return fdt_init_search_table(node_path, fdti, name, &inst_bind_list_head); +} + +TableListNode *force_list_head; + +void add_to_force_table(FDTInitFn fdt_init, const char *name, void *opaque) +{ + add_to_table(fdt_init, name, opaque, &force_list_head); +} + +int fdt_force_bind_all(FDTMachineInfo *fdti) +{ + int ret = 0; + while (force_list_head != NULL) { + TableListNode *to_delete = force_list_head; + ret |= force_list_head->fdt_init(NULL, fdti, force_list_head->opaque); + force_list_head = force_list_head->next; + free(to_delete); + } + return ret; +} + +static void dump_table(TableListNode *head) +{ + if (head == NULL) { + return; + } + printf("key : %s, opaque data %p\n", head->key, head->opaque); + dump_table(head->next); +} + +void dump_compat_table(void) +{ + printf("FDT COMPATIBILITY TABLE:\n"); + dump_table(compat_list_head); +} + +void dump_inst_bind_table(void) +{ + printf("FDT INSTANCE BINDING TABLE:\n"); + dump_table(inst_bind_list_head); +} + +void fdt_init_yield(FDTMachineInfo *fdti) +{ + static int yield_index; + int this_yield = yield_index++; + + DB_PRINT("yield #%d %p\n", this_yield, fdti->cq); + qemu_co_queue_wait(fdti->cq); + DB_PRINT("unyield #%d\n", this_yield); +} + +void fdt_init_set_opaque(FDTMachineInfo *fdti, char *node_path, void *opaque) +{ + FDTDevOpaque *dp; + for (dp = fdti->dev_opaques; + dp->node_path && strcmp(dp->node_path, node_path); + dp++); + if (!dp->node_path) { + dp->node_path = strdup(node_path); + } + dp->opaque = opaque; +} + +int fdt_init_has_opaque(FDTMachineInfo *fdti, char *node_path) +{ + FDTDevOpaque *dp; + for (dp = fdti->dev_opaques; dp->node_path; dp++) { + if (!strcmp(dp->node_path, node_path)) { + return 1; + } + } + return 0; +} + +void *fdt_init_get_opaque(FDTMachineInfo *fdti, char *node_path) +{ + FDTDevOpaque *dp; + for (dp = fdti->dev_opaques; dp->node_path; dp++) { + if (!strcmp(dp->node_path, node_path)) { + return dp->opaque; + } + } + return NULL; +} + +FDTMachineInfo *fdt_init_new_fdti(void *fdt) +{ + FDTMachineInfo *fdti = g_malloc0(sizeof(*fdti)); + fdti->fdt = fdt; + fdti->cq = g_malloc0(sizeof(*(fdti->cq))); + qemu_co_queue_init(fdti->cq); + fdti->dev_opaques = g_malloc0(sizeof(*(fdti->dev_opaques)) * + (devtree_get_num_nodes(fdt) + 1)); + return fdti; +} + +void fdt_init_destroy_fdti(FDTMachineInfo *fdti) +{ + FDTDevOpaque *dp; + for (dp = fdti->dev_opaques; dp->node_path; dp++) { + g_free(dp->node_path); + } + g_free(fdti->dev_opaques); + g_free(fdti); +} diff --git a/hw/fdt_generic.h b/hw/fdt_generic.h new file mode 100644 index 0000000..41992e3 --- /dev/null +++ b/hw/fdt_generic.h @@ -0,0 +1,92 @@ +/* + * Tables of FDT device models and their init functions. Keyed by compatibility + * strings, device instance names. + */ + +#ifndef FDT_GENERIC_H +#define FDT_GENERIC_H + +#include "qemu-common.h" +#include "sysbus.h" +#include "device_tree.h" +#include "qemu-coroutine.h" + +typedef struct FDTDevOpaque { + char *node_path; + void *opaque; +} FDTDevOpaque; + +typedef struct FDTMachineInfo { + /* the fdt blob */ + void *fdt; + /* irq descriptors for top level int controller */ + qemu_irq *irq_base; + /* per-device specific opaques */ + FDTDevOpaque *dev_opaques; + /* recheck coroutine queue */ + CoQueue *cq; +} FDTMachineInfo; + +/* create a new FDTMachineInfo. The client is responsible for setting irq_base. + * the mutex fdt_mutex is locked on return. Client must call + * fdt_init_destroy_fdti to cleanup + */ + +FDTMachineInfo *fdt_init_new_fdti(void *fdt); +void fdt_init_destroy_fdti(FDTMachineInfo *fdti); + +typedef int (*FDTInitFn)(char *, FDTMachineInfo *, void *); + +/* associate a FDTInitFn with a FDT compatibility */ + +void add_to_compat_table(FDTInitFn, const char *, void *); + +/* try and find a device model for a particular compatibility. If found, + * the FDTInitFn associated with the compat will be called and 0 will + * be returned. Returns non-zero on not found or error + */ + +int fdt_init_compat(char *, FDTMachineInfo *, const char *); + +/* same as above, but associates with a FDT node name (rather than compat) */ + +void add_to_inst_bind_table(FDTInitFn, const char *, void *); +int fdt_init_inst_bind(char *, FDTMachineInfo *, const char *); + +/* Register an FDTInitFn that should always be called upon machine creation */ + +void add_to_force_table(FDTInitFn, const char *, void *); +int fdt_force_bind_all(FDTMachineInfo *); + +void dump_compat_table(void); +void dump_inst_bind_table(void); + +/* Called from FDTInitFn's to inform the framework that a dependency is + * unresolved and the calling context needs to wait for another device to + * instantiate first. The calling thread will suspend until a change in state + * in the argument fdt machine is detected. + */ + +void fdt_init_yield(FDTMachineInfo *); + +/* set, check and get per device opaques. Keyed by fdt node_paths */ + +void fdt_init_set_opaque(FDTMachineInfo *fdti, char *node_path, void *opaque); +int fdt_init_has_opaque(FDTMachineInfo *fdti, char *node_path); +void *fdt_init_get_opaque(FDTMachineInfo *fdti, char *node_path); + +/* statically register a FDTInitFn as being associate with a compatibility */ + +#define fdt_register_compatibility_opaque(function, compat, n, opaque) \ +static void __attribute__((constructor)) \ +function ## n ## _register_imp(void) { \ + add_to_compat_table(function, compat, opaque); \ +} + +#define fdt_register_compatibility_n(function, compat, n) \ +fdt_register_compatibility_opaque(function, compat, n, NULL) + +#define fdt_register_compatibility(function, compat) \ +fdt_register_compatibility_n(function, compat, 0) + +#endif /* FDT_GENERIC_H */ diff --git a/hw/fdt_generic_util.c b/hw/fdt_generic_util.c new file mode 100644 index 0000000..3c04957 --- /dev/null +++ b/hw/fdt_generic_util.c @@ -0,0 +1,438 @@ +/* + * Utility functions for fdt generic framework + * + * Copyright (c) 2009 Edgar E. Iglesias. + * Copyright (c) 2009 Michal Simek. + * Copyright (c) 2011 PetaLogix Qld Pty Ltd. + * Copyright (c) 2011 Peter A. G. Crosthwaite . + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#define FDT_GENERIC_UTIL_ERR_DEBUG + +#ifdef FDT_GENERIC_UTIL_ERR_DEBUG +#define DB_PRINT(...) do { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } while (0); +#else + #define DB_PRINT(...) +#endif + +#include "fdt_generic_util.h" +#include "net.h" + +/* FIXME: wrap direct calls into libfdt */ + +#include + +FDTMachineInfo *fdt_generic_create_machine(void *fdt, qemu_irq *cpu_irq) +{ + char node_path[DT_PATH_LENGTH]; + + FDTMachineInfo *fdti = fdt_init_new_fdti(fdt); + + fdti->irq_base = cpu_irq; + + /* bind any force bound instances */ + fdt_force_bind_all(fdti); + + /* parse the device tree */ + if (!qemu_devtree_get_root_node(fdt, node_path)) { + simple_bus_fdt_init(node_path, fdti, NULL); + while (qemu_co_queue_enter_next(fdti->cq)); + } else { + fprintf(stderr, "FDT: ERROR: cannot get root node from device tree %s\n" + , node_path); + } + + printf("FDT: Device tree scan complete\n"); + FDTMachineInfo *ret = g_malloc0(sizeof(*ret)); + return fdti; +} + +struct FDTInitNodeArgs { + char *node_path; + FDTMachineInfo *fdti; +}; + +static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *compat); + +static void fdt_init_node(void *args) +{ + + struct FDTInitNodeArgs *a = args; + char *node_path = a->node_path; + FDTMachineInfo *fdti = a->fdti; + g_free(a); + + char *all_compats = NULL, *compat, *node_name, *next_compat; + int compat_len; + + static int entry_index; + int this_entry = entry_index++; + DB_PRINT("enter %d %s\n", this_entry, node_path); + + /* try instance binding first */ + node_name = qemu_devtree_get_node_name(fdti->fdt, node_path); + if (!node_name) { + printf("FDT: ERROR: nameless node: %s\n", node_path); + } + if (!fdt_init_inst_bind(node_path, fdti, node_name)) { + goto exit; + } + + /* fallback to compatibility binding */ + all_compats = qemu_devtree_getprop(fdti->fdt, node_path, + "compatible", &compat_len, false, NULL); + if (!all_compats) { + printf("FDT: ERROR: no compatibility found for node %s%s\n", node_path, + node_name); + DB_PRINT("exit %d\n", this_entry); + return; + } + compat = all_compats; + +try_next_compat: + if (compat_len == 0) { + goto invalidate; + } + if (!fdt_init_compat(node_path, fdti, compat)) { + goto exit; + } + if (!fdt_init_qdev(node_path, fdti, compat)) { + goto exit; + } + next_compat = rawmemchr(compat, '\0'); + compat_len -= (next_compat + 1 - compat); + if (compat_len > 0) { + *next_compat = ' '; + } + compat = next_compat+1; + goto try_next_compat; +invalidate: + printf("FDT: Unsupported peripheral invalidated %s compatibilities %s\n", + node_name, all_compats); + qemu_devtree_setprop_string(fdti->fdt, node_path, "compatible", + "invalidated"); +exit: + + DB_PRINT("exit %d\n", this_entry); + + if (!fdt_init_has_opaque(fdti, node_path)) { + fdt_init_set_opaque(fdti, node_path, NULL); + } + g_free(node_path); + g_free(all_compats); + return; +} + +int simple_bus_fdt_init(char *bus_node_path, FDTMachineInfo *fdti, void *unused) +{ + int i; + int num_children = qemu_devtree_get_num_children(fdti->fdt, bus_node_path, + 1); + char **children = qemu_devtree_get_children(fdti->fdt, bus_node_path, 1); + + DB_PRINT("num child devices: %d\n", num_children); + + for (i = 0; i < num_children; i++) { + struct FDTInitNodeArgs *init_args = g_malloc0(sizeof(*init_args)); + init_args->node_path = children[i]; + init_args->fdti = fdti; + qemu_coroutine_enter(qemu_coroutine_create(fdt_init_node), init_args); + } + + g_free(children); + return 0; +} + +qemu_irq fdt_get_irq_info(FDTMachineInfo *fdti, char *node_path, int irq_idx, + int *err, char *info) { + void *fdt = fdti->fdt; + int intc_phandle, intc_cells, idx, errl; + char intc_node_path[DT_PATH_LENGTH]; + Error *errp = NULL; + DeviceState *intc; + + if (!err) { + err = &errl; + } + intc_phandle = qemu_devtree_getprop_cell(fdt, node_path, "interrupt-parent", + 0, true, &errp); + if (errp) { + goto fail; + } + + if (qemu_devtree_get_node_by_phandle(fdt, intc_node_path, intc_phandle)) { + goto fail; + } + intc_cells = qemu_devtree_getprop_cell(fdt, intc_node_path, + "#interrupt-cells", 0, false, &errp); + if (errp) { + goto fail; + } + idx = qemu_devtree_getprop_cell(fdt, node_path, "interrupts", + intc_cells * irq_idx, false, &errp); + if (errp) { + goto fail; + } + + while (!fdt_init_has_opaque(fdti, intc_node_path)) { + fdt_init_yield(fdti); + } + intc = DEVICE(fdt_init_get_opaque(fdti, intc_node_path)); + if (!intc) { + goto fail; + } + if (info) { + char *node_name = qemu_devtree_get_node_name(fdt, intc_node_path); + sprintf(info, "%d (%s)", idx, node_name); + g_free((void *)node_name); + } + return qdev_get_gpio_in(intc, idx); +fail: + *err = 1; + if (info) { + sprintf(info, "(none)"); + } + return NULL; +} + +qemu_irq fdt_get_irq(FDTMachineInfo *fdti, char *node_path, int irq_idx) +{ + return fdt_get_irq_info(fdti, node_path, irq_idx, NULL, NULL); +} + +/* FIXME: figure out a real solution to this */ + +#define DIGIT(a) ((a) >= '0' && (a) <= '9') +#define LOWER_CASE(a) ((a) >= 'a' && (a) <= 'z') + +static void trim_xilinx_version(char *x) +{ + for (;;) { + x = strchr(x, '-'); + if (!x || strlen(x) < 7) { + return; + } + if (DIGIT(x[1]) && + x[2] == '.' && + DIGIT(x[3]) && + DIGIT(x[4]) && + x[5] == '.' && + LOWER_CASE(x[6])) { + *x = '\0'; + return; + } + x++; + } +} + +static void substitute_char(char *s, char a, char b) +{ + for (;;) { + s = strchr(s, a); + if (!s) { + return; + } + *s = b; + s++; + } +} + +static DeviceState *fdt_create_qdev_from_compat(char *compat, char **dev_type) +{ + DeviceState *ret = NULL; + + char *c = g_strdup(compat); + ret = qdev_try_create(NULL, c); + if (!ret) { + /* QEMU substitutes "."s for ","s in device names, so try with that + * substitutution + */ + substitute_char(c, ',', '.'); + ret = qdev_try_create(NULL, c); + } + if (!ret) { + /* try again with the xilinx version string trimmed */ + trim_xilinx_version(c); + ret = qdev_try_create(NULL, c); + } + + if (dev_type) { + *dev_type = c; + } else { + g_free(c); + } + return ret; +} + +static inline const char *trim_vendor(const char *s) +{ + /* FIXME: be more intelligent */ + const char *ret = memchr(s, ',', sizeof(s)); + return ret ? ret + 1 : s; +} + +/*FIXME: roll into device tree functionality */ + +static inline uint64_t get_int_be(const void *p, int len) +{ + switch (len) { + case 1: + return *((uint8_t *)p); + case 2: + return be16_to_cpu(*((uint16_t *)p)); + case 4: + return be32_to_cpu(*((uint32_t *)p)); + case 8: + return be32_to_cpu(*((uint64_t *)p)); + default: + fprintf(stderr, "unsupported integer length\n"); + abort(); + } +} + +static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *compat) +{ + int err; + qemu_irq irq; + target_phys_addr_t base; + int offset; + DeviceState *dev; + char *dev_type = NULL; + int is_intc; + Error *errp = NULL; + int i; + + dev = fdt_create_qdev_from_compat(compat, &dev_type); + if (!dev) { + DB_PRINT("no match found for %s\n", compat); + return 1; + } + /* FIXME: attach to the sysbus instead */ + object_property_add_child(container_get(qdev_get_machine(), "/unattached"), + qemu_devtree_get_node_name(fdti->fdt, node_path), + OBJECT(dev), NULL); + + fdt_init_set_opaque(fdti, node_path, dev); + + /* connect nic if appropriate */ + static int nics; + qdev_set_nic_properties(dev, &nd_table[nics]); + if (nd_table[nics].instantiated) { + DB_PRINT("NIC instantiated: %s\n", dev_type); + nics++; + } + + offset = fdt_path_offset(fdti->fdt, node_path); + for (offset = fdt_first_property_offset(fdti->fdt, offset); + offset != -FDT_ERR_NOTFOUND; + offset = fdt_next_property_offset(fdti->fdt, offset)) { + const char *propname; + int len; + const void *val = fdt_getprop_by_offset(fdti->fdt, offset, + &propname, &len); + + propname = trim_vendor(propname); + ObjectProperty *p = object_property_find(OBJECT(dev), propname, NULL); + if (p) { + DB_PRINT("matched property: %s of type %s, len %d\n", + propname, p->type, len); + } + if (!p) { + continue; + } + + /* FIXME: handle generically using accessors and stuff */ + if (!strcmp(p->type, "uint8") || !strcmp(p->type, "uint16") || + !strcmp(p->type, "uint32") || !strcmp(p->type, "uint64")) { + object_property_set_int(OBJECT(dev), get_int_be(val, len), propname, + &errp); + assert_no_error(errp); + DB_PRINT("set property %s to %#llx\n", propname, + get_int_be(val, len)); + } else if (!strcmp(p->type, "bool")) { + object_property_set_bool(OBJECT(dev), !!get_int_be(val, len), + propname, &errp); + assert_no_error(errp); + DB_PRINT("set property %s to %#llx\n", propname, + get_int_be(val, len)); + } else if (!strncmp(p->type, "link", 4)) { + char target_node_path[DT_PATH_LENGTH]; + DeviceState *linked_dev; + + if (qemu_devtree_get_node_by_phandle(fdti->fdt, target_node_path, + get_int_be(val, len))) { + abort(); + } + while (!fdt_init_has_opaque(fdti, target_node_path)) { + fdt_init_yield(fdti); + } + linked_dev = fdt_init_get_opaque(fdti, target_node_path); + object_property_set_link(OBJECT(dev), OBJECT(linked_dev), propname, + errp); + assert_no_error(errp); + } + } + + qdev_init_nofail(dev); + /* map slave attachment */ + base = qemu_devtree_getprop_cell(fdti->fdt, node_path, "reg", 0, false, + &errp); + assert_no_error(errp); + sysbus_mmio_map(sysbus_from_qdev(dev), 0, base); + + { + int len; + fdt_get_property(fdti->fdt, fdt_path_offset(fdti->fdt, node_path), + "interrupt-controller", &len); + is_intc = len >= 0; + DB_PRINT("is interrupt controller: %c\n", is_intc ? 'y' : 'n'); + } + /* connect irq */ + for (i = 0; ; ++i) { + char irq_info[1024]; + irq = fdt_get_irq_info(fdti, node_path, i, &err, irq_info); + /* INTCs inferr their top level, if no IRQ connection specified */ + if (err && is_intc) { + irq = fdti->irq_base[0]; + sysbus_connect_irq(sysbus_from_qdev(dev), 0, irq); + fprintf(stderr, "FDT: (%s) connected top level irq %s\n", dev_type, + irq_info); + break; + } + if (!err) { + sysbus_connect_irq(sysbus_from_qdev(dev), i, irq); + fprintf(stderr, "FDT: (%s) connected irq %s\n", dev_type, irq_info); + } else { + break; + } + } + + if (dev_type) { + g_free(dev_type); + } + + return 0; +} + +fdt_register_compatibility(simple_bus_fdt_init, "simple-bus"); diff --git a/hw/fdt_generic_util.h b/hw/fdt_generic_util.h new file mode 100644 index 0000000..3906721 --- /dev/null +++ b/hw/fdt_generic_util.h @@ -0,0 +1,36 @@ +#ifndef FDT_GENERIC_UTIL_H +#define FDT_GENERIC_UTIL_H + +#include "qemu-common.h" +#include "fdt_generic.h" + +/* create a fdt_generic machine. the top level cpu irqs are required for + * systems instantiating interrupt devices. The client is responsible for + * destroying the returned FDTMachineInfo (using fdt_init_destroy_fdti) + */ + +FDTMachineInfo *fdt_generic_create_machine(void *fdt, qemu_irq *cpu_irq); + +/* fdt init a simple bus. Search the bus for child nodes and instantiate or + * invalidate devices as appropriate. Conformant to FDTInitFn prototype, i.e. + * a bus may fdt_register_compatibilty this as its instantiator. + */ + +int simple_bus_fdt_init(char *node_path, FDTMachineInfo *fdti, void *priv); + +/* get an irq for a device. The interrupt parent of a device is idenitified + * and the specified irq (by the interrupts device-tree property) is retrieved + */ + +qemu_irq fdt_get_irq(FDTMachineInfo *fdti, char *node_path, int irq_idx); + +/* same as above, but poulates err with non-zero if something goes wrong, and + * populates info with a human readable string giving some basic information + * about the interrupt connection found (or not found). Both arguments are + * optional (i.e. can be NULL) + */ + +qemu_irq fdt_get_irq_info(FDTMachineInfo *fdti, char *node_path, int irq_idx, + int *err, char * info); + +#endif /* FDT_GENERIC_UTIL_H */ diff --git a/hw/microblaze/Makefile.objs b/hw/microblaze/Makefile.objs index 274d2c5..23f9cbb 100644 --- a/hw/microblaze/Makefile.objs +++ b/hw/microblaze/Makefile.objs @@ -6,4 +6,7 @@ obj-y += microblaze_pic_cpu.o obj-y += xilinx_ethlite.o obj-$(CONFIG_FDT) += ../device_tree.o +obj-$(CONFIG_FDT_GENERIC) += fdt_generic.o +obj-$(CONFIG_FDT_GENERIC) += fdt_generic_util.o + obj-y := $(addprefix ../,$(obj-y))