From nobody Sat Feb 7 03:28:36 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1765926475; cv=none; d=zohomail.com; s=zohoarc; b=Mrzot/SH3PAppu9oaQTzUFnsWk9pSe/beSpdMCy8BM0XLUmoEll0bYz5nUQGThTUxGDCHSbVhg8dQYHyP5H1s8DUw0pa4PEdpErAZaO2PGlU5T3zJuu95FCXv1H9Uzqc2jWJaFvT1xZVpZ4KRtInwVf8jrCWJmHZOHAjGXaTDfo= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1765926475; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=SEOAi+E+RsrXy3+DewpltEC8elA5fEd6cmGhrcTCr4c=; b=QBugh+5KDKr5hQYLo9jRDXFQKQwYw870j8By48EOAC+z3hpa64QCL/oejqX73PhjCdt8AvS2S2tq56Uh47sl3etURYl3HVORqD+yDZcs6F4ciev+Zmd1F0Muawv1yyk2HVZ0IM3yV4zeie8v8Bw6xBp7su14Q1daX9M9fkIHEdM= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 17659264753821022.9426109074026; Tue, 16 Dec 2025 15:07:55 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1vVe8c-0001j5-LG; Tue, 16 Dec 2025 18:07:26 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1vVd4O-0004jW-Uy for qemu-devel@nongnu.org; Tue, 16 Dec 2025 16:59:01 -0500 Received: from mail-ed1-x52d.google.com ([2a00:1450:4864:20::52d]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1vVd4J-0006b2-U9 for qemu-devel@nongnu.org; Tue, 16 Dec 2025 16:59:00 -0500 Received: by mail-ed1-x52d.google.com with SMTP id 4fb4d7f45d1cf-640c1fda178so10138844a12.1 for ; Tue, 16 Dec 2025 13:58:55 -0800 (PST) Received: from thinkpad-t470s.. (93-140-170-188.adsl.net.t-com.hr. [93.140.170.188]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-b7fe8a956a5sm420898166b.29.2025.12.16.13.58.51 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Dec 2025 13:58:52 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1765922334; x=1766527134; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=SEOAi+E+RsrXy3+DewpltEC8elA5fEd6cmGhrcTCr4c=; b=h3W3nfpcf6Gp7BEie0fJLP9w2XW0NTkRbrzvNWytwW4Ha5fqooz8qJkFOFYDQ3XOmw OcXigjZo0D8x8VmVXplPdDzeN/q6hv2HvAt113NSqZ432Sx0ObvtZ2cjuoStu7mTOx02 JMB6VYJt+TyQ7Ozj6K9JQaKsMbSlx+8Jo7mEIDHBpdvgom241w0LNujduiosRcx6BDzg Omxbltl3QnW6Tap1m7L0RcUv5yf33DKI9312vzEKHR0XJMj7B/m5dnvskRsi05Q9V78Q wn5WbbiaVayqCce2hM3VmNQUjnhgTfqpcP8HBJmxAJ8hV6Ia/q+avavumlrb2fA0kX0J vh8g== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1765922334; x=1766527134; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=SEOAi+E+RsrXy3+DewpltEC8elA5fEd6cmGhrcTCr4c=; b=HoO6CSGCt5Nw1AwfJJImEOxoZz1/dVatk4+Pq1zwNWodDOYYA35H9IE+6uRGWD5F75 lUxlgMO06SADEa4pC3amL2G1l3iqbCNPsi83K5hV3usg5y8W3jJbr9EGoJJm9l7Uy0LZ BPTW2JwVqDvgSsyP8bhvrMCkdJAfYbo+RTAEaEbpw9bZ6tYzDHn2LFAjAyLk+q5/3Xtp uuQMopkPkwMqxIaxU9i68gpGrc9VwoPAwvmnpfIaBymPO+3uwnEt0r7xO4uhe9+ICZKf DnUtL663HUOzjj3BcBIce5k2+BYkGKZOqhQGEC6JGgJB2Tsrr4Cx33KTehnE+azc5zO5 F2yA== X-Gm-Message-State: AOJu0Yzq6ZPCSwz03ASHJkL119pF/VxXkVC7wIahx0u4i1k9vJt9jeL8 dzPvgpiT/zbdsdmTKkMy0OoF8eCGaQsVNnlGcVgolVvRQ4rDp58aab0sRd5CgeYY X-Gm-Gg: AY/fxX7HNGnmPTtxzO/5Q5IcDm2tCOTsKq80gqCTvvYy9DVb+RX6Zwr4yQ7Ktvv45UR D+5S3K3cypnsTr59DQHUfdaDF7ySBmqlMjmLcTN3iAzRSDtkbAxwW7+4jmcZoTzBTxa9w35aj1X nqaw88WouNapufVPMPhuMCUOrIlBtihkR/7EHlsT8Eko3vwSrILW18nhtxeYx0WNJdaE3eLt7wh gjCR9x75m/R0hKdRpkabI6h9/S8rYiIDT/rNwD+8aSemcVSJiz1p1d8JSNJi6UCHGamU0qJTRfC 2nr5G9/0ni1eg3EYIcG7ylwSO1GQZp+v1duY77o0OSVwOvisVJwp93jE337/97WnNCn/ypdI9Bz uQHzqx4rCtK7WCJuLv2/4k5+BHDfMabWjxfbHeFGh6aKGjMcBoUVQvh9w4OF6ffJNSEc2cl4pxs dtr8nyoDi5Ctlk3EPO58j7dOwgHewu0BN/tM65yMafQYkgb6bKX2zWuxNLR7mwvQ== X-Google-Smtp-Source: AGHT+IHyhzrBvzOUaORfWxEKm+xEAsScvyub5V1qaMSNcgAp0ablwcGtQ3xUsaiPxGY2GvmaLxNbqg== X-Received: by 2002:a17:907:9411:b0:b76:74b6:dbf8 with SMTP id a640c23a62f3a-b7d236af43amr1709594666b.14.1765922332801; Tue, 16 Dec 2025 13:58:52 -0800 (PST) From: ruslichenko.r@gmail.com X-Google-Original-From: Ruslan_Ruslichenko@epam.com To: qemu-devel@nongnu.org Cc: Ruslan_Ruslichenko@epam.com, takahiro.nakata.wr@renesas.com, edgar.iglesias@amd.com, peter.maydell@linaro.org, volodymyr_babchuk@epam.com, artem_mygaiev@epam.com Subject: [PATCH RFC 1/2] hw/core: Add support for loading a Hardware Device Tree Date: Tue, 16 Dec 2025 22:58:34 +0100 Message-ID: <20251216215835.208036-2-Ruslan_Ruslichenko@epam.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20251216215835.208036-1-Ruslan_Ruslichenko@epam.com> References: <20251216215835.208036-1-Ruslan_Ruslichenko@epam.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::52d; envelope-from=ruslichenko.r@gmail.com; helo=mail-ed1-x52d.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Tue, 16 Dec 2025 18:07:18 -0500 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1765926478441154100 Content-Type: text/plain; charset="utf-8" From: Ruslan Ruslichenko 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 --- 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; } =20 +/* 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]; + =20 + 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_i= nfo *binfo, return 0; } =20 - acells =3D qemu_fdt_getprop_cell(fdt, "/", "#address-cells", - NULL, &error_fatal); - scells =3D qemu_fdt_getprop_cell(fdt, "/", "#size-cells", - NULL, &error_fatal); + acells =3D qemu_fdt_getprop_cell(fdt, "/", "#address-cells", 0, + false, &error_fatal); + scells =3D qemu_fdt_getprop_cell(fdt, "/", "#size-cells", 0, + false, &error_fatal); if (acells =3D=3D 0 || scells =3D=3D 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_b= ase, hwaddr mem_len) uint32_t acells, scells; char *nodename =3D g_strdup_printf("/memory@%" PRIx64, mem_base); =20 - acells =3D qemu_fdt_getprop_cell(fdt, "/", "#address-cells", + acells =3D qemu_fdt_getprop_cell(fdt, "/", "#address-cells", 0, NULL, &error_fatal); - scells =3D qemu_fdt_getprop_cell(fdt, "/", "#size-cells", + scells =3D 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_i= nfo *info, void *fdt) const VEDBoardInfo *daughterboard =3D (const VEDBoardInfo *)info; =20 acells =3D qemu_fdt_getprop_cell(fdt, "/", "#address-cells", - NULL, &error_fatal); + 0, 0, &error_fatal); scells =3D qemu_fdt_getprop_cell(fdt, "/", "#size-cells", - NULL, &error_fatal); + 0, 0, &error_fatal); intc =3D 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); =20 for (i =3D 0; node_path && node_path[i]; i++) { - r =3D qemu_fdt_getprop(fdt, node_path[i], "method", &prop_len,= NULL); + r =3D qemu_fdt_getprop(fdt, node_path[i], "method", &prop_len, + false, NULL); method_is_hvc =3D r && !strcmp("hvc", r); =20 /* 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 compatib= ility + * 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"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * 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 OT= HER + * 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 =3D malloc(sizeof(*nn)); + nn->next =3D *head_p; + strcpy(nn->key, key); + nn->fdt_init =3D fdt_init; + nn->opaque =3D opaque; + *head_p =3D 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 =3D *head; iter !=3D NULL; iter =3D 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 *opa= que) +{ + add_to_table(fdt_init, compat, opaque, &compat_list_head); +} + +int fdt_init_compat(char *node_path, FDTMachineInfo *fdti, const char *com= pat) +{ + return fdt_init_search_table(node_path, fdti, compat, &compat_list_hea= d); +} + +TableListNode *inst_bind_list_head; + +void add_to_inst_bind_table(FDTInitFn fdt_init, const char *name, void *op= aque) +{ + 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_he= ad); +} + +static void dump_table(TableListNode *head) +{ + TableListNode *iter; + + for (iter =3D head; iter !=3D NULL; iter =3D 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 =3D 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 *opaq= ue) +{ + FDTDevOpaque *dp; + for (dp =3D fdti->dev_opaques; + dp->node_path && strcmp(dp->node_path, node_path); + dp++); + if (!dp->node_path) { + dp->node_path =3D strdup(node_path); + } + dp->opaque =3D opaque; +} + +int fdt_init_has_opaque(FDTMachineInfo *fdti, char *node_path) +{ + FDTDevOpaque *dp; + for (dp =3D 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 *clus= ter) +{ + int i =3D get_next_cpu_cluster_id(); + DeviceState *dev =3D DEVICE(cluster); + FDTCPUCluster *cl; + + qdev_prop_set_uint32(dev, "cluster-id", i); + + cl =3D g_new0(FDTCPUCluster, 1); + cl->cpu_cluster =3D cluster; + cl->next =3D fdti->clusters; + cl->user =3D true; + + fdti->clusters =3D 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 =3D g_malloc0(sizeof(*cl)); + int i =3D get_next_cpu_cluster_id(); + char *name =3D g_strdup_printf("cluster%d", i); + Object *obj; + + obj =3D 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 =3D g_strdup(compat); + cl->cpu_cluster =3D obj; + cl->next =3D fdti->clusters; + + fdti->clusters =3D cl; + + g_free(name); + + return obj; +} + +void *fdt_init_get_cpu_cluster(FDTMachineInfo *fdti, Object *parent, char = *compat) +{ + FDTCPUCluster *cl =3D 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 =3D 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 =3D 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 =3D g_malloc0(sizeof(*fdti)); + fdti->fdt =3D fdt; + fdti->cq =3D g_malloc0(sizeof(*(fdti->cq))); + qemu_co_queue_init(fdti->cq); + fdti->dev_opaques =3D g_malloc0(sizeof(*(fdti->dev_opaques)) * + (devtree_get_num_nodes(fdt) + 1)); + return fdti; +} + +void fdt_init_destroy_fdti(FDTMachineInfo *fdti) +{ + FDTCPUCluster *cl =3D fdti->clusters; + FDTDevOpaque *dp; + + while (cl) { + FDTCPUCluster *tmp =3D cl; + cl =3D cl->next; + g_free(tmp->cpu_type); + g_free(tmp); + } + for (dp =3D 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 QOMifi= ed + * then these FDT creations should happen automatically without need for t= hese + * 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 *p= riv) +{ + Object *parent; + DeviceState *dev; + char parent_node_path[DT_PATH_LENGTH]; + char *node_name =3D 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 =3D fdt_init_get_opaque(fdti, parent_node_path); + dev =3D (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_pat= h, + 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", 1= 8); +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 [] =3D { + { .name =3D "qemu-memory-region", .parent =3D "memory-region" = }, + { .name =3D "simple-bus", .parent =3D "memory-region" = }, +}; + +static void fdt_generic_register_types(void) +{ + int i; + + for (i =3D 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 . + * + * Permission is hereby granted, free of charge, to any person obtaining a= copy + * of this software and associated documentation files (the "Software"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * 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 OT= HER + * 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" +=20 + #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); +=20 + #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); +=20 + /* FIXME: wrap direct calls into libfdt */ +=20 + #include + #include +=20 + int fdt_serial_ports; +=20 + static int simple_bus_fdt_init(char *bus_node_path, FDTMachineInfo *fdti); +=20 + static void fdt_get_irq_info_from_intc(FDTMachineInfo *fdti, qemu_irq *re= t, + char *intc_node_path, + uint32_t *cells, uint32_t num_cell= s, + uint32_t max, Error **errp); +=20 +=20 + 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; +=20 + static bool qemu_irq_shared_or_handler(bool *inputs, int n) + { + int i; +=20 + assert(n < MAX_IRQ_SHARED_INPUTS); +=20 + for (i =3D 0; i < n; ++i) { + if (inputs[i]) { + return true; + } + } + return false; + } +=20 + static bool qemu_irq_shared_and_handler(bool *inputs, int n) + { + int i; +=20 + assert(n < MAX_IRQ_SHARED_INPUTS); +=20 + for (i =3D 0; i < n; ++i) { + if (!inputs[i]) { + return false; + } + } + return true; + } +=20 + static void qemu_irq_shared_handler(void *opaque, int n, int level) + { + QEMUIRQSharedState *s =3D opaque; +=20 + assert(n < MAX_IRQ_SHARED_INPUTS); + s->inputs[n] =3D level; + qemu_set_irq(s->sink, s->merge_fn(s->inputs, s->num)); + } +=20 + static void fdt_init_all_irqs(FDTMachineInfo *fdti) + { + while (fdti->irqs) { + FDTIRQConnection *first =3D fdti->irqs; + qemu_irq sink =3D first->irq; + bool (*merge_fn)(bool *, int) =3D first->merge_fn; + int num_sources =3D 0; + FDTIRQConnection *irq; +=20 + for (irq =3D first; irq; irq =3D irq->next) { + if (irq->irq =3D=3D sink) { /* Same sink */ + num_sources++; + } + } + if (num_sources > 1) { + QEMUIRQSharedState *s =3D g_malloc0(sizeof *s); + s->sink =3D sink; + s->merge_fn =3D merge_fn; + qemu_irq *sources =3D qemu_allocate_irqs(qemu_irq_shared_hand= ler, s, + num_sources); + for (irq =3D first; irq; irq =3D irq->next) { + if (irq->irq =3D=3D sink) { + char *shared_irq_name =3D g_strdup_printf("shared-irq= -%p", + *sources); +=20 + if (irq->merge_fn !=3D merge_fn) { + fprintf(stderr, "ERROR: inconsistent IRQ merge fn= s\n"); + exit(1); + } +=20 + object_property_add_child(OBJECT(irq->dev), shared_ir= q_name, + OBJECT(*sources)); + g_free(shared_irq_name); + irq->irq =3D *(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 : ""); +=20 + qdev_connect_gpio_out_named(DEVICE(first->dev), first->name, firs= t->i, + first->irq); + fdti->irqs =3D first->next; + g_free(first); + } + } +=20 + static void fdt_init_cpu_clusters(FDTMachineInfo *fdti) + { + FDTCPUCluster *cl =3D fdti->clusters; +=20 + while (cl) { + qdev_realize(DEVICE(cl->cpu_cluster), NULL, &error_fatal); + cl =3D cl->next; + } + } +=20 + FDTMachineInfo *fdt_generic_create_machine(void *fdt, qemu_irq *cpu_irq) + { + char node_path[DT_PATH_LENGTH]; + FDTMachineInfo *fdti =3D fdt_init_new_fdti(fdt); +=20 + fdti->irq_base =3D cpu_irq; +=20 + fdt_serial_ports =3D 0; +=20 + /* 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 tre= e %s\n" + , node_path); + } +=20 + /* FIXME: Populate these from DTS and create CPU clusters. */ + //current_machine->smp.cores =3D fdt_generic_num_cpus; + //current_machine->smp.cpus =3D fdt_generic_num_cpus; + //current_machine->smp.max_cpus =3D fdt_generic_num_cpus; +=20 + bdrv_drain_all(); + DB_PRINT(0, "FDT: Device tree scan complete\n"); + return fdti; + } +=20 + struct FDTInitNodeArgs { + char *node_path; + char *parent_path; + FDTMachineInfo *fdti; + }; +=20 + static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *com= pat); +=20 + static int check_compat(const char *prefix, const char *compat, + char *node_path, FDTMachineInfo *fdti) + { + char *compat_prefixed =3D g_strconcat(prefix, compat, NULL); + const int done =3D !fdt_init_compat(node_path, fdti, compat_prefixed); + g_free(compat_prefixed); + return done; + } +=20 + static void fdt_init_node(void *args) + { + struct FDTInitNodeArgs *a =3D args; + char *node_path =3D a->node_path; + FDTMachineInfo *fdti =3D a->fdti; + g_free(a); +=20 + simple_bus_fdt_init(node_path, fdti); +=20 + char *all_compats =3D NULL, *node_name; + char *device_type =3D NULL; + int compat_len; +=20 + DB_PRINT_NP(1, "enter\n"); +=20 + /* try instance binding first */ + node_name =3D 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; + } +=20 + /* fallback to compatibility binding */ + all_compats =3D qemu_fdt_getprop(fdti->fdt, node_path, "compatible", + &compat_len, false, NULL); + if (all_compats) { + char *compat =3D all_compats; + char * const end =3D &all_compats[compat_len - 1]; /* points to n= ul */ +=20 + while (compat < end) { + if (check_compat("compatible:", compat, node_path, fdti)) { + goto exit; + } +=20 + if (!fdt_init_qdev(node_path, fdti, compat)) { + check_compat("postinit:", compat, node_path, fdti); + goto exit; + } +=20 + /* Scan forward to the end of the current compat. */ + while (compat < end && *compat) { + ++compat; + } +=20 + /* Replace nul with space for the debug printf below. */ + if (compat < end) { + *compat++ =3D ' '; + } + }; + } else { + DB_PRINT_NP(0, "no compatibility found\n"); + } +=20 + /* Try to create the device using device_type property + * Not every device tree node has compatible property, so + * try with device_type. + */ + device_type =3D 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; + } +=20 + if (!fdt_init_qdev(node_path, fdti, device_type)) { + goto exit; + } + } +=20 + 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: +=20 + DB_PRINT_NP(1, "exit\n"); +=20 + 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; + } +=20 + static int simple_bus_fdt_init(char *node_path, FDTMachineInfo *fdti) + { + int i; + int num_children =3D qemu_devtree_get_num_children(fdti->fdt, node_pa= th, + 1); + char **children; +=20 + if (num_children =3D=3D 0) { + return 0; + } + children =3D qemu_devtree_get_children(fdti->fdt, node_path, 1); +=20 + DB_PRINT_NP(num_children ? 0 : 1, "num child devices: %d\n", num_chil= dren); +=20 + for (i =3D 0; i < num_children; i++) { + struct FDTInitNodeArgs *init_args =3D g_malloc0(sizeof(*init_args= )); + init_args->node_path =3D children[i]; + init_args->fdti =3D fdti; + qemu_coroutine_enter(qemu_coroutine_create(fdt_init_node, init_ar= gs)); + } +=20 + g_free(children); + return 0; + } +=20 + 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 =3D fdti->fdt; + uint32_t parent_phandle, parent_cells =3D 0, cells[32]; + char parent_node_path[DT_PATH_LENGTH]; + DeviceState *parent; + int i; + Error *errp =3D NULL; + const char *reason =3D NULL; + bool free_reason =3D false; + const char *propname =3D gpio_set->names->propname; + const char *cells_propname =3D gpio_set->names->cells_propname; +=20 + cells[0] =3D 0; +=20 + parent_phandle =3D qemu_fdt_getprop_cell(fdt, node_path, propname, + (*cur_cell)++, false, &errp); + if (errp) { + reason =3D g_strdup_printf("Cant get phandle from \"%s\" property= \n", + propname); + *end =3D true; + free_reason =3D true; + goto fail_silent; + } + if (qemu_devtree_get_node_by_phandle(fdt, parent_node_path, + parent_phandle)) { + *end =3D true; + reason =3D "cant get node from phandle\n"; + goto fail; + } + parent_cells =3D qemu_fdt_getprop_cell(fdt, parent_node_path, + cells_propname, 0, false, &errp); + if (errp) { + *end =3D true; + reason =3D g_strdup_printf("cant get the property \"%s\" from the= " \ + "parent \"%s\"\n", + cells_propname, parent_node_path); + free_reason =3D true; + goto fail; + } +=20 + for (i =3D 0; i < parent_cells; ++i) { + cells[i] =3D qemu_fdt_getprop_cell(fdt, node_path, propname, + (*cur_cell)++, false, &errp); + if (errp) { + *end =3D true; + reason =3D "cant get cell value"; + goto fail; + } + } +=20 + while (!fdt_init_has_opaque(fdti, parent_node_path)) { + fdt_init_yield(fdti); + } + parent =3D DEVICE(fdt_init_get_opaque(fdti, parent_node_path)); +=20 + if (!parent) { + reason =3D "parent is not a device"; + goto fail_silent; + } +=20 + while (!parent->realized) { + fdt_init_yield(fdti); + } +=20 + { + const FDTGenericGPIOConnection *fgg_con =3D NULL; + uint16_t range, idx; + const char *gpio_name =3D NULL; + qemu_irq ret; +=20 + if (object_dynamic_cast(OBJECT(parent), TYPE_FDT_GENERIC_GPIO)) { + const FDTGenericGPIOSet *set; + FDTGenericGPIOClass *parent_fggc =3D + FDT_GENERIC_GPIO_GET_CLASS(parent); +=20 + for (set =3D parent_fggc->controller_gpios; set && set->names; + set++) { + if (!strcmp(gpio_set->names->cells_propname, + set->names->cells_propname)) { + fgg_con =3D set->gpios; + break; + } + } + } +=20 + /* FIXME: cells[0] is not always the fdt indexing match system */ + idx =3D cells[0] & ~(1ul << 31); + if (fgg_con) { + range =3D fgg_con->range ? fgg_con->range : 1; + while (!(idx >=3D fgg_con->fdt_index + && idx < (fgg_con->fdt_index + range)) + && fgg_con->name) { + fgg_con++; + range =3D fgg_con->range ? fgg_con->range : 1; + } +=20 + idx -=3D fgg_con->fdt_index; + gpio_name =3D fgg_con->name; + } +=20 + if (input) { + FDTIRQConnection *irq =3D g_new0(FDTIRQConnection, 1); + bool (*merge_fn)(bool *, int) =3D qemu_irq_shared_or_handler; +=20 + /* FIXME: I am kind of stealing here. Use the msb of the first + * cell to indicate the merge function. This needs to be disc= ussed + * with device-tree community on how this should be done prop= erly. + */ + if (cells[0] & (1 << 31)) { + merge_fn =3D qemu_irq_shared_and_handler; + } +=20 + DB_PRINT_NP(1, "%s GPIO output %s[%d] on %s\n", debug_success, + gpio_name ? gpio_name : "unnamed", idx, + parent_node_path); + *irq =3D (FDTIRQConnection) { + .dev =3D parent, + .name =3D gpio_name, + .merge_fn =3D merge_fn, + .i =3D idx, + .irq =3D input, + .sink_info =3D NULL, /* FIMXE */ + .next =3D fdti->irqs + }; + fdti->irqs =3D irq; + } +=20 + if (!strcmp(propname, "interrupts-extended") && + object_dynamic_cast(OBJECT(parent), TYPE_FDT_GENERIC_INTC) && + parent_cells > 1) { + qemu_irq *irqs =3D g_new0(qemu_irq, fdt_generic_num_cpus); + // int i; +=20 + fdt_get_irq_info_from_intc(fdti, irqs, parent_node_path, cell= s, + parent_cells, fdt_generic_num_cpus, &= errp); + if (errp) { + reason =3D "failed to create gpio connection"; + goto fail; + } +=20 + ret =3D NULL; + for (i =3D 0; i < fdt_generic_num_cpus; i++) { + if (irqs[i]) { + ret =3D irqs[i]; + break; + } + } + g_free(irqs); + } else { + ret =3D qdev_get_gpio_in_named(parent, gpio_name, idx); + } +=20 + 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); + } +=20 + fail_silent: + if (free_reason) { + g_free((void *)reason); + } + return NULL; + } +=20 + static void fdt_get_irq_info_from_intc(FDTMachineInfo *fdti, qemu_irq *re= t, + char *intc_node_path, + uint32_t *cells, uint32_t num_cell= s, + uint32_t max, Error **errp) + { + FDTGenericIntcClass *intc_fdt_class; + DeviceState *intc; +=20 + while (!fdt_init_has_opaque(fdti, intc_node_path)) { + fdt_init_yield(fdti); + } + intc =3D DEVICE(fdt_init_get_opaque(fdti, intc_node_path)); +=20 + if (!intc) { + goto fail; + } +=20 + while (!intc->realized) { + fdt_init_yield(fdti); + } +=20 + intc_fdt_class =3D FDT_GENERIC_INTC_GET_CLASS(intc); + if (!intc_fdt_class) { + goto fail; + } +=20 + intc_fdt_class->get_irq(FDT_GENERIC_INTC(intc), ret, cells, num_cells, + max, errp); +=20 + return; + fail: + error_setg(errp, "%s", __func__); + } +=20 + static uint32_t imap_cache[32 * 1024]; + static bool imap_cached =3D false; +=20 + qemu_irq *fdt_get_irq_info(FDTMachineInfo *fdti, char *node_path, int irq= _idx, + char *info, bool *map_mode) { + void *fdt =3D fdti->fdt; + uint32_t intc_phandle, intc_cells, cells[32]; + char intc_node_path[DT_PATH_LENGTH]; + qemu_irq *ret =3D NULL; + int i; + Error *errp =3D NULL; +=20 + intc_phandle =3D qemu_fdt_getprop_cell(fdt, node_path, "interrupt-par= ent", + 0, true, &errp); + if (errp) { + errp =3D NULL; + intc_cells =3D qemu_fdt_getprop_cell(fdt, node_path, + "#interrupt-cells", 0, true, &= errp); + *map_mode =3D true; + } else { + if (qemu_devtree_get_node_by_phandle(fdt, intc_node_path, + intc_phandle)) { + goto fail; + } +=20 + /* Check if the device is using interrupt-maps */ + qemu_fdt_getprop_cell(fdt, node_path, "interrupt-map-mask", 0, + false, &errp); + if (!errp) { + errp =3D NULL; + intc_cells =3D qemu_fdt_getprop_cell(fdt, node_path, + "#interrupt-cells", 0, + true, &errp); + *map_mode =3D true; + } else { + errp =3D NULL; + intc_cells =3D qemu_fdt_getprop_cell(fdt, intc_node_path, + "#interrupt-cells", 0, + true, &errp); + *map_mode =3D false; + } + } +=20 + if (errp) { + goto fail; + } +=20 + DB_PRINT_NP(2, "%s intc_phandle: %d\n", node_path, intc_phandle); +=20 + for (i =3D 0; i < intc_cells; ++i) { + cells[i] =3D qemu_fdt_getprop_cell(fdt, node_path, "interrupts", + intc_cells * irq_idx + i, false,= &errp); + if (errp) { + goto fail; + } + } +=20 + if (*map_mode) { + int k; + ret =3D g_new0(qemu_irq, 1); + int num_matches =3D 0; + int len; + g_autofree uint32_t* imap_mask =3D g_new(uint32_t, intc_cells); + uint32_t *imap_p; + uint32_t *imap; + bool use_parent =3D false; +=20 + for (k =3D 0; k < intc_cells; ++k) { + imap_mask[k] =3D qemu_fdt_getprop_cell(fdt, node_path, + "interrupt-map-mask", k = + 2, + true, &errp); + if (errp) { + goto fail; + } + } +=20 + /* Check if the device has an interrupt-map property */ + imap =3D qemu_fdt_getprop(fdt, node_path, "interrupt-map", &len, + use_parent, &errp); +=20 + 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 =3D true; + errp =3D NULL; +=20 + imap_p =3D qemu_fdt_getprop(fdt, node_path, "interrupt-map", + &len, use_parent, &errp); + if (!imap_cached) { + memcpy(imap_cache, imap_p, len); + imap_cached =3D true; + } + imap =3D imap_cache; +=20 + if (errp) { + goto fail; + } + } +=20 + len /=3D sizeof(uint32_t); +=20 + i =3D 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 =3D qemu_fdt_getprop(fdt, node_path, "interrupt-map"= , &len, + use_parent, &errp); +=20 + if (errp) { + goto fail; + } +=20 + len /=3D sizeof(uint32_t); + } +=20 + bool match =3D true; + uint32_t new_intc_cells, new_cells[32]; + i++; i++; /* FIXME: do address cells properly */ + for (k =3D 0; k < intc_cells; ++k) { + uint32_t map_val =3D be32_to_cpu(imap[i++]); + if ((cells[k] ^ map_val) & imap_mask[k]) { + match =3D 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 =3D be32_to_cpu(imap[i++]); + if (intc_phandle & (0xffu << 24)) { + new_intc_cells =3D (intc_phandle >> 24) - 1; + } else { + if (qemu_devtree_get_node_by_phandle(fdt, intc_node_path, + intc_phandle)) { + goto fail; + } + new_intc_cells =3D qemu_fdt_getprop_cell(fdt, intc_node_p= ath, + "#interrupt-cells"= , 0, + false, &errp); + imap[i - 1] =3D cpu_to_be32(intc_phandle | + (new_intc_cells + 1) << 24); + if (errp) { + goto fail; + } + } + for (k =3D 0; k < new_intc_cells; ++k) { + new_cells[k] =3D be32_to_cpu(imap[i++]); + } + if (match) { + num_matches++; + ret =3D g_renew(qemu_irq, ret, num_matches + 1); + if (intc_phandle & (0xffu << 24)) { + if (qemu_devtree_get_node_by_phandle(fdt, intc_node_p= ath, + intc_phandle & + ((1 << 24) - 1))= ) { + goto fail; + } + } +=20 + DB_PRINT_NP(2, "Getting IRQ information: %s -> 0x%x (%s)\= n", + node_path, intc_phandle, intc_node_path); +=20 + memset(&ret[num_matches], 0, sizeof(*ret)); + fdt_get_irq_info_from_intc(fdti, &ret[num_matches-1], int= c_node_path, + new_cells, new_intc_cells, 1, = NULL); + if (info) { + sprintf(info, "%s", intc_node_path); + info +=3D strlen(info) + 1; + } + } + } + return ret; + } +=20 + DB_PRINT_NP(2, "Getting IRQ information: %s -> %s\n", + node_path, intc_node_path); +=20 + ret =3D g_new0(qemu_irq, fdt_generic_num_cpus + 2); + fdt_get_irq_info_from_intc(fdti, ret, intc_node_path, cells, intc_cel= ls, + fdt_generic_num_cpus, &errp); +=20 + if (errp) { + goto fail; + } +=20 + /* FIXME: Phase out this info bussiness */ + if (info) { + sprintf(info, "%s", intc_node_path); + } +=20 + return ret; +=20 + fail: + if (errp) { + sprintf(info, "%s", error_get_pretty(errp)); + } else { + sprintf(info, "(none)"); + } + return NULL; + } +=20 + 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); + } +=20 + /* FIXME: figure out a real solution to this */ +=20 + #define DIGIT(a) ((a) >=3D '0' && (a) <=3D '9') + #define LOWER_CASE(a) ((a) >=3D 'a' && (a) <=3D 'z') +=20 + static void trim_version(char *x) + { + long result; +=20 + for (;;) { + x =3D strchr(x, '-'); + if (!x) { + return; + } + if (DIGIT(x[1])) { + /* Try to trim Xilinx version suffix */ + const char *p; +=20 + qemu_strtol(x + 1, &p, 0, &result); +=20 + if ( *p =3D=3D '.') { + *x =3D 0; + return; + } else if ( *p =3D=3D 0) { + return; + } + } else if (x[1] =3D=3D 'r' && x[3] =3D=3D 'p') { + /* Try to trim ARM version suffix */ + if (DIGIT(x[2]) && DIGIT(x[4])) { + *x =3D 0; + return; + } + } + x++; + } + } +=20 + static void substitute_char(char *s, char a, char b) + { + for (;;) { + s =3D strchr(s, a); + if (!s) { + return; + } + *s =3D b; + s++; + } + } +=20 + static inline const char *trim_vendor(const char *s) + { + /* FIXME: be more intelligent */ + const char *ret =3D memchr(s, ',', strlen(s)); + return ret ? ret + 1 : s; + } +=20 + /* + * Try to attach by matching drive created by '-blockdev node-name=3DLABE= L' + * iff the FDT node contains property 'blockdev-node-name=3DLABEL'. + * + * 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_1= 1.pdf + */ + static bool fdt_attach_blockdev(FDTMachineInfo *fdti, + char *node_path, Object *dev) + { + static const char propname[] =3D "blockdev-node-name"; + char *label; +=20 + /* Inspect FDT node for blockdev-only binding */ + label =3D qemu_fdt_getprop(fdti->fdt, node_path, propname, + NULL, false, NULL); +=20 + /* Skip legacy node */ + if (!label) { + return false; + } +=20 + /* + * 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' =3D ", + node_path, propname); + goto ret; + } +=20 + if (!bdrv_find_node(label)) { + goto ret; + } +=20 + object_property_set_str(OBJECT(dev), "drive", label, NULL); + ret: + g_free(label); + return true; + } +=20 + static void fdt_attach_blockdev_noname(FDTMachineInfo *fdti, + char *node_path, Object *dev) + { + char *blockdev_name =3D NULL; +=20 + blockdev_name =3D qemu_fdt_getprop_string(fdti->fdt, node_path, + "blockdev-node-name", 0, false, NULL); + if (!blockdev_name) { + blockdev_name =3D 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); + } +=20 + static void fdt_attach_drive(FDTMachineInfo *fdti, char *node_path, + Object *dev, BlockInterfaceType drive_type) + { + DriveInfo *dinfo =3D NULL; + uint32_t *di_val =3D NULL; + int di_len =3D 0; +=20 + /* Do nothing if the device is not a block front-end */ + if (!object_property_find(OBJECT(dev), "drive")) { + return; + } +=20 + /* Try non-legacy */ + if (fdt_attach_blockdev(fdti, node_path, dev)) { + return; + } +=20 + /* + * Try legacy with explicit 'drive-index' binding, or next-unit + * as fallback binding. + */ + di_val =3D qemu_fdt_getprop(fdti->fdt, node_path, "drive-index", + &di_len, false, NULL); +=20 + if (di_val && (di_len =3D=3D sizeof(*di_val))) { + dinfo =3D drive_get_by_index(drive_type, be32_to_cpu(*di_val)); + } else { + dinfo =3D drive_get_next(drive_type); + } +=20 + if (dinfo) { + qdev_prop_set_drive(DEVICE(dev), "drive", + blk_by_legacy_dinfo(dinfo)); + } +=20 + return; + } +=20 + static void fdt_attach_indexed_drive(FDTMachineInfo *fdti, char *node_pat= h, + Object *dev, uint32_t di_default, + BlockInterfaceType drive_type) + { + static const char prop[] =3D "drive-index"; + uint32_t *di_val =3D NULL; + int di_len =3D 0; +=20 + di_val =3D qemu_fdt_getprop(fdti->fdt, node_path, prop, &di_len, fals= e, NULL); + if (!di_val || (di_len !=3D sizeof(*di_val))) { + uint32_t di =3D cpu_to_be32(di_default); + int r; +=20 + r =3D 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)); + } + } +=20 + fdt_attach_drive(fdti, node_path, dev, drive_type); + } +=20 + static Object *fdt_create_from_compat(const char *compat, char **dev_type) + { + Object *ret =3D NULL; + char *c =3D g_strdup(compat); +=20 + /* Try to create the object */ + ret =3D object_new(c); +=20 + if (!ret) { + /* Trim the version off the end and try again */ + trim_version(c); + ret =3D object_new(c); +=20 + if (!ret) { + /* Replace commas with full stops */ + substitute_char(c, ',', '.'); + ret =3D object_new(c); + } + } +=20 + if (!ret) { + /* Restart with the orginal string and now replace commas with fu= ll stops + * and try again. This means that versions are still included. + */ + g_free(c); + c =3D g_strdup(compat); + substitute_char(c, ',', '.'); + ret =3D object_new(c); + } +=20 + if (dev_type) { + *dev_type =3D c; + } else { + g_free(c); + } +=20 + if (!ret) { + const char *no_vendor =3D trim_vendor(compat); +=20 + if (no_vendor !=3D compat) { + return fdt_create_from_compat(no_vendor, dev_type); + } + } + return ret; + } +=20 + /*FIXME: roll into device tree functionality */ +=20 + 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(); + } + } +=20 + /* FIXME: use structs instead of parallel arrays */ +=20 + static const char *fdt_generic_reg_size_prop_names[] =3D { + "#address-cells", + "#size-cells", + "#bus-cells", + "#priority-cells", + }; +=20 + static const int fdt_generic_reg_cells_defaults[] =3D { + 1, + 1, + 0, + 0, + }; +=20 + /* + * 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 *co= mpat) + { + char *abort_on_error; + char *warn_on_error; +=20 + warn_on_error =3D qemu_fdt_getprop(fdti->fdt, node_path, + "qemu-fdt-warn-on-error", 0, + true, NULL); + abort_on_error =3D 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); + } + } +=20 + if (abort_on_error) { + error_report("Failed to create %s", compat); + error_setg(&error_fatal, "%s", abort_on_error); + } + } +=20 + static void fdt_init_qdev_link_prop(Object *obj, ObjectProperty *p, + FDTMachineInfo *fdti, + const char *node_path, + const QEMUDevtreeProp *prop) + { + int len =3D prop->len; + void *val =3D prop->value; + const char *propname =3D prop->name; +=20 + Object *linked_dev, *proxy; + char target_node_path[DT_PATH_LENGTH]; + g_autofree char *propname_target =3D g_strconcat(propname, "-target",= NULL); + Error *errp =3D NULL; +=20 + if (qemu_devtree_get_node_by_phandle(fdti->fdt, target_node_path, + get_int_be(val, len))) { + abort(); + } +=20 + while (!fdt_init_has_opaque(fdti, target_node_path)) { + fdt_init_yield(fdti); + } + linked_dev =3D fdt_init_get_opaque(fdti, target_node_path); +=20 + proxy =3D 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", propn= ame); + linked_dev =3D proxy; + } +=20 + if (!linked_dev) { + return; + } +=20 + errp =3D 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 =3D len / 2; + int region =3D 0; +=20 + if (len > 4) { + region =3D get_int_be(val + offset, len - offset); + } +=20 + alias_mr =3D sysbus_mmio_get_region(SYS_BUS_DEVICE(linked_dev), r= egion); +=20 + object_property_set_link(obj, propname, OBJECT(alias_mr), &error_= abort); +=20 + } +=20 + DB_PRINT_NP(0, "set link %s\n", propname); + } +=20 + static void fdt_init_qdev_scalar_prop(Object *obj, ObjectProperty *p, + FDTMachineInfo *fdti, + const char *node_path, + const QEMUDevtreeProp *prop) + { + const char *propname =3D trim_vendor(prop->name); + void *val =3D prop->value; + int len =3D prop->len; +=20 + /* 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; + } +=20 + 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; + } +=20 + 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; + } +=20 + 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; + } +=20 + DB_PRINT_NP(0, "WARNING: property is of unknown type\n"); + } +=20 + static size_t fdt_array_elem_len(FDTMachineInfo *fdti, + const char *node_path, + const char *propname) + { + g_autofree char *elem_cells_propname =3D NULL; + Error *err =3D NULL; + uint32_t elem_cells; +=20 + /* + * Default element size to 1 uint32_t cell, unless it is explicitly + * given in the same FDT node (not inherited). + */ + elem_cells_propname =3D g_strconcat("#", propname, "-cells", NULL); + elem_cells =3D qemu_fdt_getprop_cell(fdti->fdt, node_path, + elem_cells_propname, 0, false, &er= r); +=20 + return (err ? 1 : elem_cells) * 4; + } +=20 +static ObjectProperty *fdt_array_elem_prop(Object *obj, + const char *propname, int k) +{ + g_autofree char *elem_name =3D 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 =3D 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, ObjectPropert= y *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.ht= ml + * + * 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 =3D g_strdup(e->name); + void *elem_ptr =3D object_field_prop_ptr(obj, e->opaque); +=20 + object_property_del(obj, elem_name); + e =3D object_property_add_link(obj, elem_name, + elem_link_type, elem_ptr, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); +=20 + return e; + } +=20 + static void fdt_init_qdev_array_prop(Object *obj, + FDTMachineInfo *fdti, + const char *node_path, + QEMUDevtreeProp *prop) + { + const char *propname =3D trim_vendor(prop->name); + int nr =3D prop->len; + Error *local_err =3D NULL; + char *len_name; + uint32_t elem_len; + g_autofree char *elem_link_type =3D NULL; + ObjectProperty *e; +=20 + if (!prop->value || !nr) { + return; + } +=20 + elem_len =3D fdt_array_elem_len(fdti, node_path, propname); + if (nr % elem_len) { + return; + } +=20 + nr /=3D elem_len; +=20 + /* + * 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 =3D g_strconcat(PROP_ARRAY_LEN_PREFIX, propname, NULL); + object_property_set_int(obj, len_name, nr, &local_err); + g_free(len_name); +=20 + if (local_err) { + error_free(local_err); + return; + } +=20 + e =3D fdt_array_elem_prop(obj, propname, 0); + if (!e) { + return; + } +=20 + elem_link_type =3D fdt_array_elem_type(e); +=20 + while (nr--) { + QEMUDevtreeProp q; +=20 + e =3D fdt_array_elem_prop(obj, propname, nr); + if (!e) { + continue; + } +=20 + q =3D (QEMUDevtreeProp){.name =3D e->name, + .value =3D prop->value + nr * elem_len, + .len =3D elem_len, + }; +=20 + if (elem_link_type) { + e =3D fdt_array_link_elem_prop(obj, e, elem_link_type); + q.name =3D 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); + } + } + } +=20 + static void fdt_prop_override(char *node_path, + QEMUDevtreeProp *props, + QEMUDevtreeProp *prop, + const char *prefix, + const char *propname) + { + char *pfxPropname =3D g_strdup_printf("%s-%s", prefix, propname); + QEMUDevtreeProp *pp; +=20 + pp =3D qemu_devtree_prop_search(props, pfxPropname); + if (pp) { + g_free(prop->value); + prop->len =3D pp->len; + prop->value =3D g_memdup(pp->value, pp->len); + DB_PRINT_NP(1, "Found %s property match: %s\n", + prefix, pfxPropname); + } + g_free(pfxPropname); + } +=20 + static bool ready_to_realize(DeviceState *dev) + { + FDTGenericHelperClass *fghc; +=20 + if (!object_dynamic_cast(OBJECT(dev), TYPE_FDT_GENERIC_HELPER)) { + return true; + } +=20 + fghc =3D FDT_GENERIC_HELPER_GET_CLASS(dev); +=20 + if (!fghc->ready_to_realize) { + return true; + } +=20 + return fghc->ready_to_realize(dev); + } +=20 + static int fdt_init_qdev(char *node_path, FDTMachineInfo *fdti, char *com= pat) + { + Object *dev, *parent; + char *dev_type =3D NULL; + bool is_direct_linux; + int is_intc; + Error *errp =3D NULL; + int i, j; + QEMUDevtreeProp *prop, *props; + char parent_node_path[DT_PATH_LENGTH]; + const FDTGenericGPIOSet *gpio_set =3D NULL; + /* Allocate a large number and assert if something goes over */ + FDTGenericGPIOSet tmp_gpio_set[64]; + FDTGenericGPIOClass *fggc =3D NULL; +=20 + if (!compat) { + return 1; + } + dev =3D 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); +=20 + /* Are we doing a direct Linux boot? */ + is_direct_linux =3D object_property_get_bool(OBJECT(qdev_get_machine(= )), + "linux", NULL); +=20 + /* 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_cp= us); + } +=20 + 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); + } +=20 + parent =3D fdt_init_get_opaque(fdti, parent_node_path); +=20 + if (object_dynamic_cast(dev, TYPE_CPU)) { + parent =3D fdt_init_get_cpu_cluster(fdti, parent, compat); + } +=20 + 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 =3D parent; + unsigned int depth =3D 0; +=20 + 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_BU= S)) { + /* + * Assert against insanely deep hierarchies which are an + * indication of loops. + */ + assert(depth < 4096); +=20 + parent_bus =3D parent_bus->parent; + depth++; + } +=20 + 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=3Dx. + */ + parent_bus =3D OBJECT(sysbus_get_default()); + } +=20 + 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); + } +=20 + /* 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); +=20 + /* Call FDT Generic hooks for overriding prop default values. */ + if (object_dynamic_cast(dev, TYPE_FDT_GENERIC_PROPS)) { + FDTGenericPropsClass *k =3D FDT_GENERIC_PROPS_GET_CLASS(dev); +=20 + assert(k->set_props); + k->set_props(OBJECT(dev), &error_fatal); + } +=20 + props =3D qemu_devtree_get_props(fdti->fdt, node_path); + for (prop =3D props; prop->name; prop++) { + const char *propname =3D trim_vendor(prop->name); + int len; + void *val; + ObjectProperty *p =3D 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 m= ax + * length of 30 characters. We already break that limit if us= ing + * direct-linux-start-powered-off, for example. + */ + fdt_prop_override(node_path, props, prop, "direct-lnx", propn= ame); + } +=20 + val =3D prop->value; + len =3D prop->len; +=20 + p =3D 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; + } +=20 + if (!strcmp(propname, "type")) { + continue; + } +=20 + /* Special case for chardevs. It's an ordered list of strings. */ + if (!strcmp(propname, "chardev") && !strcmp(p->type, "str")) { + const char *chardev =3D val; + const char *chardevs_end =3D chardev + len; +=20 + assert(errp =3D=3D NULL); + while (chardev < chardevs_end) { + object_property_set_str(OBJECT(dev), propname, (const cha= r *)chardev, &errp); + if (!errp) { + DB_PRINT_NP(0, "set property %s to %s\n", propname, + chardev); + break; + } + chardev +=3D strlen(chardev) + 1; + errp =3D NULL; + } + assert(errp =3D=3D NULL); + continue; + } +=20 + fdt_init_qdev_scalar_prop(OBJECT(dev), p, fdti, node_path, prop); + } +=20 + if (object_dynamic_cast(dev, TYPE_DEVICE)) { + DeviceClass *dc =3D DEVICE_GET_CLASS(dev); + /* connect nic if appropriate */ + static int nics; + const char *short_name =3D qemu_devtree_get_node_name(fdti->fdt, = node_path); +=20 + 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++; + } +=20 + /* 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 =3D NULL; + use_blkdev =3D 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; +=20 + di =3D object_property_get_uint(dev, "crc-zpads", + &error_abort) =3D=3D 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; +=20 + di =3D 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); + } +=20 + /* Regular TYPE_DEVICE houskeeping */ + DB_PRINT_NP(0, "Short naming node: %s\n", short_name); + (DEVICE(dev))->id =3D g_strdup(short_name); +=20 + 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); + } +=20 + object_property_set_bool(OBJECT(dev), "realized", true, &erro= r_fatal); + qemu_register_reset((void (*)(void *))dc->legacy_reset, dev); + } + } +=20 + if (object_dynamic_cast(dev, TYPE_SYS_BUS_DEVICE) || + object_dynamic_cast(dev, TYPE_FDT_GENERIC_MMAP)) { + FDTGenericRegPropInfo reg =3D {0}; + char parent_path[DT_PATH_LENGTH]; + int cell_idx =3D 0; + bool extended =3D true; +=20 + qemu_fdt_getprop_cell(fdti->fdt, node_path, "reg-extended", 0, fa= lse, + &errp); + if (errp) { + error_free(errp); + errp =3D NULL; + extended =3D false; + qemu_devtree_getparent(fdti->fdt, parent_path, node_path); + } +=20 + for (reg.n =3D 0;; reg.n++) { + char ph_parent[DT_PATH_LENGTH]; + const char *pnp =3D parent_path; +=20 + reg.parents =3D g_renew(Object *, reg.parents, reg.n + 1); + reg.parents[reg.n] =3D parent; +=20 + if (extended) { + int p_ph =3D qemu_fdt_getprop_cell(fdti->fdt, node_path, + "reg-extended", cell_idx= ++, + false, &errp); + if (errp) { + error_free(errp); + errp =3D 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] =3D fdt_init_get_opaque(fdti, ph_paren= t); + pnp =3D ph_parent; + } +=20 + for (i =3D 0; i < FDT_GENERIC_REG_TUPLE_LENGTH; ++i) { + const char *size_prop_name =3D fdt_generic_reg_size_prop_= names[i]; + int nc =3D qemu_fdt_getprop_cell(fdti->fdt, pnp, size_pro= p_name, + 0, true, &errp); +=20 + if (errp) { + int size_default =3D fdt_generic_reg_cells_defaults[i= ]; +=20 + DB_PRINT_NP(0, "WARNING: no %s for %s container, assu= ming " + "default of %d\n", size_prop_name, pnp, + size_default); + nc =3D size_default; + error_free(errp); + errp =3D NULL; + } +=20 + reg.x[i] =3D g_renew(uint64_t, reg.x[i], reg.n + 1); + reg.x[i][reg.n] =3D nc ? + qemu_fdt_getprop_sized_cell(fdti->fdt, node_path, + extended ? "reg-extended" + : "reg", + cell_idx, nc, &errp) + : 0; + cell_idx +=3D nc; + if (errp) { + goto exit_reg_parse; + } + } + } + exit_reg_parse: +=20 + if (object_dynamic_cast(dev, TYPE_FDT_GENERIC_MMAP)) { + FDTGenericMMapClass *fmc =3D 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); + } + } + } + } +=20 + if (object_dynamic_cast(dev, TYPE_SYS_BUS_DEVICE)) { + { + int len; + fdt_get_property(fdti->fdt, fdt_path_offset(fdti->fdt, node_p= ath), + "interrupt-controller", &len); + is_intc =3D len >=3D 0; + DB_PRINT_NP(is_intc ? 0 : 1, "is interrupt controller: %c\n", + is_intc ? 'y' : 'n'); + } + /* connect irq */ + j =3D 0; + for (i =3D 0;; i++) { + char irq_info[6 * 1024]; + char *irq_info_p =3D irq_info; + bool map_mode; + int len =3D -1; + qemu_irq *irqs =3D fdt_get_irq_info(fdti, node_path, i, irq_i= nfo, + &map_mode); + /* INTCs inferr their top level, if no IRQ connection specifi= ed */ + fdt_get_property(fdti->fdt, fdt_path_offset(fdti->fdt, node_p= ath), + "interrupts-extended", &len); + if (!irqs && is_intc && i =3D=3D 0 && len <=3D 0) { + FDTGenericIntc *id =3D (FDTGenericIntc *)object_dynamic_c= ast( + dev, TYPE_FDT_GENERIC_INTC); + FDTGenericIntcClass *idc =3D FDT_GENERIC_INTC_GET_CLASS(i= d); + 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 =3D NULL; + idc->auto_parent(id, &err); + } else { + irqs =3D fdti->irq_base; + } + } + if (!irqs) { + break; + } + while (*irqs) { + FDTIRQConnection *irq =3D g_new0(FDTIRQConnection, 1); + *irq =3D (FDTIRQConnection) { + .dev =3D DEVICE(dev), + .name =3D SYSBUS_DEVICE_GPIO_IRQ, + .merge_fn =3D qemu_irq_shared_or_handler, + .i =3D j, + .irq =3D *irqs, + .sink_info =3D g_strdup(irq_info_p), + .next =3D fdti->irqs + }; + if (!map_mode) { + j++; + } else { + irq_info_p +=3D strlen(irq_info_p) + 1; + } + fdti->irqs =3D irq; + irqs++; + } + if (map_mode) { + j++; + } + } + } +=20 + if (object_dynamic_cast(dev, TYPE_FDT_GENERIC_GPIO)) { + fggc =3D FDT_GENERIC_GPIO_GET_CLASS(dev); + gpio_set =3D fggc->client_gpios; +=20 + /* + * 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 =3D 0; + const FDTGenericGPIOSet *p_gpio; +=20 + for (p_gpio =3D gpio_set; p_gpio->names; p_gpio++) { + assert(gpio_cnt < ARRAY_SIZE(tmp_gpio_set)); + tmp_gpio_set[gpio_cnt] =3D *p_gpio; + gpio_cnt++; + } +=20 + for (p_gpio =3D default_gpio_sets; p_gpio->names; p_gpio++) { + assert(gpio_cnt < ARRAY_SIZE(tmp_gpio_set)); + tmp_gpio_set[gpio_cnt] =3D *p_gpio; + gpio_cnt++; + } +=20 + gpio_set =3D tmp_gpio_set; + } + } +=20 + if (!gpio_set) { + gpio_set =3D default_gpio_sets; + } +=20 + for (; object_dynamic_cast(dev, TYPE_DEVICE) && gpio_set->names; + gpio_set++) { + bool end =3D false; + int cur_cell =3D 0; +=20 + for (i =3D 0; !end; i++) { + char *debug_success; + const FDTGenericGPIOConnection *c =3D gpio_set->gpios; + const char *gpio_name =3D NULL; + uint16_t named_idx =3D 0; + qemu_irq input, output; + memset(&input, 0, sizeof(input)); +=20 + if (c) { + uint16_t range =3D c->range ? c->range : 1; + while (((c->fdt_index > i) || ((c->fdt_index + range) <= =3D i)) + && c->name) { + c++; + range =3D c->range ? c->range : 1; + } + named_idx =3D i - c->fdt_index; + gpio_name =3D c->name; + } + if (!gpio_name) { + const char *names_propname =3D gpio_set->names->names_pro= pname; + gpio_name =3D qemu_fdt_getprop_string(fdti->fdt, node_pat= h, + names_propname, i, fa= lse, + NULL); + } + if (!gpio_name) { + input =3D qdev_get_gpio_in(DEVICE(dev), i); + } else { + input =3D qdev_get_gpio_in_named(DEVICE(dev), gpio_name, + named_idx); + } + debug_success =3D g_strdup_printf("Wiring GPIO input %s[%" PR= Id16 "] " + "to", gpio_name, named_idx); + output =3D fdt_get_gpio(fdti, node_path, &cur_cell, input, gp= io_set, + debug_success, &end); + g_free(debug_success); + if (output) { + FDTIRQConnection *irq =3D g_new0(FDTIRQConnection, 1); + *irq =3D (FDTIRQConnection) { + .dev =3D DEVICE(dev), + .name =3D gpio_name, + .merge_fn =3D qemu_irq_shared_or_handler, + .i =3D named_idx, + .irq =3D output, + .sink_info =3D NULL, /*FIXME */ + .next =3D fdti->irqs + }; + fdti->irqs =3D irq; + DB_PRINT_NP(1, "... GPIO output %s[%" PRId16 "]\n", gpio_= name, + named_idx); + } + } + } +=20 + if (dev_type) { + g_free(dev_type); + } +=20 + return 0; + } +=20 + static const TypeInfo fdt_generic_intc_info =3D { + .name =3D TYPE_FDT_GENERIC_INTC, + .parent =3D TYPE_INTERFACE, + .class_size =3D sizeof(FDTGenericIntcClass), + }; +=20 + static const TypeInfo fdt_generic_mmap_info =3D { + .name =3D TYPE_FDT_GENERIC_MMAP, + .parent =3D TYPE_INTERFACE, + .class_size =3D sizeof(FDTGenericMMapClass), + }; +=20 + static const TypeInfo fdt_generic_gpio_info =3D { + .name =3D TYPE_FDT_GENERIC_GPIO, + .parent =3D TYPE_INTERFACE, + .class_size =3D sizeof(FDTGenericGPIOClass), + }; +=20 + static const TypeInfo fdt_generic_props_info =3D { + .name =3D TYPE_FDT_GENERIC_PROPS, + .parent =3D TYPE_INTERFACE, + .class_size =3D sizeof(FDTGenericPropsClass), + }; +=20 + static const TypeInfo fdt_generic_helper_info =3D { + .name =3D TYPE_FDT_GENERIC_HELPER, + .parent =3D TYPE_INTERFACE, + .class_size =3D sizeof(FDTGenericHelperClass), + }; +=20 + 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); + } +=20 + type_init(fdt_generic_intc_register_types) +=20 \ 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 *v= alue, Error **errp) ms->dtb =3D g_strdup(value); } =20 +static char *machine_get_hw_dtb(Object *obj, Error **errp) +{ + MachineState *ms =3D MACHINE(obj); + + return g_strdup(ms->hw_dtb); +} + +static void machine_set_hw_dtb(Object *obj, const char *value, Error **err= p) +{ + MachineState *ms =3D MACHINE(obj); + + ms->hw_dtb =3D g_strdup(value); +} + static char *machine_get_dumpdtb(Object *obj, Error **errp) { MachineState *ms =3D MACHINE(obj); @@ -1284,6 +1298,11 @@ static void machine_initfn(Object *obj) ms->ram_size =3D mc->default_ram_size; ms->maxram_size =3D mc->default_ram_size; =20 + 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 =3D 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 =20 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 { =20 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 compatib= ility + * 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 st= ate + * 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 *opaq= ue); +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 *clus= ter); + +/* 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_devi= ces.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 *opa= que); + +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 retri= eved + */ + +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 informati= on + * 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_INT= C) +#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-t= uple + * can describe more than one connection. So populates an array with a= ll + * relevant IRQs. + * + * @obj - interrupt controller to get irqs input for ("interrupt-paren= t") + * @irqs - array to populate with irqs (must be >=3D @max length + * @cells - interrupt cells values. Must be >=3D 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 e= rror + * (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 inter= rupt + * controller to implement its own auto-connections. Is called if an + * interrupt controller itself (detected via "interrupt-controller") h= as 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_MMA= P) +#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 l= ength + * 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_GPI= O) +#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 = =3D { + .propname =3D "power-gpios", + .cells_propname =3D "#gpio-cells", + .names_propname =3D "power-gpio-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_reset_gpio = =3D { + .propname =3D "reset-gpios", + .cells_propname =3D "#gpio-cells", + .names_propname =3D "reset-gpio-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_resets =3D { + .propname =3D "resets", + .cells_propname =3D "#reset-cells", + .names_propname =3D "reset-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_gpio =3D { + .propname =3D "gpios", + .cells_propname =3D "#gpio-cells", + .names_propname =3D "gpio-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_clock =3D { + .propname =3D "clocks", + .cells_propname =3D "#clock-cells", + .names_propname =3D "clock-names", +}; + +static const FDTGenericGPIONameSet fdt_generic_gpio_name_set_interrupts = =3D { + .propname =3D "interrupts-extended", + .cells_propname =3D "#interrupt-cells", + .names_propname =3D "interrupt-names", +}; + +static const FDTGenericGPIOSet default_gpio_sets [] =3D { + { .names =3D &fdt_generic_gpio_name_set_gpio }, + { + .names =3D &fdt_generic_gpio_name_set_reset_gpio, + .gpios =3D (FDTGenericGPIOConnection[]) { + { .name =3D "rst_cntrl", .fdt_index =3D 0, .range =3D 6 }, + }, + }, + { + .names =3D &fdt_generic_gpio_name_set_resets, + .gpios =3D (FDTGenericGPIOConnection[]) { + { .name =3D "rst_cntrl", .fdt_index =3D 0, .range =3D 6 }, + }, + }, + { + .names =3D &fdt_generic_gpio_name_set_power_gpio, + .gpios =3D (FDTGenericGPIOConnection[]) { + { .name =3D "pwr_cntrl", .fdt_index =3D 0, .range =3D 1 }, + }, + }, + { .names =3D &fdt_generic_gpio_name_set_clock }, + { .names =3D &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 defi= ne + * 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 l= ist + * with .name =3D "foo' and fdt_index =3D 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. wit= h the + * example above, gpio "foo" would bt the first gpio defined for the d= evice. + */ + + 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 =3D (_bitmask), \ .set_default =3D false) =20 +#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; }; =20 +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) =20 +/* device entries */ +#define LOG_FDT (1 << 24) + /* Lock/unlock output. */ =20 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, in= t 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); =20 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 *n= ode_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 fr= om - * 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); \ }) =20 +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 de= pth); +char **qemu_devtree_get_children(void *fdt, const char *node_path, int dep= th); +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 phand= le); +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 ind= ividual + * 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 =20 /** * 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 =3D QSIMPLEQ_HEAD_INITIALIZER= (nd_queue); =20 static GHashTable *nic_model_help; =20 -static int nb_nics; -static NICInfo nd_table[MAX_NICS]; +//static int nb_nics; +//static NICInfo nd_table[MAX_NICS]; =20 /***********************************************************/ /* 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) =20 ERST =20 +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 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" =20 #include =20 @@ -428,8 +429,9 @@ int qemu_fdt_setprop_string_array(void *fdt, const char= *node_path, return ret; } =20 -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 =3D 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, err= p); + } 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); } =20 -uint32_t qemu_fdt_getprop_cell(void *fdt, const char *node_path, - const char *property, int *lenp, Error **er= rp) +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=3D NULL; =20 - if (!lenp) { - lenp =3D &len; + if (!errp) { + errp =3D &err; + } + + prop =3D qemu_fdt_getprop(fdt, node_path, property, &len, inherit, err= p); + if (*errp) { + return NULL; + } + while (cell) { + void *term =3D 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 =3D term - prop + 1; + len -=3D diff; + assert(len >=3D 0); + prop +=3D 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 =3D 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 =3D qemu_fdt_getprop(fdt, node_path, property, &len, + inherit, errp); + if (errp && *errp) { return 0; - } else if (*lenp !=3D 4) { + } + if (len < (offset+1)*4) { error_setg(errp, "%s: %s/%s not 4 bytes long (not a cell?)", __func__, node_path, property); - *lenp =3D -EINVAL; return 0; } - return be32_to_cpu(*p); + ret =3D 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 =3D 0; + for (;size ;size--) { + ret <<=3D 32; + ret |=3D 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 =3D fdt_get_phandle(fdt, findnode_nofail(fdt, path)); + if (r =3D=3D 0) { + qemu_log("%s: Couldn't find phandle for %s: %s", __func__, + path, fdt_strerror(r)); + } + + return r; } =20 uint32_t qemu_fdt_get_phandle(void *fdt, const char *path) @@ -633,6 +713,277 @@ out: return ret; } =20 +char *qemu_devtree_get_node_name(void *fdt, const char *node_path) +{ + const char *ret =3D 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 =3D fdt_path_offset(fdt, node_path); + int ret =3D 0; + + for (offset =3D fdt_first_property_offset(fdt, offset); + offset !=3D -FDT_ERR_NOTFOUND; + offset =3D 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 =3D g_new0(QEMUDevtreeProp, + qemu_devtree_num_props(fdt, node_path)= + 1); + int offset =3D fdt_path_offset(fdt, node_path); + int i =3D 0; + + for (offset =3D fdt_first_property_offset(fdt, offset); + offset !=3D -FDT_ERR_NOTFOUND; + offset =3D fdt_next_property_offset(fdt, offset)) { + const char *propname; + const void *val =3D fdt_getprop_by_offset(fdt, offset, &propname, + &ret[i].len); + + ret[i].name =3D g_strdup(propname); + ret[i].value =3D 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 =3D fdt_path_offset(fdt, node_path); + int root_depth =3D fdt_node_depth(fdt, offset); + int cur_depth =3D root_depth; + + if (num) { + *num =3D 0; + } + for (;;) { + offset =3D fdt_next_node(fdt, offset, &cur_depth); + if (cur_depth <=3D root_depth) { + break; + } + if (cur_depth <=3D root_depth + depth || depth =3D=3D 0) { + if (returned_paths) { + returned_paths[*num] =3D g_malloc0(DT_PATH_LENGTH); + fdt_get_path(fdt, offset, returned_paths[*num], DT_PATH_LE= NGTH); + } + if (num) { + (*num)++; + } + } + } +} + +char **qemu_devtree_get_children(void *fdt, const char *node_path, int dep= th) +{ + int num_children =3D qemu_devtree_get_num_children(fdt, node_path, dep= th); + char **ret =3D 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 de= pth) +{ + 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 =3D 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 =3D 0; + char *name =3D NULL; + + do { + char *at; + + offset =3D fdt_next_node(fdt, offset, NULL); + name =3D (void *)fdt_get_name(fdt, offset, NULL); + if (!name) { + continue; + } + at =3D 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 =3D 0; + char *name =3D NULL; + uint32_t n =3D 0; + char node_p[DT_PATH_LENGTH]; + char **node_path =3D NULL; + + do { + char *at; + + offset =3D fdt_next_node(fdt, offset, NULL); + name =3D (void *)fdt_get_name(fdt, offset, NULL); + + if (!name) { + continue; + } + + at =3D memchr(name, '@', strlen(name)); + if (!strncmp(name, cmpname, at ? at - name : strlen(name))) { + if (fdt_get_path(fdt, offset, node_p, DT_PATH_LENGTH) >=3D 0) { + if (node_path =3D=3D NULL) { + node_path =3D (char **) g_new(char *, 1); + } else { + node_path =3D (char **) g_renew(char *, *node_path, n); + } + node_path[n] =3D g_strdup(node_p); + n++; + } + } + } while (offset > 0); + + *array =3D node_path; + return n; +} + +char *qemu_devtree_get_child_by_name(void *fdt, char *parent_path, + const char *cmpname) +{ + int offset =3D 0; + int parent_offset; + int namelen =3D strlen(cmpname); + char child_path[DT_PATH_LENGTH]; + + parent_offset =3D fdt_path_offset(fdt, parent_path); + + if (parent_offset > 0) { + offset =3D fdt_subnode_offset_namelen(fdt, parent_offset, + cmpname, namelen); + if (fdt_get_path(fdt, offset, child_path, DT_PATH_LENGTH) =3D=3D 0= ) { + return g_strdup(child_path); + } + } + + return NULL; +} + +int qemu_devtree_get_node_by_phandle(void *fdt, char *node_path, int phand= le) +{ + 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 =3D fdt_path_offset(fdt, current); + int parent_offset =3D fdt_supernode_atdepth_offset(fdt, offset, + fdt_node_depth(fdt, offset) - 1, NULL); + + return parent_offset >=3D 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 =3D 0, offset =3D 0; + + if (num_nodes) { + *num_nodes =3D 0; + } + for (;;) { + offset =3D fdt_next_node(fdt, offset, &depth); + if (num_nodes) { + (*num_nodes)++; + } + if (offset <=3D 0 || depth <=3D 0) { + break; + } + + if (info_dump) { + char node_path[DT_PATH_LENGTH]; + char *all_compats =3D NULL; + int compat_len; + Error *errp =3D NULL; + + if (fdt_get_path(fdt, offset, node_path, DT_PATH_LENGTH)) { + sprintf(node_path, "(none)"); + } else { + all_compats =3D qemu_fdt_getprop(fdt, node_path, "compatib= le", + &compat_len, false, &errp); + } + if (!errp) { + char *i =3D all_compats; + for (;;) { + char *j =3D memchr(i, '\0', DT_PATH_LENGTH); + compat_len -=3D ((j+1)-i); + if (!compat_len) { + break; + } + *j =3D ' '; + i =3D 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 =3D 1; int vga_interface_type =3D 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; --=20 2.43.0 From nobody Sat Feb 7 03:28:36 2026 Delivered-To: importer@patchew.org Authentication-Results: mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass(p=none dis=none) header.from=gmail.com ARC-Seal: i=1; a=rsa-sha256; t=1765926483; cv=none; d=zohomail.com; s=zohoarc; b=lF78XkXRTukWCbkJDRR2lKnIPO/X70XDMB1BMgdKznFBlHfpHBwsMtNca0C3UOmiSOOTxkgZ0PXsSXS1dxJkl4Bx3Q6qnfMTTCwbRpJGBQrHi9KMagufhgOCwe4d7H2I2y39fORPxfM48xNAwluHRHUTHqVdcHxvF3W9iLcT7TM= ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=zohomail.com; s=zohoarc; t=1765926483; h=Content-Transfer-Encoding:Cc:Cc:Date:Date:From:From:In-Reply-To:List-Subscribe:List-Post:List-Id:List-Archive:List-Help:List-Unsubscribe:MIME-Version:Message-ID:References:Sender:Subject:Subject:To:To:Message-Id:Reply-To; bh=2OHRpKevWV4+Y8gDYYG+ZbSkYrLef3a5xPcEOQRGygI=; b=ltPKWeW+xyQbPy3j49p0asjYVGAPm9jtSPRQeN1/cls7q3gRKuIgQXEYrqHYZgtMf13ptCKccH07DSzu3/Y5e7n2Q1Gs130K4t3I2n9NehjNDGR/kwz2R17Td+alN0hN5fGmf+3o/Qc2RyNjvmVUWKYylJvCUGUi7eTUU+fbzOE= ARC-Authentication-Results: i=1; mx.zohomail.com; dkim=pass; spf=pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) smtp.mailfrom=qemu-devel-bounces+importer=patchew.org@nongnu.org; dmarc=pass header.from= (p=none dis=none) Return-Path: Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mx.zohomail.com with SMTPS id 1765926483026711.0181702196878; Tue, 16 Dec 2025 15:08:03 -0800 (PST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1vVe8V-0001i7-M4; Tue, 16 Dec 2025 18:07:20 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1vVd4T-0004kg-E3 for qemu-devel@nongnu.org; Tue, 16 Dec 2025 16:59:06 -0500 Received: from mail-ej1-x62b.google.com ([2a00:1450:4864:20::62b]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1vVd4M-0006ch-MT for qemu-devel@nongnu.org; Tue, 16 Dec 2025 16:59:05 -0500 Received: by mail-ej1-x62b.google.com with SMTP id a640c23a62f3a-b73161849e1so1178778566b.2 for ; Tue, 16 Dec 2025 13:58:57 -0800 (PST) Received: from thinkpad-t470s.. (93-140-170-188.adsl.net.t-com.hr. [93.140.170.188]) by smtp.googlemail.com with ESMTPSA id a640c23a62f3a-b7fe8a956a5sm420898166b.29.2025.12.16.13.58.53 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 16 Dec 2025 13:58:54 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1765922336; x=1766527136; darn=nongnu.org; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:from:to:cc:subject:date :message-id:reply-to; bh=2OHRpKevWV4+Y8gDYYG+ZbSkYrLef3a5xPcEOQRGygI=; b=nI01R3QtdWRh4f4K4eT06b4WAMn75hX3+3qW95eVdwu3UhEtJG56gETAI3LgfFMELx 7YBzeOe//YitlBiVSnU6KBC4S1yQkW4GgI29Cgh+Eb4XwVvBGjvOyapbQ81TJ0EsuBfj 3VxRV0wJlqlFWxDUk+TBQ6GmjZqNjkiGJcb7dAarMNDk8F1ltsqMmJPchBt8sYOLsNy4 08m7ObtIRBe2vPAJuiM2rZY6YzRhYgthyKXSow85tzkB1ZsOWNY/yOsFpm/uLAdoP1hH td4PMli/L+PGcGgTcvJWQ6/yezKeddN8zZhashCLLk0+uKQ2rlDQwqPD+dLK7ov23daz Daww== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1765922336; x=1766527136; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=2OHRpKevWV4+Y8gDYYG+ZbSkYrLef3a5xPcEOQRGygI=; b=uWD66mqrZT1aYPnR6iASjldrB3vUnB5x6CUfcOeLlDSZJWKTue/51GPV03wHr3PUJb fljy7XwTZeloOg2RqojFhYnArdr1wcsmc3IVmoOq8xN79Hg9o3kq9ovy239n2jds9JeI 37E+En81yIj/TRt8P07sZ1IOTMOyK3XR2TABhmQgBYboKX6SHfQeC4sZZaV02gcfWNBF jhPREv6dsOGWyqHedryrMaQERqTECLr2WQemIPy0dyO8RLGuN4sRY5EEdRg+W87ua/It STCqDaLBzNFqCAYPXF0lDEIlb+mLel/MtyjNOtHFlA0W0CvSW5gSf9nczpV1vV2ORPRO XjVA== X-Gm-Message-State: AOJu0Yzl/roHLmcfrMEZOK5ftA5v2hsEXvNodQNzTU4Mel/PZBISAH6H dySakWlUUaXcSvJs/WGh0Lt0/A/gTu78VsC/3I0yojORqh3ZNZOc0DMUzJXu2KoH X-Gm-Gg: AY/fxX4931S8RxkVxaeu9bm7xbmOFT6yy+jEYYmpNItGMBubB/5pudAi55B9H1JQqjf 4Uv4izhX/UharNUgBrGMW2/1LF8fA1T/ryIiTGAGYDQv6PfHTLm/tQCD0YYR/7VR8ru/3R7P1Uv /d25BJ4TO7SOp372eHbUJ7SvpDbdUoVOAdFdVb35KJe4hxyc9bXvm2QOuhaN8lDyst5qHz8cH+h oCVwq0VHGiDX0AN0bwqDHD09b7WeLTZDsty5y8swzFh4sk1FyPfxwz5gNgGPWkM0WP4aZO4JcpM fqnLJ10GAtWoR8qTjaxc93pjIQhuDUB1Nniq1ojJGwRFHvAPnPBxpxxz6RElrbpN8KxrnYpkv0F TH2L+vAa9RdWHN+1z8WPmmwK2tcG/eVFfO4WMyHjkzG4OWUMoUtpv4tEH9YcNhZqjouGIXSRqg+ 2GbJIrd75BtZVRtXYNNl9V7vZ0OVyGIYhdTYHAFSSs1M5TmwJLWjPdVDOv5P61ZA== X-Google-Smtp-Source: AGHT+IHl9jyQ0uwjkGYGgjzfRdvF53GFQbFCOJD7aGmR2jwJ/fx8GiorGOAU2m2G5CdDwJ8pJxfkPQ== X-Received: by 2002:a17:907:940d:b0:b76:7fe7:ff37 with SMTP id a640c23a62f3a-b7d23663374mr1764025066b.18.1765922335383; Tue, 16 Dec 2025 13:58:55 -0800 (PST) From: ruslichenko.r@gmail.com X-Google-Original-From: Ruslan_Ruslichenko@epam.com To: qemu-devel@nongnu.org Cc: Ruslan_Ruslichenko@epam.com, takahiro.nakata.wr@renesas.com, edgar.iglesias@amd.com, peter.maydell@linaro.org, volodymyr_babchuk@epam.com, artem_mygaiev@epam.com Subject: [PATCH RFC 2/2] hw/core: add remote-port protocol implementation Date: Tue, 16 Dec 2025 22:58:35 +0100 Message-ID: <20251216215835.208036-3-Ruslan_Ruslichenko@epam.com> X-Mailer: git-send-email 2.43.0 In-Reply-To: <20251216215835.208036-1-Ruslan_Ruslichenko@epam.com> References: <20251216215835.208036-1-Ruslan_Ruslichenko@epam.com> MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Received-SPF: pass (zohomail.com: domain of gnu.org designates 209.51.188.17 as permitted sender) client-ip=209.51.188.17; envelope-from=qemu-devel-bounces+importer=patchew.org@nongnu.org; helo=lists.gnu.org; Received-SPF: pass client-ip=2a00:1450:4864:20::62b; envelope-from=ruslichenko.r@gmail.com; helo=mail-ej1-x62b.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Tue, 16 Dec 2025 18:07:18 -0500 X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+importer=patchew.org@nongnu.org Sender: qemu-devel-bounces+importer=patchew.org@nongnu.org X-ZohoMail-DKIM: pass (identity @gmail.com) X-ZM-MESSAGEID: 1765926484591158500 Content-Type: text/plain; charset="utf-8" From: Ruslan Ruslichenko Remote Port protocol is used for HW simulation, which allows QEMU to communicate with external simulators such as SystemC. The implementation was long available as downstream patches, for example within https://github.com/Xilinx/qemu repo. For more details regarding the protocol and simulation mechanism, following document is available: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/862421112/Co-simulati= on Signed-off-by: Ruslan Ruslichenko --- chardev/char-fe.c | 9 + hw/core/fdt_generic_util.c | 103 +++ hw/core/meson.build | 7 + hw/core/qdev-properties.c | 5 + hw/core/remote-port-ats.c | 405 ++++++++++ hw/core/remote-port-gpio.c | 199 +++++ hw/core/remote-port-memory-master.c | 402 ++++++++++ hw/core/remote-port-memory-slave.c | 257 +++++++ hw/core/remote-port-proto.c | 523 +++++++++++++ hw/core/remote-port-stream.c | 233 ++++++ hw/core/remote-port.c | 986 +++++++++++++++++++++++++ hw/core/trace-events | 17 + hw/misc/meson.build | 1 + hw/misc/xlnx-serbs.c | 43 ++ include/chardev/char-fe.h | 2 + include/chardev/char.h | 2 + include/hw/misc/xlnx-serbs.h | 36 + include/hw/qdev-properties.h | 2 + include/hw/remote-port-ats.h | 74 ++ include/hw/remote-port-device.h | 56 ++ include/hw/remote-port-gpio.h | 32 + include/hw/remote-port-memory-master.h | 62 ++ include/hw/remote-port-memory-slave.h | 33 + include/hw/remote-port-proto.h | 537 ++++++++++++++ include/hw/remote-port.h | 149 ++++ include/system/dma.h | 19 + include/system/memory.h | 50 ++ include/system/system.h | 2 + qemu-options.hx | 21 + system/physmem.c | 57 ++ system/vl.c | 5 + 31 files changed, 4329 insertions(+) create mode 100644 hw/core/remote-port-ats.c create mode 100644 hw/core/remote-port-gpio.c create mode 100644 hw/core/remote-port-memory-master.c create mode 100644 hw/core/remote-port-memory-slave.c create mode 100644 hw/core/remote-port-proto.c create mode 100644 hw/core/remote-port-stream.c create mode 100644 hw/core/remote-port.c create mode 100644 hw/misc/xlnx-serbs.c create mode 100644 include/hw/misc/xlnx-serbs.h create mode 100644 include/hw/remote-port-ats.h create mode 100644 include/hw/remote-port-device.h create mode 100644 include/hw/remote-port-gpio.h create mode 100644 include/hw/remote-port-memory-master.h create mode 100644 include/hw/remote-port-memory-slave.h create mode 100644 include/hw/remote-port-proto.h create mode 100644 include/hw/remote-port.h diff --git a/chardev/char-fe.c b/chardev/char-fe.c index 34b83fc1c4..bb197d61df 100644 --- a/chardev/char-fe.c +++ b/chardev/char-fe.c @@ -346,6 +346,15 @@ void qemu_chr_fe_set_open(CharFrontend *c, bool is_ope= n) } } =20 +void qemu_chr_fe_set_blocking(CharFrontend *be, bool blocking) +{ + Chardev *chr =3D be->chr; + + if (chr && CHARDEV_GET_CLASS(chr)->chr_set_blocking) { + CHARDEV_GET_CLASS(chr)->chr_set_blocking(chr, blocking); + } +} + guint qemu_chr_fe_add_watch(CharFrontend *c, GIOCondition cond, FEWatchFunc func, void *user_data) { diff --git a/hw/core/fdt_generic_util.c b/hw/core/fdt_generic_util.c index 8af3cabe51..2777ed891a 100644 --- a/hw/core/fdt_generic_util.c +++ b/hw/core/fdt_generic_util.c @@ -68,6 +68,9 @@ } \ } while (0); =20 + #include "hw/remote-port-device.h" + #include "hw/remote-port.h" +=20 /* FIXME: wrap direct calls into libfdt */ =20 #include @@ -1446,6 +1449,17 @@ static ObjectProperty *fdt_array_link_elem_prop(Obje= ct *obj, ObjectProperty *e, } fdt_init_set_opaque(fdti, node_path, dev); =20 + /* Set the default sync-quantum based on the global one. Node propert= ies + * in the dtb can later override this value. */ + if (global_sync_quantum) { + ObjectProperty *p; +=20 + p =3D object_property_find(OBJECT(dev), "sync-quantum"); + if (p) { + object_property_set_int(OBJECT(dev), "sync-quantum", global_s= ync_quantum, &errp); + } + } +=20 /* Call FDT Generic hooks for overriding prop default values. */ if (object_dynamic_cast(dev, TYPE_FDT_GENERIC_PROPS)) { FDTGenericPropsClass *k =3D FDT_GENERIC_PROPS_GET_CLASS(dev); @@ -1512,6 +1526,73 @@ static ObjectProperty *fdt_array_link_elem_prop(Obje= ct *obj, ObjectProperty *e, fdt_init_qdev_scalar_prop(OBJECT(dev), p, fdti, node_path, prop); } =20 + /* FIXME: not pretty, but is half a sane dts binding */ + if (object_dynamic_cast(dev, TYPE_REMOTE_PORT_DEVICE)) { + //int i; +=20 + for (i =3D 0;;++i) { + char adaptor_node_path[DT_PATH_LENGTH]; + uint32_t adaptor_phandle, chan; + DeviceState *adaptor; + char *name; +=20 + adaptor_phandle =3D qemu_fdt_getprop_cell(fdti->fdt, node_pat= h, + "remote-ports", + 2 * i, false, &errp); + if (errp) { + DB_PRINT_NP(1, "cant get phandle from \"remote-ports\" " + "property\n"); + break; + } + if (qemu_devtree_get_node_by_phandle(fdti->fdt, adaptor_node_= path, + adaptor_phandle)) { + DB_PRINT_NP(1, "cant get node from phandle\n"); + break; + } + while (!fdt_init_has_opaque(fdti, adaptor_node_path)) { + fdt_init_yield(fdti); + } + adaptor =3D DEVICE(fdt_init_get_opaque(fdti, adaptor_node_pat= h)); + name =3D g_strdup_printf("rp-adaptor%" PRId32, i); + object_property_set_link(OBJECT(dev), name, OBJECT(adaptor), = &errp); + DB_PRINT_NP(0, "connecting RP to adaptor %s channel %d", + object_get_canonical_path(OBJECT(adaptor)), i); + g_free(name); + if (errp) { + DB_PRINT_NP(1, "cant set adaptor link for device property= \n"); + break; + } +=20 + chan =3D qemu_fdt_getprop_cell(fdti->fdt, node_path, "remote-= ports", + 2 * i + 1, false, &errp); + if (errp) { + DB_PRINT_NP(1, "cant get channel from \"remote-ports\" " + "property\n"); + break; + } +=20 + name =3D g_strdup_printf("rp-chan%" PRId32, i); + object_property_set_int(OBJECT(dev), name, chan, &errp); + /* Not critical - device has right to not care about channel + * numbers if its a pure slave (only responses). + */ + if (errp) { + DB_PRINT_NP(1, "cant set %s property %s\n", name, error_g= et_pretty(errp)); + errp =3D NULL; + } + g_free(name); +=20 + name =3D g_strdup_printf("remote-port-dev%d", chan); + object_property_set_link(OBJECT(adaptor), name, OBJECT(dev), = &errp); + g_free(name); + if (errp) { + DB_PRINT_NP(1, "cant set device link for adaptor\n"); + break; + } + } + errp =3D NULL; + } +=20 if (object_dynamic_cast(dev, TYPE_DEVICE)) { DeviceClass *dc =3D DEVICE_GET_CLASS(dev); /* connect nic if appropriate */ @@ -1527,6 +1608,28 @@ static ObjectProperty *fdt_array_link_elem_prop(Obje= ct *obj, ObjectProperty *e, nics++; } =20 + /* We don't want to connect remote port chardev's to the user fac= ing + * serial devices. + */ + if (!object_dynamic_cast(dev, TYPE_REMOTE_PORT)) { + /* Connect chardev if we can */ + if (serial_hd(fdt_serial_ports)) { + Chardev *value =3D (Chardev*) serial_hd(fdt_serial_ports); + char *chardev; +=20 + /* Check if the device already has a chardev. */ + chardev =3D object_property_get_str(dev, "chardev", &errp= ); + if (!errp && !strcmp(chardev, "")) { + object_property_set_str(dev, "chardev", value->label,= &errp); + if (!errp) { + /* It worked, the device is a charecter device */ + fdt_serial_ports++; + } + } + errp =3D NULL; + } + } +=20 /* We also need to externally connect drives. Let's try to do that * here. */ diff --git a/hw/core/meson.build b/hw/core/meson.build index cea3b4660e..84171ef649 100644 --- a/hw/core/meson.build +++ b/hw/core/meson.build @@ -48,6 +48,13 @@ system_ss.add(files( 'sysbus.c', 'vm-change-state-handler.c', 'clock-vmstate.c', + 'remote-port-proto.c', + 'remote-port.c', + 'remote-port-memory-master.c', + 'remote-port-memory-slave.c', + 'remote-port-gpio.c', + 'remote-port-stream.c', + 'remote-port-ats.c' )) user_ss.add(files( 'cpu-user.c', diff --git a/hw/core/qdev-properties.c b/hw/core/qdev-properties.c index 422a486969..b6d9c273c3 100644 --- a/hw/core/qdev-properties.c +++ b/hw/core/qdev-properties.c @@ -52,6 +52,11 @@ void qdev_prop_allow_set_link_before_realize(const Objec= t *obj, } } =20 +void qdev_prop_allow_set_link(const Object *obj, const char *name, + Object *val, Error **errp) +{ +} + void *object_field_prop_ptr(Object *obj, const Property *prop) { void *ptr =3D obj; diff --git a/hw/core/remote-port-ats.c b/hw/core/remote-port-ats.c new file mode 100644 index 0000000000..3b223b912a --- /dev/null +++ b/hw/core/remote-port-ats.c @@ -0,0 +1,405 @@ +/* + * QEMU remote port ATS + * + * Copyright (c) 2021 Xilinx Inc + * Written by Francisco Iglesias + * + * Permission is hereby granted, free of charge, to any person obtaining a= copy + * of this software and associated documentation files (the "Software"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * 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 OT= HER + * 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 "system/system.h" +#include "system/dma.h" +#include "qemu/log.h" +#include "qapi/qmp/qerror.h" +#include "qapi/error.h" +#include "hw/qdev-core.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "trace.h" + +#include "hw/remote-port-proto.h" +#include "hw/remote-port-device.h" +#include "hw/remote-port-ats.h" + +typedef struct ATSIOMMUNotifier { + IOMMUNotifier n; + MemoryRegion *mr; + RemotePortATS *rp_ats; + int iommu_idx; +} ATSIOMMUNotifier; + +IOMMUTLBEntry *rp_ats_cache_lookup_translation(RemotePortATSCache *cache, + hwaddr translated_addr, + hwaddr len) +{ + RemotePortATSCacheClass *c =3D REMOTE_PORT_ATS_CACHE_GET_CLASS(cache); + + return c->lookup_translation(cache, translated_addr, len); +} + +static IOMMUTLBEntry *rp_ats_lookup_translation(RemotePortATSCache *cache, + hwaddr translated_addr, + hwaddr len) +{ + RemotePortATS *s =3D REMOTE_PORT_ATS(cache); + + for (int i =3D 0; i < s->cache->len; i++) { + IOMMUTLBEntry *iotlb =3D g_array_index(s->cache, IOMMUTLBEntry *, = i); + hwaddr masked_start =3D (translated_addr & ~iotlb->addr_mask); + hwaddr masked_end =3D ((translated_addr + len - 1) & ~iotlb->addr_= mask); + + if (masked_start =3D=3D iotlb->translated_addr && + masked_end =3D=3D iotlb->translated_addr) { + return iotlb; + } + } + + return NULL; +} + +static void rp_ats_cache_remove(RemotePortATS *s, IOMMUTLBEntry *iotlb) +{ + for (int i =3D 0; i < s->cache->len; i++) { + IOMMUTLBEntry *tmp =3D g_array_index(s->cache, IOMMUTLBEntry *, i); + hwaddr masked_start =3D (tmp->iova & ~iotlb->addr_mask); + hwaddr masked_end =3D ((tmp->iova | tmp->addr_mask) & ~iotlb->addr= _mask); + + if (masked_start =3D=3D iotlb->iova || masked_end =3D=3D iotlb->io= va) { + g_array_remove_index_fast(s->cache, i); + } + } +} + +static void rp_ats_invalidate(RemotePortATS *s, IOMMUTLBEntry *iotlb) +{ + size_t pktlen =3D sizeof(struct rp_pkt_ats); + struct rp_pkt_ats pkt; + RemotePortRespSlot *rsp_slot; + RemotePortDynPkt *rsp; + size_t enclen; + int64_t clk; + uint32_t id; + hwaddr len =3D iotlb->addr_mask + 1; + + id =3D rp_new_id(s->rp); + clk =3D rp_normalized_vmclk(s->rp); + + enclen =3D rp_encode_ats_inv(id, s->rp_dev, + &pkt, + clk, + 0, + iotlb->iova, + len, + 0, + 0); + assert(enclen =3D=3D pktlen); + + rp_rsp_mutex_lock(s->rp); + rp_write(s->rp, (void *) &pkt, enclen); + + rsp_slot =3D rp_dev_wait_resp(s->rp, s->rp_dev, id); + rsp =3D &rsp_slot->rsp; + + /* We dont support out of order answers yet. */ + assert(rsp->pkt->hdr.id =3D=3D id); + + rp_resp_slot_done(s->rp, rsp_slot); + rp_rsp_mutex_unlock(s->rp); +} + +static void rp_ats_cache_insert(RemotePortATS *s, + hwaddr iova, + hwaddr translated_addr, + hwaddr mask, + AddressSpace *target_as) +{ + IOMMUTLBEntry *iotlb; + + /* + * Invalidate all current translations that collide with the new one a= nd + * does not have the same target_as. This means that translated_addres= ses + * towards the same addresses but in different target address spaces a= re + * not allowed. + */ + for (int i =3D 0; i < s->cache->len; i++) { + //IOMMUTLBEntry *iotlb =3D g_array_index(s->cache, IOMMUTLBEntry *= , i); + iotlb =3D g_array_index(s->cache, IOMMUTLBEntry *, i); + hwaddr masked_start =3D (translated_addr & ~iotlb->addr_mask); + hwaddr masked_end =3D ((translated_addr | mask) & ~iotlb->addr_mas= k); + bool spans_region =3D masked_start < iotlb->translated_addr && + masked_end > iotlb->translated_addr; + + if (masked_start =3D=3D iotlb->translated_addr || + masked_end =3D=3D iotlb->translated_addr || spans_region) { + hwaddr masked_iova_start; + hwaddr masked_iova_end; + + /* + * Invalidated & remove the mapping if the address range hit i= n the + * cache but the target_as is different. + */ + if (iotlb->target_as !=3D target_as) { + rp_ats_invalidate(s, iotlb); + g_array_remove_index_fast(s->cache, i); + continue; + } + + /* + * Remove duplicates with a smaller range length since the new + * mapping will span over it. + */ + masked_iova_start =3D (iova & ~iotlb->addr_mask); + masked_iova_end =3D ((iova | mask) & ~iotlb->addr_mask); + spans_region =3D masked_iova_start < iotlb->iova && + masked_iova_end > iotlb->iova; + + if (masked_iova_start =3D=3D iotlb->iova || + masked_iova_end =3D=3D iotlb->iova || spans_region) { + + if ((iotlb->addr_mask + 1) < (mask + 1)) { + g_array_remove_index_fast(s->cache, i); + } else { + /* + * The new mapping is smaller or equal in size and is = thus + * already cached. + */ + return; + } + } + } + } + + iotlb =3D g_new0(IOMMUTLBEntry, 1); + iotlb->iova =3D iova; + iotlb->translated_addr =3D translated_addr; + iotlb->addr_mask =3D mask; + iotlb->target_as =3D target_as; + g_array_append_val(s->cache, iotlb); +} + +static void rp_ats_iommu_unmap_notify(IOMMUNotifier *n, IOMMUTLBEntry *iot= lb) +{ + ATSIOMMUNotifier *notifier =3D container_of(n, ATSIOMMUNotifier, n); + RemotePortATS *s =3D notifier->rp_ats; + + rp_ats_invalidate(s, iotlb); + rp_ats_cache_remove(s, iotlb); +} + +static bool ats_translate_address(RemotePortATS *s, struct rp_pkt *pkt, + hwaddr *phys_addr, hwaddr *phys_len) +{ + MemTxAttrs attrs =3D MEMTXATTRS_UNSPECIFIED; + IOMMUMemoryRegion *iommu_mr; + AddressSpace *target_as; + MemoryRegion *mr; + int prot =3D 0; + + RCU_READ_LOCK_GUARD(); + + mr =3D ats_do_translate(&s->as, pkt->ats.addr, phys_addr, phys_len, + &target_as, &prot, attrs); + if (!mr) { + return false; + } + + iommu_mr =3D memory_region_get_iommu(mr); + if (iommu_mr) { + int iommu_idx =3D memory_region_iommu_attrs_to_index(iommu_mr, att= rs); + ATSIOMMUNotifier *notifier; + int i; + + for (i =3D 0; i < s->iommu_notifiers->len; i++) { + notifier =3D g_array_index(s->iommu_notifiers, ATSIOMMUNotifie= r *, i); + if (notifier->mr =3D=3D mr && notifier->iommu_idx =3D=3D iommu= _idx) { + break; + } + } + + /* Register a notifier if not found. */ + if (i =3D=3D s->iommu_notifiers->len) { + Error *err =3D NULL; + bool ret; + + s->iommu_notifiers =3D g_array_set_size(s->iommu_notifiers, i = + 1); + notifier =3D g_new0(ATSIOMMUNotifier, 1); + g_array_index(s->iommu_notifiers, ATSIOMMUNotifier *, i) =3D n= otifier; + + notifier->mr =3D mr; + notifier->iommu_idx =3D iommu_idx; + notifier->rp_ats =3D s; + + iommu_notifier_init(¬ifier->n, + rp_ats_iommu_unmap_notify, + IOMMU_NOTIFIER_UNMAP, + 0, + HWADDR_MAX, + iommu_idx); + + ret =3D memory_region_register_iommu_notifier(mr, ¬ifier->n= , &err); + if (ret) { + error_report_err(err); + exit(1); + } + } + } + + if (!(prot & IOMMU_RO)) { + pkt->ats.attributes &=3D ~(RP_ATS_ATTR_exec | RP_ATS_ATTR_read); + } + if (!(prot & IOMMU_WO)) { + pkt->ats.attributes &=3D ~(RP_ATS_ATTR_write); + } + + rp_ats_cache_insert(s, pkt->ats.addr, *phys_addr, *phys_len - 1, targe= t_as); + + return true; +} + +static void rp_ats_req(RemotePortDevice *dev, struct rp_pkt *pkt) +{ + RemotePortATS *s =3D REMOTE_PORT_ATS(dev); + size_t pktlen =3D sizeof(struct rp_pkt_ats); + hwaddr phys_addr =3D 0; + hwaddr phys_len =3D (hwaddr)(-1); + uint64_t result; + size_t enclen; + int64_t delay; + int64_t clk; + + assert(!(pkt->hdr.flags & RP_PKT_FLAGS_response)); + + rp_dpkt_alloc(&s->rsp, pktlen); + + result =3D ats_translate_address(s, pkt, &phys_addr, &phys_len) ? + RP_ATS_RESULT_ok : RP_ATS_RESULT_error; + + /* + * delay here could be set to the annotated cost of doing issuing + * these accesses. QEMU doesn't support this kind of annotations + * at the moment. So we just clear the delay. + */ + delay =3D 0; + clk =3D pkt->ats.timestamp + delay; + + enclen =3D rp_encode_ats_req(pkt->hdr.id, pkt->hdr.dev, + &s->rsp.pkt->ats, + clk, + pkt->ats.attributes, + phys_addr, + phys_len, + result, + pkt->hdr.flags | RP_PKT_FLAGS_response); + assert(enclen =3D=3D pktlen); + + rp_write(s->rp, (void *)s->rsp.pkt, enclen); +} + +static void rp_ats_realize(DeviceState *dev, Error **errp) +{ + RemotePortATS *s =3D REMOTE_PORT_ATS(dev); + + s->peer =3D rp_get_peer(s->rp); + address_space_init(&s->as, s->mr ? s->mr : get_system_memory(), "ats-a= s"); + + s->iommu_notifiers =3D g_array_new(false, true, sizeof(ATSIOMMUNotifie= r *)); + s->cache =3D g_array_new(false, true, sizeof(IOMMUTLBEntry *)); +} + +static void rp_ats_init(Object *obj) +{ + RemotePortATS *s =3D REMOTE_PORT_ATS(obj); + + object_property_add_link(obj, "rp-adaptor0", "remote-port", + (Object **)&s->rp, + qdev_prop_allow_set_link, + OBJ_PROP_LINK_STRONG); + object_property_add_link(obj, "mr", TYPE_MEMORY_REGION, + (Object **)&s->mr, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); +} + +static void rp_ats_unrealize(DeviceState *dev) +{ + RemotePortATS *s =3D REMOTE_PORT_ATS(dev); + ATSIOMMUNotifier *notifier; + int i; + + for (i =3D 0; i < s->iommu_notifiers->len; i++) { + notifier =3D g_array_index(s->iommu_notifiers, ATSIOMMUNotifier *,= i); + memory_region_unregister_iommu_notifier(notifier->mr, ¬ifier->n= ); + g_free(notifier); + } + g_array_free(s->iommu_notifiers, true); + + address_space_destroy(&s->as); + + for (i =3D 0; i < s->cache->len; i++) { + IOMMUTLBEntry *tmp =3D g_array_index(s->cache, IOMMUTLBEntry *, i); + g_free(tmp); + } + g_array_free(s->cache, true); +} + +static Property rp_properties[] =3D { + DEFINE_PROP_UINT32("rp-chan0", RemotePortATS, rp_dev, 0), +}; + +static void rp_ats_class_init(ObjectClass *oc, const void *data) +{ + RemotePortDeviceClass *rpdc =3D REMOTE_PORT_DEVICE_CLASS(oc); + RemotePortATSCacheClass *atscc =3D REMOTE_PORT_ATS_CACHE_CLASS(oc); + DeviceClass *dc =3D DEVICE_CLASS(oc); + + device_class_set_props_n(dc, rp_properties, ARRAY_SIZE(rp_properties)); + + rpdc->ops[RP_CMD_ats_req] =3D rp_ats_req; + dc->realize =3D rp_ats_realize; + dc->unrealize =3D rp_ats_unrealize; + atscc->lookup_translation =3D rp_ats_lookup_translation; +} + +static const TypeInfo rp_ats_info =3D { + .name =3D TYPE_REMOTE_PORT_ATS, + .parent =3D TYPE_DEVICE, + .instance_size =3D sizeof(RemotePortATS), + .instance_init =3D rp_ats_init, + .class_init =3D rp_ats_class_init, + .interfaces =3D (InterfaceInfo[]) { + { TYPE_REMOTE_PORT_DEVICE }, + { TYPE_REMOTE_PORT_ATS_CACHE }, + { }, + }, +}; + +static const TypeInfo rp_ats_cache_info =3D { + .name =3D TYPE_REMOTE_PORT_ATS_CACHE, + .parent =3D TYPE_INTERFACE, + .class_size =3D sizeof(RemotePortATSCacheClass), +}; + +static void rp_register_types(void) +{ + type_register_static(&rp_ats_cache_info); + type_register_static(&rp_ats_info); +} + +type_init(rp_register_types) diff --git a/hw/core/remote-port-gpio.c b/hw/core/remote-port-gpio.c new file mode 100644 index 0000000000..2de6570d01 --- /dev/null +++ b/hw/core/remote-port-gpio.c @@ -0,0 +1,199 @@ +/* + * Copyright (c) 2013 Xilinx Inc + * Written by Edgar E. Iglesias + * Written by Peter Crosthwaite + * + * This code is licensed under the GNU GPL. + */ + +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "system/system.h" +#include "system/dma.h" +#include "qemu/log.h" +#include "qapi/qmp/qerror.h" +#include "qapi/error.h" +#include "hw/qdev-core.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "trace.h" + +#include "hw/fdt_generic_util.h" + +#include "hw/remote-port.h" +#include "hw/remote-port-proto.h" +#include "hw/remote-port-device.h" +#include "hw/remote-port-gpio.h" + +#define CACHE_INVALID -1 + +static void rp_gpio_handler(void *opaque, int irq, int level) +{ + RemotePortGPIO *s =3D opaque; + struct rp_pkt pkt; + size_t len; + int64_t clk; + uint32_t id =3D rp_new_id(s->rp); + uint32_t flags =3D s->posted_updates ? RP_PKT_FLAGS_posted : 0; + + /* If we hit the cache, return early. */ + if (s->cache[irq] !=3D CACHE_INVALID && s->cache[irq] =3D=3D level) { + return; + } + /* Update the cache and update the remote peer. */ + s->cache[irq] =3D level; + + clk =3D rp_normalized_vmclk(s->rp); + len =3D rp_encode_interrupt_f(id, s->rp_dev, &pkt.interrupt, clk, + irq, 0, level, flags); + + trace_remote_port_gpio_tx_interrupt(id, flags, s->rp_dev, 0, irq, leve= l); + + if (s->peer->caps.wire_posted_updates && !s->posted_updates) { + rp_rsp_mutex_lock(s->rp); + } + + rp_write(s->rp, (void *)&pkt, len); + + /* If peer supports posted updates it will respect our flag and + * not respond. */ + if (s->peer->caps.wire_posted_updates && !s->posted_updates) { + RemotePortRespSlot *rsp_slot; + struct rp_pkt_interrupt *intr; + + rsp_slot =3D rp_dev_wait_resp(s->rp, s->rp_dev, id); + assert(rsp_slot->rsp.pkt->hdr.id =3D=3D id); + + intr =3D &rsp_slot->rsp.pkt->interrupt; + trace_remote_port_gpio_rx_interrupt(intr->hdr.id, intr->hdr.flags, + intr->hdr.dev, intr->vector, intr->line, intr->val); + + rp_resp_slot_done(s->rp, rsp_slot); + rp_rsp_mutex_unlock(s->rp); + } +} + +static void rp_gpio_interrupt(RemotePortDevice *rpdev, struct rp_pkt *pkt) +{ + RemotePortGPIO *s =3D REMOTE_PORT_GPIO(rpdev); + + trace_remote_port_gpio_rx_interrupt(pkt->hdr.id, pkt->hdr.flags, + pkt->hdr.dev, pkt->interrupt.vector, pkt->interrupt.line, + pkt->interrupt.val); + + qemu_set_irq(s->gpio_out[pkt->interrupt.line], pkt->interrupt.val); + + if (s->peer->caps.wire_posted_updates + && !(pkt->hdr.flags & RP_PKT_FLAGS_posted)) { + RemotePortDynPkt rsp =3D {0}; + size_t len; + + /* Need to reply. */ + rp_dpkt_alloc(&rsp, sizeof(struct rp_pkt_interrupt)); + len =3D rp_encode_interrupt_f(pkt->hdr.id, pkt->hdr.dev, + &rsp.pkt->interrupt, + pkt->interrupt.timestamp, + pkt->interrupt.line, + pkt->interrupt.vector, + pkt->interrupt.val, + pkt->hdr.flags | RP_PKT_FLAGS_response= ); + + trace_remote_port_gpio_tx_interrupt(pkt->hdr.id, + pkt->hdr.flags | RP_PKT_FLAGS_response, pkt->hdr.dev, + pkt->interrupt.vector, pkt->interrupt.line, pkt->interrupt.val= ); + + rp_write(s->rp, (void *)rsp.pkt, len); + } +} + +static void rp_gpio_reset(DeviceState *dev) +{ + RemotePortGPIO *s =3D REMOTE_PORT_GPIO(dev); + + /* Mark as invalid. */ + memset(s->cache, CACHE_INVALID, s->num_gpios); +} + +static void rp_gpio_realize(DeviceState *dev, Error **errp) +{ + RemotePortGPIO *s =3D REMOTE_PORT_GPIO(dev); + unsigned int i; + + s->peer =3D rp_get_peer(s->rp); + + s->gpio_out =3D g_new0(qemu_irq, s->num_gpios); + qdev_init_gpio_out(dev, s->gpio_out, s->num_gpios); + qdev_init_gpio_in(dev, rp_gpio_handler, s->num_gpios); + + for (i =3D 0; i < s->num_gpios; i++) { + sysbus_init_irq(SYS_BUS_DEVICE(s), &s->gpio_out[i]); + } +} + +static void rp_gpio_init(Object *obj) +{ + RemotePortGPIO *rpms =3D REMOTE_PORT_GPIO(obj); + + object_property_add_link(obj, "rp-adaptor0", "remote-port", + (Object **)&rpms->rp, + qdev_prop_allow_set_link, + OBJ_PROP_LINK_STRONG); +} + +static Property rp_properties[] =3D { + DEFINE_PROP_UINT32("rp-chan0", RemotePortGPIO, rp_dev, 0), + DEFINE_PROP_UINT32("num-gpios", RemotePortGPIO, num_gpios, 16), + DEFINE_PROP_UINT16("cell-offset-irq-num", RemotePortGPIO, + cell_offset_irq_num, 0), + DEFINE_PROP_BOOL("posted-updates", RemotePortGPIO, posted_updates, tru= e), +}; + +static int rp_fdt_get_irq(FDTGenericIntc *obj, qemu_irq *irqs, + uint32_t *cells, int ncells, int max, + Error **errp) +{ + RemotePortGPIO *s =3D REMOTE_PORT_GPIO(obj); + + if (cells[s->cell_offset_irq_num] >=3D s->num_gpios) { + error_setg(errp, "RP-GPIO was setup for %u interrupts: index %" + PRIu32 " requested", s->num_gpios, + cells[s->cell_offset_irq_num]); + return 0; + } + + (*irqs) =3D qdev_get_gpio_in(DEVICE(obj), cells[s->cell_offset_irq_num= ]); + return 1; +}; + +static void rp_gpio_class_init(ObjectClass *oc, const void *data) +{ + RemotePortDeviceClass *rpdc =3D REMOTE_PORT_DEVICE_CLASS(oc); + DeviceClass *dc =3D DEVICE_CLASS(oc); + FDTGenericIntcClass *fgic =3D FDT_GENERIC_INTC_CLASS(oc); + + rpdc->ops[RP_CMD_interrupt] =3D rp_gpio_interrupt; + dc->legacy_reset =3D rp_gpio_reset; + dc->realize =3D rp_gpio_realize; + device_class_set_props_n(dc, rp_properties, ARRAY_SIZE(rp_properties)); + fgic->get_irq =3D rp_fdt_get_irq; +} + +static const TypeInfo rp_info =3D { + .name =3D TYPE_REMOTE_PORT_GPIO, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RemotePortGPIO), + .instance_init =3D rp_gpio_init, + .class_init =3D rp_gpio_class_init, + .interfaces =3D (InterfaceInfo[]) { + { TYPE_REMOTE_PORT_DEVICE }, + { TYPE_FDT_GENERIC_INTC }, + { }, + }, +}; + +static void rp_register_types(void) +{ + type_register_static(&rp_info); +} + +type_init(rp_register_types) diff --git a/hw/core/remote-port-memory-master.c b/hw/core/remote-port-memo= ry-master.c new file mode 100644 index 0000000000..a71ab286f9 --- /dev/null +++ b/hw/core/remote-port-memory-master.c @@ -0,0 +1,402 @@ +/* + * QEMU remote port memory master. + * + * Copyright (c) 2014 Xilinx Inc + * Written by Edgar E. Iglesias + * + * This code is licensed under the GNU GPL. + */ + +#include "qemu/osdep.h" +#include "system/system.h" +#include "qemu/log.h" +#include "qapi/qmp/qerror.h" +#include "qapi/error.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "trace.h" + +#include "hw/remote-port-proto.h" +#include "hw/remote-port.h" +#include "hw/remote-port-device.h" +#include "hw/remote-port-memory-master.h" + +#include "hw/fdt_generic_util.h" + +#ifndef REMOTE_PORT_ERR_DEBUG +#define REMOTE_PORT_DEBUG_LEVEL 0 +#else +#define REMOTE_PORT_DEBUG_LEVEL 1 +#endif + +#define DB_PRINT_L(level, ...) do { \ + if (REMOTE_PORT_DEBUG_LEVEL > level) { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } \ +} while (0) + +#define REMOTE_PORT_MEMORY_MASTER_PARENT_CLASS \ + object_class_get_parent( \ + object_class_by_name(TYPE_REMOTE_PORT_MEMORY_MASTER)) + +#define RP_MAX_ACCESS_SIZE 4096 + +static void rp_mm_serbs_timer_config(xlnx_serbs_if *serbs, int id, int tim= ems, + bool enable) +{ + RemotePortMemoryMaster *s =3D REMOTE_PORT_MEMORY_MASTER(serbs); + + s->serbs_id =3D id; + s->rp_timeout =3D enable ? timems : 0; +} + +static void rp_mm_serbs_timout_set(xlnx_serbs_if *serbs, int id, bool leve= l) +{ + RemotePortMemoryMaster *s =3D REMOTE_PORT_MEMORY_MASTER(serbs); + + s->rp_timeout_err =3D level; +} + +static int rp_mm_get_timeout(MemoryTransaction *tr) +{ + RemotePortMap *map =3D tr->opaque; + RemotePortMemoryMaster *s; + + if (!map || !map->parent || + !object_dynamic_cast(OBJECT(map->parent), + TYPE_REMOTE_PORT_MEMORY_MASTER)) { + return 0; + } + s =3D REMOTE_PORT_MEMORY_MASTER(map->parent); + return s->rp_timeout; +} + +static bool rp_mm_timeout_err_state_get(MemoryTransaction *tr) +{ + RemotePortMap *map =3D tr->opaque; + RemotePortMemoryMaster *s; + + if (!map || !map->parent || + !object_dynamic_cast(OBJECT(map->parent), + TYPE_REMOTE_PORT_MEMORY_MASTER)) { + return false; + } + + s =3D REMOTE_PORT_MEMORY_MASTER(map->parent); + return s->rp_timeout_err; +} + +static void rp_mm_timeout_err_state_set(MemoryTransaction *tr, bool level) +{ + RemotePortMap *map =3D tr->opaque; + RemotePortMemoryMaster *s; + + if (!map || !map->parent || + !object_dynamic_cast(OBJECT(map->parent), + TYPE_REMOTE_PORT_MEMORY_MASTER)) { + return; + } + + s =3D REMOTE_PORT_MEMORY_MASTER(map->parent); + if (s->serbsIf) { + s->rp_timeout_err =3D level; + xlnx_serbs_if_timeout_set(s->serbsIf, s->serbs_id, s->rp_timeout_e= rr); + } + +} + +MemTxResult rp_mm_access_with_def_attr(RemotePort *rp, uint32_t rp_dev, + struct rp_peer_state *peer, + MemoryTransaction *tr, + bool relative, uint64_t offset, + uint32_t def_attr) +{ + uint64_t addr =3D tr->addr; + RemotePortRespSlot *rsp_slot; + RemotePortDynPkt *rsp; + struct { + struct rp_pkt_busaccess_ext_base pkt; + uint8_t reserved[RP_MAX_ACCESS_SIZE]; + } pay; + uint8_t *data =3D rp_busaccess_tx_dataptr(peer, &pay.pkt); + struct rp_encode_busaccess_in in =3D {0}; + int i; + int len; + int rp_timeout =3D rp_mm_get_timeout(tr); + MemTxResult ret; + + if (rp_timeout && rp_mm_timeout_err_state_get(tr)) { + return MEMTX_ERROR; + } + DB_PRINT_L(0, "addr: %" HWADDR_PRIx " data: %" PRIx64 "\n", + addr, tr->data.u64); + + if (tr->rw) { + /* Data up to 8 bytes is passed as values. */ + if (tr->size <=3D 8) { + for (i =3D 0; i < tr->size; i++) { + data[i] =3D tr->data.u64 >> (i * 8); + } + } else { + memcpy(data, tr->data.p8, tr->size); + } + } + + addr +=3D relative ? 0 : offset; + + in.cmd =3D tr->rw ? RP_CMD_write : RP_CMD_read; + in.id =3D rp_new_id(rp); + in.dev =3D rp_dev; + in.clk =3D rp_normalized_vmclk(rp); + in.master_id =3D tr->attr.requester_id; + in.addr =3D addr; + in.attr =3D def_attr; + in.attr |=3D tr->attr.secure ? RP_BUS_ATTR_SECURE : 0; + in.size =3D tr->size; + in.stream_width =3D tr->size; + len =3D rp_encode_busaccess(peer, &pay.pkt, &in); + len +=3D tr->rw ? tr->size : 0; + + trace_remote_port_memory_master_tx_busaccess(rp_cmd_to_string(in.cmd), + in.id, in.flags, in.dev, in.addr, in.size, in.attr); + + rp_rsp_mutex_lock(rp); + rp_write(rp, (void *) &pay, len); + + if (!rp_timeout) { + rsp_slot =3D rp_dev_wait_resp(rp, in.dev, in.id); + } else { + rsp_slot =3D rp_dev_timed_wait_resp(rp, in.dev, in.id, rp_timeout); + if (rsp_slot->valid =3D=3D false) { + /* + * Timeout error + */ + rp_rsp_mutex_unlock(rp); + rp_mm_timeout_err_state_set(tr, true); + return MEMTX_ERROR; + } + } + rsp =3D &rsp_slot->rsp; + + /* We dont support out of order answers yet. */ + assert(rsp->pkt->hdr.id =3D=3D in.id); + + switch (rp_get_busaccess_response(rsp->pkt)) { + case RP_RESP_OK: + ret =3D MEMTX_OK; + break; + case RP_RESP_ADDR_ERROR: + ret =3D MEMTX_DECODE_ERROR; + break; + default: + ret =3D MEMTX_ERROR; + break; + } + + if (ret =3D=3D MEMTX_OK && !tr->rw) { + data =3D rp_busaccess_rx_dataptr(peer, &rsp->pkt->busaccess_ext_ba= se); + /* Data up to 8 bytes is return as values. */ + if (tr->size <=3D 8) { + for (i =3D 0; i < tr->size; i++) { + tr->data.u64 |=3D ((uint64_t) data[i]) << (i * 8); + } + } else { + memcpy(tr->data.p8, data, tr->size); + } + } + + trace_remote_port_memory_master_rx_busaccess( + rp_cmd_to_string(rsp->pkt->hdr.cmd), rsp->pkt->hdr.id, + rsp->pkt->hdr.flags, rsp->pkt->hdr.dev, rsp->pkt->busaccess.addr, + rsp->pkt->busaccess.len, rsp->pkt->busaccess.attributes); + + if (rp_timeout) { + for (i =3D 0; i < ARRAY_SIZE(rp->dev_state[rp_dev].rsp_queue); i++= ) { + if (rp->dev_state[rp_dev].rsp_queue[i].used && + rp->dev_state[rp_dev].rsp_queue[i].valid) { + rp_resp_slot_done(rp, &rp->dev_state[rp_dev].rsp_queue[i]); + } + } + } else { + rp_resp_slot_done(rp, rsp_slot); + } + rp_rsp_mutex_unlock(rp); + + /* + * For strongly ordered or transactions that don't allow Early Acking, + * we need to drain the pending RP processing queue here. This is + * because RP handles responses in parallel with normal requests so + * they may get reordered. This becomes visible for example with reads + * to read-to-clear registers that clear interrupts. Even though the + * lowering of the interrupt-wires arrives to us before the read-resp, + * we may handle the response before the wire update, resulting in + * spurious interrupts. + * + * This has some room for optimization but for now we use the big hamm= er + * and drain the entire qeueue. + */ + rp_process(rp); + + /* Reads are sync-points, roll the sync timer. */ + rp_restart_sync_timer(rp); + DB_PRINT_L(1, "\n"); + return ret; +} + +MemTxResult rp_mm_access(RemotePort *rp, uint32_t rp_dev, + struct rp_peer_state *peer, + MemoryTransaction *tr, + bool relative, uint64_t offset) +{ + return rp_mm_access_with_def_attr(rp, rp_dev, peer, tr, relative, offs= et, + 0); +} + +static MemTxResult rp_access(MemoryTransaction *tr) +{ + RemotePortMap *map =3D tr->opaque; + RemotePortMemoryMaster *s =3D map->parent; + + return rp_mm_access(s->rp, s->rp_dev, s->peer, tr, s->relative, + map->offset); +} + +static const MemoryRegionOps rp_ops_template =3D { + .access =3D rp_access, + .valid.max_access_size =3D RP_MAX_ACCESS_SIZE, + .valid.unaligned =3D true, + .impl.max_access_size =3D RP_MAX_ACCESS_SIZE, + .impl.unaligned =3D false, + .endianness =3D DEVICE_LITTLE_ENDIAN, +}; + +static void rp_memory_master_realize(DeviceState *dev, Error **errp) +{ + RemotePortMemoryMaster *s =3D REMOTE_PORT_MEMORY_MASTER(dev); + int i; + + /* Sanity check max access size. */ + if (s->max_access_size > RP_MAX_ACCESS_SIZE) { + error_setg(errp, "%s: max-access-size %d too large! MAX is %d", + TYPE_REMOTE_PORT_MEMORY_MASTER, s->max_access_size, + RP_MAX_ACCESS_SIZE); + return; + } + + if (s->max_access_size < 4) { + error_setg(errp, "%s: max-access-size %d too small! MIN is 4", + TYPE_REMOTE_PORT_MEMORY_MASTER, s->max_access_size); + return; + } + + assert(s->rp); + s->peer =3D rp_get_peer(s->rp); + + /* Create a single static region if configuration says so. */ + if (s->map_num) { + /* Initialize rp_ops from template. */ + s->rp_ops =3D g_malloc(sizeof *s->rp_ops); + memcpy(s->rp_ops, &rp_ops_template, sizeof *s->rp_ops); + s->rp_ops->valid.max_access_size =3D s->max_access_size; + s->rp_ops->impl.max_access_size =3D s->max_access_size; + + s->mmaps =3D g_new0(typeof(*s->mmaps), s->map_num); + for (i =3D 0; i < s->map_num; ++i) { + char *name =3D g_strdup_printf("rp-%d", i); + + s->mmaps[i].offset =3D s->map_offset; + memory_region_init_io(&s->mmaps[i].iomem, OBJECT(dev), s->rp_o= ps, + &s->mmaps[i], name, s->map_size); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmaps[i].iomem); + s->mmaps[i].parent =3D s; + g_free(name); + } + } +} + +static void rp_memory_master_init(Object *obj) +{ + RemotePortMemoryMaster *rpms =3D REMOTE_PORT_MEMORY_MASTER(obj); + object_property_add_link(obj, "rp-adaptor0", "remote-port", + (Object **)&rpms->rp, + qdev_prop_allow_set_link, + OBJ_PROP_LINK_STRONG); + object_property_add_link(obj, "serbs-if", TYPE_XLNX_SERBS_IF, + (Object **)&rpms->serbsIf, + qdev_prop_allow_set_link, + OBJ_PROP_LINK_STRONG); +} + +static bool rp_parse_reg(FDTGenericMMap *obj, FDTGenericRegPropInfo reg, + Error **errp) +{ + RemotePortMemoryMaster *s =3D REMOTE_PORT_MEMORY_MASTER(obj); + FDTGenericMMapClass *parent_fmc =3D + FDT_GENERIC_MMAP_CLASS(REMOTE_PORT_MEMORY_MASTER_PARENT_CLASS); + int i; + + /* Initialize rp_ops from template. */ + s->rp_ops =3D g_malloc(sizeof *s->rp_ops); + memcpy(s->rp_ops, &rp_ops_template, sizeof *s->rp_ops); + s->rp_ops->valid.max_access_size =3D s->max_access_size; + s->rp_ops->impl.max_access_size =3D s->max_access_size; + + s->mmaps =3D g_new0(typeof(*s->mmaps), reg.n); + for (i =3D 0; i < reg.n; ++i) { + char *name =3D g_strdup_printf("rp-%d", i); + + s->mmaps[i].offset =3D reg.a[i]; + memory_region_init_io(&s->mmaps[i].iomem, OBJECT(obj), s->rp_ops, + &s->mmaps[i], name, reg.s[i]); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->mmaps[i].iomem); + s->mmaps[i].parent =3D s; + g_free(name); + } + + return parent_fmc ? parent_fmc->parse_reg(obj, reg, errp) : false; +} + +static Property rp_properties[] =3D { + DEFINE_PROP_UINT32("map-num", RemotePortMemoryMaster, map_num, 0), + DEFINE_PROP_UINT64("map-offset", RemotePortMemoryMaster, map_offset, 0= ), + DEFINE_PROP_UINT64("map-size", RemotePortMemoryMaster, map_size, 0), + DEFINE_PROP_UINT32("rp-chan0", RemotePortMemoryMaster, rp_dev, 0), + DEFINE_PROP_BOOL("relative", RemotePortMemoryMaster, relative, false), + DEFINE_PROP_UINT32("max-access-size", RemotePortMemoryMaster, + max_access_size, RP_MAX_ACCESS_SIZE), +}; + +static void rp_memory_master_class_init(ObjectClass *oc, const void *data) +{ + FDTGenericMMapClass *fmc =3D FDT_GENERIC_MMAP_CLASS(oc); + DeviceClass *dc =3D DEVICE_CLASS(oc); + xlnx_serbs_if_class *sc =3D XLNX_SERBS_IF_CLASS(oc); + device_class_set_props_n(dc, rp_properties, ARRAY_SIZE(rp_properties)); + dc->realize =3D rp_memory_master_realize; + fmc->parse_reg =3D rp_parse_reg; + sc->timer_config =3D rp_mm_serbs_timer_config; + sc->timeout_set =3D rp_mm_serbs_timout_set; +} + +static const TypeInfo rp_info =3D { + .name =3D TYPE_REMOTE_PORT_MEMORY_MASTER, + .parent =3D TYPE_SYS_BUS_DEVICE, + .instance_size =3D sizeof(RemotePortMemoryMaster), + .instance_init =3D rp_memory_master_init, + .class_init =3D rp_memory_master_class_init, + .interfaces =3D (InterfaceInfo[]) { + { TYPE_FDT_GENERIC_MMAP }, + { TYPE_REMOTE_PORT_DEVICE }, + { TYPE_XLNX_SERBS_IF }, + { }, + }, +}; + +static void rp_register_types(void) +{ + type_register_static(&rp_info); +} + +type_init(rp_register_types) diff --git a/hw/core/remote-port-memory-slave.c b/hw/core/remote-port-memor= y-slave.c new file mode 100644 index 0000000000..47c844b8cc --- /dev/null +++ b/hw/core/remote-port-memory-slave.c @@ -0,0 +1,257 @@ +/* + * QEMU remote port memory slave. Read and write transactions + * recieved from the remote port are translated into an address space. + * + * Copyright (c) 2013 Xilinx Inc + * Written by Edgar E. Iglesias + * Written by Peter Crosthwaite + * + * This code is licensed under the GNU GPL. + */ + +#include "qemu/osdep.h" +#include "system/system.h" +#include "system/dma.h" +#include "qemu/log.h" +#include "qapi/qmp/qerror.h" +#include "qapi/error.h" +#include "hw/qdev-core.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "qemu/error-report.h" +#include "trace.h" +#include "qemu/cutils.h" + +#include "hw/remote-port-proto.h" +#include "hw/remote-port-device.h" +#include "hw/remote-port-memory-slave.h" + +#ifndef REMOTE_PORT_ERR_DEBUG +#define REMOTE_PORT_DEBUG_LEVEL 0 +#else +#define REMOTE_PORT_DEBUG_LEVEL 1 +#endif + +#define DB_PRINT_L(level, ...) do { \ + if (REMOTE_PORT_DEBUG_LEVEL > level) { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } \ +} while (0); + +/* Slow path dealing with odd stuff like byte-enables. */ +static MemTxResult process_data_slow(RemotePortMemorySlave *s, + AddressSpace *as, + struct rp_pkt *pkt, + DMADirection dir, + uint8_t *data, uint8_t *byte_en) +{ + unsigned int i; + unsigned int byte_en_len =3D pkt->busaccess_ext_base.byte_enable_len; + unsigned int sw =3D pkt->busaccess.stream_width; + MemTxResult ret =3D MEMTX_OK; + + assert(sw); + + for (i =3D 0; i < pkt->busaccess.len; i++) { + if (byte_en && !byte_en[i % byte_en_len]) { + continue; + } + + ret =3D dma_memory_rw_attr(as, pkt->busaccess.addr + i % sw, + data + i, 1, dir, s->attr); + + if (ret !=3D MEMTX_OK) { + break; + } + } + + return ret; +} + +static AddressSpace *get_as_for_phys_busaccess(RemotePortMemorySlave *s, + struct rp_pkt *pkt) +{ + if (s->ats_cache) { + IOMMUTLBEntry *iotlb =3D + rp_ats_cache_lookup_translation(s->ats_cache, + pkt->busaccess.addr, + pkt->busaccess.len); + if (iotlb) { + /* Return the matching address space found. */ + return iotlb->target_as; + } + } + /* Emit a warning on errors since this really should not happen. */ + warn_report("Physical address error detected (range address: 0x%" + HWADDR_PRIx ", len: 0x%" PRIx32 " contains untranslated " + "addresses)", pkt->busaccess.addr, pkt->busaccess.len); + return NULL; +} + +static void rp_cmd_rw(RemotePortMemorySlave *s, struct rp_pkt *pkt, + DMADirection dir) +{ + size_t pktlen =3D sizeof(struct rp_pkt_busaccess_ext_base); + struct rp_encode_busaccess_in in =3D {0}; + size_t enclen; + int64_t delay; + uint8_t *data =3D NULL; + uint8_t *byte_en; + MemTxResult ret; + AddressSpace *as; + + byte_en =3D rp_busaccess_byte_en_ptr(s->peer, &pkt->busaccess_ext_base= ); + + if (dir =3D=3D DMA_DIRECTION_TO_DEVICE) { + pktlen +=3D pkt->busaccess.len; + } else { + data =3D rp_busaccess_rx_dataptr(s->peer, &pkt->busaccess_ext_base= ); + } + + assert(pkt->busaccess.width =3D=3D 0); + assert(!(pkt->hdr.flags & RP_PKT_FLAGS_response)); + + rp_dpkt_alloc(&s->rsp, pktlen); + if (dir =3D=3D DMA_DIRECTION_TO_DEVICE) { + data =3D rp_busaccess_tx_dataptr(s->peer, + &s->rsp.pkt->busaccess_ext_base); + } + if (dir =3D=3D DMA_DIRECTION_FROM_DEVICE && REMOTE_PORT_DEBUG_LEVEL > = 0) { + DB_PRINT_L(0, "address: %" PRIx64 "\n", pkt->busaccess.addr); + qemu_hexdump(stderr, ": write: ", + (const char *) data, pkt->busaccess.len); + } + trace_remote_port_memory_slave_rx_busaccess(rp_cmd_to_string(pkt->hdr.= cmd), + pkt->hdr.id, pkt->hdr.flags, pkt->hdr.dev, pkt->busaccess.addr, + pkt->busaccess.len, pkt->busaccess.attributes); + + s->attr.secure =3D !!(pkt->busaccess.attributes & RP_BUS_ATTR_SECURE); + s->attr.requester_id =3D pkt->busaccess.master_id; + + if (pkt->busaccess.attributes & RP_BUS_ATTR_PHYS_ADDR) { + as =3D get_as_for_phys_busaccess(s, pkt); + } else { + as =3D &s->as; + } + if (as) { + if (byte_en || pkt->busaccess.stream_width !=3D pkt->busaccess.len= ) { + ret =3D process_data_slow(s, as, pkt, dir, data, byte_en); + } else { + ret =3D dma_memory_rw_attr(as, pkt->busaccess.addr, data, + pkt->busaccess.len, dir, s->attr); + } + } else { + ret =3D MEMTX_ERROR; + } + + if (dir =3D=3D DMA_DIRECTION_TO_DEVICE && REMOTE_PORT_DEBUG_LEVEL > 0)= { + DB_PRINT_L(0, "address: %" PRIx64 "\n", pkt->busaccess.addr); + qemu_hexdump(stderr, ": read: ", + (const char *) data, pkt->busaccess.len); + } + /* delay here could be set to the annotated cost of doing issuing + these accesses. QEMU doesn't support this kind of annotations + at the moment. So we just clear the delay. */ + delay =3D 0; + + rp_encode_busaccess_in_rsp_init(&in, pkt); + in.clk =3D pkt->busaccess.timestamp + delay; + + switch (ret) { + case MEMTX_ERROR: + in.attr |=3D RP_RESP_BUS_GENERIC_ERROR << RP_BUS_RESP_SHIFT; + break; + case MEMTX_DECODE_ERROR: + in.attr |=3D RP_RESP_ADDR_ERROR << RP_BUS_RESP_SHIFT; + break; + default: + /* MEMTX_OK maps to RP_RESP_OK. */ + break; + } + + enclen =3D rp_encode_busaccess(s->peer, &s->rsp.pkt->busaccess_ext_bas= e, + &in); + assert(enclen <=3D pktlen); + + trace_remote_port_memory_slave_tx_busaccess(rp_cmd_to_string(in.cmd), + in.id, in.flags, in.dev, in.addr, in.size, in.attr); + + rp_write(s->rp, (void *)s->rsp.pkt, enclen); +} + +static void rp_memory_slave_realize(DeviceState *dev, Error **errp) +{ + RemotePortMemorySlave *s =3D REMOTE_PORT_MEMORY_SLAVE(dev); + + s->peer =3D rp_get_peer(s->rp); + address_space_init(&s->as, s->mr ? s->mr : get_system_memory(), "dma"); +} + +static void rp_memory_slave_write(RemotePortDevice *s, struct rp_pkt *pkt) +{ + return rp_cmd_rw(REMOTE_PORT_MEMORY_SLAVE(s), pkt, + DMA_DIRECTION_FROM_DEVICE); +} + +static void rp_memory_slave_read(RemotePortDevice *s, struct rp_pkt *pkt) +{ + return rp_cmd_rw(REMOTE_PORT_MEMORY_SLAVE(s), pkt, + DMA_DIRECTION_TO_DEVICE); +} + +static void rp_memory_slave_init(Object *obj) +{ + RemotePortMemorySlave *rpms =3D REMOTE_PORT_MEMORY_SLAVE(obj); + + object_property_add_link(obj, "rp-adaptor0", "remote-port", + (Object **)&rpms->rp, + qdev_prop_allow_set_link, + OBJ_PROP_LINK_STRONG); + object_property_add_link(obj, "mr", TYPE_MEMORY_REGION, + (Object **)&rpms->mr, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); + object_property_add_link(obj, "rp-ats-cache", TYPE_REMOTE_PORT_ATS_CAC= HE, + (Object **)&rpms->ats_cache, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); +} + +static void rp_memory_slave_unrealize(DeviceState *dev) +{ + RemotePortMemorySlave *s =3D REMOTE_PORT_MEMORY_SLAVE(dev); + + address_space_destroy(&s->as); +} + +static void rp_memory_slave_class_init(ObjectClass *oc, const void *data) +{ + RemotePortDeviceClass *rpdc =3D REMOTE_PORT_DEVICE_CLASS(oc); + DeviceClass *dc =3D DEVICE_CLASS(oc); + + rpdc->ops[RP_CMD_write] =3D rp_memory_slave_write; + rpdc->ops[RP_CMD_read] =3D rp_memory_slave_read; + dc->realize =3D rp_memory_slave_realize; + dc->unrealize =3D rp_memory_slave_unrealize; +} + +static const TypeInfo rp_info =3D { + .name =3D TYPE_REMOTE_PORT_MEMORY_SLAVE, + .parent =3D TYPE_DEVICE, + .instance_size =3D sizeof(RemotePortMemorySlave), + .instance_init =3D rp_memory_slave_init, + .class_init =3D rp_memory_slave_class_init, + .interfaces =3D (InterfaceInfo[]) { + { TYPE_REMOTE_PORT_DEVICE }, + { }, + }, +}; + +static void rp_register_types(void) +{ + type_register_static(&rp_info); +} + +type_init(rp_register_types) diff --git a/hw/core/remote-port-proto.c b/hw/core/remote-port-proto.c new file mode 100644 index 0000000000..9f09dc8d16 --- /dev/null +++ b/hw/core/remote-port-proto.c @@ -0,0 +1,523 @@ +/* + * Remote-port protocol + * + * Copyright (c) 2013 Xilinx Inc + * Written by Edgar E. Iglesias + * + * Permission is hereby granted, free of charge, to any person obtaining a= copy + * of this software and associated documentation files (the "Software"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * 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 OT= HER + * 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. + */ + +#ifndef _DEFAULT_SOURCE +# define _DEFAULT_SOURCE +#endif +#include +#include +#include +#include +#include +#include "hw/remote-port-proto.h" + +#undef MIN +#define MIN(x, y) (x < y ? x : y) + +#if defined(__linux__) +# include +#elif defined(__FreeBSD__) || defined(__NetBSD__) +# include +#elif defined(__OpenBSD__) +# include +# define be16toh(x) betoh16(x) +# define be32toh(x) betoh32(x) +# define be64toh(x) betoh64(x) +#elif defined(__WIN32) +/* We assume little endian. */ +# define htobe64(x) _byteswap_uint64(x) +# define htobe32(x) _byteswap_ulong(x) +# define htobe16(x) _byteswap_ushort(x) + +# define be64toh(x) _byteswap_uint64(x) +# define be32toh(x) _byteswap_ulong(x) +# define be16toh(x) _byteswap_ushort(x) +#elif defined(__APPLE__) +# include + +/* We assume little endian. */ +# define htobe64(x) OSSwapHostToBigInt64(x) +# define htobe32(x) OSSwapHostToBigInt32(x) +# define htobe16(x) OSSwapHostToBigInt16(x) + +# define be64toh(x) OSSwapBigToHostInt64(x) +# define be32toh(x) OSSwapBigToHostInt32(x) +# define be16toh(x) OSSwapBigToHostInt16(x) +#endif + +/* Fallback for ancient Linux systems. */ +#ifndef htobe64 +# include + +# if __BYTE_ORDER =3D=3D __LITTLE_ENDIAN +# define htobe64(x) bswap_64(x) +# define htobe32(x) bswap_32(x) +# define htobe16(x) bswap_16(x) + +# define be64toh(x) bswap_64(x) +# define be32toh(x) bswap_32(x) +# define be16toh(x) bswap_16(x) +# else +# define htobe64(x) x +# define htobe32(x) x +# define htobe16(x) x + +# define be64toh(x) x +# define be32toh(x) x +# define be16toh(x) x +# endif +#endif + +static const char *rp_cmd_names[RP_CMD_max + 1] =3D { + [RP_CMD_nop] =3D "nop", + [RP_CMD_hello] =3D "hello", + [RP_CMD_cfg] =3D "cfg", + [RP_CMD_read] =3D "read", + [RP_CMD_write] =3D "write", + [RP_CMD_interrupt] =3D "interrupt", + [RP_CMD_sync] =3D "sync", + [RP_CMD_ats_req] =3D "ats_request", + [RP_CMD_ats_inv] =3D "ats_invalidation", +}; + +const char *rp_cmd_to_string(enum rp_cmd cmd) +{ + assert(cmd <=3D RP_CMD_max); + return rp_cmd_names[cmd]; +} + +int rp_decode_hdr(struct rp_pkt *pkt) +{ + int used =3D 0; + + pkt->hdr.cmd =3D be32toh(pkt->hdr.cmd); + pkt->hdr.len =3D be32toh(pkt->hdr.len); + pkt->hdr.id =3D be32toh(pkt->hdr.id); + pkt->hdr.flags =3D be32toh(pkt->hdr.flags); + pkt->hdr.dev =3D be32toh(pkt->hdr.dev); + used +=3D sizeof pkt->hdr; + return used; +} + +int rp_decode_payload(struct rp_pkt *pkt) +{ + int used =3D 0; + /* Master_id has an odd decoding due to historical reasons. */ + uint64_t master_id; + + switch (pkt->hdr.cmd) { + case RP_CMD_hello: + assert(pkt->hdr.len >=3D sizeof pkt->hello.version); + pkt->hello.version.major =3D be16toh(pkt->hello.version.major); + pkt->hello.version.minor =3D be16toh(pkt->hello.version.minor); + used +=3D sizeof pkt->hello.version; + + if ((pkt->hdr.len - used) >=3D sizeof pkt->hello.caps) { + void *offset; + int i; + + pkt->hello.caps.offset =3D be32toh(pkt->hello.caps.offset); + pkt->hello.caps.len =3D be16toh(pkt->hello.caps.len); + + offset =3D (char *)pkt + pkt->hello.caps.offset; + for (i =3D 0; i < pkt->hello.caps.len; i++) { + uint32_t cap; + + /* We don't know if offset is 32bit aligned so use + * memcpy to do the endian conversion. */ + memcpy(&cap, offset + i * sizeof cap, sizeof cap); + cap =3D be32toh(cap); + memcpy(offset + i * sizeof cap, &cap, sizeof cap); + } + used +=3D sizeof pkt->hello.caps; + } else { + pkt->hello.caps.offset =3D 0; + pkt->hello.caps.len =3D 0; + } + + /* Consume everything ignoring additional headers we do not yet + * know about. */ + used =3D pkt->hdr.len; + break; + case RP_CMD_write: + case RP_CMD_read: + assert(pkt->hdr.len >=3D sizeof pkt->busaccess - sizeof pkt->hdr); + pkt->busaccess.timestamp =3D be64toh(pkt->busaccess.timestamp); + pkt->busaccess.addr =3D be64toh(pkt->busaccess.addr); + pkt->busaccess.attributes =3D be64toh(pkt->busaccess.attributes); + pkt->busaccess.len =3D be32toh(pkt->busaccess.len); + pkt->busaccess.width =3D be32toh(pkt->busaccess.width); + pkt->busaccess.stream_width =3D be32toh(pkt->busaccess.stream_widt= h); + master_id =3D be16toh(pkt->busaccess.master_id); + + used +=3D sizeof pkt->busaccess - sizeof pkt->hdr; + + if (pkt->busaccess.attributes & RP_BUS_ATTR_EXT_BASE) { + struct rp_pkt_busaccess_ext_base *pext =3D &pkt->busaccess_ext= _base; + + assert(pkt->hdr.len >=3D sizeof *pext - sizeof pkt->hdr); + master_id |=3D (uint64_t)be16toh(pext->master_id_31_16) << 16; + master_id |=3D (uint64_t)be32toh(pext->master_id_63_32) << 32; + pext->data_offset =3D be32toh(pext->data_offset); + pext->next_offset =3D be32toh(pext->next_offset); + pext->byte_enable_offset =3D be32toh(pext->byte_enable_offset); + pext->byte_enable_len =3D be32toh(pext->byte_enable_len); + + used +=3D sizeof *pext - sizeof pkt->busaccess; + } + pkt->busaccess.master_id =3D master_id; + break; + case RP_CMD_interrupt: + pkt->interrupt.timestamp =3D be64toh(pkt->interrupt.timestamp); + pkt->interrupt.vector =3D be64toh(pkt->interrupt.vector); + pkt->interrupt.line =3D be32toh(pkt->interrupt.line); + pkt->interrupt.val =3D pkt->interrupt.val; + used +=3D pkt->hdr.len; + break; + case RP_CMD_sync: + pkt->sync.timestamp =3D be64toh(pkt->interrupt.timestamp); + used +=3D pkt->hdr.len; + break; + case RP_CMD_ats_req: + case RP_CMD_ats_inv: + pkt->ats.attributes =3D be64toh(pkt->ats.attributes); + pkt->ats.addr =3D be64toh(pkt->ats.addr); + pkt->ats.len =3D be64toh(pkt->ats.len); + pkt->ats.result =3D be32toh(pkt->ats.result); + break; + default: + break; + } + return used; +} + +void rp_encode_hdr(struct rp_pkt_hdr *hdr, uint32_t cmd, uint32_t id, + uint32_t dev, uint32_t len, uint32_t flags) +{ + hdr->cmd =3D htobe32(cmd); + hdr->len =3D htobe32(len); + hdr->id =3D htobe32(id); + hdr->dev =3D htobe32(dev); + hdr->flags =3D htobe32(flags); +} + +size_t rp_encode_hello_caps(uint32_t id, uint32_t dev, struct rp_pkt_hello= *pkt, + uint16_t version_major, uint16_t version_minor, + uint32_t *caps, uint32_t *caps_out, + uint32_t caps_len) +{ + size_t psize =3D sizeof *pkt + sizeof caps[0] * caps_len; + unsigned int i; + + rp_encode_hdr(&pkt->hdr, RP_CMD_hello, id, dev, + psize - sizeof pkt->hdr, 0); + pkt->version.major =3D htobe16(version_major); + pkt->version.minor =3D htobe16(version_minor); + + /* Feature list is appeneded right after the hello packet. */ + pkt->caps.offset =3D htobe32(sizeof *pkt); + pkt->caps.len =3D htobe16(caps_len); + + for (i =3D 0; i < caps_len; i++) { + uint32_t cap; + + cap =3D caps[i]; + caps_out[i] =3D htobe32(cap); + } + return sizeof *pkt; +} + +static void rp_encode_busaccess_common(struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t s= ize, + uint32_t width, uint32_t stream_width) +{ + pkt->timestamp =3D htobe64(clk); + pkt->master_id =3D htobe16(master_id); + pkt->addr =3D htobe64(addr); + pkt->attributes =3D htobe64(attr); + pkt->len =3D htobe32(size); + pkt->width =3D htobe32(width); + pkt->stream_width =3D htobe32(stream_width); +} + +size_t rp_encode_read(uint32_t id, uint32_t dev, + struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t size, + uint32_t width, uint32_t stream_width) +{ + rp_encode_hdr(&pkt->hdr, RP_CMD_read, id, dev, + sizeof *pkt - sizeof pkt->hdr, 0); + rp_encode_busaccess_common(pkt, clk, master_id, addr, attr, + size, width, stream_width); + return sizeof *pkt; +} + +size_t rp_encode_read_resp(uint32_t id, uint32_t dev, + struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t size, + uint32_t width, uint32_t stream_width) +{ + rp_encode_hdr(&pkt->hdr, RP_CMD_read, id, dev, + sizeof *pkt - sizeof pkt->hdr + size, RP_PKT_FLAGS_respo= nse); + rp_encode_busaccess_common(pkt, clk, master_id, addr, attr, + size, width, stream_width); + return sizeof *pkt + size; +} + +size_t rp_encode_write(uint32_t id, uint32_t dev, + struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t size, + uint32_t width, uint32_t stream_width) +{ + rp_encode_hdr(&pkt->hdr, RP_CMD_write, id, dev, + sizeof *pkt - sizeof pkt->hdr + size, 0); + rp_encode_busaccess_common(pkt, clk, master_id, addr, attr, + size, width, stream_width); + return sizeof *pkt; +} + +size_t rp_encode_write_resp(uint32_t id, uint32_t dev, + struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t size, + uint32_t width, uint32_t stream_width) +{ + rp_encode_hdr(&pkt->hdr, RP_CMD_write, id, dev, + sizeof *pkt - sizeof pkt->hdr, RP_PKT_FLAGS_response); + rp_encode_busaccess_common(pkt, clk, master_id, addr, attr, + size, width, stream_width); + return sizeof *pkt; +} + +/* New API for extended header. */ +size_t rp_encode_busaccess(struct rp_peer_state *peer, + struct rp_pkt_busaccess_ext_base *pkt, + struct rp_encode_busaccess_in *in) { + struct rp_pkt_busaccess *pkt_v4_0 =3D (void *) pkt; + uint32_t hsize =3D 0; + uint32_t ret_size =3D 0; + + /* Allocate packet space. */ + if (in->cmd =3D=3D RP_CMD_write && !(in->flags & RP_PKT_FLAGS_response= )) { + hsize =3D in->size; + } + if (in->cmd =3D=3D RP_CMD_read && (in->flags & RP_PKT_FLAGS_response))= { + hsize =3D in->size; + ret_size =3D in->size; + } + + /* If peer does not support the busaccess base extensions, use the + * old layout. For responses, what matters is if we're responding + * to a packet with the extensions. + */ + if (!peer->caps.busaccess_ext_base && !(in->attr & RP_BUS_ATTR_EXT_BAS= E)) { + /* Old layout. */ + assert(in->master_id < UINT16_MAX); + + rp_encode_hdr(&pkt->hdr, in->cmd, in->id, in->dev, + sizeof *pkt_v4_0 - sizeof pkt->hdr + hsize, in->flags); + rp_encode_busaccess_common(pkt_v4_0, in->clk, in->master_id, + in->addr, in->attr, + in->size, in->width, in->stream_width); + return sizeof *pkt_v4_0 + ret_size; + } + + /* Encode the extended fields. */ + pkt->master_id_31_16 =3D htobe16(in->master_id >> 16); + pkt->master_id_63_32 =3D htobe32(in->master_id >> 32); + + /* We always put data right after the header. */ + pkt->data_offset =3D htobe32(sizeof *pkt); + pkt->next_offset =3D 0; + + pkt->byte_enable_offset =3D htobe32(sizeof *pkt + hsize); + pkt->byte_enable_len =3D htobe32(in->byte_enable_len); + hsize +=3D in->byte_enable_len; + + rp_encode_hdr(&pkt->hdr, in->cmd, in->id, in->dev, + sizeof *pkt - sizeof pkt->hdr + hsize, in->flags); + rp_encode_busaccess_common(pkt_v4_0, in->clk, in->master_id, in->addr, + in->attr | RP_BUS_ATTR_EXT_BASE, + in->size, in->width, in->stream_width); + + return sizeof *pkt + ret_size; +} + +size_t rp_encode_interrupt_f(uint32_t id, uint32_t dev, + struct rp_pkt_interrupt *pkt, + int64_t clk, + uint32_t line, uint64_t vector, uint8_t val, + uint32_t flags) +{ + rp_encode_hdr(&pkt->hdr, RP_CMD_interrupt, id, dev, + sizeof *pkt - sizeof pkt->hdr, flags); + pkt->timestamp =3D htobe64(clk); + pkt->vector =3D htobe64(vector); + pkt->line =3D htobe32(line); + pkt->val =3D val; + return sizeof *pkt; +} + +size_t rp_encode_interrupt(uint32_t id, uint32_t dev, + struct rp_pkt_interrupt *pkt, + int64_t clk, + uint32_t line, uint64_t vector, uint8_t val) +{ + return rp_encode_interrupt_f(id, dev, pkt, clk, line, vector, val, 0); +} + +static size_t rp_encode_ats_common(uint32_t cmd, uint32_t id, uint32_t dev, + struct rp_pkt_ats *pkt, + int64_t clk, uint64_t attr, uint64_t addr, + uint64_t len, uint64_t result, uint32_t flags) +{ + rp_encode_hdr(&pkt->hdr, cmd, id, dev, + sizeof *pkt - sizeof pkt->hdr, flags); + pkt->timestamp =3D htobe64(clk); + pkt->attributes =3D htobe64(attr); + pkt->addr =3D htobe64(addr); + pkt->len =3D htobe64(len); + pkt->result =3D htobe32(result); + return sizeof *pkt; +} + +size_t rp_encode_ats_req(uint32_t id, uint32_t dev, + struct rp_pkt_ats *pkt, + int64_t clk, uint64_t attr, uint64_t addr, + uint64_t len, uint64_t result, uint32_t flags) +{ + return rp_encode_ats_common(RP_CMD_ats_req, id, dev, + pkt, clk, attr, + addr, len, result, flags); +} + +size_t rp_encode_ats_inv(uint32_t id, uint32_t dev, + struct rp_pkt_ats *pkt, + int64_t clk, uint64_t attr, uint64_t addr, + uint64_t len, uint64_t result, uint32_t flags) +{ + return rp_encode_ats_common(RP_CMD_ats_inv, id, dev, + pkt, clk, attr, + addr, len, result, flags); +} + +static size_t rp_encode_sync_common(uint32_t id, uint32_t dev, + struct rp_pkt_sync *pkt, + int64_t clk, uint32_t flags) +{ + rp_encode_hdr(&pkt->hdr, RP_CMD_sync, id, dev, + sizeof *pkt - sizeof pkt->hdr, flags); + pkt->timestamp =3D htobe64(clk); + return sizeof *pkt; +} + +size_t rp_encode_sync(uint32_t id, uint32_t dev, + struct rp_pkt_sync *pkt, + int64_t clk) +{ + return rp_encode_sync_common(id, dev, pkt, clk, 0); +} + +size_t rp_encode_sync_resp(uint32_t id, uint32_t dev, + struct rp_pkt_sync *pkt, + int64_t clk) +{ + return rp_encode_sync_common(id, dev, pkt, clk, RP_PKT_FLAGS_response); +} + +void rp_process_caps(struct rp_peer_state *peer, + void *caps, size_t caps_len) +{ + int i; + + assert(peer->caps.busaccess_ext_base =3D=3D false); + + for (i =3D 0; i < caps_len; i++) { + uint32_t cap; + + memcpy(&cap, caps + i * sizeof cap, sizeof cap); + + switch (cap) { + case CAP_BUSACCESS_EXT_BASE: + peer->caps.busaccess_ext_base =3D true; + break; + case CAP_BUSACCESS_EXT_BYTE_EN: + peer->caps.busaccess_ext_byte_en =3D true; + break; + case CAP_WIRE_POSTED_UPDATES: + peer->caps.wire_posted_updates =3D true; + break; + case CAP_ATS: + peer->caps.ats =3D true; + break; + } + } +} + + +void rp_dpkt_alloc(RemotePortDynPkt *dpkt, size_t size) +{ + if (dpkt->size < size) { + char *u8; + dpkt->pkt =3D realloc(dpkt->pkt, size); + u8 =3D (void *) dpkt->pkt; + memset(u8 + dpkt->size, 0, size - dpkt->size); + dpkt->size =3D size; + } +} + +void rp_dpkt_swap(RemotePortDynPkt *a, RemotePortDynPkt *b) +{ + struct rp_pkt *tmp_pkt; + size_t tmp_size; + + tmp_pkt =3D a->pkt; + tmp_size =3D a->size; + a->pkt =3D b->pkt; + a->size =3D b->size; + b->pkt =3D tmp_pkt; + b->size =3D tmp_size; +} + +bool rp_dpkt_is_valid(RemotePortDynPkt *dpkt) +{ + return dpkt->size > 0 && dpkt->pkt->hdr.len; +} + +void rp_dpkt_invalidate(RemotePortDynPkt *dpkt) +{ + assert(rp_dpkt_is_valid(dpkt)); + dpkt->pkt->hdr.len =3D 0; +} + +inline void rp_dpkt_free(RemotePortDynPkt *dpkt) +{ + dpkt->size =3D 0; + free(dpkt->pkt); +} diff --git a/hw/core/remote-port-stream.c b/hw/core/remote-port-stream.c new file mode 100644 index 0000000000..903664df4c --- /dev/null +++ b/hw/core/remote-port-stream.c @@ -0,0 +1,233 @@ +/* + * Copyright (c) 2013 Xilinx Inc + * Copyright (c) 2013 Peter Crosthwaite + * Copyright (c) 2013 Edgar E. Iglesias + * + * Permission is hereby granted, free of charge, to any person obtaining a= copy + * of this software and associated documentation files (the "Software"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * 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 OT= HER + * 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/sysbus.h" +#include "qemu/log.h" + +#include "qemu/bitops.h" +#include "qapi/qmp/qerror.h" +#include "hw/stream.h" +#include "qapi/error.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "hw/remote-port-device.h" +#include "trace.h" + +#ifndef REMOTE_PORT_STREAM_ERR_DEBUG +#define REMOTE_PORT_STREAM_ERR_DEBUG 0 +#endif + +#define TYPE_REMOTE_PORT_STREAM "remote-port-stream" + +#define REMOTE_PORT_STREAM(obj) \ + OBJECT_CHECK(RemotePortStream, (obj), TYPE_REMOTE_PORT_STREAM) + +typedef struct RemotePortStream RemotePortStream; + +struct RemotePortStream { + DeviceState parent_obj; + + RemotePort *rp; + uint32_t rp_dev; + uint16_t stream_width; + + StreamSink *tx_dev; + + StreamCanPushNotifyFn notify; + void *notify_opaque; + + uint8_t *buf; + struct rp_pkt pkt; + + bool rsp_pending; + uint32_t current_id; +}; + +static void rp_stream_notify(void *opaque) +{ + RemotePortStream *s =3D REMOTE_PORT_STREAM(opaque); + + /* FIXME: This is completely bogus. */ + if (s->buf && stream_can_push(s->tx_dev, rp_stream_notify, s)) { + RemotePortDynPkt rsp; + struct rp_encode_busaccess_in in =3D {0}; + size_t pktlen =3D sizeof(struct rp_pkt_busaccess_ext_base); + size_t enclen; + int64_t delay =3D 0; /* FIXME - Implement */ + + size_t ret =3D stream_push(s->tx_dev, s->buf, 4, 0); + assert(ret =3D=3D 4); + s->buf =3D NULL; + + memset(&rsp, 0, sizeof(rsp)); + rp_dpkt_alloc(&rsp, pktlen); + rp_encode_busaccess_in_rsp_init(&in, &s->pkt); + in.clk =3D s->pkt.busaccess.timestamp + delay; + enclen =3D rp_encode_busaccess(rp_get_peer(s->rp), + &rsp.pkt->busaccess_ext_base, + &in); + assert(enclen <=3D pktlen); + trace_remote_port_stream_tx_busaccess(rp_cmd_to_string(in.cmd), + in.id, in.flags, in.dev, in.addr, in.size, in.attr); + + rp_write(s->rp, (void *)rsp.pkt, pktlen); + } +} + +static void rp_stream_write(RemotePortDevice *obj, struct rp_pkt *pkt) +{ + RemotePortStream *s =3D REMOTE_PORT_STREAM(obj); + + trace_remote_port_stream_rx_busaccess(rp_cmd_to_string(pkt->hdr.cmd), + pkt->hdr.id, pkt->hdr.flags, pkt->hdr.dev, pkt->busaccess.addr, + pkt->busaccess.len, pkt->busaccess.attributes); + + assert(pkt->busaccess.width =3D=3D 0); + assert(pkt->busaccess.stream_width =3D=3D pkt->busaccess.len); + assert(pkt->busaccess.addr =3D=3D 0); + + if (pkt->hdr.flags & RP_PKT_FLAGS_response) { + /* FXIME - probably need to do syncs and stuff */ + assert(s->rsp_pending); + s->rsp_pending =3D false; + if (s->notify) { + StreamCanPushNotifyFn notify =3D s->notify; + s->notify =3D NULL; + notify(s->notify_opaque); + } + } else { + assert(!s->buf); + s->buf =3D g_memdup(pkt + 1, 4); + s->pkt =3D *pkt; + rp_stream_notify(s); + } +} + +static bool rp_stream_stream_can_push(StreamSink *obj, + StreamCanPushNotifyFn notify, + void *notify_opaque) +{ + RemotePortStream *s =3D REMOTE_PORT_STREAM(obj); + + if (s->rsp_pending) { + s->notify =3D notify; + s->notify_opaque =3D notify_opaque; + return false; + } + return true; +} + +static size_t rp_stream_stream_push(StreamSink *obj, uint8_t *buf, + size_t len, bool eop) +{ + RemotePortStream *s =3D REMOTE_PORT_STREAM(obj); + RemotePortDynPkt rsp; + struct rp_pkt_busaccess_ext_base pkt; + struct rp_encode_busaccess_in in =3D {0}; + uint64_t rp_attr =3D eop ? RP_BUS_ATTR_EOP : 0; + int64_t clk; + int enclen; + + clk =3D rp_normalized_vmclk(s->rp); + + in.cmd =3D RP_CMD_write; + in.id =3D rp_new_id(s->rp); + in.dev =3D s->rp_dev; + in.clk =3D clk; + in.attr =3D rp_attr; + in.size =3D len; + in.stream_width =3D s->stream_width; + enclen =3D rp_encode_busaccess(rp_get_peer(s->rp), &pkt, &in); + + trace_remote_port_stream_tx_busaccess(rp_cmd_to_string(in.cmd), + in.id, in.flags, in.dev, in.addr, in.size, in.attr); + + rp_rsp_mutex_lock(s->rp); + rp_write(s->rp, (void *) &pkt, enclen); + rp_write(s->rp, buf, len); + rsp =3D rp_wait_resp(s->rp); + assert(rsp.pkt->hdr.id =3D=3D be32_to_cpu(pkt.hdr.id)); + + trace_remote_port_stream_rx_busaccess( + rp_cmd_to_string(rsp.pkt->hdr.cmd), rsp.pkt->hdr.id, + rsp.pkt->hdr.flags, rsp.pkt->hdr.dev, rsp.pkt->busaccess.addr, + rsp.pkt->busaccess.len, rsp.pkt->busaccess.attributes); + + rp_dpkt_invalidate(&rsp); + rp_rsp_mutex_unlock(s->rp); + rp_restart_sync_timer(s->rp); + return len; +} + +static void rp_stream_init(Object *obj) +{ + RemotePortStream *s =3D REMOTE_PORT_STREAM(obj); + + object_property_add_link(obj, "axistream-connected", TYPE_STREAM_SINK, + (Object **)&s->tx_dev, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); + object_property_add_link(obj, "rp-adaptor0", "remote-port", + (Object **)&s->rp, + qdev_prop_allow_set_link, + OBJ_PROP_LINK_STRONG); +} + +static Property rp_properties[] =3D { + DEFINE_PROP_UINT32("rp-chan0", RemotePortStream, rp_dev, 0), + DEFINE_PROP_UINT16("stream-width", RemotePortStream, stream_width, 4), +}; + +static void rp_stream_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(oc); + StreamSinkClass *ssc =3D STREAM_SINK_CLASS(oc); + RemotePortDeviceClass *rpdc =3D REMOTE_PORT_DEVICE_CLASS(oc); + + ssc->push =3D rp_stream_stream_push; + ssc->can_push =3D rp_stream_stream_can_push; + device_class_set_props_n(dc, rp_properties, ARRAY_SIZE(rp_properties)); + rpdc->ops[RP_CMD_write] =3D rp_stream_write; +} + +static const TypeInfo rp_stream_info =3D { + .name =3D TYPE_REMOTE_PORT_STREAM, + .parent =3D TYPE_DEVICE, + .instance_size =3D sizeof(RemotePortStream), + .class_init =3D rp_stream_class_init, + .instance_init =3D rp_stream_init, + .interfaces =3D (InterfaceInfo[]) { + { TYPE_STREAM_SINK }, + { TYPE_REMOTE_PORT_DEVICE }, + { }, + } +}; + +static void rp_stream_register_types(void) +{ + type_register_static(&rp_stream_info); +} + +type_init(rp_stream_register_types) diff --git a/hw/core/remote-port.c b/hw/core/remote-port.c new file mode 100644 index 0000000000..b3282b80d3 --- /dev/null +++ b/hw/core/remote-port.c @@ -0,0 +1,986 @@ +/* + * QEMU remote attach + * + * Copyright (c) 2013 Xilinx Inc + * Written by Edgar E. Iglesias + * + * This code is licensed under the GNU GPL. + */ + +#include "qemu/osdep.h" +#include "system/system.h" +#include "system/dma.h" +#include "chardev/char.h" +#include "system/cpus.h" +#include "system/cpu-timers.h" +#include "exec/icount.h" +#include "system/reset.h" +#include "hw/sysbus.h" +#include "hw/hw.h" +#include "hw/ptimer.h" +#include "qemu/sockets.h" +#include "qemu/thread.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "migration/vmstate.h" +#include "hw/qdev-properties.h" +#include "hw/qdev-properties-system.h" +#include "qemu/cutils.h" + +#ifndef _WIN32 +#include +#endif + +#include "hw/fdt_generic_util.h" +#include "hw/remote-port-proto.h" +#include "hw/remote-port-device.h" +#include "hw/remote-port.h" + +#define D(x) +#define SYNCD(x) + +#ifndef REMOTE_PORT_ERR_DEBUG +#define REMOTE_PORT_DEBUG_LEVEL 0 +#else +#define REMOTE_PORT_DEBUG_LEVEL 1 +#endif + +#define DB_PRINT_L(level, ...) do { \ + if (REMOTE_PORT_DEBUG_LEVEL > level) { \ + fprintf(stderr, ": %s: ", __func__); \ + fprintf(stderr, ## __VA_ARGS__); \ + } \ +} while (0); + +#define REMOTE_PORT_CLASS(klass) \ + OBJECT_CLASS_CHECK(RemotePortClass, (klass), TYPE_REMOTE_PORT) + +static bool time_warp_enable =3D true; + +bool rp_time_warp_enable(bool en) +{ + bool ret =3D time_warp_enable; + + time_warp_enable =3D en; + return ret; +} + +static void rp_event_read(void *opaque); +static void sync_timer_hit(void *opaque); +static void syncresp_timer_hit(void *opaque); + +static void rp_pkt_dump(const char *prefix, const char *buf, size_t len) +{ + qemu_hexdump(stdout, prefix, buf, len); +} + +uint32_t rp_new_id(RemotePort *s) +{ + return qatomic_fetch_inc(&s->current_id); +} + +void rp_rsp_mutex_lock(RemotePort *s) +{ + qemu_mutex_lock(&s->rsp_mutex); +} + +void rp_rsp_mutex_unlock(RemotePort *s) +{ + qemu_mutex_unlock(&s->rsp_mutex); +} + +int64_t rp_normalized_vmclk(RemotePort *s) +{ + int64_t clk; + + clk =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + clk -=3D s->peer.clk_base; + return clk; +} + +static void rp_restart_sync_timer_bare(RemotePort *s) +{ + if (!s->do_sync) { + return; + } + + if (s->sync.quantum) { + ptimer_stop(s->sync.ptimer); + ptimer_set_limit(s->sync.ptimer, s->sync.quantum, 1); + ptimer_run(s->sync.ptimer, 1); + } +} + +void rp_restart_sync_timer(RemotePort *s) +{ + if (s->doing_sync) { + return; + } + ptimer_transaction_begin(s->sync.ptimer); + rp_restart_sync_timer_bare(s); + ptimer_transaction_commit(s->sync.ptimer); +} + +static void rp_fatal_error(RemotePort *s, const char *reason) +{ + int64_t clk =3D rp_normalized_vmclk(s); + error_report("%s: %s clk=3D%" PRIu64 " ns\n", s->prefix, reason, clk); + exit(EXIT_FAILURE); +} + +static ssize_t rp_recv(RemotePort *s, void *buf, size_t count) +{ + ssize_t r; + + r =3D qemu_chr_fe_read_all(&s->chr, buf, count); + if (r <=3D 0) { + return r; + } + if (r !=3D count) { + error_report("%s: Bad read, expected %zd but got %zd\n", + s->prefix, count, r); + rp_fatal_error(s, "Bad read"); + } + + return r; +} + +ssize_t rp_write(RemotePort *s, const void *buf, size_t count) +{ + ssize_t r; + + qemu_mutex_lock(&s->write_mutex); + r =3D qemu_chr_fe_write_all(&s->chr, buf, count); + qemu_mutex_unlock(&s->write_mutex); + assert(r =3D=3D count); + if (r <=3D 0) { + error_report("%s: Disconnected r=3D%zd buf=3D%p count=3D%zd\n", + s->prefix, r, buf, count); + rp_fatal_error(s, "Bad write"); + } + return r; +} + +static unsigned int rp_has_work(RemotePort *s) +{ + unsigned int work =3D s->rx_queue.wpos - s->rx_queue.rpos; + return work; +} + +/* Response handling. */ +RemotePortRespSlot *rp_dev_timed_wait_resp(RemotePort *s, uint32_t dev, + uint32_t id, int timems) +{ + int i; + + assert(s->devs[dev]); + + /* Find a free slot. */ + for (i =3D 0; i < ARRAY_SIZE(s->dev_state[dev].rsp_queue); i++) { + if (s->dev_state[dev].rsp_queue[i].used =3D=3D false) { + break; + } + } + + if (i =3D=3D ARRAY_SIZE(s->dev_state[dev].rsp_queue) || + s->dev_state[dev].rsp_queue[i].used =3D=3D true) { + error_report("Number of outstanding transactions exceeded! %d", + RP_MAX_OUTSTANDING_TRANSACTIONS); + rp_fatal_error(s, "Internal error"); + } + + /* Got a slot, fill it in. */ + s->dev_state[dev].rsp_queue[i].id =3D id; + s->dev_state[dev].rsp_queue[i].valid =3D false; + s->dev_state[dev].rsp_queue[i].used =3D true; + + while (!s->dev_state[dev].rsp_queue[i].valid) { + rp_rsp_mutex_unlock(s); + rp_event_read(s); + rp_rsp_mutex_lock(s); + if (s->dev_state[dev].rsp_queue[i].valid) { + break; + } + if (!rp_has_work(s)) { + if (timems) { + if (!qemu_cond_timedwait(&s->progress_cond, &s->rsp_mutex, + timems)) { + /* + * TimeOut! + */ + break; + } + } else { + qemu_cond_wait(&s->progress_cond, &s->rsp_mutex); + } + } + } + return &s->dev_state[dev].rsp_queue[i]; +} + +RemotePortRespSlot *rp_dev_wait_resp(RemotePort *s, uint32_t dev, uint32_t= id) +{ + return rp_dev_timed_wait_resp(s, dev, id, 0); +} + +RemotePortDynPkt rp_wait_resp(RemotePort *s) +{ + while (!rp_dpkt_is_valid(&s->rspqueue)) { + rp_rsp_mutex_unlock(s); + rp_event_read(s); + rp_rsp_mutex_lock(s); + /* Need to recheck the condition with the rsp lock taken. */ + if (rp_dpkt_is_valid(&s->rspqueue)) { + break; + } + D(qemu_log("%s: wait for progress\n", __func__)); + if (!rp_has_work(s)) { + qemu_cond_wait(&s->progress_cond, &s->rsp_mutex); + } + } + return s->rspqueue; +} + +static void rp_cmd_hello(RemotePort *s, struct rp_pkt *pkt) +{ + s->peer.version =3D pkt->hello.version; + if (pkt->hello.version.major !=3D RP_VERSION_MAJOR) { + error_report("remote-port version missmatch remote=3D%d.%d local= =3D%d.%d\n", + pkt->hello.version.major, pkt->hello.version.minor, + RP_VERSION_MAJOR, RP_VERSION_MINOR); + rp_fatal_error(s, "Bad version"); + } + + if (pkt->hello.caps.len) { + void *caps =3D (char *) pkt + pkt->hello.caps.offset; + + rp_process_caps(&s->peer, caps, pkt->hello.caps.len); + } +} + +static void rp_cmd_sync(RemotePort *s, struct rp_pkt *pkt) +{ + size_t enclen; + int64_t clk; + int64_t diff; + + assert(!(pkt->hdr.flags & RP_PKT_FLAGS_response)); + + clk =3D rp_normalized_vmclk(s); + diff =3D pkt->sync.timestamp - clk; + + enclen =3D rp_encode_sync_resp(pkt->hdr.id, pkt->hdr.dev, &s->sync.rsp= .sync, + pkt->sync.timestamp); + assert(enclen =3D=3D sizeof s->sync.rsp.sync); + + /* We have temporarily disabled blocking syncs into QEMU. */ + if (diff <=3D 0LL || true) { + /* We are already a head of time. Respond and issue a sync. */ + SYNCD(printf("%s: sync resp %lu\n", s->prefix, pkt->sync.timestamp= )); + rp_write(s, (void *) &s->sync.rsp, enclen); + return; + } + + SYNCD(printf("%s: delayed sync resp - start diff=3D%ld (ts=3D%lu clk= =3D%lu)\n", + s->prefix, pkt->sync.timestamp - clk, pkt->sync.timestamp, clk)); + + ptimer_transaction_begin(s->sync.ptimer_resp); + ptimer_set_limit(s->sync.ptimer_resp, diff, 1); + ptimer_run(s->sync.ptimer_resp, 1); + s->sync.resp_timer_enabled =3D true; + ptimer_transaction_commit(s->sync.ptimer_resp); +} + +static void rp_say_hello(RemotePort *s) +{ + struct rp_pkt_hello pkt; + uint32_t caps[] =3D { + CAP_BUSACCESS_EXT_BASE, + CAP_BUSACCESS_EXT_BYTE_EN, + CAP_WIRE_POSTED_UPDATES, + CAP_ATS, + }; + size_t len; + + len =3D rp_encode_hello_caps(s->current_id++, 0, &pkt, RP_VERSION_MAJO= R, + RP_VERSION_MINOR, + caps, caps, sizeof caps / sizeof caps[0]); + rp_write(s, (void *) &pkt, len); + + if (sizeof caps) { + rp_write(s, caps, sizeof caps); + } +} + +static void rp_say_sync(RemotePort *s, int64_t clk) +{ + struct rp_pkt_sync pkt; + size_t len; + + len =3D rp_encode_sync(s->current_id++, 0, &pkt, clk); + rp_write(s, (void *) &pkt, len); +} + +static void syncresp_timer_hit(void *opaque) +{ + RemotePort *s =3D REMOTE_PORT(opaque); + + s->sync.resp_timer_enabled =3D false; + SYNCD(printf("%s: delayed sync response - send\n", s->prefix)); + rp_write(s, (void *) &s->sync.rsp, sizeof s->sync.rsp.sync); + memset(&s->sync.rsp, 0, sizeof s->sync.rsp); +} + +static void sync_timer_hit(void *opaque) +{ + RemotePort *s =3D REMOTE_PORT(opaque); + int64_t clk; + RemotePortDynPkt rsp; + + clk =3D rp_normalized_vmclk(s); + if (s->sync.resp_timer_enabled) { + SYNCD(printf("%s: sync while delaying a resp! clk=3D%lu\n", + s->prefix, clk)); + s->sync.need_sync =3D true; + rp_restart_sync_timer_bare(s); + return; + } + + /* Sync. */ + s->doing_sync =3D true; + s->sync.need_sync =3D false; + qemu_mutex_lock(&s->rsp_mutex); + /* Send the sync. */ + rp_say_sync(s, clk); + + SYNCD(printf("%s: syncing wait for resp %lu\n", s->prefix, clk)); + rsp =3D rp_wait_resp(s); + rp_dpkt_invalidate(&rsp); + qemu_mutex_unlock(&s->rsp_mutex); + s->doing_sync =3D false; + + rp_restart_sync_timer_bare(s); +} + +static char *rp_sanitize_prefix(RemotePort *s) +{ + char *sanitized_name; + char *c; + + sanitized_name =3D g_strdup(s->prefix); + for (c =3D sanitized_name; *c !=3D '\0'; c++) { + if (*c =3D=3D '/') + *c =3D '_'; + } + return sanitized_name; +} + +static char *rp_autocreate_chardesc(RemotePort *s, bool server) +{ + char *prefix; + char *chardesc; + int r; + + prefix =3D rp_sanitize_prefix(s); + r =3D asprintf(&chardesc, "unix:%s/qemu-rport-%s%s", + machine_path, prefix, server ? ",wait,server" : ""); + assert(r > 0); + free(prefix); + return chardesc; +} + +static Chardev *rp_autocreate_chardev(RemotePort *s, char *name) +{ + Chardev *chr =3D NULL; + char *chardesc; + char *s_path; + int r; + + r =3D asprintf(&s_path, "%s/qemu-rport-%s", machine_path, + rp_sanitize_prefix(s)); + assert(r > 0); + if (g_file_test(s_path, G_FILE_TEST_EXISTS)) { + chardesc =3D rp_autocreate_chardesc(s, false); + chr =3D qemu_chr_new_noreplay(name, chardesc, false, NULL); + free(chardesc); + } + free(s_path); + + if (!chr) { + chardesc =3D rp_autocreate_chardesc(s, true); + chr =3D qemu_chr_new_noreplay(name, chardesc, false, NULL); + free(chardesc); + } + return chr; +} + +void rp_process(RemotePort *s) +{ + while (true) { + struct rp_pkt *pkt; + unsigned int rpos; + bool actioned =3D false; + RemotePortDevice *dev; + RemotePortDeviceClass *rpdc; + + qemu_mutex_lock(&s->rsp_mutex); + if (!rp_has_work(s)) { + qemu_mutex_unlock(&s->rsp_mutex); + break; + } + rpos =3D s->rx_queue.rpos; + + pkt =3D s->rx_queue.pkt[rpos].pkt; + D(qemu_log("%s: io-thread rpos=3D%d wpos=3D%d cmd=3D%d dev=3D%d\n", + s->prefix, s->rx_queue.rpos, s->rx_queue.wpos, + pkt->hdr.cmd, pkt->hdr.dev)); + + /* To handle recursiveness, we need to advance the index + * index before processing the packet. */ + s->rx_queue.rpos++; + s->rx_queue.rpos %=3D ARRAY_SIZE(s->rx_queue.pkt); + qemu_mutex_unlock(&s->rsp_mutex); + + dev =3D s->devs[pkt->hdr.dev]; + if (dev) { + rpdc =3D REMOTE_PORT_DEVICE_GET_CLASS(dev); + if (rpdc->ops[pkt->hdr.cmd]) { + rpdc->ops[pkt->hdr.cmd](dev, pkt); + actioned =3D true; + } + } + + switch (pkt->hdr.cmd) { + case RP_CMD_sync: + rp_cmd_sync(s, pkt); + break; + default: + assert(actioned); + } + + s->rx_queue.inuse[rpos] =3D false; + qemu_sem_post(&s->rx_queue.sem); + } +} + +static void rp_event_read(void *opaque) +{ + RemotePort *s =3D REMOTE_PORT(opaque); + unsigned char buf[32]; + ssize_t r; + + /* We don't care about the data. Just read it out to clear the event. = */ + do { +#ifdef _WIN32 + r =3D qemu_recv_wrap(s->event.pipe.read, buf, sizeof buf, 0); +#else + r =3D read(s->event.pipe.read, buf, sizeof buf); +#endif + if (r =3D=3D 0) { + return; + } + } while (r =3D=3D sizeof buf || (r < 0 && errno =3D=3D EINTR)); + + rp_process(s); +} + +static void rp_event_notify(RemotePort *s) +{ + unsigned char d =3D 0; + ssize_t r; + +#ifdef _WIN32 + /* Mingw is sensitive about doing write's to socket descriptors. */ + r =3D qemu_send_wrap(s->event.pipe.write, &d, sizeof d, 0); +#else + r =3D qemu_write_full(s->event.pipe.write, &d, sizeof d); +#endif + if (r =3D=3D 0) { + hw_error("%s: pipe closed\n", s->prefix); + } +} + +/* Handover a pkt to CPU or IO-thread context. */ +static void rp_pt_handover_pkt(RemotePort *s, RemotePortDynPkt *dpkt) +{ + bool full; + + /* Take the rsp lock around the wpos update, otherwise + rp_wait_resp will race with us. */ + qemu_mutex_lock(&s->rsp_mutex); + s->rx_queue.wpos++; + s->rx_queue.wpos %=3D ARRAY_SIZE(s->rx_queue.pkt); + smp_mb(); + rp_event_notify(s); + qemu_cond_signal(&s->progress_cond); + qemu_mutex_unlock(&s->rsp_mutex); + + do { + full =3D s->rx_queue.inuse[s->rx_queue.wpos]; + if (full) { + qemu_log("%s: FULL rx queue %d\n", __func__, s->rx_queue.wpos); + if (qemu_sem_timedwait(&s->rx_queue.sem, 2 * 1000) !=3D 0) { +#ifndef _WIN32 + int sval; + +#ifndef CONFIG_SEM_TIMEDWAIT + sval =3D s->rx_queue.sem.count; +#else + sem_getvalue(&s->rx_queue.sem.sem, &sval); +#endif + qemu_log("semwait: %d rpos=3D%u wpos=3D%u\n", sval, + s->rx_queue.rpos, s->rx_queue.wpos); +#endif + qemu_log("Deadlock?\n"); + } + } + } while (full); +} + +static bool rp_pt_cmd_sync(RemotePort *s, struct rp_pkt *pkt) +{ + size_t enclen; + int64_t clk; + int64_t diff =3D 0; + struct rp_pkt rsp; + + assert(!(pkt->hdr.flags & RP_PKT_FLAGS_response)); + + if (use_icount) { + clk =3D rp_normalized_vmclk(s); + diff =3D pkt->sync.timestamp - clk; + } + enclen =3D rp_encode_sync_resp(pkt->hdr.id, pkt->hdr.dev, &rsp.sync, + pkt->sync.timestamp); + assert(enclen =3D=3D sizeof rsp.sync); + + if (!use_icount || diff < s->sync.quantum) { + /* We are still OK. */ + rp_write(s, (void *) &rsp, enclen); + return true; + } + + /* We need IO or CPU thread sync. */ + return false; +} + +static bool rp_pt_process_pkt(RemotePort *s, RemotePortDynPkt *dpkt) +{ + struct rp_pkt *pkt =3D dpkt->pkt; + + D(qemu_log("%s: cmd=3D%x id=3D%d dev=3D%d rsp=3D%d\n", __func__, pkt->= hdr.cmd, + pkt->hdr.id, pkt->hdr.dev, + pkt->hdr.flags & RP_PKT_FLAGS_response)); + + if (pkt->hdr.dev >=3D ARRAY_SIZE(s->devs)) { + /* FIXME: Respond with an error. */ + return true; + } + + if (pkt->hdr.flags & RP_PKT_FLAGS_response) { + uint32_t dev =3D pkt->hdr.dev; + uint32_t id =3D pkt->hdr.id; + int i; + + if (pkt->hdr.flags & RP_PKT_FLAGS_posted) { + printf("Drop response for posted packets\n"); + return true; + } + + qemu_mutex_lock(&s->rsp_mutex); + + /* Try to find a per-device slot first. */ + for (i =3D 0; i < ARRAY_SIZE(s->dev_state[dev].rsp_queue); i++) { + if (s->devs[dev] && s->dev_state[dev].rsp_queue[i].used =3D=3D= true + && s->dev_state[dev].rsp_queue[i].id =3D=3D id) { + break; + } + } + + if (i < ARRAY_SIZE(s->dev_state[dev].rsp_queue)) { + /* Found a per device one. */ + assert(s->dev_state[dev].rsp_queue[i].valid =3D=3D false); + + rp_dpkt_swap(&s->dev_state[dev].rsp_queue[i].rsp, dpkt); + s->dev_state[dev].rsp_queue[i].valid =3D true; + + qemu_cond_signal(&s->progress_cond); + } else { + rp_dpkt_swap(&s->rspqueue, dpkt); + qemu_cond_signal(&s->progress_cond); + } + + qemu_mutex_unlock(&s->rsp_mutex); + return true; + } + + switch (pkt->hdr.cmd) { + case RP_CMD_hello: + rp_cmd_hello(s, pkt); + break; + case RP_CMD_sync: + if (rp_pt_cmd_sync(s, pkt)) { + return true; + } + /* Fall-through. */ + case RP_CMD_read: + case RP_CMD_write: + case RP_CMD_interrupt: + case RP_CMD_ats_req: + case RP_CMD_ats_inv: + rp_pt_handover_pkt(s, dpkt); + break; + default: + assert(0); + break; + } + return false; +} + +static int rp_read_pkt(RemotePort *s, RemotePortDynPkt *dpkt) +{ + struct rp_pkt *pkt =3D dpkt->pkt; + int used; + int r; + + r =3D rp_recv(s, pkt, sizeof pkt->hdr); + if (r <=3D 0) { + return r; + } + used =3D rp_decode_hdr((void *) &pkt->hdr); + assert(used =3D=3D sizeof pkt->hdr); + + if (pkt->hdr.len) { + rp_dpkt_alloc(dpkt, sizeof pkt->hdr + pkt->hdr.len); + /* pkt may move due to realloc. */ + pkt =3D dpkt->pkt; + r =3D rp_recv(s, &pkt->hdr + 1, pkt->hdr.len); + if (r <=3D 0) { + return r; + } + rp_decode_payload(pkt); + } + + return used + r; +} + +static void *rp_protocol_thread(void *arg) +{ + RemotePort *s =3D REMOTE_PORT(arg); + unsigned int i; + int r; + + /* Make sure we have a decent bufsize to start with. */ + rp_dpkt_alloc(&s->rsp, sizeof s->rsp.pkt->busaccess + 1024); + rp_dpkt_alloc(&s->rspqueue, sizeof s->rspqueue.pkt->busaccess + 1024); + for (i =3D 0; i < ARRAY_SIZE(s->rx_queue.pkt); i++) { + rp_dpkt_alloc(&s->rx_queue.pkt[i], + sizeof s->rx_queue.pkt[i].pkt->busaccess + 1024); + s->rx_queue.inuse[i] =3D false; + } + + rp_say_hello(s); + + while (1) { + RemotePortDynPkt *dpkt; + unsigned int wpos =3D s->rx_queue.wpos; + bool handled; + + dpkt =3D &s->rx_queue.pkt[wpos]; + s->rx_queue.inuse[wpos] =3D true; + + r =3D rp_read_pkt(s, dpkt); + if (r <=3D 0) { + /* Disconnected. */ + break; + } + if (0) { + rp_pkt_dump("rport-pkt", (void *) dpkt->pkt, + sizeof dpkt->pkt->hdr + dpkt->pkt->hdr.len); + } + handled =3D rp_pt_process_pkt(s, dpkt); + if (handled) { + s->rx_queue.inuse[wpos] =3D false; + } + } + + if (!s->finalizing) { + rp_fatal_error(s, "Disconnected"); + } + return NULL; +} + +static void rp_reset(DeviceState *dev) +{ + RemotePort *s =3D REMOTE_PORT(dev); + + if (s->reset_done) { + return; + } + + qemu_thread_create(&s->thread, "remote-port", rp_protocol_thread, s, + QEMU_THREAD_JOINABLE); + + rp_restart_sync_timer(s); + s->reset_done =3D true; +} + +static void rp_realize(DeviceState *dev, Error **errp) +{ + RemotePort *s =3D REMOTE_PORT(dev); + int r; + Error *err =3D NULL; + + s->prefix =3D object_get_canonical_path(OBJECT(dev)); + + s->peer.clk_base =3D qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + qemu_mutex_init(&s->write_mutex); + qemu_mutex_init(&s->rsp_mutex); + qemu_cond_init(&s->progress_cond); + + if (!qemu_chr_fe_get_driver(&s->chr)) { + char *name; + Chardev *chr =3D NULL; + static int nr =3D 0; + + r =3D asprintf(&name, "rport%d", nr); + nr++; + assert(r > 0); + + if (s->chrdev_id) { + chr =3D qemu_chr_find(s->chrdev_id); + } + + if (chr) { + /* Found the chardev via commandline */ + } else if (s->chardesc) { + chr =3D qemu_chr_new(name, s->chardesc, NULL); + } else { + if (!machine_path) { + error_report("%s: Missing chardesc prop." + " Forgot -machine-path?\n", + s->prefix); + exit(EXIT_FAILURE); + } + chr =3D rp_autocreate_chardev(s, name); + } + + free(name); + if (!chr) { + error_report("%s: Unable to create remort-port channel %s\n", + s->prefix, s->chardesc); + exit(EXIT_FAILURE); + } + + qdev_prop_set_chr(dev, "chardev", chr); + s->chrdev =3D chr; + } + + /* Force RP sockets into blocking mode since our RP-thread will deal + * with the IO and bypassing QEMUs main-loop. + */ + qemu_chr_fe_set_blocking(&s->chr, true); + +#ifdef _WIN32 + /* Create a socket connection between two sockets. We auto-bind + * and read out the port selected by the kernel. + */ + { + char *name; + SocketAddress *sock; + int port; + int listen_sk; + + sock =3D socket_parse("127.0.0.1:0", &error_abort); + listen_sk =3D socket_listen(sock, 1, &error_abort); + + if (s->event.pipe.read < 0) { + perror("socket read"); + exit(EXIT_FAILURE); + } + + { + struct sockaddr_in saddr; + socklen_t slen =3D sizeof saddr; + int r; + + r =3D getsockname(listen_sk, (struct sockaddr *) &saddr, &slen= ); + if (r < 0) { + perror("getsockname"); + exit(EXIT_FAILURE); + } + port =3D htons(saddr.sin_port); + } + + name =3D g_strdup_printf("127.0.0.1:%d", port); + s->event.pipe.write =3D inet_connect(name, &error_abort); + g_free(name); + if (s->event.pipe.write < 0) { + perror("socket write"); + exit(EXIT_FAILURE); + } + + for (;;) { + struct sockaddr_in saddr; + socklen_t slen =3D sizeof saddr; + int fd; + + slen =3D sizeof(saddr); + fd =3D qemu_accept(listen_sk, (struct sockaddr *)&saddr, &slen= ); + if (fd < 0 && errno !=3D EINTR) { + close(listen_sk); + return; + } else if (fd >=3D 0) { + close(listen_sk); + s->event.pipe.read =3D fd; + break; + } + } + + if (!qemu_set_blocking(s->event.pipe.read, false, &err)) { + error_report("%s: Unable to set non-block for internal pipes\n= ", + s->prefix); + exit(EXIT_FAILURE); + } + qemu_set_fd_handler(s->event.pipe.read, rp_event_read, NULL, s); + } +#else + if (!g_unix_open_pipe(s->event.pipes, FD_CLOEXEC, NULL)) { + error_report("%s: Unable to create remort-port internal pipes\n", + s->prefix); + exit(EXIT_FAILURE); + } + + if (!qemu_set_blocking(s->event.pipe.read, false, &err)) { + error_report("%s: Unable to set non-block for internal pipes\n", + s->prefix); + exit(EXIT_FAILURE); + } + + qemu_set_fd_handler(s->event.pipe.read, rp_event_read, NULL, s); +#endif + + + /* Pick up the quantum from the local property setup. + After config negotiation with the peer, sync.quantum value might + change. */ + s->sync.quantum =3D s->peer.local_cfg.quantum; + + s->sync.ptimer =3D ptimer_init(sync_timer_hit, s, PTIMER_POLICY_LEGACY= ); + s->sync.ptimer_resp =3D ptimer_init(syncresp_timer_hit, s, + PTIMER_POLICY_LEGACY); + + /* The Sync-quantum is expressed in nano-seconds. */ + ptimer_transaction_begin(s->sync.ptimer); + ptimer_set_freq(s->sync.ptimer, 1000 * 1000 * 1000); + ptimer_transaction_commit(s->sync.ptimer); + + ptimer_transaction_begin(s->sync.ptimer_resp); + ptimer_set_freq(s->sync.ptimer_resp, 1000 * 1000 * 1000); + ptimer_transaction_commit(s->sync.ptimer_resp); + + qemu_sem_init(&s->rx_queue.sem, ARRAY_SIZE(s->rx_queue.pkt) - 1); +} + +static void rp_unrealize(DeviceState *dev) +{ + RemotePort *s =3D REMOTE_PORT(dev); + + s->finalizing =3D true; + + /* Unregister handler. */ + qemu_set_fd_handler(s->event.pipe.read, NULL, NULL, s); + + info_report("%s: Wait for remote-port to disconnect\n", s->prefix); + qemu_chr_fe_disconnect(&s->chr); + qemu_thread_join(&s->thread); + + close(s->event.pipe.read); + close(s->event.pipe.write); + object_unparent(OBJECT(s->chrdev)); +} + +static const VMStateDescription vmstate_rp =3D { + .name =3D TYPE_REMOTE_PORT, + .version_id =3D 1, + .minimum_version_id =3D 1, + .fields =3D (VMStateField[]) { + VMSTATE_END_OF_LIST(), + } +}; + +static Property rp_properties[] =3D { + DEFINE_PROP_CHR("chardev", RemotePort, chr), + DEFINE_PROP_STRING("chardesc", RemotePort, chardesc), + DEFINE_PROP_STRING("chrdev-id", RemotePort, chrdev_id), + DEFINE_PROP_BOOL("sync", RemotePort, do_sync, false), + DEFINE_PROP_UINT64("sync-quantum", RemotePort, peer.local_cfg.quantum, + 1000000), +}; + +static void rp_init(Object *obj) +{ + RemotePort *s =3D REMOTE_PORT(obj); + int t; + int i; + + for (i =3D 0; i < REMOTE_PORT_MAX_DEVS; ++i) { + char *name =3D g_strdup_printf("remote-port-dev%d", i); + object_property_add_link(obj, name, TYPE_REMOTE_PORT_DEVICE, + (Object **)&s->devs[i], + qdev_prop_allow_set_link, + OBJ_PROP_LINK_STRONG); + g_free(name); + + + for (t =3D 0; t < RP_MAX_OUTSTANDING_TRANSACTIONS; t++) { + s->dev_state[i].rsp_queue[t].used =3D false; + s->dev_state[i].rsp_queue[t].valid =3D false; + rp_dpkt_alloc(&s->dev_state[i].rsp_queue[t].rsp, + sizeof s->dev_state[i].rsp_queue[t].rsp.pkt->busaccess + 10= 24); + } + } +} + +struct rp_peer_state *rp_get_peer(RemotePort *s) +{ + return &s->peer; +} + +static void rp_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc =3D DEVICE_CLASS(klass); + + dc->legacy_reset =3D rp_reset; + dc->realize =3D rp_realize; + dc->unrealize =3D rp_unrealize; + dc->vmsd =3D &vmstate_rp; + device_class_set_props_n(dc, rp_properties, ARRAY_SIZE(rp_properties)); +} + +static const TypeInfo rp_info =3D { + .name =3D TYPE_REMOTE_PORT, + .parent =3D TYPE_DEVICE, + .instance_size =3D sizeof(RemotePort), + .instance_init =3D rp_init, + .class_init =3D rp_class_init, + .interfaces =3D (InterfaceInfo[]) { + { }, + }, +}; + +static const TypeInfo rp_device_info =3D { + .name =3D TYPE_REMOTE_PORT_DEVICE, + .parent =3D TYPE_INTERFACE, + .class_size =3D sizeof(RemotePortDeviceClass), +}; + +static void rp_register_types(void) +{ + type_register_static(&rp_info); + type_register_static(&rp_device_info); +} + +type_init(rp_register_types) diff --git a/hw/core/trace-events b/hw/core/trace-events index 2cf085ac66..a1472ab2b2 100644 --- a/hw/core/trace-events +++ b/hw/core/trace-events @@ -32,3 +32,20 @@ clock_set_mul_div(const char *clk, uint32_t oldmul, uint= 32_t mul, uint32_t olddi =20 # cpu-common.c cpu_reset(int cpu_index) "%d" + +# remote-port-memory-master.c +remote_port_memory_master_tx_busaccess(const char *cmd, uint32_t id, uint3= 2_t flags, uint32_t dev, uint64_t addr, uint32_t len, uint64_t attr) "cmd= =3D%s, id=3D0x%"PRIx32", flags=3D0x%"PRIx32", dev=3D0x%"PRIx32", addr=3D0x%= "PRIx64", len=3D0x%"PRIx32", attr=3D0x%"PRIx64 +remote_port_memory_master_rx_busaccess(const char *cmd, uint32_t id, uint3= 2_t flags, uint32_t dev, uint64_t addr, uint32_t len, uint64_t attr) "cmd= =3D%s, id=3D0x%"PRIx32", flags=3D0x%"PRIx32", dev=3D0x%"PRIx32", addr=3D0x%= "PRIx64", len=3D0x%"PRIx32", attr=3D0x%"PRIx64 + +# remote-port-memory-slave.c +remote_port_memory_slave_tx_busaccess(const char *cmd, uint32_t id, uint32= _t flags, uint32_t dev, uint64_t addr, uint32_t len, uint64_t attr) "cmd=3D= %s, id=3D0x%"PRIx32", flags=3D0x%"PRIx32", dev=3D0x%"PRIx32", addr=3D0x%"PR= Ix64", len=3D0x%"PRIx32", attr=3D0x%"PRIx64 +remote_port_memory_slave_rx_busaccess(const char *cmd, uint32_t id, uint32= _t flags, uint32_t dev, uint64_t addr, uint32_t len, uint64_t attr) "cmd=3D= %s, id=3D0x%"PRIx32", flags=3D0x%"PRIx32", dev=3D0x%"PRIx32", addr=3D0x%"PR= Ix64", len=3D0x%"PRIx32", attr=3D0x%"PRIx64 + +# remote-port-memory-gpio.c +remote_port_gpio_tx_interrupt(uint32_t id, uint32_t flags, uint32_t dev, u= int64_t vector, uint32_t irq, uint32_t val) "id=3D0x%"PRIx32", flags=3D0x%"= PRIx32", dev=3D0x%"PRIx32", vector=3D0x%"PRIx64", irq=3D0x%"PRIx32", level= =3D0x%"PRIx32 +remote_port_gpio_rx_interrupt(uint32_t id, uint32_t flags, uint32_t dev, u= int64_t vector, uint32_t irq, uint32_t val) "id=3D0x%"PRIx32", flags=3D0x%"= PRIx32", dev=3D0x%"PRIx32", vector=3D0x%"PRIx64", irq=3D0x%"PRIx32", level= =3D0x%"PRIx32 + +# remote-port-stream.c +remote_port_stream_tx_busaccess(const char *cmd, uint32_t id, uint32_t fla= gs, uint32_t dev, uint64_t addr, uint32_t len, uint64_t attr) "cmd=3D%s, i= d=3D0x%"PRIx32", flags=3D0x%"PRIx32", dev=3D0x%"PRIx32", addr=3D0x%"PRIx64"= , len=3D0x%"PRIx32", attr=3D0x%"PRIx64 +remote_port_stream_rx_busaccess(const char *cmd, uint32_t id, uint32_t fla= gs, uint32_t dev, uint64_t addr, uint32_t len, uint64_t attr) "cmd=3D%s, i= d=3D0x%"PRIx32", flags=3D0x%"PRIx32", dev=3D0x%"PRIx32", addr=3D0x%"PRIx64"= , len=3D0x%"PRIx32", attr=3D0x%"PRIx64 + diff --git a/hw/misc/meson.build b/hw/misc/meson.build index b1d8d8e5d2..371af17c41 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -8,6 +8,7 @@ system_ss.add(when: 'CONFIG_UNIMP', if_true: files('unimp.c= ')) system_ss.add(when: 'CONFIG_EMPTY_SLOT', if_true: files('empty_slot.c')) system_ss.add(when: 'CONFIG_LED', if_true: files('led.c')) system_ss.add(when: 'CONFIG_PVPANIC_COMMON', if_true: files('pvpanic.c')) +system_ss.add(files('xlnx-serbs.c')) =20 # ARM devices system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c')) diff --git a/hw/misc/xlnx-serbs.c b/hw/misc/xlnx-serbs.c new file mode 100644 index 0000000000..1b1f2e5bd6 --- /dev/null +++ b/hw/misc/xlnx-serbs.c @@ -0,0 +1,43 @@ +/* + * Xilinx Versal SERBS + * + * SPDX-FileCopyrightText: 2023 AMD + * SPDX-FileContributor: Author: Sai Pavan Boddu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/xlnx-serbs.h" + +void xlnx_serbs_if_timer_config(xlnx_serbs_if *sif, int id, + int timems, bool enable) +{ + xlnx_serbs_if_class *Klass =3D XLNX_SERBS_IF_GET_CLASS(sif); + + if (Klass->timer_config) { + Klass->timer_config(sif, id, timems, enable); + } +} + +void xlnx_serbs_if_timeout_set(xlnx_serbs_if *sif, int id, bool level) +{ + xlnx_serbs_if_class *Klass =3D XLNX_SERBS_IF_GET_CLASS(sif); + + if (Klass->timeout_set) { + Klass->timeout_set(sif, id, level); + } +} + +static const TypeInfo serbs_if_info =3D { + .name =3D TYPE_XLNX_SERBS_IF, + .parent =3D TYPE_INTERFACE, + .class_size =3D sizeof(xlnx_serbs_if_class), +}; + +static void serbs_if_types(void) +{ + type_register_static(&serbs_if_info); +} + +type_init(serbs_if_types); diff --git a/include/chardev/char-fe.h b/include/chardev/char-fe.h index 5f8a6df17d..5a2e3d60ee 100644 --- a/include/chardev/char-fe.h +++ b/include/chardev/char-fe.h @@ -179,6 +179,8 @@ void qemu_chr_fe_printf(CharFrontend *c, const char *fm= t, ...) G_GNUC_PRINTF(2, 3); =20 =20 +void qemu_chr_fe_set_blocking(CharFrontend *be, bool blocking); + /** * FEWatchFunc: a #GSourceFunc called when any conditions requested by * qemu_chr_fe_add_watch() is satisfied. diff --git a/include/chardev/char.h b/include/chardev/char.h index b65e9981c1..1dd2e7741a 100644 --- a/include/chardev/char.h +++ b/include/chardev/char.h @@ -305,6 +305,8 @@ struct ChardevClass { /* notify the backend of frontend open state */ void (*chr_set_fe_open)(Chardev *chr, int fe_open); =20 + void (*chr_set_blocking)(Chardev *chr, bool blocking); + /* handle various events */ void (*chr_be_event)(Chardev *s, QEMUChrEvent event); }; diff --git a/include/hw/misc/xlnx-serbs.h b/include/hw/misc/xlnx-serbs.h new file mode 100644 index 0000000000..ecf839acb3 --- /dev/null +++ b/include/hw/misc/xlnx-serbs.h @@ -0,0 +1,36 @@ +/* + * Xilinx Versal SERBS + * + * SPDX-FileCopyrightText: 2023 AMD + * SPDX-FileContributor: Author: Sai Pavan Boddu + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef XLNX_VERSAL_SERBS_H +#define XLNX_VERSAL_SERBS_H + +#include "qom/object.h" +#define TYPE_XLNX_SERBS_IF "xlnx-serbs-if" + +typedef struct xlnx_serbs_if_class xlnx_serbs_if_class; +DECLARE_CLASS_CHECKERS(xlnx_serbs_if_class, XLNX_SERBS_IF, TYPE_XLNX_SERBS= _IF) + +#define XLNX_SERBS_IF(obj) \ + INTERFACE_CHECK(xlnx_serbs_if, (obj), TYPE_XLNX_SERBS_IF) + +typedef struct xlnx_serbs_if { + Object Parent; +} xlnx_serbs_if; + +typedef struct xlnx_serbs_if_class { + InterfaceClass parent; + + void (*timer_config)(xlnx_serbs_if *sif, int id, int timems, bool enab= le); + void (*timeout_set)(xlnx_serbs_if *sif, int id, bool level); +} xlnx_serbs_if_class; + +void xlnx_serbs_if_timer_config(xlnx_serbs_if *sif, int id, int timems, + bool enable); +void xlnx_serbs_if_timeout_set(xlnx_serbs_if *sif, int id, bool level); +#endif diff --git a/include/hw/qdev-properties.h b/include/hw/qdev-properties.h index 8553abdda1..07f88513a4 100644 --- a/include/hw/qdev-properties.h +++ b/include/hw/qdev-properties.h @@ -269,6 +269,8 @@ void qdev_alias_all_properties(DeviceState *target, Obj= ect *source); void qdev_prop_set_after_realize(DeviceState *dev, const char *name, Error **errp); =20 +void qdev_prop_allow_set_link(const Object *obj, const char *name, + Object *val, Error **errp); /** * qdev_prop_allow_set_link_before_realize: * diff --git a/include/hw/remote-port-ats.h b/include/hw/remote-port-ats.h new file mode 100644 index 0000000000..178e51d52a --- /dev/null +++ b/include/hw/remote-port-ats.h @@ -0,0 +1,74 @@ +/* + * QEMU remote port ATS + * + * Copyright (c) 2021 Xilinx Inc + * Written by Francisco Iglesias + * + * Permission is hereby granted, free of charge, to any person obtaining a= copy + * of this software and associated documentation files (the "Software"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * 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 OT= HER + * 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. + */ +#ifndef REMOTE_PORT_ATS_H +#define REMOTE_PORT_ATS_H + +#include "hw/remote-port.h" + +#define TYPE_REMOTE_PORT_ATS "remote-port-ats" +#define REMOTE_PORT_ATS(obj) \ + OBJECT_CHECK(RemotePortATS, (obj), TYPE_REMOTE_PORT_ATS) + +typedef struct { + /* private */ + SysBusDevice parent; + + /* public */ + struct RemotePort *rp; + struct rp_peer_state *peer; + MemoryRegion *mr; + AddressSpace as; + RemotePortDynPkt rsp; + GArray *iommu_notifiers; + uint32_t rp_dev; + GArray *cache; /* Translation cache */ +} RemotePortATS; + +#define TYPE_REMOTE_PORT_ATS_CACHE "remote-port-ats-cache" + +#define REMOTE_PORT_ATS_CACHE_CLASS(klass) \ + OBJECT_CLASS_CHECK(RemotePortATSCacheClass, (klass), \ + TYPE_REMOTE_PORT_ATS_CACHE) +#define REMOTE_PORT_ATS_CACHE_GET_CLASS(obj) \ + OBJECT_GET_CLASS(RemotePortATSCacheClass, (obj), TYPE_REMOTE_PORT_ATS_= CACHE) +#define REMOTE_PORT_ATS_CACHE(obj) \ + INTERFACE_CHECK(RemotePortATSCache, (obj), TYPE_REMOTE_PORT_ATS_CACHE) + +typedef struct RemotePortATSCache { + Object Parent; +} RemotePortATSCache; + +typedef struct RemotePortATSCacheClass { + InterfaceClass parent; + + IOMMUTLBEntry *(*lookup_translation)(RemotePortATSCache *cache, + hwaddr translated_addr, + hwaddr len); +} RemotePortATSCacheClass; + +IOMMUTLBEntry *rp_ats_cache_lookup_translation(RemotePortATSCache *cache, + hwaddr translated_addr, + hwaddr len); +#endif diff --git a/include/hw/remote-port-device.h b/include/hw/remote-port-devic= e.h new file mode 100644 index 0000000000..9b031e5e87 --- /dev/null +++ b/include/hw/remote-port-device.h @@ -0,0 +1,56 @@ +#ifndef REMOTE_PORT_DEVICE_H +#define REMOTE_PORT_DEVICE_H + +#include "qemu/help-texts.h" +#include "qom/object.h" +#include "hw/remote-port-proto.h" + +#define TYPE_REMOTE_PORT_DEVICE "remote-port-device" + +#define REMOTE_PORT_DEVICE_CLASS(klass) \ + OBJECT_CLASS_CHECK(RemotePortDeviceClass, (klass), TYPE_REMOTE_PORT_D= EVICE) +#define REMOTE_PORT_DEVICE_GET_CLASS(obj) \ + OBJECT_GET_CLASS(RemotePortDeviceClass, (obj), TYPE_REMOTE_PORT_DEVICE) +#define REMOTE_PORT_DEVICE(obj) \ + INTERFACE_CHECK(RemotePortDevice, (obj), TYPE_REMOTE_PORT_DEVICE) + +typedef struct RemotePort RemotePort; + +typedef struct RemotePortDevice { + /*< private >*/ + Object parent_obj; +} RemotePortDevice; + +typedef struct RemotePortDeviceClass { + /*< private >*/ + InterfaceClass parent_class; + + /*< public >*/ + /** + * ops - operations to perform when remote port packets are recieved f= or + * this device. Function N will be called for a remote port packet with + * cmd =3D=3D N in the header. + * + * @obj - Remote port device to recieve packet + * @pkt - remote port packets + */ + + void (*ops[RP_CMD_max+1])(RemotePortDevice *obj, struct rp_pkt *pkt); + +} RemotePortDeviceClass; + +uint32_t rp_new_id(RemotePort *s); +/* FIXME: Cleanup and reduce the API complexity for dealing with responses= . */ +void rp_rsp_mutex_lock(RemotePort *s); +void rp_rsp_mutex_unlock(RemotePort *s); +void rp_restart_sync_timer(RemotePort *s); + +ssize_t rp_write(RemotePort *s, const void *buf, size_t count); + +RemotePortDynPkt rp_wait_resp(RemotePort *s); + +int64_t rp_normalized_vmclk(RemotePort *s); + +struct rp_peer_state *rp_get_peer(RemotePort *s); + +#endif diff --git a/include/hw/remote-port-gpio.h b/include/hw/remote-port-gpio.h new file mode 100644 index 0000000000..3a0c7c0a6e --- /dev/null +++ b/include/hw/remote-port-gpio.h @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2013 Xilinx Inc + * Written by Edgar E. Iglesias + * Written by Peter Crosthwaite + * + * This code is licensed under the GNU GPL. + */ +#ifndef REMOTE_PORT_GPIO_H +#define REMOTE_PORT_GPIO_H + +#define TYPE_REMOTE_PORT_GPIO "remote-port-gpio" +#define REMOTE_PORT_GPIO(obj) \ + OBJECT_CHECK(RemotePortGPIO, (obj), TYPE_REMOTE_PORT_GPIO) + +#define MAX_GPIOS 960 + +typedef struct RemotePortGPIO { + /* private */ + SysBusDevice parent; + /* public */ + + int8_t cache[MAX_GPIOS]; + uint32_t num_gpios; + qemu_irq *gpio_out; + uint16_t cell_offset_irq_num; + + bool posted_updates; + uint32_t rp_dev; + struct RemotePort *rp; + struct rp_peer_state *peer; +} RemotePortGPIO; +#endif diff --git a/include/hw/remote-port-memory-master.h b/include/hw/remote-por= t-memory-master.h new file mode 100644 index 0000000000..cbdcd72d29 --- /dev/null +++ b/include/hw/remote-port-memory-master.h @@ -0,0 +1,62 @@ +/* + * QEMU remote port memory master. Read and write transactions + * recieved from QEMU are transmitted over remote-port. + * + * Copyright (c) 2020 Xilinx Inc + * Written by Edgar E. Iglesias + * + * This code is licensed under the GNU GPL. + */ +#ifndef REMOTE_PORT_MEMORY_MASTER_H +#define REMOTE_PORT_MEMORY_MASTER_H + +#include "hw/remote-port.h" +#include "hw/misc/xlnx-serbs.h" + +#define TYPE_REMOTE_PORT_MEMORY_MASTER "remote-port-memory-master" +#define REMOTE_PORT_MEMORY_MASTER(obj) \ + OBJECT_CHECK(RemotePortMemoryMaster, (obj), \ + TYPE_REMOTE_PORT_MEMORY_MASTER) + +typedef struct RemotePortMemoryMaster RemotePortMemoryMaster; + +typedef struct RemotePortMap { + void *parent; + MemoryRegion iomem; + uint32_t rp_dev; + uint64_t offset; +} RemotePortMap; + +struct RemotePortMemoryMaster { + /* private */ + SysBusDevice parent; + + MemoryRegionOps *rp_ops; + RemotePortMap *mmaps; + xlnx_serbs_if *serbsIf; + + /* public */ + uint32_t map_num; + uint64_t map_offset; + uint64_t map_size; + uint32_t rp_dev; + bool relative; + uint32_t max_access_size; + struct RemotePort *rp; + struct rp_peer_state *peer; + int rp_timeout; + int serbs_id; + bool rp_timeout_err; +}; + +MemTxResult rp_mm_access(RemotePort *rp, uint32_t rp_dev, + struct rp_peer_state *peer, + MemoryTransaction *tr, + bool relative, uint64_t offset); + +MemTxResult rp_mm_access_with_def_attr(RemotePort *rp, uint32_t rp_dev, + struct rp_peer_state *peer, + MemoryTransaction *tr, + bool relative, uint64_t offset, + uint32_t def_attr); +#endif diff --git a/include/hw/remote-port-memory-slave.h b/include/hw/remote-port= -memory-slave.h new file mode 100644 index 0000000000..f0b75f90f4 --- /dev/null +++ b/include/hw/remote-port-memory-slave.h @@ -0,0 +1,33 @@ +/* + * QEMU remote port memory slave. Read and write transactions + * recieved from the remote port are translated into an address space. + * + * Copyright (c) 2013 Xilinx Inc + * Written by Edgar E. Iglesias + * Written by Peter Crosthwaite + * + * This code is licensed under the GNU GPL. + */ +#ifndef REMOTE_PORT_MEMORY_SLAVE_H +#define REMOTE_PORT_MEMORY_SLAVE_H + +#include "hw/remote-port-ats.h" + +#define TYPE_REMOTE_PORT_MEMORY_SLAVE "remote-port-memory-slave" +#define REMOTE_PORT_MEMORY_SLAVE(obj) \ + OBJECT_CHECK(RemotePortMemorySlave, (obj), \ + TYPE_REMOTE_PORT_MEMORY_SLAVE) + +typedef struct RemotePortMemorySlave { + /* private */ + SysBusDevice parent; + /* public */ + struct RemotePort *rp; + struct rp_peer_state *peer; + MemoryRegion *mr; + AddressSpace as; + MemTxAttrs attr; + RemotePortDynPkt rsp; + RemotePortATSCache *ats_cache; +} RemotePortMemorySlave; +#endif diff --git a/include/hw/remote-port-proto.h b/include/hw/remote-port-proto.h new file mode 100644 index 0000000000..28bf538497 --- /dev/null +++ b/include/hw/remote-port-proto.h @@ -0,0 +1,537 @@ +/* + * QEMU remote port protocol parts. + * + * Copyright (c) 2013 Xilinx Inc + * Written by Edgar E. Iglesias + * + * Permission is hereby granted, free of charge, to any person obtaining a= copy + * of this software and associated documentation files (the "Software"), t= o deal + * in the Software without restriction, including without limitation the r= ights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or se= ll + * 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 OT= HER + * 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. + */ +#ifndef REMOTE_PORT_PROTO_H__ +#define REMOTE_PORT_PROTO_H__ + +#include +#include + +/* + * Remote-Port (RP) is an inter-simulator protocol. It assumes a reliable + * point to point communcation with the remote simulation environment. + * + * Setup + * In the SETUP phase a mandatory HELLO packet is exchanged with optional + * CFG packets following. HELLO packets are useful to ensure that both + * sides are speaking the same protocol and using compatible versions. + * + * CFG packets are used to negotiate configuration options. At the moment + * these remain unimplemented. + * + * Once the session is up, communication can start through various other + * commands. The list can be found further down this document. + * Commands are carried over RP packets. Every RP packet contains a header + * with length, flags and an ID to track potential responses. + * The header is followed by a packet specific payload. You'll find the + * details of the various commands packet layouts here. Some commands can + * carry data/blobs in their payload. + */ + + +#define RP_VERSION_MAJOR 4 +#define RP_VERSION_MINOR 3 + +#if defined(_WIN32) && defined(__MINGW32__) +/* mingw GCC has a bug with packed attributes. */ +#define PACKED __attribute__ ((gcc_struct, packed)) +#else +#define PACKED __attribute__ ((packed)) +#endif + +/* Could be auto generated. */ +enum rp_cmd { + RP_CMD_nop =3D 0, + RP_CMD_hello =3D 1, + RP_CMD_cfg =3D 2, + RP_CMD_read =3D 3, + RP_CMD_write =3D 4, + RP_CMD_interrupt =3D 5, + RP_CMD_sync =3D 6, + RP_CMD_ats_req =3D 7, + RP_CMD_ats_inv =3D 8, + RP_CMD_max =3D 8 +}; + +enum { + RP_OPT_quantum =3D 0, +}; + +struct rp_cfg_state { + uint64_t quantum; +}; + +enum { + RP_PKT_FLAGS_optional =3D 1 << 0, + RP_PKT_FLAGS_response =3D 1 << 1, + + /* Posted hint. + * When set this means that the receiver is not required to respond to + * the message. Since it's just a hint, the sender must be prepared to + * drop responses. Note that since flags are echoed back in responses + * a response to a posted packet will be easy to identify early in the + * protocol stack. + */ + RP_PKT_FLAGS_posted =3D 1 << 2, +}; + +struct rp_pkt_hdr { + uint32_t cmd; + uint32_t len; + uint32_t id; + uint32_t flags; + uint32_t dev; +} PACKED; + +struct rp_pkt_cfg { + struct rp_pkt_hdr hdr; + uint32_t opt; + uint8_t set; +} PACKED; + +struct rp_version { + uint16_t major; + uint16_t minor; +} PACKED; + +struct rp_capabilities { + /* Offset from start of packet. */ + uint32_t offset; + uint16_t len; + uint16_t reserved0; +} PACKED; + +enum { + CAP_BUSACCESS_EXT_BASE =3D 1, /* New header layout. */ + CAP_BUSACCESS_EXT_BYTE_EN =3D 2, /* Support for Byte Enables. */ + + /* + * Originally, all interrupt/wire updates over remote-port were posted. + * This turned out to be a bad idea. To fix it without breaking backwa= rds + * compatibility, we add the WIRE Posted updates capability. + * + * If the peer supportes this, it will respect the RP_PKT_FLAGS_posted + * flag. If the peer doesn't support this capability, senders need to + * be aware that the peer will not respond to wire updates regardless + * of the posted header-flag. + */ + CAP_WIRE_POSTED_UPDATES =3D 3, + + CAP_ATS =3D 4, /* Address translation services */ +}; + +struct rp_pkt_hello { + struct rp_pkt_hdr hdr; + struct rp_version version; + struct rp_capabilities caps; +} PACKED; + +enum { + /* Remote port responses. */ + RP_RESP_OK =3D 0x0, + RP_RESP_BUS_GENERIC_ERROR =3D 0x1, + RP_RESP_ADDR_ERROR =3D 0x2, + RP_RESP_MAX =3D 0xF, +}; + +enum { + RP_BUS_ATTR_EOP =3D (1 << 0), + RP_BUS_ATTR_SECURE =3D (1 << 1), + RP_BUS_ATTR_EXT_BASE =3D (1 << 2), + RP_BUS_ATTR_PHYS_ADDR =3D (1 << 3), + + /* + * The access targets the I/O address space. + */ + RP_BUS_ATTR_IO_ACCESS =3D (1 << 4), + + /* + * Bits [11:8] are allocated for storing transaction response codes. + * These new response codes are backward compatible as existing + * implementations will not set/read these bits. + * For existing implementations, these bits will be zero which is RESP= _OKAY. + */ + RP_BUS_RESP_SHIFT =3D 8, + RP_BUS_RESP_MASK =3D (RP_RESP_MAX << RP_BUS_RESP_SHIFT), +}; + +struct rp_pkt_busaccess { + struct rp_pkt_hdr hdr; + uint64_t timestamp; + uint64_t attributes; + uint64_t addr; + + /* Length in bytes. */ + uint32_t len; + + /* Width of each beat in bytes. Set to zero for unknown (let the remote + side choose). */ + uint32_t width; + + /* Width of streaming, must be a multiple of width. + addr should repeat itself around this width. Set to same as len + for incremental (normal) accesses. In bytes. */ + uint32_t stream_width; + + /* Implementation specific source or master-id. */ + uint16_t master_id; +} PACKED; + + +/* This is the new extended busaccess packet layout. */ +struct rp_pkt_busaccess_ext_base { + struct rp_pkt_hdr hdr; + uint64_t timestamp; + uint64_t attributes; + uint64_t addr; + + /* Length in bytes. */ + uint32_t len; + + /* Width of each beat in bytes. Set to zero for unknown (let the remote + side choose). */ + uint32_t width; + + /* Width of streaming, must be a multiple of width. + addr should repeat itself around this width. Set to same as len + for incremental (normal) accesses. In bytes. */ + uint32_t stream_width; + + /* Implementation specific source or master-id. */ + uint16_t master_id; + /* ---- End of 4.0 base busaccess. ---- */ + + uint16_t master_id_31_16; /* MasterID bits [31:16]. */ + uint32_t master_id_63_32; /* MasterID bits [63:32]. */ + /* --------------------------------------------------- + * Since hdr is 5 x 32bit, we are now 64bit aligned. */ + + uint32_t data_offset; /* Offset to data from start of pkt. */ + uint32_t next_offset; /* Offset to next extension. 0 if none. */ + + uint32_t byte_enable_offset; + uint32_t byte_enable_len; + + /* ---- End of CAP_BUSACCESS_EXT_BASE. ---- */ + + /* If new features are needed that may always occupy space + * in the header, then add a new capability and extend the + * this area with new fields. + * Will help receivers find data_offset and next offset, + * even those that don't know about extended fields. + */ +} PACKED; + +struct rp_pkt_interrupt { + struct rp_pkt_hdr hdr; + uint64_t timestamp; + uint64_t vector; + uint32_t line; + uint8_t val; +} PACKED; + +struct rp_pkt_sync { + struct rp_pkt_hdr hdr; + uint64_t timestamp; +} PACKED; + +enum { + RP_ATS_ATTR_exec =3D 1 << 0, + RP_ATS_ATTR_read =3D 1 << 1, + RP_ATS_ATTR_write =3D 1 << 2, +}; + +enum { + RP_ATS_RESULT_ok =3D 0, + RP_ATS_RESULT_error =3D 1, +}; + +struct rp_pkt_ats { + struct rp_pkt_hdr hdr; + uint64_t timestamp; + uint64_t attributes; + uint64_t addr; + uint64_t len; + uint32_t result; + uint64_t reserved0; + uint64_t reserved1; + uint64_t reserved2; + uint64_t reserved3; +} PACKED; + +struct rp_pkt { + union { + struct rp_pkt_hdr hdr; + struct rp_pkt_hello hello; + struct rp_pkt_busaccess busaccess; + struct rp_pkt_busaccess_ext_base busaccess_ext_base; + struct rp_pkt_interrupt interrupt; + struct rp_pkt_sync sync; + struct rp_pkt_ats ats; + }; +}; + +struct rp_peer_state { + void *opaque; + + struct rp_pkt pkt; + bool hdr_used; + + struct rp_version version; + + struct { + bool busaccess_ext_base; + bool busaccess_ext_byte_en; + bool wire_posted_updates; + bool ats; + } caps; + + /* Used to normalize our clk. */ + int64_t clk_base; + + struct rp_cfg_state local_cfg; + struct rp_cfg_state peer_cfg; +}; + +const char *rp_cmd_to_string(enum rp_cmd cmd); +int rp_decode_hdr(struct rp_pkt *pkt); +int rp_decode_payload(struct rp_pkt *pkt); + +void rp_encode_hdr(struct rp_pkt_hdr *hdr, + uint32_t cmd, uint32_t id, uint32_t dev, uint32_t len, + uint32_t flags); + +/* + * caps is a an array of supported capabilities by the implementor. + * caps_out is the encoded (network byte order) version of the + * same array. It should be sent to the peer after the hello packet. + */ +size_t rp_encode_hello_caps(uint32_t id, uint32_t dev, struct rp_pkt_hello= *pkt, + uint16_t version_major, uint16_t version_minor, + uint32_t *caps, uint32_t *features_out, + uint32_t features_len); + +/* rp_encode_hello is deprecated in favor of hello_caps. */ +static inline size_t __attribute__ ((deprecated)) +rp_encode_hello(uint32_t id, uint32_t dev, struct rp_pkt_hello *pkt, + uint16_t version_major, uint16_t version_minor) { + return rp_encode_hello_caps(id, dev, pkt, version_major, version_minor, + NULL, NULL, 0); +} + +static inline void *__attribute__ ((deprecated)) +rp_busaccess_dataptr(struct rp_pkt_busaccess *pkt) +{ + /* Right after the packet. */ + return pkt + 1; +} + +/* + * rp_busaccess_rx_dataptr + * + * Predicts the dataptr for a packet to be transmitted. + * This should only be used if you are trying to keep + * the entire packet in a linear buffer. + */ +static inline unsigned char * +rp_busaccess_tx_dataptr(struct rp_peer_state *peer, + struct rp_pkt_busaccess_ext_base *pkt) +{ + unsigned char *p =3D (unsigned char *) pkt; + + if (peer->caps.busaccess_ext_base) { + /* We always put our data right after the header. */ + return p + sizeof *pkt; + } else { + /* Right after the old packet layout. */ + return p + sizeof(struct rp_pkt_busaccess); + } +} + +/* + * rp_busaccess_rx_dataptr + * + * Extracts the dataptr from a received packet. + */ +static inline unsigned char * +rp_busaccess_rx_dataptr(struct rp_peer_state *peer, + struct rp_pkt_busaccess_ext_base *pkt) +{ + unsigned char *p =3D (unsigned char *) pkt; + + if (pkt->attributes & RP_BUS_ATTR_EXT_BASE) { + return p + pkt->data_offset; + } else { + /* Right after the old packet layout. */ + return p + sizeof(struct rp_pkt_busaccess); + } +} + +static inline unsigned char * +rp_busaccess_byte_en_ptr(struct rp_peer_state *peer, + struct rp_pkt_busaccess_ext_base *pkt) +{ + unsigned char *p =3D (unsigned char *) pkt; + + if ((pkt->attributes & RP_BUS_ATTR_EXT_BASE) + && pkt->byte_enable_len) { + assert(pkt->byte_enable_offset >=3D sizeof *pkt); + assert(pkt->byte_enable_offset + pkt->byte_enable_len + <=3D pkt->hdr.len + sizeof pkt->hdr); + return p + pkt->byte_enable_offset; + } + return NULL; +} + +size_t __attribute__ ((deprecated)) +rp_encode_read(uint32_t id, uint32_t dev, + struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t size, + uint32_t width, uint32_t stream_width); + +size_t __attribute__ ((deprecated)) +rp_encode_read_resp(uint32_t id, uint32_t dev, + struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t size, + uint32_t width, uint32_t stream_width); + +size_t __attribute__ ((deprecated)) +rp_encode_write(uint32_t id, uint32_t dev, + struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t size, + uint32_t width, uint32_t stream_width); + +size_t __attribute__ ((deprecated)) +rp_encode_write_resp(uint32_t id, uint32_t dev, + struct rp_pkt_busaccess *pkt, + int64_t clk, uint16_t master_id, + uint64_t addr, uint64_t attr, uint32_t size, + uint32_t width, uint32_t stream_width); + +struct rp_encode_busaccess_in { + uint32_t cmd; + uint32_t id; + uint32_t flags; + uint32_t dev; + int64_t clk; + uint64_t master_id; + uint64_t addr; + uint64_t attr; + uint32_t size; + uint32_t width; + uint32_t stream_width; + uint32_t byte_enable_len; +}; + +/* Prepare encode_busaccess input parameters for a packet response. */ +static inline void +rp_encode_busaccess_in_rsp_init(struct rp_encode_busaccess_in *in, + struct rp_pkt *pkt) { + memset(in, 0, sizeof *in); + in->cmd =3D pkt->hdr.cmd; + in->id =3D pkt->hdr.id; + in->flags =3D pkt->hdr.flags | RP_PKT_FLAGS_response; + in->dev =3D pkt->hdr.dev; + /* FIXME: Propagate all master_id fields? */ + in->master_id =3D pkt->busaccess.master_id; + in->addr =3D pkt->busaccess.addr; + in->size =3D pkt->busaccess.len; + in->width =3D pkt->busaccess.width; + in->stream_width =3D pkt->busaccess.stream_width; + in->byte_enable_len =3D 0; +} +size_t rp_encode_busaccess(struct rp_peer_state *peer, + struct rp_pkt_busaccess_ext_base *pkt, + struct rp_encode_busaccess_in *in); + +size_t rp_encode_interrupt_f(uint32_t id, uint32_t dev, + struct rp_pkt_interrupt *pkt, + int64_t clk, + uint32_t line, uint64_t vector, uint8_t val, + uint32_t flags); + +size_t rp_encode_interrupt(uint32_t id, uint32_t dev, + struct rp_pkt_interrupt *pkt, + int64_t clk, + uint32_t line, uint64_t vector, uint8_t val); + +size_t rp_encode_sync(uint32_t id, uint32_t dev, + struct rp_pkt_sync *pkt, + int64_t clk); + +size_t rp_encode_sync_resp(uint32_t id, uint32_t dev, + struct rp_pkt_sync *pkt, + int64_t clk); + +size_t rp_encode_ats_req(uint32_t id, uint32_t dev, + struct rp_pkt_ats *pkt, + int64_t clk, uint64_t attr, uint64_t addr, + uint64_t size, uint64_t result, uint32_t flags); + +size_t rp_encode_ats_inv(uint32_t id, uint32_t dev, + struct rp_pkt_ats *pkt, + int64_t clk, uint64_t attr, uint64_t addr, + uint64_t size, uint64_t result, uint32_t flags); + +void rp_process_caps(struct rp_peer_state *peer, + void *caps, size_t caps_len); + +/* Dynamically resizable remote port pkt. */ + +typedef struct RemotePortDynPkt { + struct rp_pkt *pkt; + size_t size; +} RemotePortDynPkt; + +/* + * Make sure dpkt is allocated and has enough room. + */ + +void rp_dpkt_alloc(RemotePortDynPkt *dpkt, size_t size); + +void rp_dpkt_swap(RemotePortDynPkt *a, RemotePortDynPkt *b); + +/* + * Check if the dpkt is valid. Used for debugging purposes. + */ + +bool rp_dpkt_is_valid(RemotePortDynPkt *dpkt); + +/* + * Invalidate the dpkt. Used for debugging purposes. + */ + +void rp_dpkt_invalidate(RemotePortDynPkt *dpkt); + +void rp_dpkt_free(RemotePortDynPkt *dpkt); + +static inline int rp_get_busaccess_response(struct rp_pkt *pkt) +{ + return (pkt->busaccess_ext_base.attributes & RP_BUS_RESP_MASK) >> + RP_BUS_RESP_SH= IFT; +} +#endif diff --git a/include/hw/remote-port.h b/include/hw/remote-port.h new file mode 100644 index 0000000000..ad90233a60 --- /dev/null +++ b/include/hw/remote-port.h @@ -0,0 +1,149 @@ +/* + * QEMU remote port. + * + * Copyright (c) 2013 Xilinx Inc + * Written by Edgar E. Iglesias + * + * This code is licensed under the GNU GPL. + */ +#ifndef REMOTE_PORT_H__ +#define REMOTE_PORT_H__ + +#include +#include "hw/remote-port-proto.h" +#include "hw/remote-port-device.h" +#include "chardev/char.h" +#include "chardev/char-fe.h" +#include "hw/ptimer.h" +#include "qobject/qdict.h" + +#define TYPE_REMOTE_PORT "remote-port" +#define REMOTE_PORT(obj) OBJECT_CHECK(RemotePort, (obj), TYPE_REMOTE_PORT) + +typedef struct RemotePortRespSlot { + RemotePortDynPkt rsp; + uint32_t id; + bool used; + bool valid; +} RemotePortRespSlot; + +struct RemotePort { + DeviceState parent; + + QemuThread thread; + union { + int pipes[2]; + struct { + int read; + int write; + } pipe; + } event; + Chardev *chrdev; + CharFrontend chr; + bool do_sync; + bool doing_sync; + bool finalizing; + /* To serialize writes to fd. */ + QemuMutex write_mutex; + + char *chardesc; + char *chrdev_id; + struct rp_peer_state peer; + + struct { + ptimer_state *ptimer; + ptimer_state *ptimer_resp; + bool resp_timer_enabled; + bool need_sync; + struct rp_pkt rsp; + uint64_t quantum; + } sync; + + QemuMutex rsp_mutex; + QemuCond progress_cond; + +#define RX_QUEUE_SIZE 1024 + struct { + /* This array must be sized minimum 2 and always a power of 2. */ + RemotePortDynPkt pkt[RX_QUEUE_SIZE]; + bool inuse[RX_QUEUE_SIZE]; + QemuSemaphore sem; + unsigned int wpos; + unsigned int rpos; + } rx_queue; + + /* + * rsp holds responses for the remote side. + * Used by the slave. + */ + RemotePortDynPkt rsp; + + /* + * rspqueue holds received responses from the remote side. + * Only one for the moment but it might grow. + * Used by the master. + */ + RemotePortDynPkt rspqueue; + + bool resets[32]; + + const char *prefix; + const char *remote_prefix; + + uint32_t current_id; + bool reset_done; + +#define REMOTE_PORT_MAX_DEVS 1024 +#define RP_MAX_OUTSTANDING_TRANSACTIONS 32 + struct { + RemotePortRespSlot rsp_queue[RP_MAX_OUTSTANDING_TRANSACTIONS]; + } dev_state[REMOTE_PORT_MAX_DEVS]; + + RemotePortDevice *devs[REMOTE_PORT_MAX_DEVS]; +}; + +/** + * rp_device_attach: + * @adaptor: The adaptor onto which to attach the device + * @dev: The device to be attached to the adaptor + * @rp_nr: The remote-port adaptor nr. A device may attach to multiple + * adaptors. + * @dev_nr: The device/channel number to bind the device to. + * @errp: returns an error if this function fails + * + * Attaches a device onto an adaptor and binds it to a device number. + */ +void rp_device_attach(Object *adaptor, Object *dev, + int rp_nr, int dev_nr, + Error **errp); +void rp_device_detach(Object *adaptor, Object *dev, + int rp_nr, int dev_nr, + Error **errp); +bool rp_time_warp_enable(bool en); + +/** + * rp_device_add + * @opts: qdev opts created by the qdev subsystem + * @dev: The device to be connected + * @errp: Returns an error if the function fails + * + * Function used in qdev-monitor.c to connect remote port devices. + * Returns teue on success and false on failure. + */ +bool rp_device_add(const QDict *opts, DeviceState *dev, Error **errp); + +static inline void rp_resp_slot_done(RemotePort *s, + RemotePortRespSlot *rsp_slot) +{ + rp_dpkt_invalidate(&rsp_slot->rsp); + rsp_slot->id =3D ~0; + rsp_slot->used =3D false; + rsp_slot->valid =3D false; +} + +RemotePortRespSlot *rp_dev_wait_resp(RemotePort *s, uint32_t dev, uint32_t= id); +RemotePortRespSlot *rp_dev_timed_wait_resp(RemotePort *s, uint32_t dev, + uint32_t id, int timems); +void rp_process(RemotePort *s); + +#endif diff --git a/include/system/dma.h b/include/system/dma.h index 82e7ad5437..811ff32d69 100644 --- a/include/system/dma.h +++ b/include/system/dma.h @@ -88,6 +88,15 @@ static inline MemTxResult dma_memory_rw_relaxed(AddressS= pace *as, buf, len, dir =3D=3D DMA_DIRECTION_FROM_DEVICE= ); } =20 +static inline MemTxResult dma_memory_rw_relaxed_attr(AddressSpace *as, + dma_addr_t addr, void *buf, + dma_addr_t len, DMADirection = dir, + MemTxAttrs attr) +{ + return address_space_rw(as, addr, attr, + buf, len, dir =3D=3D DMA_DIRECTION_FROM_DEVICE= ); +} + static inline MemTxResult dma_memory_read_relaxed(AddressSpace *as, dma_addr_t addr, void *buf, dma_addr_t le= n) @@ -107,6 +116,16 @@ static inline MemTxResult dma_memory_write_relaxed(Add= ressSpace *as, MEMTXATTRS_UNSPECIFIED); } =20 +static inline MemTxResult dma_memory_rw_attr(AddressSpace *as, dma_addr_t = addr, + void *buf, dma_addr_t len, + DMADirection dir, + MemTxAttrs attr) +{ + dma_barrier(as, dir); + + return dma_memory_rw_relaxed_attr(as, addr, buf, len, dir, attr); +} + /** * dma_memory_rw: Read from or write to an address space from DMA controll= er. * diff --git a/include/system/memory.h b/include/system/memory.h index 3bd5ffa5e0..bcfeb624d8 100644 --- a/include/system/memory.h +++ b/include/system/memory.h @@ -27,6 +27,7 @@ #include "qom/object.h" #include "qemu/rcu.h" =20 +extern const char *machine_path; #define RAM_ADDR_INVALID (~(ram_addr_t)0) =20 #define MAX_PHYS_ADDR_SPACE_BITS 62 @@ -36,6 +37,11 @@ DECLARE_INSTANCE_CHECKER(MemoryRegion, MEMORY_REGION, TYPE_MEMORY_REGION) =20 +#define TYPE_MEMORY_TRANSACTION_ATTR "memory-transaction-attr" +#define MEMORY_TRANSACTION_ATTR(obj) \ + OBJECT_CHECK(MemTxAttrs, (obj), TYPE_MEMORY_TRANSACTION_ATTR) + + #define TYPE_IOMMU_MEMORY_REGION "iommu-memory-region" typedef struct IOMMUMemoryRegionClass IOMMUMemoryRegionClass; DECLARE_OBJ_CHECKERS(IOMMUMemoryRegion, IOMMUMemoryRegionClass, @@ -77,6 +83,29 @@ extern unsigned int global_dirty_tracking; =20 typedef struct MemoryRegionOps MemoryRegionOps; =20 +typedef struct MemoryTransaction +{ + union { + /* + * Data is passed by values up to 64bit sizes. Beyond + * that, a pointer is passed in p8. + * + * Note that p8 has no alignment restrictions. + */ + uint8_t *p8; + uint64_t u64; + uint32_t u32; + uint16_t u16; + uint8_t u8; + } data; + bool rw; + hwaddr addr; + unsigned int size; + MemTxAttrs attr; + void *opaque; +} MemoryTransaction; + + struct ReservedRegion { Range range; unsigned type; @@ -291,6 +320,8 @@ static inline void iommu_notifier_init(IOMMUNotifier *n= , IOMMUNotify fn, * Memory region callbacks */ struct MemoryRegionOps { + /* FIXME: Remove */ + MemTxResult (*access)(MemoryTransaction *tr); /* Read from the memory region. @addr is relative to @mr; @size is * in bytes. */ uint64_t (*read)(void *opaque, @@ -428,6 +459,9 @@ struct IOMMUMemoryRegionClass { */ IOMMUTLBEntry (*translate)(IOMMUMemoryRegion *iommu, hwaddr addr, IOMMUAccessFlags flag, int iommu_idx); + IOMMUTLBEntry (*translate_attr)(IOMMUMemoryRegion *iommu, hwaddr addr, + bool is_write, MemTxAttrs *attr); + =20 /** * @get_min_page_size: * @@ -2724,6 +2758,10 @@ MemTxResult memory_region_dispatch_write(MemoryRegio= n *mr, */ void address_space_init(AddressSpace *as, MemoryRegion *root, const char *= name); =20 + +/* Remove this */ +AddressSpace *address_space_init_shareable(MemoryRegion *root, + const char *name); /** * address_space_destroy: destroy an address space * @@ -3030,6 +3068,18 @@ static inline MemoryRegion *address_space_translate(= AddressSpace *as, addr, xlat, len, is_write, attrs); } =20 +MemoryRegion *ats_do_translate(AddressSpace *as, + hwaddr addr, + hwaddr *xlat, + hwaddr *plen_out, + AddressSpace **target_as, + int *prot, + MemTxAttrs attrs); + +MemoryRegion *address_space_translate_attr(AddressSpace *as, hwaddr addr, + hwaddr *xlat, hwaddr *plen, + bool is_write, MemTxAttrs *attr); + /* address_space_access_valid: check for validity of accessing an address * space range * diff --git a/include/system/system.h b/include/system/system.h index 03a2d0e900..1bc17e94f3 100644 --- a/include/system/system.h +++ b/include/system/system.h @@ -57,6 +57,8 @@ bool is_mlock_on_fault(MlockState); =20 extern MlockState mlock_state; =20 +extern uint64_t global_sync_quantum; + #define MAX_OPTION_ROMS 16 typedef struct QEMUOptionRom { const char *name; diff --git a/qemu-options.hx b/qemu-options.hx index 10567c5c45..82e45207b3 100644 --- a/qemu-options.hx +++ b/qemu-options.hx @@ -763,6 +763,27 @@ SRST Preallocate memory when using -mem-path. ERST =20 +DEF("sync-quantum", HAS_ARG, QEMU_OPTION_sync_quantum, + "-sync-quantum Max time between synchroniation, nanoseconds.\n", + QEMU_ARCH_ALL) +SRST +``-sync-quantum val`` + Maximum time between synchronization . + This value is used to drive periodic synchronization with remote port = peers. + It is also used to set device models sync-quantum properties controlli= ng + the maximum amount of ahead of time simulation that is prefered (only = a hint). +ERST + +DEF("machine-path", HAS_ARG, QEMU_OPTION_machine_path, + "-machine-path DIR A directory in which to create machine nodes\n", + QEMU_ARCH_ALL) +SRST +``-machine-path path`` + Selects the machine path. + Multi-arch machine nodes will be created in . + This option also sets -mem-shared-path to the given path. +ERST + DEF("k", HAS_ARG, QEMU_OPTION_k, "-k language use keyboard layout (for example 'fr' for French)\n", QEMU_ARCH_ALL) diff --git a/system/physmem.c b/system/physmem.c index c9869e4049..0323352242 100644 --- a/system/physmem.c +++ b/system/physmem.c @@ -528,6 +528,63 @@ static MemoryRegionSection flatview_do_translate(FlatV= iew *fv, return *section; } =20 +/* This function is called from RCU critical section */ +MemoryRegion *ats_do_translate(AddressSpace *as, + hwaddr addr, + hwaddr *xlat, + hwaddr *plen_out, + AddressSpace **target_as, + int *prot, + MemTxAttrs attrs) +{ + MemoryRegionSection *section; + IOMMUMemoryRegion *iommu_mr; + hwaddr plen =3D (hwaddr)(-1); + FlatView *fv =3D address_space_to_flatview(as); + IOMMUMemoryRegionClass *imrc; + IOMMUTLBEntry iotlb; + int iommu_idx =3D 0; + + if (!plen_out) { + plen_out =3D &plen; + } + + /* This can be MMIO, so setup MMIO bit. */ + section =3D address_space_translate_internal( + flatview_to_dispatch(fv), addr, xlat, + plen_out, true); + + /* Illegal translation */ + if (section->mr =3D=3D &io_mem_unassigned) { + return NULL; + } + + iommu_mr =3D memory_region_get_iommu(section->mr); + if (!iommu_mr) { + return NULL; + } + + imrc =3D memory_region_get_iommu_class_nocheck(iommu_mr); + + if (imrc->attrs_to_index) { + iommu_idx =3D imrc->attrs_to_index(iommu_mr, attrs); + } + + if (imrc->translate_attr) { + iotlb =3D imrc->translate_attr(iommu_mr, addr, false, &attrs); + } else { + iotlb =3D imrc->translate(iommu_mr, addr, IOMMU_RO, iommu_idx); + } + + *xlat =3D ((iotlb.translated_addr & ~iotlb.addr_mask) + | (addr & iotlb.addr_mask)); + *plen_out =3D MIN(*plen_out, (addr | iotlb.addr_mask) - addr + 1); + *prot =3D iotlb.perm; + *target_as =3D iotlb.target_as; + + return section->mr; +} + /* Called from RCU critical section */ IOMMUTLBEntry address_space_get_iotlb_entry(AddressSpace *as, hwaddr addr, bool is_write, MemTxAttrs attr= s) diff --git a/system/vl.c b/system/vl.c index c18306e914..76d08d2078 100644 --- a/system/vl.c +++ b/system/vl.c @@ -183,6 +183,8 @@ static QemuPluginList plugin_list =3D QTAILQ_HEAD_INITI= ALIZER(plugin_list); static BlockdevOptionsQueue bdo_queue =3D QSIMPLEQ_HEAD_INITIALIZER(bdo_qu= eue); static bool nographic =3D false; static int mem_prealloc; /* force preallocation of physical target memory = */ +uint64_t global_sync_quantum =3D 0; +const char *machine_path =3D NULL; static const char *vga_model =3D NULL; static DisplayOptions dpy; static int num_serial_hds; @@ -3129,6 +3131,9 @@ void qemu_init(int argc, char **argv) case QEMU_OPTION_mem_prealloc: mem_prealloc =3D 1; break; + case QEMU_OPTION_sync_quantum: + global_sync_quantum =3D strtoull(optarg, (char **) &optarg= , 10); + break; case QEMU_OPTION_d: log_mask =3D optarg; break; --=20 2.43.0