From 5b0b43e0e25dbeed62a2155e7f3be562b9ceb9bd Mon Sep 17 00:00:00 2001 From: Asherah Connor Date: Fri, 19 Mar 2021 18:21:40 +1100 Subject: [PATCH] x86: qemu: move QFW to its own uclass 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). include/qfw.h is cleaned up and documentation added. Signed-off-by: Asherah Connor Reviewed-by: Simon Glass Reviewed-by: Bin Meng Tested-by: Bin Meng --- 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 | 104 +++++++++++++ drivers/misc/Kconfig | 11 +- drivers/misc/Makefile | 5 +- drivers/misc/qfw.c | 245 +++++++++++-------------------- drivers/misc/qfw_pio.c | 69 +++++++++ include/dm/uclass-id.h | 1 + include/qfw.h | 204 ++++++++++++++++++++----- 13 files changed, 492 insertions(+), 275 deletions(-) create mode 100644 common/qfw.c create mode 100644 drivers/misc/qfw_pio.c 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 215b8b26fd..0952ae23f8 100644 --- a/common/Makefile +++ b/common/Makefile @@ -138,3 +138,5 @@ obj-$(CONFIG_$(SPL_TPL_)YMODEM_SUPPORT) += xyzModem.o obj-$(CONFIG_AVB_VERIFY) += avb_verify.o obj-$(CONFIG_SCP03) += scp03.o + +obj-$(CONFIG_QFW) += qfw.o diff --git a/common/qfw.c b/common/qfw.c new file mode 100644 index 0000000000..90cbb8c5dd --- /dev/null +++ b/common/qfw.c @@ -0,0 +1,104 @@ +// 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..53bff72428 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -55,7 +55,10 @@ 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 +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(); + + ops->read_entry_dma(qdev->dev, &dma); } -bool qemu_fwcfg_present(void) +void qfw_read_entry(struct udevice *dev, u16 entry, u32 size, void *address) { - return fwcfg_present; -} + struct qfw_dev *qdev = dev_get_uclass_priv(dev); -bool qemu_fwcfg_dma_present(void) -{ - return fwcfg_dma_present; -} - -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/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. * - * @ops: arch specific read operations + * Used internally by driver implementations on successful probe. + * + * 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; /** - * Get system cpu number + * qfw_get_dev() - Get QEMU firmware config device. + * @devp: Pointer to be filled with address of the qfw device. * - * @return: cpu number in system + * 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 qemu_fwcfg_online_cpus(void); - -/* 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); - -bool qemu_fwcfg_present(void); -bool qemu_fwcfg_dma_present(void); +int qfw_get_dev(struct udevice **devp); /** - * qemu_cpu_fixup() - Fix up the CPUs for QEMU + * 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 0 if OK, -ENODEV if no CPUs, -ENOMEM if out of memory, other -ve on - * on other error + * Reads a QEMU firmware config entry using @dev. DMA will be used if the QEMU + * machine supports it, otherwise PIO/MMIO. + */ +void qfw_read_entry(struct udevice *dev, u16 entry, u32 size, void *address); + +/** + * 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); + +/** + * 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. + * + * Return: 0 on success, -ENODEV if no CPUs, -ENOMEM if out of memory, other < + * 0 on on other error. */ int qemu_cpu_fixup(void);