From patchwork Mon Mar 1 06:34:49 2021 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Asherah Connor X-Patchwork-Id: 1445418 X-Patchwork-Delegate: trini@ti.com Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Authentication-Results: ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=lists.denx.de (client-ip=2a01:238:438b:c500:173d:9f52:ddab:ee01; helo=phobos.denx.de; envelope-from=u-boot-bounces@lists.denx.de; receiver=) Authentication-Results: ozlabs.org; dkim=pass (2048-bit key; unprotected) header.d=kivikakk.ee header.i=@kivikakk.ee header.a=rsa-sha256 header.s=fm1 header.b=HrkIf/rZ; dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.a=rsa-sha256 header.s=fm2 header.b=vWU2LyMW; dkim-atps=neutral Received: from phobos.denx.de (phobos.denx.de [IPv6:2a01:238:438b:c500:173d:9f52:ddab:ee01]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 4Dpr9n4jhqz9sSC for ; Mon, 1 Mar 2021 17:35:39 +1100 (AEDT) Received: from h2850616.stratoserver.net (localhost [IPv6:::1]) by phobos.denx.de (Postfix) with ESMTP id 1C93181444; Mon, 1 Mar 2021 07:35:30 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=none (p=none dis=none) header.from=kivikakk.ee Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=u-boot-bounces@lists.denx.de Authentication-Results: phobos.denx.de; dkim=pass (2048-bit key; unprotected) header.d=kivikakk.ee header.i=@kivikakk.ee header.b="HrkIf/rZ"; dkim=pass (2048-bit key; unprotected) header.d=messagingengine.com header.i=@messagingengine.com header.b="vWU2LyMW"; dkim-atps=neutral Received: by phobos.denx.de (Postfix, from userid 109) id 8447E80765; Mon, 1 Mar 2021 07:35:24 +0100 (CET) X-Spam-Checker-Version: SpamAssassin 3.4.2 (2018-09-13) on phobos.denx.de X-Spam-Level: X-Spam-Status: No, score=-2.0 required=5.0 tests=BAYES_00,DKIM_SIGNED, DKIM_VALID,DKIM_VALID_AU,RCVD_IN_MSPIKE_H3,RCVD_IN_MSPIKE_WL, SPF_HELO_PASS autolearn=ham autolearn_force=no version=3.4.2 Received: from new1-smtp.messagingengine.com (new1-smtp.messagingengine.com [66.111.4.221]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)) (No client certificate requested) by phobos.denx.de (Postfix) with ESMTPS id 2F6C4803AB for ; Mon, 1 Mar 2021 07:35:16 +0100 (CET) Authentication-Results: phobos.denx.de; dmarc=none (p=none dis=none) header.from=kivikakk.ee Authentication-Results: phobos.denx.de; spf=pass smtp.mailfrom=ashe@kivikakk.ee Received: from compute5.internal (compute5.nyi.internal [10.202.2.45]) by mailnew.nyi.internal (Postfix) with ESMTP id 78E725801BC; Mon, 1 Mar 2021 01:35:15 -0500 (EST) Received: from mailfrontend2 ([10.202.2.163]) by compute5.internal (MEProxy); Mon, 01 Mar 2021 01:35:15 -0500 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kivikakk.ee; h= from:to:cc:subject:date:message-id:in-reply-to:references :mime-version:content-transfer-encoding; s=fm1; bh=IxpGb9bYADQzU KLazlTlSakj8ZU8tqk/iqMLiAqTrWw=; b=HrkIf/rZM9yPSx2FB6aBUpO2+8eDz EaJMKZ+mqPpLHtSrLryckWMtdT6OGdv7bKWMvjAK4+Jdh1/jby1f8RNA1OeNACU4 iJVOIy2fg3sMIW5EOIJLiGyfYIYPvDdHjWk3QLkdKnhHatY6CjuCpT/vZvVzTEZF hSN08EjEzginDIZ7yuuTSY3+k6JqCrdBDLW/vK69+9VzfgdrNYihiRy5+eg1aqPB W51QgEVWiObfN5l86sgBQCTqXNJifLKMGTSU+sBi19d7VU/+NnV95MsZYwvksW1L mA2wk0J0NC3l2ZHvDMAwzT2LHFNcKMnRWxCPAtv/jwG0Iq8PwFUXJCLGQ== DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d= messagingengine.com; h=cc:content-transfer-encoding:date:from :in-reply-to:message-id:mime-version:references:subject:to :x-me-proxy:x-me-proxy:x-me-sender:x-me-sender:x-sasl-enc; s= fm2; bh=IxpGb9bYADQzUKLazlTlSakj8ZU8tqk/iqMLiAqTrWw=; b=vWU2LyMW AoWRmqVDLbQNADz3iZQZTpg+LKzDod3Rb7TFi63JHAFRq9I4AlJ6OqBoDotRuXv+ LwLZt8x1dlLssfi1tlQDmdwHNhUkS1TB3TDNy4FYdOsaBaqH9LegQJaj+K+g08Iu VqQqpTmwi+AZhDIziEqmFM52obqwFOts8hSe1gIU+dHztZbgx7DfCEGmbn86x3hO QVpJndzeBEeI7S9J4EdogJHQUS9V/n33pLeregxxBlQwus+SBp7w/LPafDaq1bw2 voglFhzfNqTh7M7Y+LnTK2oPcF86wr0irPFrSN9BOGTezSHKJ+WKNYgp9Yk+OtRy GaNldQd91JMegw== X-ME-Sender: X-ME-Proxy-Cause: gggruggvucftvghtrhhoucdtuddrgeduledrleejgdeljecutefuodetggdotefrodftvf curfhrohhfihhlvgemucfhrghsthforghilhdpqfgfvfdpuffrtefokffrpgfnqfghnecu uegrihhlohhuthemuceftddtnecusecvtfgvtghiphhivghnthhsucdlqddutddtmdenuc fjughrpefhvffufffkofgjfhgggfestdekredtredttdenucfhrhhomheptehshhgvrhgr hhcuvehonhhnohhruceorghshhgvsehkihhvihhkrghkkhdrvggvqeenucggtffrrghtth gvrhhnpeeijeffuefgueelgeeijefhgeffvefhhfejudelfeeukeefjeeugfdvvdekieej teenucffohhmrghinhepghhithhhuhgsrdgtohhmnecukfhppeduudelrddukedrfedurd dufeehnecuvehluhhsthgvrhfuihiivgeptdenucfrrghrrghmpehmrghilhhfrhhomhep rghshhgvsehkihhvihhkrghkkhdrvggv X-ME-Proxy: Received: from ravenlin.tomodachi (119-18-31-135.77121f.mel.static.aussiebb.net [119.18.31.135]) by mail.messagingengine.com (Postfix) with ESMTPA id 94B6A1080054; Mon, 1 Mar 2021 01:35:10 -0500 (EST) From: Asherah Connor To: u-boot@lists.denx.de Cc: Bin Meng , Simon Glass , Asherah Connor , AKASHI Takahiro , Bharat Gooty , Heinrich Schuchardt , Igor Opaniuk , Masahiro Yamada , Michal Simek , Peng Fan , Pragnesh Patel , Tero Kristo , Trevor Woerner , Wolfgang Wallner Subject: [PATCH v5 1/3] x86: qemu: move QFW to its own uclass Date: Mon, 1 Mar 2021 17:34:49 +1100 Message-Id: <20210301063451.5156-2-ashe@kivikakk.ee> X-Mailer: git-send-email 2.20.1 In-Reply-To: <20210301063451.5156-1-ashe@kivikakk.ee> References: <20210301063451.5156-1-ashe@kivikakk.ee> MIME-Version: 1.0 X-BeenThere: u-boot@lists.denx.de X-Mailman-Version: 2.1.34 Precedence: list List-Id: U-Boot discussion List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: u-boot-bounces@lists.denx.de Sender: "U-Boot" X-Virus-Scanned: clamav-milter 0.102.4 at phobos.denx.de X-Virus-Status: Clean We move qfw into its own uclass and split the PIO functions into a specific driver for that uclass. The PIO driver is selected in the qemu-x86 board config (this covers x86 and x86_64). A sandbox driver and test are added for the uclass, and a test in QEMU added for qfw functionality to confirm it doesn't break in real world use. include/qfw.h is cleaned up and documentation added. Signed-off-by: Asherah Connor --- Changes in v5: * Split conversion of existing x86-only QFW to DM into its own patch. * Fix qfw_get_dev() so it actually returns -ENODEV when device is missing. * Add CONFIG_QFW_PIO. * Choose CONFIG_QFW_PIO in board/emulation/qemu-x86/Kconfig. * QFW sandbox driver no longer inserts itself from device tree; wasn't relevant to implementation and the ``compatible'' string was too made-up. Use U_BOOT_DRVINFO like the other QFW driver in this patch. * Relevant documentation and sandbox/qemu tests are now here. arch/x86/cpu/qemu/cpu.c | 9 +- arch/x86/cpu/qemu/qemu.c | 49 +------ arch/x86/cpu/qfw_cpu.c | 11 +- board/emulation/qemu-x86/Kconfig | 1 + cmd/qfw.c | 56 ++++--- common/Makefile | 2 + common/qfw.c | 105 +++++++++++++ drivers/misc/Kconfig | 11 +- drivers/misc/Makefile | 6 +- drivers/misc/qfw.c | 243 +++++++++++-------------------- drivers/misc/qfw_pio.c | 69 +++++++++ drivers/misc/qfw_sandbox.c | 128 ++++++++++++++++ include/dm/uclass-id.h | 1 + include/qfw.h | 200 +++++++++++++++++++++---- test/dm/Makefile | 1 + test/dm/qfw.c | 42 ++++++ test/py/tests/test_qfw.py | 21 +++ 17 files changed, 683 insertions(+), 272 deletions(-) create mode 100644 common/qfw.c create mode 100644 drivers/misc/qfw_pio.c create mode 100644 drivers/misc/qfw_sandbox.c create mode 100644 test/dm/qfw.c create mode 100644 test/py/tests/test_qfw.py diff --git a/arch/x86/cpu/qemu/cpu.c b/arch/x86/cpu/qemu/cpu.c index 9ce86b379c..735b656084 100644 --- a/arch/x86/cpu/qemu/cpu.c +++ b/arch/x86/cpu/qemu/cpu.c @@ -22,7 +22,14 @@ int cpu_qemu_get_desc(const struct udevice *dev, char *buf, int size) static int cpu_qemu_get_count(const struct udevice *dev) { - return qemu_fwcfg_online_cpus(); + int ret; + struct udevice *qfw_dev; + + ret = qfw_get_dev(&qfw_dev); + if (ret) + return ret; + + return qfw_online_cpus(qfw_dev); } static const struct cpu_ops cpu_qemu_ops = { diff --git a/arch/x86/cpu/qemu/qemu.c b/arch/x86/cpu/qemu/qemu.c index 044a429c13..e54082df7f 100644 --- a/arch/x86/cpu/qemu/qemu.c +++ b/arch/x86/cpu/qemu/qemu.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -16,47 +17,9 @@ static bool i440fx; -#ifdef CONFIG_QFW - -/* on x86, the qfw registers are all IO ports */ -#define FW_CONTROL_PORT 0x510 -#define FW_DATA_PORT 0x511 -#define FW_DMA_PORT_LOW 0x514 -#define FW_DMA_PORT_HIGH 0x518 - -static void qemu_x86_fwcfg_read_entry_pio(uint16_t entry, - uint32_t size, void *address) -{ - uint32_t i = 0; - uint8_t *data = address; - - /* - * writting FW_CFG_INVALID will cause read operation to resume at - * last offset, otherwise read will start at offset 0 - * - * Note: on platform where the control register is IO port, the - * endianness is little endian. - */ - if (entry != FW_CFG_INVALID) - outw(cpu_to_le16(entry), FW_CONTROL_PORT); - - /* the endianness of data register is string-preserving */ - while (size--) - data[i++] = inb(FW_DATA_PORT); -} - -static void qemu_x86_fwcfg_read_entry_dma(struct fw_cfg_dma_access *dma) -{ - /* the DMA address register is big endian */ - outl(cpu_to_be32((uintptr_t)dma), FW_DMA_PORT_HIGH); - - while (be32_to_cpu(dma->control) & ~FW_CFG_DMA_ERROR) - __asm__ __volatile__ ("pause"); -} - -static struct fw_cfg_arch_ops fwcfg_x86_ops = { - .arch_read_pio = qemu_x86_fwcfg_read_entry_pio, - .arch_read_dma = qemu_x86_fwcfg_read_entry_dma +#if CONFIG_IS_ENABLED(QFW_PIO) +U_BOOT_DRVINFO(x86_qfw_pio) = { + .name = "qfw_pio", }; #endif @@ -132,10 +95,6 @@ static void qemu_chipset_init(void) enable_pm_ich9(); } - -#ifdef CONFIG_QFW - qemu_fwcfg_init(&fwcfg_x86_ops); -#endif } #if !CONFIG_IS_ENABLED(SPL_X86_32BIT_INIT) diff --git a/arch/x86/cpu/qfw_cpu.c b/arch/x86/cpu/qfw_cpu.c index b959eaddde..ee00b8fe73 100644 --- a/arch/x86/cpu/qfw_cpu.c +++ b/arch/x86/cpu/qfw_cpu.c @@ -18,7 +18,7 @@ int qemu_cpu_fixup(void) int cpu_num; int cpu_online; struct uclass *uc; - struct udevice *dev, *pdev; + struct udevice *dev, *pdev, *qfwdev; struct cpu_plat *plat; char *cpu; @@ -39,6 +39,13 @@ int qemu_cpu_fixup(void) return -ENODEV; } + /* get qfw dev */ + ret = qfw_get_dev(&qfwdev); + if (ret) { + printf("unable to find qfw device\n"); + return ret; + } + /* calculate cpus that are already bound */ cpu_num = 0; for (uclass_find_first_device(UCLASS_CPU, &dev); @@ -48,7 +55,7 @@ int qemu_cpu_fixup(void) } /* get actual cpu number */ - cpu_online = qemu_fwcfg_online_cpus(); + cpu_online = qfw_online_cpus(qfwdev); if (cpu_online < 0) { printf("unable to get online cpu number: %d\n", cpu_online); return cpu_online; diff --git a/board/emulation/qemu-x86/Kconfig b/board/emulation/qemu-x86/Kconfig index 6d19299d8b..9bb8a726ed 100644 --- a/board/emulation/qemu-x86/Kconfig +++ b/board/emulation/qemu-x86/Kconfig @@ -20,6 +20,7 @@ config BOARD_SPECIFIC_OPTIONS # dummy def_bool y select X86_RESET_VECTOR select QEMU + select QFW_PIO select BOARD_ROMSIZE_KB_1024 imply VIRTIO_PCI imply VIRTIO_NET diff --git a/cmd/qfw.c b/cmd/qfw.c index bb571487f0..e6a9fdb2af 100644 --- a/cmd/qfw.c +++ b/cmd/qfw.c @@ -8,19 +8,22 @@ #include #include #include +#include + +static struct udevice *qfw_dev; /* * This function prepares kernel for zboot. It loads kernel data * to 'load_addr', initrd to 'initrd_addr' and kernel command * line using qemu fw_cfg interface. */ -static int qemu_fwcfg_setup_kernel(void *load_addr, void *initrd_addr) +static int qemu_fwcfg_cmd_setup_kernel(void *load_addr, void *initrd_addr) { char *data_addr; uint32_t setup_size, kernel_size, cmdline_size, initrd_size; - qemu_fwcfg_read_entry(FW_CFG_SETUP_SIZE, 4, &setup_size); - qemu_fwcfg_read_entry(FW_CFG_KERNEL_SIZE, 4, &kernel_size); + qfw_read_entry(qfw_dev, FW_CFG_SETUP_SIZE, 4, &setup_size); + qfw_read_entry(qfw_dev, FW_CFG_KERNEL_SIZE, 4, &kernel_size); if (setup_size == 0 || kernel_size == 0) { printf("warning: no kernel available\n"); @@ -28,28 +31,28 @@ static int qemu_fwcfg_setup_kernel(void *load_addr, void *initrd_addr) } data_addr = load_addr; - qemu_fwcfg_read_entry(FW_CFG_SETUP_DATA, - le32_to_cpu(setup_size), data_addr); + qfw_read_entry(qfw_dev, FW_CFG_SETUP_DATA, + le32_to_cpu(setup_size), data_addr); data_addr += le32_to_cpu(setup_size); - qemu_fwcfg_read_entry(FW_CFG_KERNEL_DATA, - le32_to_cpu(kernel_size), data_addr); + qfw_read_entry(qfw_dev, FW_CFG_KERNEL_DATA, + le32_to_cpu(kernel_size), data_addr); data_addr += le32_to_cpu(kernel_size); data_addr = initrd_addr; - qemu_fwcfg_read_entry(FW_CFG_INITRD_SIZE, 4, &initrd_size); + qfw_read_entry(qfw_dev, FW_CFG_INITRD_SIZE, 4, &initrd_size); if (initrd_size == 0) { printf("warning: no initrd available\n"); } else { - qemu_fwcfg_read_entry(FW_CFG_INITRD_DATA, - le32_to_cpu(initrd_size), data_addr); + qfw_read_entry(qfw_dev, FW_CFG_INITRD_DATA, + le32_to_cpu(initrd_size), data_addr); data_addr += le32_to_cpu(initrd_size); } - qemu_fwcfg_read_entry(FW_CFG_CMDLINE_SIZE, 4, &cmdline_size); + qfw_read_entry(qfw_dev, FW_CFG_CMDLINE_SIZE, 4, &cmdline_size); if (cmdline_size) { - qemu_fwcfg_read_entry(FW_CFG_CMDLINE_DATA, - le32_to_cpu(cmdline_size), data_addr); + qfw_read_entry(qfw_dev, FW_CFG_CMDLINE_DATA, + le32_to_cpu(cmdline_size), data_addr); /* * if kernel cmdline only contains '\0', (e.g. no -append * when invoking qemu), do not update bootargs @@ -72,21 +75,20 @@ static int qemu_fwcfg_setup_kernel(void *load_addr, void *initrd_addr) return 0; } -static int qemu_fwcfg_list_firmware(void) +static int qemu_fwcfg_cmd_list_firmware(void) { int ret; struct fw_cfg_file_iter iter; struct fw_file *file; /* make sure fw_list is loaded */ - ret = qemu_fwcfg_read_firmware_list(); + ret = qfw_read_firmware_list(qfw_dev); if (ret) return ret; - - for (file = qemu_fwcfg_file_iter_init(&iter); - !qemu_fwcfg_file_iter_end(&iter); - file = qemu_fwcfg_file_iter_next(&iter)) { + for (file = qfw_file_iter_init(qfw_dev, &iter); + !qfw_file_iter_end(&iter); + file = qfw_file_iter_next(&iter)) { printf("%-56s\n", file->cfg.name); } @@ -96,7 +98,7 @@ static int qemu_fwcfg_list_firmware(void) static int qemu_fwcfg_do_list(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { - if (qemu_fwcfg_list_firmware() < 0) + if (qemu_fwcfg_cmd_list_firmware() < 0) return CMD_RET_FAILURE; return 0; @@ -105,14 +107,7 @@ static int qemu_fwcfg_do_list(struct cmd_tbl *cmdtp, int flag, static int qemu_fwcfg_do_cpus(struct cmd_tbl *cmdtp, int flag, int argc, char *const argv[]) { - int ret = qemu_fwcfg_online_cpus(); - if (ret < 0) { - printf("QEMU fw_cfg interface not found\n"); - return CMD_RET_FAILURE; - } - - printf("%d cpu(s) online\n", qemu_fwcfg_online_cpus()); - + printf("%d cpu(s) online\n", qfw_online_cpus(qfw_dev)); return 0; } @@ -153,7 +148,7 @@ static int qemu_fwcfg_do_load(struct cmd_tbl *cmdtp, int flag, return CMD_RET_FAILURE; } - return qemu_fwcfg_setup_kernel(load_addr, initrd_addr); + return qemu_fwcfg_cmd_setup_kernel(load_addr, initrd_addr); } static struct cmd_tbl fwcfg_commands[] = { @@ -168,7 +163,8 @@ static int do_qemu_fw(struct cmd_tbl *cmdtp, int flag, int argc, int ret; struct cmd_tbl *fwcfg_cmd; - if (!qemu_fwcfg_present()) { + ret = qfw_get_dev(&qfw_dev); + if (ret) { printf("QEMU fw_cfg interface not found\n"); return CMD_RET_USAGE; } diff --git a/common/Makefile b/common/Makefile index daeea67cf2..f174a06c33 100644 --- a/common/Makefile +++ b/common/Makefile @@ -137,3 +137,5 @@ obj-$(CONFIG_CMD_LOADB) += xyzModem.o obj-$(CONFIG_$(SPL_TPL_)YMODEM_SUPPORT) += xyzModem.o obj-$(CONFIG_AVB_VERIFY) += avb_verify.o + +obj-$(CONFIG_QFW) += qfw.o diff --git a/common/qfw.c b/common/qfw.c new file mode 100644 index 0000000000..9a1d1eda5f --- /dev/null +++ b/common/qfw.c @@ -0,0 +1,105 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * (C) Copyright 2015 Miao Yan + * (C) Copyright 2021 Asherah Connor + */ + +#include +#include +#include +#include + +int qfw_get_dev(struct udevice **devp) +{ + return uclass_first_device_err(UCLASS_QFW, devp); +} + +int qfw_online_cpus(struct udevice *dev) +{ + u16 nb_cpus; + + qfw_read_entry(dev, FW_CFG_NB_CPUS, 2, &nb_cpus); + + return le16_to_cpu(nb_cpus); +} + +int qfw_read_firmware_list(struct udevice *dev) +{ + int i; + u32 count; + struct fw_file *file; + struct list_head *entry; + + struct qfw_dev *qdev = dev_get_uclass_priv(dev); + + /* don't read it twice */ + if (!list_empty(&qdev->fw_list)) + return 0; + + qfw_read_entry(dev, FW_CFG_FILE_DIR, 4, &count); + if (!count) + return 0; + + count = be32_to_cpu(count); + for (i = 0; i < count; i++) { + file = malloc(sizeof(*file)); + if (!file) { + printf("error: allocating resource\n"); + goto err; + } + qfw_read_entry(dev, FW_CFG_INVALID, + sizeof(struct fw_cfg_file), &file->cfg); + file->addr = 0; + list_add_tail(&file->list, &qdev->fw_list); + } + + return 0; + +err: + list_for_each(entry, &qdev->fw_list) { + file = list_entry(entry, struct fw_file, list); + free(file); + } + + return -ENOMEM; +} + +struct fw_file *qfw_find_file(struct udevice *dev, const char *name) +{ + struct list_head *entry; + struct fw_file *file; + + struct qfw_dev *qdev = dev_get_uclass_priv(dev); + + list_for_each(entry, &qdev->fw_list) { + file = list_entry(entry, struct fw_file, list); + if (!strcmp(file->cfg.name, name)) + return file; + } + + return NULL; +} + +struct fw_file *qfw_file_iter_init(struct udevice *dev, + struct fw_cfg_file_iter *iter) +{ + struct qfw_dev *qdev = dev_get_uclass_priv(dev); + + iter->entry = qdev->fw_list.next; + iter->end = &qdev->fw_list; + return list_entry((struct list_head *)iter->entry, + struct fw_file, list); +} + +struct fw_file *qfw_file_iter_next(struct fw_cfg_file_iter *iter) +{ + iter->entry = ((struct list_head *)iter->entry)->next; + return list_entry((struct list_head *)iter->entry, + struct fw_file, list); +} + +bool qfw_file_iter_end(struct fw_cfg_file_iter *iter) +{ + return iter->entry == iter->end; +} + diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 7d2a299779..3a254eba4b 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -368,8 +368,15 @@ config WINBOND_W83627 config QFW bool help - Hidden option to enable QEMU fw_cfg interface. This will be selected by - either CONFIG_CMD_QFW or CONFIG_GENERATE_ACPI_TABLE. + Hidden option to enable QEMU fw_cfg interface and uclass. This will + be selected by either CONFIG_CMD_QFW or CONFIG_GENERATE_ACPI_TABLE. + +config QFW_PIO + bool + depends on QFW + help + Hidden option to enable PIO QEMU fw_cfg interface. This will be + selected by the appropriate QEMU board. config I2C_EEPROM bool "Enable driver for generic I2C-attached EEPROMs" diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 1a49396007..2864b84af9 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -55,7 +55,11 @@ obj-$(CONFIG_NUVOTON_NCT6102D) += nuvoton_nct6102d.o obj-$(CONFIG_P2SB) += p2sb-uclass.o obj-$(CONFIG_PCA9551_LED) += pca9551_led.o obj-$(CONFIG_$(SPL_)PWRSEQ) += pwrseq-uclass.o -obj-$(CONFIG_QFW) += qfw.o +ifdef CONFIG_QFW +obj-y += qfw.o +obj-$(CONFIG_QFW_PIO) += qfw_pio.o +obj-$(CONFIG_SANDBOX) += qfw_sandbox.o +endif obj-$(CONFIG_ROCKCHIP_EFUSE) += rockchip-efuse.o obj-$(CONFIG_ROCKCHIP_OTP) += rockchip-otp.o obj-$(CONFIG_SANDBOX) += syscon_sandbox.o misc_sandbox.o diff --git a/drivers/misc/qfw.c b/drivers/misc/qfw.c index f6eb6583ed..ea00be88a8 100644 --- a/drivers/misc/qfw.c +++ b/drivers/misc/qfw.c @@ -1,25 +1,22 @@ // SPDX-License-Identifier: GPL-2.0+ /* * (C) Copyright 2015 Miao Yan + * (C) Copyright 2021 Asherah Connor */ +#define LOG_CATEGORY UCLASS_QFW + #include #include #include #include #include #include -#include +#include +#include #ifdef CONFIG_GENERATE_ACPI_TABLE #include #endif -#include - -static bool fwcfg_present; -static bool fwcfg_dma_present; -static struct fw_cfg_arch_ops *fwcfg_arch_ops; - -static LIST_HEAD(fw_list); #ifdef CONFIG_GENERATE_ACPI_TABLE /* @@ -32,7 +29,8 @@ static LIST_HEAD(fw_list); * be ignored. * @return: 0 on success, or negative value on failure */ -static int bios_linker_allocate(struct bios_linker_entry *entry, ulong *addr) +static int bios_linker_allocate(struct udevice *dev, + struct bios_linker_entry *entry, ulong *addr) { uint32_t size, align; struct fw_file *file; @@ -45,7 +43,7 @@ static int bios_linker_allocate(struct bios_linker_entry *entry, ulong *addr) return -EINVAL; } - file = qemu_fwcfg_find_file(entry->alloc.file); + file = qfw_find_file(dev, entry->alloc.file); if (!file) { printf("error: can't find file %s\n", entry->alloc.file); return -ENOENT; @@ -75,8 +73,8 @@ static int bios_linker_allocate(struct bios_linker_entry *entry, ulong *addr) debug("bios_linker_allocate: allocate file %s, size %u, zone %d, align %u, addr 0x%lx\n", file->cfg.name, size, entry->alloc.zone, align, aligned_addr); - qemu_fwcfg_read_entry(be16_to_cpu(file->cfg.select), - size, (void *)aligned_addr); + qfw_read_entry(dev, be16_to_cpu(file->cfg.select), size, + (void *)aligned_addr); file->addr = aligned_addr; /* adjust address for low memory allocation */ @@ -94,16 +92,17 @@ static int bios_linker_allocate(struct bios_linker_entry *entry, ulong *addr) * ACPI tables * @return: 0 on success, or negative value on failure */ -static int bios_linker_add_pointer(struct bios_linker_entry *entry) +static int bios_linker_add_pointer(struct udevice *dev, + struct bios_linker_entry *entry) { struct fw_file *dest, *src; uint32_t offset = le32_to_cpu(entry->pointer.offset); uint64_t pointer = 0; - dest = qemu_fwcfg_find_file(entry->pointer.dest_file); + dest = qfw_find_file(dev, entry->pointer.dest_file); if (!dest || !dest->addr) return -ENOENT; - src = qemu_fwcfg_find_file(entry->pointer.src_file); + src = qfw_find_file(dev, entry->pointer.src_file); if (!src || !src->addr) return -ENOENT; @@ -127,13 +126,14 @@ static int bios_linker_add_pointer(struct bios_linker_entry *entry) * checksums * @return: 0 on success, or negative value on failure */ -static int bios_linker_add_checksum(struct bios_linker_entry *entry) +static int bios_linker_add_checksum(struct udevice *dev, + struct bios_linker_entry *entry) { struct fw_file *file; uint8_t *data, cksum = 0; uint8_t *cksum_start; - file = qemu_fwcfg_find_file(entry->cksum.file); + file = qfw_find_file(dev, entry->cksum.file); if (!file || !file->addr) return -ENOENT; @@ -149,20 +149,27 @@ static int bios_linker_add_checksum(struct bios_linker_entry *entry) /* This function loads and patches ACPI tables provided by QEMU */ ulong write_acpi_tables(ulong addr) { - int i, ret = 0; + int i, ret; struct fw_file *file; struct bios_linker_entry *table_loader; struct bios_linker_entry *entry; uint32_t size; + struct udevice *dev; + + ret = qfw_get_dev(&dev); + if (ret) { + printf("error: no qfw\n"); + return addr; + } /* make sure fw_list is loaded */ - ret = qemu_fwcfg_read_firmware_list(); + ret = qfw_read_firmware_list(dev); if (ret) { printf("error: can't read firmware file list\n"); return addr; } - file = qemu_fwcfg_find_file("etc/table-loader"); + file = qfw_find_file(dev, "etc/table-loader"); if (!file) { printf("error: can't find etc/table-loader\n"); return addr; @@ -180,24 +187,23 @@ ulong write_acpi_tables(ulong addr) return addr; } - qemu_fwcfg_read_entry(be16_to_cpu(file->cfg.select), - size, table_loader); + qfw_read_entry(dev, be16_to_cpu(file->cfg.select), size, table_loader); for (i = 0; i < (size / sizeof(*entry)); i++) { entry = table_loader + i; switch (le32_to_cpu(entry->command)) { case BIOS_LINKER_LOADER_COMMAND_ALLOCATE: - ret = bios_linker_allocate(entry, &addr); + ret = bios_linker_allocate(dev, entry, &addr); if (ret) goto out; break; case BIOS_LINKER_LOADER_COMMAND_ADD_POINTER: - ret = bios_linker_add_pointer(entry); + ret = bios_linker_add_pointer(dev, entry); if (ret) goto out; break; case BIOS_LINKER_LOADER_COMMAND_ADD_CHECKSUM: - ret = bios_linker_add_checksum(entry); + ret = bios_linker_add_checksum(dev, entry); if (ret) goto out; break; @@ -209,9 +215,9 @@ ulong write_acpi_tables(ulong addr) out: if (ret) { struct fw_cfg_file_iter iter; - for (file = qemu_fwcfg_file_iter_init(&iter); - !qemu_fwcfg_file_iter_end(&iter); - file = qemu_fwcfg_file_iter_next(&iter)) { + for (file = qfw_file_iter_init(dev, &iter); + !qfw_file_iter_end(&iter); + file = qfw_file_iter_next(&iter)) { if (file->addr) { free((void *)file->addr); file->addr = 0; @@ -225,170 +231,89 @@ out: ulong acpi_get_rsdp_addr(void) { + int ret; struct fw_file *file; + struct udevice *dev; - file = qemu_fwcfg_find_file("etc/acpi/rsdp"); + ret = qfw_get_dev(&dev); + if (ret) { + printf("error: no qfw\n"); + return 0; + } + + file = qfw_find_file(dev, "etc/acpi/rsdp"); return file->addr; } #endif -/* Read configuration item using fw_cfg PIO interface */ -static void qemu_fwcfg_read_entry_pio(uint16_t entry, - uint32_t size, void *address) +static void qfw_read_entry_io(struct qfw_dev *qdev, u16 entry, u32 size, + void *address) { - debug("qemu_fwcfg_read_entry_pio: entry 0x%x, size %u address %p\n", - entry, size, address); + struct dm_qfw_ops *ops = dm_qfw_get_ops(qdev->dev); - return fwcfg_arch_ops->arch_read_pio(entry, size, address); + debug("%s: entry 0x%x, size %u address %p\n", __func__, entry, size, + address); + + ops->read_entry_io(qdev->dev, entry, size, address); } -/* Read configuration item using fw_cfg DMA interface */ -static void qemu_fwcfg_read_entry_dma(uint16_t entry, - uint32_t size, void *address) +static void qfw_read_entry_dma(struct qfw_dev *qdev, u16 entry, u32 size, + void *address) { - struct fw_cfg_dma_access dma; + struct dm_qfw_ops *ops = dm_qfw_get_ops(qdev->dev); - dma.length = cpu_to_be32(size); - dma.address = cpu_to_be64((uintptr_t)address); - dma.control = cpu_to_be32(FW_CFG_DMA_READ); + struct qfw_dma dma = { + .length = cpu_to_be32(size), + .address = cpu_to_be64((uintptr_t)address), + .control = cpu_to_be32(FW_CFG_DMA_READ), + }; /* - * writting FW_CFG_INVALID will cause read operation to resume at - * last offset, otherwise read will start at offset 0 + * writing FW_CFG_INVALID will cause read operation to resume at last + * offset, otherwise read will start at offset 0 */ if (entry != FW_CFG_INVALID) dma.control |= cpu_to_be32(FW_CFG_DMA_SELECT | (entry << 16)); - barrier(); - - debug("qemu_fwcfg_read_entry_dma: entry 0x%x, size %u address %p, control 0x%x\n", + debug("%s: entry 0x%x, size %u address %p, control 0x%x\n", __func__, entry, size, address, be32_to_cpu(dma.control)); - fwcfg_arch_ops->arch_read_dma(&dma); -} + barrier(); -bool qemu_fwcfg_present(void) -{ - return fwcfg_present; + ops->read_entry_dma(qdev->dev, &dma); } -bool qemu_fwcfg_dma_present(void) +void qfw_read_entry(struct udevice *dev, u16 entry, u32 size, void *address) { - return fwcfg_dma_present; -} + struct qfw_dev *qdev = dev_get_uclass_priv(dev); -void qemu_fwcfg_read_entry(uint16_t entry, uint32_t length, void *address) -{ - if (fwcfg_dma_present) - qemu_fwcfg_read_entry_dma(entry, length, address); + if (qdev->dma_present) + qfw_read_entry_dma(qdev, entry, size, address); else - qemu_fwcfg_read_entry_pio(entry, length, address); + qfw_read_entry_io(qdev, entry, size, address); } -int qemu_fwcfg_online_cpus(void) +int qfw_register(struct udevice *dev) { - uint16_t nb_cpus; + struct qfw_dev *qdev = dev_get_uclass_priv(dev); + u32 qemu, dma_enabled; - if (!fwcfg_present) + qdev->dev = dev; + INIT_LIST_HEAD(&qdev->fw_list); + + qfw_read_entry_io(qdev, FW_CFG_SIGNATURE, 4, &qemu); + if (be32_to_cpu(qemu) != QEMU_FW_CFG_SIGNATURE) return -ENODEV; - qemu_fwcfg_read_entry(FW_CFG_NB_CPUS, 2, &nb_cpus); - - return le16_to_cpu(nb_cpus); -} - -int qemu_fwcfg_read_firmware_list(void) -{ - int i; - uint32_t count; - struct fw_file *file; - struct list_head *entry; - - /* don't read it twice */ - if (!list_empty(&fw_list)) - return 0; - - qemu_fwcfg_read_entry(FW_CFG_FILE_DIR, 4, &count); - if (!count) - return 0; - - count = be32_to_cpu(count); - for (i = 0; i < count; i++) { - file = malloc(sizeof(*file)); - if (!file) { - printf("error: allocating resource\n"); - goto err; - } - qemu_fwcfg_read_entry(FW_CFG_INVALID, - sizeof(struct fw_cfg_file), &file->cfg); - file->addr = 0; - list_add_tail(&file->list, &fw_list); - } + qfw_read_entry_io(qdev, FW_CFG_ID, 1, &dma_enabled); + if (dma_enabled & FW_CFG_DMA_ENABLED) + qdev->dma_present = true; return 0; - -err: - list_for_each(entry, &fw_list) { - file = list_entry(entry, struct fw_file, list); - free(file); - } - - return -ENOMEM; } -struct fw_file *qemu_fwcfg_find_file(const char *name) -{ - struct list_head *entry; - struct fw_file *file; - - list_for_each(entry, &fw_list) { - file = list_entry(entry, struct fw_file, list); - if (!strcmp(file->cfg.name, name)) - return file; - } - - return NULL; -} - -struct fw_file *qemu_fwcfg_file_iter_init(struct fw_cfg_file_iter *iter) -{ - iter->entry = fw_list.next; - return list_entry((struct list_head *)iter->entry, - struct fw_file, list); -} - -struct fw_file *qemu_fwcfg_file_iter_next(struct fw_cfg_file_iter *iter) -{ - iter->entry = ((struct list_head *)iter->entry)->next; - return list_entry((struct list_head *)iter->entry, - struct fw_file, list); -} - -bool qemu_fwcfg_file_iter_end(struct fw_cfg_file_iter *iter) -{ - return iter->entry == &fw_list; -} - -void qemu_fwcfg_init(struct fw_cfg_arch_ops *ops) -{ - uint32_t qemu; - uint32_t dma_enabled; - - fwcfg_present = false; - fwcfg_dma_present = false; - fwcfg_arch_ops = NULL; - - if (!ops || !ops->arch_read_pio || !ops->arch_read_dma) - return; - fwcfg_arch_ops = ops; - - qemu_fwcfg_read_entry_pio(FW_CFG_SIGNATURE, 4, &qemu); - if (be32_to_cpu(qemu) == QEMU_FW_CFG_SIGNATURE) - fwcfg_present = true; - - if (fwcfg_present) { - qemu_fwcfg_read_entry_pio(FW_CFG_ID, 1, &dma_enabled); - if (dma_enabled & FW_CFG_DMA_ENABLED) - fwcfg_dma_present = true; - } -} +UCLASS_DRIVER(qfw) = { + .id = UCLASS_QFW, + .name = "qfw", + .per_device_auto = sizeof(struct qfw_dev), +}; diff --git a/drivers/misc/qfw_pio.c b/drivers/misc/qfw_pio.c new file mode 100644 index 0000000000..e2f628d338 --- /dev/null +++ b/drivers/misc/qfw_pio.c @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * PIO interface for QFW + * + * (C) Copyright 2015 Miao Yan + * (C) Copyright 2021 Asherah Connor + */ + +#define LOG_CATEGORY UCLASS_QFW + +#include +#include +#include + +/* + * PIO ports are correct for x86, which appears to be the only arch that uses + * PIO. + */ +#define FW_CONTROL_PORT 0x510 +#define FW_DATA_PORT 0x511 +#define FW_DMA_PORT_LOW 0x514 +#define FW_DMA_PORT_HIGH 0x518 + +static void qfw_pio_read_entry_io(struct udevice *dev, u16 entry, u32 size, + void *address) +{ + /* + * writing FW_CFG_INVALID will cause read operation to resume at last + * offset, otherwise read will start at offset 0 + * + * Note: on platform where the control register is IO port, the + * endianness is little endian. + */ + if (entry != FW_CFG_INVALID) + outw(cpu_to_le16(entry), FW_CONTROL_PORT); + + /* the endianness of data register is string-preserving */ + u32 i = 0; + u8 *data = address; + + while (size--) + data[i++] = inb(FW_DATA_PORT); +} + +/* Read configuration item using fw_cfg DMA interface */ +static void qfw_pio_read_entry_dma(struct udevice *dev, struct qfw_dma *dma) +{ + /* the DMA address register is big-endian */ + outl(cpu_to_be32((uintptr_t)dma), FW_DMA_PORT_HIGH); + + while (be32_to_cpu(dma->control) & ~FW_CFG_DMA_ERROR); +} + +static int qfw_pio_probe(struct udevice *dev) +{ + return qfw_register(dev); +} + +static struct dm_qfw_ops qfw_pio_ops = { + .read_entry_io = qfw_pio_read_entry_io, + .read_entry_dma = qfw_pio_read_entry_dma, +}; + +U_BOOT_DRIVER(qfw_pio) = { + .name = "qfw_pio", + .id = UCLASS_QFW, + .probe = qfw_pio_probe, + .ops = &qfw_pio_ops, +}; diff --git a/drivers/misc/qfw_sandbox.c b/drivers/misc/qfw_sandbox.c new file mode 100644 index 0000000000..7ce532310f --- /dev/null +++ b/drivers/misc/qfw_sandbox.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Sandbox interface for QFW + * + * (C) Copyright 2015 Miao Yan + * (C) Copyright 2021 Asherah Connor + */ + +#define LOG_CATEGORY UCLASS_QFW + +#include +#include +#include +#include +#include + +struct qfw_sandbox_plat { + u8 file_dir_offset; +}; + +static void qfw_sandbox_read_entry_io(struct udevice *dev, u16 entry, u32 size, + void *address) +{ + debug("%s: entry 0x%x size %u address %p\n", __func__, entry, size, + address); + + switch (entry) { + case FW_CFG_SIGNATURE: + if (size == 4) + *((u32 *)address) = cpu_to_be32(QEMU_FW_CFG_SIGNATURE); + break; + case FW_CFG_ID: + /* Advertise DMA support */ + if (size == 1) + *((u8 *)address) = FW_CFG_DMA_ENABLED; + break; + default: + debug("%s got unsupported entry 0x%x\n", __func__, entry); + /* + * Sandbox driver doesn't support other entries here, assume we use DMA + * to read them -- the uclass driver will exclusively use it when + * advertised. + */ + } +} + +static void qfw_sandbox_read_entry_dma(struct udevice *dev, struct qfw_dma *dma) +{ + u16 entry; + u32 control = be32_to_cpu(dma->control); + void *address = (void *)be64_to_cpu(dma->address); + u32 length = be32_to_cpu(dma->length); + struct qfw_sandbox_plat *plat = dev_get_plat(dev); + struct fw_cfg_file *file; + + debug("%s\n", __func__); + + if (!(control & FW_CFG_DMA_READ)) + return; + + if (control & FW_CFG_DMA_SELECT) { + /* Start new read. */ + entry = control >> 16; + + /* Arbitrary values to be used by tests. */ + switch (entry) { + case FW_CFG_NB_CPUS: + if (length == 2) + *((u16 *)address) = cpu_to_le16(5); + break; + case FW_CFG_FILE_DIR: + if (length == 4) { + *((u32 *)address) = cpu_to_be32(2); + plat->file_dir_offset = 1; + } + break; + default: + debug("%s got unsupported entry 0x%x\n", __func__, + entry); + } + } else if (plat->file_dir_offset && length == 64) { + file = address; + switch (plat->file_dir_offset) { + case 1: + file->size = cpu_to_be32(8); + file->select = cpu_to_be16(FW_CFG_FILE_FIRST); + strcpy(file->name, "test-one"); + plat->file_dir_offset++; + break; + case 2: + file->size = cpu_to_be32(8); + file->select = cpu_to_be16(FW_CFG_FILE_FIRST + 1); + strcpy(file->name, "test-two"); + plat->file_dir_offset++; + break; + } + } + + /* + * Signal that we are finished. No-one checks this in sandbox -- + * normally the platform-specific driver looks for it -- but let's + * replicate the behaviour in case someone relies on it later. + */ + dma->control = 0; +} + +static int qfw_sandbox_probe(struct udevice *dev) +{ + return qfw_register(dev); +} + +static struct dm_qfw_ops qfw_sandbox_ops = { + .read_entry_io = qfw_sandbox_read_entry_io, + .read_entry_dma = qfw_sandbox_read_entry_dma, +}; + +U_BOOT_DRIVER(qfw_sandbox) = { + .name = "qfw_sandbox", + .id = UCLASS_QFW, + .plat_auto = sizeof(struct qfw_sandbox_plat), + .probe = qfw_sandbox_probe, + .ops = &qfw_sandbox_ops, +}; + +U_BOOT_DRVINFO(qfw_sandbox) = { + .name = "qfw_sandbox", +}; + diff --git a/include/dm/uclass-id.h b/include/dm/uclass-id.h index d75de368c5..d800f679d5 100644 --- a/include/dm/uclass-id.h +++ b/include/dm/uclass-id.h @@ -90,6 +90,7 @@ enum uclass_id { UCLASS_POWER_DOMAIN, /* (SoC) Power domains */ UCLASS_PWM, /* Pulse-width modulator */ UCLASS_PWRSEQ, /* Power sequence device */ + UCLASS_QFW, /* QEMU firmware config device */ UCLASS_RAM, /* RAM controller */ UCLASS_REGULATOR, /* Regulator device */ UCLASS_REMOTEPROC, /* Remote Processor device */ diff --git a/include/qfw.h b/include/qfw.h index cea8e11d44..7ca132e66a 100644 --- a/include/qfw.h +++ b/include/qfw.h @@ -8,7 +8,12 @@ #include -enum qemu_fwcfg_items { +/* + * List of firmware configuration item selectors. The official source of truth + * for these is the QEMU source itself; see + * https://github.com/qemu/qemu/blob/master/hw/nvram/fw_cfg.c + */ +enum { FW_CFG_SIGNATURE = 0x00, FW_CFG_ID = 0x01, FW_CFG_UUID = 0x02, @@ -66,8 +71,10 @@ enum { #define FW_CFG_DMA_SKIP (1 << 2) #define FW_CFG_DMA_SELECT (1 << 3) +/* Bit set in FW_CFG_ID response to indicate DMA interface availability. */ #define FW_CFG_DMA_ENABLED (1 << 1) +/* Structs read from FW_CFG_FILE_DIR. */ struct fw_cfg_file { __be32 size; __be16 select; @@ -82,19 +89,7 @@ struct fw_file { }; struct fw_cfg_file_iter { - struct list_head *entry; /* structure to iterate file list */ -}; - -struct fw_cfg_dma_access { - __be32 control; - __be32 length; - __be64 address; -}; - -struct fw_cfg_arch_ops { - void (*arch_read_pio)(uint16_t selector, uint32_t size, - void *address); - void (*arch_read_dma)(struct fw_cfg_dma_access *dma); + struct list_head *entry, *end; /* structures to iterate file list */ }; struct bios_linker_entry { @@ -146,37 +141,178 @@ struct bios_linker_entry { }; } __packed; +/* DMA transfer control data between UCLASS_QFW and QEMU. */ +struct qfw_dma { + __be32 control; + __be32 length; + __be64 address; +}; + +/* uclass per-device configuration information */ +struct qfw_dev { + struct udevice *dev; /* Transport device */ + bool dma_present; /* DMA interface usable? */ + struct list_head fw_list; /* Cached firmware file list */ +}; + +/* Ops used internally between UCLASS_QFW and its driver implementations. */ +struct dm_qfw_ops { + /** + * read_entry_io() - Read a firmware config entry using the regular + * IO interface for the platform (either PIO or MMIO) + * + * Supply %FW_CFG_INVALID as the entry to continue a previous read. In + * this case, no selector will be issued before reading. + * + * @dev: Device to use + * @entry: Firmware config entry number (e.g. %FW_CFG_SIGNATURE) + * @size: Number of bytes to read + * @address: Target location for read + */ + void (*read_entry_io)(struct udevice *dev, u16 entry, u32 size, + void *address); + + /** + * read_entry_dma() - Read a firmware config entry using the DMA + * interface + * + * Supply FW_CFG_INVALID as the entry to continue a previous read. In + * this case, no selector will be issued before reading. + * + * This method assumes DMA availability has already been confirmed. + * + * @dev: Device to use + * @dma: DMA transfer control struct + */ + void (*read_entry_dma)(struct udevice *dev, struct qfw_dma *dma); +}; + +#define dm_qfw_get_ops(dev) \ + ((struct dm_qfw_ops *)(dev)->driver->ops) + /** - * Initialize QEMU fw_cfg interface + * qfw_register() - Called by a qfw driver after successful probe. + * @dev: Device registering itself with the uclass. + * + * Used internally by driver implementations on successful probe. * - * @ops: arch specific read operations + * Return: 0 on success, negative otherwise. */ -void qemu_fwcfg_init(struct fw_cfg_arch_ops *ops); +int qfw_register(struct udevice *dev); -void qemu_fwcfg_read_entry(uint16_t entry, uint32_t length, void *address); -int qemu_fwcfg_read_firmware_list(void); -struct fw_file *qemu_fwcfg_find_file(const char *name); +struct udevice; + +/** + * qfw_get_dev() - Get QEMU firmware config device. + * @devp: Pointer to be filled with address of the qfw device. + * + * Gets the active QEMU firmware config device, for use with qfw_read_entry() + * and others. + * + * Return: 0 on success, -ENODEV if the device is not available. + */ +int qfw_get_dev(struct udevice **devp); /** - * Get system cpu number + * qfw_read_entry() - Read a QEMU firmware config entry + * @dev: QFW device to use. + * @entry: Firmware config entry number (e.g. %FW_CFG_SIGNATURE). + * @size: Number of bytes to read. + * @address: Target location for read. * - * @return: cpu number in system + * Reads a QEMU firmware config entry using @dev. DMA will be used if the QEMU + * machine supports it, otherwise PIO/MMIO. */ -int qemu_fwcfg_online_cpus(void); +void qfw_read_entry(struct udevice *dev, u16 entry, u32 size, void *address); -/* helper functions to iterate firmware file list */ -struct fw_file *qemu_fwcfg_file_iter_init(struct fw_cfg_file_iter *iter); -struct fw_file *qemu_fwcfg_file_iter_next(struct fw_cfg_file_iter *iter); -bool qemu_fwcfg_file_iter_end(struct fw_cfg_file_iter *iter); +/** + * qfw_read_firmware_list() - Read and cache the QEMU firmware config file + * list. + * @dev: QFW device to use. + * + * Reads the QEMU firmware config file list, caching it against @dev for later + * use with qfw_find_file(). + * + * If the list has already been read, does nothing and returns 0 (success). + * + * Return: 0 on success, -ENOMEM if unable to allocate. + */ +int qfw_read_firmware_list(struct udevice *dev); + +/** + * qfw_find_file() - Find a file by name in the QEMU firmware config file + * list. + * @dev: QFW device to use. + * @name: Name of file to locate (e.g. "etc/table-loader"). + * + * Finds a file by name in the QEMU firmware config file list cached against + * @dev. You must call qfw_read_firmware_list() successfully first for this to + * succeed. + * + * Return: Pointer to &struct fw_file if found, %NULL if not present. + */ +struct fw_file *qfw_find_file(struct udevice *dev, const char *name); -bool qemu_fwcfg_present(void); -bool qemu_fwcfg_dma_present(void); +/** + * qfw_online_cpus() - Get number of CPUs in system from QEMU firmware config. + * @dev: QFW device to use. + * + * Asks QEMU to report how many CPUs it is emulating for the machine. + * + * Return: Number of CPUs in the system. + */ +int qfw_online_cpus(struct udevice *dev); + +/** + * qfw_file_iter_init() - Start iterating cached firmware file list. + * @dev: QFW device to use. + * @iter: Iterator to be initialised. + * + * Starts iterating the cached firmware file list in @dev. You must call + * qfw_read_firmware_list() successfully first, otherwise you will always get + * an empty list. + * + * qfw_file_iter_init() returns the first &struct fw_file, but it may be + * invalid if the list is empty. Check that ``!qfw_file_iter_end(&iter)`` + * first. + * + * Return: The first &struct fw_file item in the firmware file list, if any. + * Only valid when qfw_file_iter_end() is not true after the call. + */ +struct fw_file *qfw_file_iter_init(struct udevice *dev, + struct fw_cfg_file_iter *iter); + +/** + * qfw_file_iter_next() - Iterate cached firmware file list. + * @iter: Iterator to use. + * + * Continues iterating the cached firmware file list in @dev. You must call + * qfw_file_iter_init() first to initialise it. Check that + * ``!qfw_file_iter_end(&iter)`` before using the return value of this + * function. + * + * Return: The next &struct fw_file item in the firmware file list. Only valid + * when qfw_file_iter_end() is not true after the call. + */ +struct fw_file *qfw_file_iter_next(struct fw_cfg_file_iter *iter); + +/** + * qfw_file_iter_end() - Check if iter is at end of list. + * @iter: Iterator to use. + * + * Checks whether or not the iterator is at its end position. If so, the + * qfw_file_iter_init() or qfw_file_iter_next() call that immediately preceded + * returned invalid data. + * + * Return: True if the iterator is at its end; false otherwise. + */ +bool qfw_file_iter_end(struct fw_cfg_file_iter *iter); /** - * qemu_cpu_fixup() - Fix up the CPUs for QEMU + * qemu_cpu_fixup() - Fix up the CPUs for QEMU. * - * @return 0 if OK, -ENODEV if no CPUs, -ENOMEM if out of memory, other -ve on - * on other error + * Return: 0 on success, -ENODEV if no CPUs, -ENOMEM if out of memory, other < + * 0 on on other error. */ int qemu_cpu_fixup(void); diff --git a/test/dm/Makefile b/test/dm/Makefile index fd1455109d..fc2902a3ea 100644 --- a/test/dm/Makefile +++ b/test/dm/Makefile @@ -98,5 +98,6 @@ endif ifneq ($(CONFIG_EFI_PARTITION),) obj-$(CONFIG_FASTBOOT_FLASH_MMC) += fastboot.o endif +obj-$(CONFIG_QFW) += qfw.o endif endif # !SPL diff --git a/test/dm/qfw.c b/test/dm/qfw.c new file mode 100644 index 0000000000..f3f3568983 --- /dev/null +++ b/test/dm/qfw.c @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright 2021 Asherah Connor + */ + +#include +#include +#include +#include +#include +#include + +/* + * Exercise the device enough to be satisfied the initialisation and DMA + * interfaces work. + */ + +static int dm_test_qfw_cpus(struct unit_test_state *uts) +{ + struct udevice *dev; + + ut_assertok(uclass_first_device_err(UCLASS_QFW, &dev)); + ut_asserteq(5, qfw_online_cpus(dev)); + + return 0; +} + +DM_TEST(dm_test_qfw_cpus, UT_TESTF_SCAN_PDATA); + +static int dm_test_qfw_firmware_list(struct unit_test_state *uts) +{ + struct udevice *dev; + struct fw_file *file; + + ut_assertok(uclass_first_device_err(UCLASS_QFW, &dev)); + ut_assertok(qfw_read_firmware_list(dev)); + ut_assertok_ptr((file = qfw_find_file(dev, "test-one"))); + + return 0; +} + +DM_TEST(dm_test_qfw_firmware_list, UT_TESTF_SCAN_PDATA); diff --git a/test/py/tests/test_qfw.py b/test/py/tests/test_qfw.py new file mode 100644 index 0000000000..a2631a0fa6 --- /dev/null +++ b/test/py/tests/test_qfw.py @@ -0,0 +1,21 @@ +# SPDX-License-Identifier: GPL-2.0+ +# Copyright (c) 2021, Asherah Connor + +# Test qfw command implementation + +import pytest + +@pytest.mark.buildconfigspec('cmd_qfw') +def test_qfw_cpus(u_boot_console): + "Test QEMU firmware config reports the CPU count correctly." + + output = u_boot_console.run_command('qfw cpus') + assert '1 cpu(s) online' in output + +@pytest.mark.buildconfigspec('cmd_qfw') +def test_qfw_list(u_boot_console): + "Test QEMU firmware config lists devices." + + output = u_boot_console.run_command('qfw list') + assert 'bootorder' in output + assert 'etc/table-loader' in output