[PATCH v5 0/3] Move qfw to DM, add Arm support

This series moves the QFW driver into a uclass, UCLASS_QFW, and splits the driver into qfw_pio and qfw_mmio. Each driver is selected on the appropriate QEMU board. A sandbox driver is also added, and a DM unit test against that driver. The qfw command is tested in QEMU, and documentation added.
Version 5 reworks the series into three patches: 1. Do the DM conversion of existing QFW on x86 2. Add MMIO driver 3. Add the MMIO driver to qemu-arm
To view the changes online, see: https://git.src.kameliya.ee/~kameliya/u-boot/log/qfw-priv
Changes in v5: * Split conversion of existing x86-only QFW to DM into its own patch. * Split MMIO driver into its own commit. * Split adding MMIO driver to QEMU arm/64 into own commit.
Asherah Connor (3): x86: qemu: move QFW to its own uclass qemu: add MMIO driver for QFW qemu: arm: select QFW, MMIO on qemu-arm
arch/x86/cpu/qemu/cpu.c | 9 +- arch/x86/cpu/qemu/qemu.c | 49 +------ arch/x86/cpu/qfw_cpu.c | 11 +- board/emulation/qemu-arm/Kconfig | 2 + board/emulation/qemu-x86/Kconfig | 1 + cmd/qfw.c | 56 ++++--- common/Makefile | 2 + common/qfw.c | 105 +++++++++++++ drivers/misc/Kconfig | 18 ++- drivers/misc/Makefile | 7 +- drivers/misc/qfw.c | 243 +++++++++++-------------------- drivers/misc/qfw_mmio.c | 119 +++++++++++++++ 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 +++ 19 files changed, 812 insertions(+), 272 deletions(-) create mode 100644 common/qfw.c create mode 100644 drivers/misc/qfw_mmio.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

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 ashe@kivikakk.ee
---
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 <init.h> #include <pci.h> #include <qfw.h> +#include <dm/platdata.h> #include <asm/irq.h> #include <asm/post.h> #include <asm/processor.h> @@ -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 <env.h> #include <errno.h> #include <qfw.h> +#include <dm.h> + +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 yanmiaobest@gmail.com + * (C) Copyright 2021 Asherah Connor ashe@kivikakk.ee + */ + +#include <dm.h> +#include <dm/uclass.h> +#include <qfw.h> +#include <stdlib.h> + +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 yanmiaobest@gmail.com + * (C) Copyright 2021 Asherah Connor ashe@kivikakk.ee */
+#define LOG_CATEGORY UCLASS_QFW + #include <common.h> #include <command.h> #include <errno.h> #include <log.h> #include <malloc.h> #include <qfw.h> -#include <asm/io.h> +#include <dm.h> +#include <misc.h> #ifdef CONFIG_GENERATE_ACPI_TABLE #include <asm/tables.h> #endif -#include <linux/list.h> - -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 yanmiaobest@gmail.com + * (C) Copyright 2021 Asherah Connor ashe@kivikakk.ee + */ + +#define LOG_CATEGORY UCLASS_QFW + +#include <asm/io.h> +#include <dm/device.h> +#include <qfw.h> + +/* + * 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 yanmiaobest@gmail.com + * (C) Copyright 2021 Asherah Connor ashe@kivikakk.ee + */ + +#define LOG_CATEGORY UCLASS_QFW + +#include <asm/types.h> +#include <asm/io.h> +#include <dm.h> +#include <dm/device.h> +#include <qfw.h> + +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 <linux/list.h>
-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 ashe@kivikakk.ee + */ + +#include <common.h> +#include <qfw.h> +#include <dm.h> +#include <asm/test.h> +#include <dm/test.h> +#include <test/ut.h> + +/* + * 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 ashe@kivikakk.ee + +# 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

Hi Asherah,
On Mon, 1 Mar 2021 at 01:35, Asherah Connor ashe@kivikakk.ee wrote:
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 ashe@kivikakk.ee
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
This looks sensible but it really is a huge patch. Can it be split up a bit?
Regards, Simon

Hi Simon,
On 21/03/04 11:03:p, Simon Glass wrote:
This looks sensible but it really is a huge patch. Can it be split up a bit?
This is a rejig of v4 previously Reviewed-by: you, see https://patchwork.ozlabs.org/project/uboot/list/?series=230778&state=%2a.
Bin Meng suggested the patch be reworked along these lines so that MMIO and Arm support were added separately, see https://patchwork.ozlabs.org/project/uboot/patch/20210224032323.15798-2-ashe....
I'm happy to rework it further but it's not clear to me what the best way to do that is. Perhaps separating out the qfw.h Sphinx documentation and sandbox driver/tests? Let me know, I would like to get it right.
Thanks,
Asherah

Hi Asherah,
On Thu, 4 Mar 2021 at 21:16, Asherah Connor ashe@kivikakk.ee wrote:
Hi Simon,
On 21/03/04 11:03:p, Simon Glass wrote:
This looks sensible but it really is a huge patch. Can it be split up a bit?
This is a rejig of v4 previously Reviewed-by: you, see https://patchwork.ozlabs.org/project/uboot/list/?series=230778&state=%2a.
If I previously reviewed it you can add my tag.
Bin Meng suggested the patch be reworked along these lines so that MMIO and Arm support were added separately, see https://patchwork.ozlabs.org/project/uboot/patch/20210224032323.15798-2-ashe....
I'm happy to rework it further but it's not clear to me what the best way to do that is. Perhaps separating out the qfw.h Sphinx documentation and sandbox driver/tests? Let me know, I would like to get it right.
I think sandbox driver/tests being separate is a good idea.
Regards, Simon

Hi Asherah,
On Fri, Mar 5, 2021 at 10:31 PM Simon Glass sjg@chromium.org wrote:
Hi Asherah,
On Thu, 4 Mar 2021 at 21:16, Asherah Connor ashe@kivikakk.ee wrote:
Hi Simon,
On 21/03/04 11:03:p, Simon Glass wrote:
This looks sensible but it really is a huge patch. Can it be split up a bit?
This is a rejig of v4 previously Reviewed-by: you, see https://patchwork.ozlabs.org/project/uboot/list/?series=230778&state=%2a.
If I previously reviewed it you can add my tag.
Bin Meng suggested the patch be reworked along these lines so that MMIO and Arm support were added separately, see https://patchwork.ozlabs.org/project/uboot/patch/20210224032323.15798-2-ashe....
Thanks for making the split.
I'm happy to rework it further but it's not clear to me what the best way to do that is. Perhaps separating out the qfw.h Sphinx documentation and sandbox driver/tests? Let me know, I would like to get it right.
I think sandbox driver/tests being separate is a good idea.
Yes, as Simon said, sandbox driver/test should be a separate patch.
Regards, Bin

Hi Bin, Simon,
On 21/03/05 10:03:p, Bin Meng wrote:
On Fri, Mar 5, 2021 at 10:31 PM Simon Glass sjg@chromium.org wrote:
If I previously reviewed it you can add my tag.
I wasn't sure of the correct etiquette when a large rearrangement of the patches had been done; I'll keep this in mind.
I think sandbox driver/tests being separate is a good idea.
Yes, as Simon said, sandbox driver/test should be a separate patch.
This will be sent along shortly. The initial rework commit email is still fairly sizeable (+490 -272), but I've compared this series with v4 (which had sjg's Reviewed-by: on all initially) and confirmed nothing new has crept in other than:
* part 1: * adding config QFW_PIO to select the PIO driver * completing documentation to Sphinx standard * part 2: * nothing new * part 3: * adding config QFW_MMIO to select the driver * part 4: * just adds two `select's to Kconfig
So there should be only these parts of the patch needing careful review. Given parts 1, 3 and 4 are still at least partly unreviewed, I will not carry the Reviewed-by: over. 2 is good to go.
Best,
Asherah

Add MMIO driver for QFW.
Note that there is no consumer as of this patch.
Signed-off-by: Asherah Connor ashe@kivikakk.ee ---
Changes in v5: * Split MMIO driver into its own commit. * Add CONFIG_QFW_MMIO for selection by arch/board.
drivers/misc/Kconfig | 7 +++ drivers/misc/Makefile | 1 + drivers/misc/qfw_mmio.c | 119 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 drivers/misc/qfw_mmio.c
diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index 3a254eba4b..c650471ff7 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -378,6 +378,13 @@ config QFW_PIO Hidden option to enable PIO QEMU fw_cfg interface. This will be selected by the appropriate QEMU board.
+config QFW_MMIO + bool + depends on QFW + help + Hidden option to enable MMIO QEMU fw_cfg interface. This will be + selected by the appropriate QEMU board. + config I2C_EEPROM bool "Enable driver for generic I2C-attached EEPROMs" depends on MISC diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 2864b84af9..0c67d43a5d 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -58,6 +58,7 @@ obj-$(CONFIG_$(SPL_)PWRSEQ) += pwrseq-uclass.o ifdef CONFIG_QFW obj-y += qfw.o obj-$(CONFIG_QFW_PIO) += qfw_pio.o +obj-$(CONFIG_QFW_MMIO) += qfw_mmio.o obj-$(CONFIG_SANDBOX) += qfw_sandbox.o endif obj-$(CONFIG_ROCKCHIP_EFUSE) += rockchip-efuse.o diff --git a/drivers/misc/qfw_mmio.c b/drivers/misc/qfw_mmio.c new file mode 100644 index 0000000000..f397384054 --- /dev/null +++ b/drivers/misc/qfw_mmio.c @@ -0,0 +1,119 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * MMIO interface for QFW + * + * (C) Copyright 2015 Miao Yan yanmiaobest@gmail.com + * (C) Copyright 2021 Asherah Connor ashe@kivikakk.ee + */ + +#define LOG_CATEGORY UCLASS_QFW + +#include <asm/types.h> +#include <asm/io.h> +#include <dm.h> +#include <dm/device.h> +#include <qfw.h> + +struct qfw_mmio { + /* + * Each access to the 64-bit data register can be 8/16/32/64 bits wide. + */ + union { + u8 data8; + u16 data16; + u32 data32; + u64 data64; + }; + u16 selector; + u8 padding[6]; + u64 dma; +}; + +struct qfw_mmio_plat { + volatile struct qfw_mmio *mmio; +}; + +static void qfw_mmio_read_entry_io(struct udevice *dev, u16 entry, u32 size, + void *address) +{ + struct qfw_mmio_plat *plat = dev_get_plat(dev); + + /* + * 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 MMIO, the register + * is big endian. + */ + if (entry != FW_CFG_INVALID) + plat->mmio->selector = cpu_to_be16(entry); + + /* the endianness of data register is string-preserving */ + while (size >= 8) { + *(u64 *)address = plat->mmio->data64; + address += 8; + size -= 8; + } + while (size >= 4) { + *(u32 *)address = plat->mmio->data32; + address += 4; + size -= 4; + } + while (size >= 2) { + *(u16 *)address = plat->mmio->data16; + address += 2; + size -= 2; + } + while (size >= 1) { + *(u8 *)address = plat->mmio->data8; + address += 1; + size -= 1; + } +} + +/* Read configuration item using fw_cfg DMA interface */ +static void qfw_mmio_read_entry_dma(struct udevice *dev, struct qfw_dma *dma) +{ + struct qfw_mmio_plat *plat = dev_get_plat(dev); + + /* the DMA address register is big-endian */ + plat->mmio->dma = cpu_to_be64((uintptr_t)dma); + + while (be32_to_cpu(dma->control) & ~FW_CFG_DMA_ERROR); +} + +static int qfw_mmio_of_to_plat(struct udevice *dev) +{ + struct qfw_mmio_plat *plat = dev_get_plat(dev); + + plat->mmio = map_physmem(dev_read_addr(dev), + sizeof(struct qfw_mmio), + MAP_NOCACHE); + + return 0; +} + +static int qfw_mmio_probe(struct udevice *dev) +{ + return qfw_register(dev); +} + +static struct dm_qfw_ops qfw_mmio_ops = { + .read_entry_io = qfw_mmio_read_entry_io, + .read_entry_dma = qfw_mmio_read_entry_dma, +}; + +static const struct udevice_id qfw_mmio_ids[] = { + { .compatible = "qemu,fw-cfg-mmio" }, + {} +}; + +U_BOOT_DRIVER(qfw_mmio) = { + .name = "qfw_mmio", + .id = UCLASS_QFW, + .of_match = qfw_mmio_ids, + .plat_auto = sizeof(struct qfw_mmio_plat), + .of_to_plat = qfw_mmio_of_to_plat, + .probe = qfw_mmio_probe, + .ops = &qfw_mmio_ops, +};

Select CMD_QFW and QFW_MMIO in the qemu-arm board (covers arm and arm64).
Signed-off-by: Asherah Connor ashe@kivikakk.ee
---
Changes in v5: * Split adding MMIO driver to QEMU arm/64 into own commit. * Use the generic qemu-arm board config rather than adding to multiple defconfigs.
board/emulation/qemu-arm/Kconfig | 2 ++ 1 file changed, 2 insertions(+)
diff --git a/board/emulation/qemu-arm/Kconfig b/board/emulation/qemu-arm/Kconfig index fb8d38f5b8..95dbefa78b 100644 --- a/board/emulation/qemu-arm/Kconfig +++ b/board/emulation/qemu-arm/Kconfig @@ -5,6 +5,8 @@ config SYS_TEXT_BASE
config BOARD_SPECIFIC_OPTIONS # dummy def_bool y + select CMD_QFW + select QFW_MMIO imply VIRTIO_MMIO imply VIRTIO_PCI imply VIRTIO_NET
participants (3)
-
Asherah Connor
-
Bin Meng
-
Simon Glass