summaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
authorGuEe-GUI <[email protected]>2026-03-01 13:10:54 +0800
committerRbb666 <[email protected]>2026-06-15 10:00:57 +0800
commita777a34758a72a037858f26e90e5e5a5f628bc81 (patch)
treeeffc843a070bcff2ff51a8a14f9a2e25e29cf8f6 /components
parent8517f2d086924f66fcb867f7a84ff2855f9926bd (diff)
[dm][firmware] support QEMU fw config
Signed-off-by: GuEe-GUI <[email protected]>
Diffstat (limited to 'components')
-rwxr-xr-xcomponents/drivers/firmware/Kconfig5
-rw-r--r--components/drivers/firmware/qemu/SConscript15
-rw-r--r--components/drivers/firmware/qemu/fw_cfg.c518
-rw-r--r--components/drivers/firmware/qemu/fw_cfg.h109
4 files changed, 647 insertions, 0 deletions
diff --git a/components/drivers/firmware/Kconfig b/components/drivers/firmware/Kconfig
index 4261e6157e..6aa179dde5 100755
--- a/components/drivers/firmware/Kconfig
+++ b/components/drivers/firmware/Kconfig
@@ -3,6 +3,11 @@ menuconfig RT_USING_FIRMWARE
depends on RT_USING_DM
default n
+config RT_FIRMWARE_QEMU_FW_CFG
+ bool "QEMU Firmware Configuration"
+ depends on RT_USING_FIRMWARE
+ default n
+
if RT_USING_FIRMWARE
rsource "arm_scmi/Kconfig"
osource "$(SOC_DM_FIRMWARE_DIR)/Kconfig"
diff --git a/components/drivers/firmware/qemu/SConscript b/components/drivers/firmware/qemu/SConscript
new file mode 100644
index 0000000000..1f5108aa31
--- /dev/null
+++ b/components/drivers/firmware/qemu/SConscript
@@ -0,0 +1,15 @@
+from building import *
+
+group = []
+
+if not GetDepend(['RT_FIRMWARE_QEMU_FW_CFG']):
+ Return('group')
+
+cwd = GetCurrentDir()
+CPPPATH = [cwd + '/../../include']
+
+src = Glob('*.c')
+
+group = DefineGroup('DeviceDrivers', src, depend = [''], CPPPATH = CPPPATH)
+
+Return('group')
diff --git a/components/drivers/firmware/qemu/fw_cfg.c b/components/drivers/firmware/qemu/fw_cfg.c
new file mode 100644
index 0000000000..20a535866a
--- /dev/null
+++ b/components/drivers/firmware/qemu/fw_cfg.c
@@ -0,0 +1,518 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2023-02-25 GuEe-GUI the first version
+ */
+
+#include <rthw.h>
+#include <rtthread.h>
+#include <rtdevice.h>
+#include <rtservice.h>
+
+#define DBG_TAG "fw.qemu"
+#define DBG_LVL DBG_INFO
+#include <rtdbg.h>
+
+#include <mmu.h>
+#include <cpuport.h>
+
+#include "fw_cfg.h"
+
+/* arch-specific ctrl & data register offsets are not available in ACPI, DT */
+#if !(defined(FW_CFG_CTRL_OFF) && defined(FW_CFG_DATA_OFF))
+# if (defined(ARCH_ARM) || defined(ARCH_ARMV8) || defined(ARCH_LOONGARCH) || defined(ARCH_RISCV))
+# define FW_CFG_CTRL_OFF 0x08
+# define FW_CFG_DATA_OFF 0x00
+# define FW_CFG_DMA_OFF 0x10
+# elif defined(ARCH_PARISC) /* parisc */
+# define FW_CFG_CTRL_OFF 0x00
+# define FW_CFG_DATA_OFF 0x04
+# elif (defined(ARCH_PPC) || defined(ARCH_SPARC32)) /* ppc/mac,sun4m */
+# define FW_CFG_CTRL_OFF 0x00
+# define FW_CFG_DATA_OFF 0x02
+# elif (defined(ARCH_IA32) || defined(ARCH_SPARC64)) /* x86, sun4u */
+# define FW_CFG_CTRL_OFF 0x00
+# define FW_CFG_DATA_OFF 0x01
+# define FW_CFG_DMA_OFF 0x04
+# else
+# error "QEMU FW_CFG not available on this architecture!"
+# endif
+#endif
+
+struct fw_cfg_device
+{
+ struct rt_device parent;
+
+ rt_uint32_t size;
+ rt_uint16_t select;
+ char name[FW_CFG_MAX_FILE_PATH];
+};
+#define raw_to_fw_cfg_device(raw) rt_container_of(raw, struct fw_cfg_device, parent)
+
+static void *_fw_cfg_dev_base;
+static void *_fw_cfg_reg_ctrl;
+static void *_fw_cfg_reg_data;
+static void *_fw_cfg_reg_dma;
+static rt_bool_t _fw_cfg_is_mmio;
+static rt_uint32_t _fw_cfg_rev = 0;
+
+static RT_DEFINE_SPINLOCK(_fw_cfg_dev_lock);
+
+static void fw_cfg_sel_endianness(rt_uint16_t key)
+{
+ if (_fw_cfg_is_mmio)
+ {
+ HWREG16(_fw_cfg_reg_ctrl) = rt_cpu_to_be16(key);
+ }
+ else
+ {
+ HWREG16(_fw_cfg_reg_ctrl) = key;
+ }
+}
+
+static rt_base_t fw_cfg_read_blob(rt_uint16_t key, void *buf, rt_off_t pos, rt_size_t count)
+{
+ rt_uint8_t tmp;
+
+ rt_spin_lock(&_fw_cfg_dev_lock);
+ fw_cfg_sel_endianness(key);
+
+ while (pos-- > 0)
+ {
+ tmp = HWREG8(_fw_cfg_reg_data);
+ }
+
+ if (count)
+ {
+ int loop = count;
+ rt_uint8_t *buffer = buf;
+
+ do {
+ tmp = HWREG8(_fw_cfg_reg_data);
+ *buffer++ = tmp;
+ } while (--loop);
+ }
+
+ rt_spin_unlock(&_fw_cfg_dev_lock);
+
+ return count;
+}
+
+rt_inline rt_bool_t fw_cfg_dma_enabled(void)
+{
+ return (_fw_cfg_rev & FW_CFG_VERSION_DMA) && _fw_cfg_reg_dma;
+}
+
+/* qemu fw_cfg device is sync today, but spec says it may become async */
+rt_inline void fw_cfg_wait_for_control(struct fw_cfg_dma_access *dma)
+{
+ for (;;)
+ {
+ rt_uint32_t ctrl = rt_be32_to_cpu(HWREG32(&dma->control));
+
+ /* Do not reorder the read to dma->control */
+ rt_hw_rmb();
+
+ if ((ctrl & ~FW_CFG_DMA_CTL_ERROR) == 0)
+ {
+ break;
+ }
+
+ rt_hw_cpu_relax();
+ }
+}
+
+rt_inline rt_base_t fw_cfg_dma_transfer(void *address, rt_uint32_t length, rt_uint32_t control)
+{
+ rt_ubase_t dma_pa;
+ rt_base_t res = length;
+ struct fw_cfg_dma_access dma =
+ {
+ .address = rt_cpu_to_be64((rt_uint64_t)(address ? rt_kmem_v2p(address) : 0)),
+ .length = rt_cpu_to_be32(length),
+ .control = rt_cpu_to_be32(control),
+ };
+
+ dma_pa = (rt_ubase_t)rt_kmem_v2p(&dma);
+
+ HWREG32(_fw_cfg_reg_dma) = rt_cpu_to_be32((rt_uint64_t)dma_pa >> 32);
+ /* Force memory to sync before notifying device via MMIO */
+ rt_hw_wmb();
+ HWREG32(_fw_cfg_reg_dma + 4) = rt_cpu_to_be32(dma_pa);
+
+ fw_cfg_wait_for_control(&dma);
+
+ if ((rt_be32_to_cpu(HWREG32(&dma.control)) & FW_CFG_DMA_CTL_ERROR))
+ {
+ res = -RT_EIO;
+ }
+
+ return res;
+}
+
+rt_inline rt_base_t fw_cfg_write_blob(rt_uint16_t key, void *buf, rt_off_t pos, rt_size_t count)
+{
+ rt_base_t res = count;
+
+ rt_spin_lock(&_fw_cfg_dev_lock);
+
+ if (pos == 0)
+ {
+ res = fw_cfg_dma_transfer(buf, count, key << 16 | FW_CFG_DMA_CTL_SELECT | FW_CFG_DMA_CTL_WRITE);
+ }
+ else
+ {
+ fw_cfg_sel_endianness(key);
+ res = fw_cfg_dma_transfer(RT_NULL, pos, FW_CFG_DMA_CTL_SKIP);
+
+ if (res >= 0)
+ {
+ res = fw_cfg_dma_transfer(buf, count, FW_CFG_DMA_CTL_WRITE);
+ }
+ }
+
+ rt_spin_unlock(&_fw_cfg_dev_lock);
+
+ return res;
+}
+
+#ifdef RT_GRAPHIC_FB
+struct ramfb_device
+{
+ struct rt_graphic_device parent;
+
+ const struct fw_cfg_file *file;
+};
+
+extern void platform_get_ramfb_params(rt_uint32_t *width, rt_uint32_t *height);
+
+static rt_uint32_t ramfb_formats[] =
+{
+#define fourcc_code(a, b, c, d) \
+ ((rt_uint32_t)(a) | \
+ ((rt_uint32_t)(b) << 8) | \
+ ((rt_uint32_t)(c) << 16) | \
+ ((rt_uint32_t)(d) << 24))
+
+ fourcc_code('A', 'R', '2', '4'),
+ fourcc_code('A', 'B', '2', '4'),
+ fourcc_code('R', 'G', '2', '4'),
+};
+
+static rt_uint32_t ramfb_modes[] =
+{
+ RTGRAPHIC_PIXEL_FORMAT_ARGB888,
+ RTGRAPHIC_PIXEL_FORMAT_ABGR888,
+ RTGRAPHIC_PIXEL_FORMAT_RGB888,
+};
+
+static rt_err_t ramfb_plane_fb_remap(struct rt_graphic_plane *plane,
+ rt_uint32_t mode, struct rt_device_rect_info *rect)
+{
+ void *framebuffer;
+ rt_size_t framebuffer_size;
+ rt_uint32_t stride, format, bpp;
+ struct fw_cfg_ram_fb ram_fb;
+ struct ramfb_device *ramfb = rt_container_of(plane->graphic, struct ramfb_device, parent);
+
+ bpp = rt_graphic_mode_bpp(mode);
+ stride = RT_ALIGN(rect->width * (bpp / 8), sizeof(rt_uint32_t));
+ framebuffer_size = rect->height * stride;
+ framebuffer = rt_malloc_align(framebuffer_size, ARCH_PAGE_SIZE);
+
+ if (!framebuffer)
+ {
+ return -RT_ENOMEM;
+ }
+
+ for (int i = 0; i < RT_ARRAY_SIZE(ramfb_modes); ++i)
+ {
+ if (mode == ramfb_modes[i])
+ {
+ format = ramfb_formats[i];
+ break;
+ }
+ }
+
+ ram_fb.addr = rt_cpu_to_be64((rt_ubase_t)rt_kmem_v2p(framebuffer));
+ ram_fb.fourcc = rt_cpu_to_be32(format);
+ ram_fb.flags = rt_cpu_to_be32(0);
+ ram_fb.width = rt_cpu_to_be32(rect->width);
+ ram_fb.height = rt_cpu_to_be32(rect->height);
+ ram_fb.stride = rt_cpu_to_be32(stride);
+
+ if (fw_cfg_write_blob(rt_be16_to_cpu(ramfb->file->select),
+ &ram_fb, 0, sizeof(struct fw_cfg_ram_fb)) < 0)
+ {
+ rt_free_align(framebuffer);
+ return -RT_ERROR;
+ }
+
+ if (plane->framebuffer)
+ {
+ rt_free_align(plane->framebuffer);
+ }
+
+ plane->bits_per_pixel = bpp;
+ plane->line_length = stride;
+
+ plane->framebuffer = framebuffer;
+ plane->screen_len = framebuffer_size;
+ plane->framebuffer_len = framebuffer_size;
+
+ return RT_EOK;
+}
+
+const struct rt_graphic_plane_ops ramfb_plane_ops =
+{
+ .fb_remap = ramfb_plane_fb_remap,
+};
+
+static rt_err_t fw_cfg_setup_ramfb(const struct fw_cfg_file *file)
+{
+ rt_err_t err;
+ rt_uint32_t width, height;
+ struct ramfb_device *ramfb = rt_calloc(1, sizeof(*ramfb));
+
+ if (!ramfb)
+ {
+ return -RT_ENOMEM;
+ }
+
+ ramfb->file = file;
+ platform_get_ramfb_params(&width, &height);
+
+ if ((err = rt_graphic_device_simple_register(&ramfb->parent,
+ width, height, 0, &ramfb_plane_ops,
+ ramfb_modes, RT_ARRAY_SIZE(ramfb_modes))))
+ {
+ rt_free(ramfb);
+ }
+
+ return err;
+}
+#endif /* RT_GRAPHIC_FB */
+
+static rt_ssize_t fw_cfg_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size)
+{
+ rt_ssize_t res;
+ struct fw_cfg_device *fw = raw_to_fw_cfg_device(dev);
+
+ if (pos <= fw->size)
+ {
+ if (size > fw->size - pos)
+ {
+ size = fw->size - pos;
+ }
+
+ res = fw_cfg_read_blob(fw->select, buffer, pos, size);
+ }
+ else
+ {
+ res = 0;
+ }
+
+ return res;
+}
+
+static struct rt_device_ops fw_cfg_ops =
+{
+ .read = fw_cfg_read,
+};
+
+static const char * const fw_export[] =
+{
+ "etc/acpi/rsdp", "acpi_rsdp",
+ "etc/acpi/tables", "acpi_tables",
+};
+
+static rt_err_t fw_cfg_register_file(const struct fw_cfg_file *file)
+{
+ rt_err_t err;
+ const char *name = RT_NULL;
+ struct fw_cfg_device *fw;
+
+#ifdef RT_GRAPHIC_FB
+ if (fw_cfg_dma_enabled() && !rt_strcmp(file->name, FW_CFG_RAMFB_FILENAME))
+ {
+ if (fw_cfg_setup_ramfb(file) < 0)
+ {
+ LOG_W("failed to setup ramfb");
+ }
+
+ return RT_EOK;
+ }
+#endif /* RT_GRAPHIC_FB */
+
+ for (int i = 0; i < RT_ARRAY_SIZE(fw_export); i += 2)
+ {
+ if (!rt_strcmp(fw_export[i], file->name))
+ {
+ name = fw_export[i + 1];
+ break;
+ }
+ }
+
+ if (!name)
+ {
+ return RT_EOK;
+ }
+
+ if (!(fw = rt_malloc(sizeof(*fw))))
+ {
+ return -RT_ENOMEM;
+ }
+
+ fw->size = rt_be32_to_cpu(file->size);
+ fw->select = rt_be16_to_cpu(file->select);
+ rt_strncpy(fw->name, file->name, FW_CFG_MAX_FILE_PATH);
+
+ fw->parent.type = RT_Device_Class_Char;
+#ifdef RT_USING_DEVICE_OPS
+ fw->parent.ops = &fw_cfg_ops;
+#else
+ fw->parent.read = fw_cfg_read,
+#endif
+
+ if ((err = rt_device_register(&fw->parent, name, RT_DEVICE_FLAG_RDONLY)))
+ {
+ rt_free(fw);
+ return err;
+ }
+
+ return RT_EOK;
+}
+
+static rt_err_t fw_cfg_register_dir_entries(void)
+{
+ rt_err_t err = 0;
+ rt_uint32_t count;
+ rt_size_t dir_size;
+ rt_be32_t files_count;
+ struct fw_cfg_file *dir;
+
+ err = fw_cfg_read_blob(FW_CFG_FILE_DIR, &files_count, 0, sizeof(files_count));
+
+ if (err < 0)
+ {
+ return err;
+ }
+
+ count = rt_be32_to_cpu(files_count);
+ dir_size = count * sizeof(struct fw_cfg_file);
+
+ dir = rt_malloc(dir_size);
+
+ if (!dir)
+ {
+ return -RT_ENOMEM;
+ }
+
+ err = fw_cfg_read_blob(FW_CFG_FILE_DIR, dir, sizeof(files_count), dir_size);
+
+ if (err < 0)
+ {
+ return err;
+ }
+
+ for (int i = 0; i < count; ++i)
+ {
+ if ((err = fw_cfg_register_file(&dir[i])))
+ {
+ break;
+ }
+ }
+
+ rt_free(dir);
+
+ return err;
+}
+
+static rt_err_t qemu_fw_cfg_probe(struct rt_platform_device *pdev)
+{
+ rt_le32_t rev;
+ rt_err_t err = RT_EOK;
+ char sig[FW_CFG_SIG_SIZE];
+ rt_uint32_t ctrl = FW_CFG_CTRL_OFF, data = FW_CFG_DATA_OFF, dma;
+ struct rt_device *dev = &pdev->parent;
+
+#ifdef FW_CFG_DMA_OFF
+ dma = FW_CFG_DMA_OFF;
+#else
+ dma = RT_UINT32_MAX;
+#endif
+
+ rt_dm_dev_prop_read_u32(dev, "ctrl", &ctrl);
+ rt_dm_dev_prop_read_u32(dev, "data", &data);
+ rt_dm_dev_prop_read_u32(dev, "dma", &dma);
+
+ if (!(_fw_cfg_dev_base = rt_dm_dev_iomap(dev, 0)))
+ {
+ err = -RT_EIO;
+ goto _fail;
+ }
+
+#ifdef ARCH_SUPPORT_PIO
+ _fw_cfg_is_mmio = RT_FALSE;
+#else
+ _fw_cfg_is_mmio = RT_TRUE;
+#endif
+
+ _fw_cfg_reg_ctrl = _fw_cfg_dev_base + ctrl;
+ _fw_cfg_reg_data = _fw_cfg_dev_base + data;
+
+ if (dma != RT_UINT32_MAX)
+ {
+ _fw_cfg_reg_dma = _fw_cfg_dev_base + dma;
+ }
+ else
+ {
+ _fw_cfg_reg_dma = RT_NULL;
+ }
+
+ if (fw_cfg_read_blob(FW_CFG_SIGNATURE, sig, 0, FW_CFG_SIG_SIZE) < 0 ||
+ rt_memcmp(sig, "QEMU", FW_CFG_SIG_SIZE))
+ {
+ err = -RT_ENOSYS;
+ goto _fail;
+ }
+
+ if (fw_cfg_read_blob(FW_CFG_ID, &rev, 0, sizeof(rev)) < 0)
+ {
+ err = -RT_ENOSYS;
+ goto _fail;
+ }
+
+ _fw_cfg_rev = rt_le32_to_cpu(rev);
+
+ fw_cfg_register_dir_entries();
+
+_fail:
+ return err;
+}
+
+static const struct rt_ofw_node_id qemu_fw_cfg_ofw_ids[] =
+{
+ { .compatible = "qemu,fw-cfg-mmio", },
+ { /* sentinel */ }
+};
+
+static struct rt_platform_driver qemu_fw_cfg_driver =
+{
+ .name = "qemu-fw-cfg",
+ .ids = qemu_fw_cfg_ofw_ids,
+
+ .probe = qemu_fw_cfg_probe,
+};
+
+static int qemu_fw_cfg_drv_register(void)
+{
+ rt_platform_driver_register(&qemu_fw_cfg_driver);
+
+ return 0;
+}
+INIT_SUBSYS_EXPORT(qemu_fw_cfg_drv_register);
diff --git a/components/drivers/firmware/qemu/fw_cfg.h b/components/drivers/firmware/qemu/fw_cfg.h
new file mode 100644
index 0000000000..b0719de77f
--- /dev/null
+++ b/components/drivers/firmware/qemu/fw_cfg.h
@@ -0,0 +1,109 @@
+/*
+ * Copyright (c) 2006-2023, RT-Thread Development Team
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ *
+ * Change Logs:
+ * Date Author Notes
+ * 2023-02-25 GuEe-GUI the first version
+ */
+
+#ifndef __QEMU_FW_CFG_H__
+#define __QEMU_FW_CFG_H__
+
+#include <rtdef.h>
+#include <drivers/core/dm.h>
+
+#define FW_CFG_ACPI_DEVICE_ID "QEMU0002"
+
+/* selector key values for "well-known" fw_cfg entries */
+#define FW_CFG_SIGNATURE 0x00
+#define FW_CFG_ID 0x01
+#define FW_CFG_UUID 0x02
+#define FW_CFG_RAM_SIZE 0x03
+#define FW_CFG_NOGRAPHIC 0x04
+#define FW_CFG_NB_CPUS 0x05
+#define FW_CFG_MACHINE_ID 0x06
+#define FW_CFG_KERNEL_ADDR 0x07
+#define FW_CFG_KERNEL_SIZE 0x08
+#define FW_CFG_KERNEL_CMDLINE 0x09
+#define FW_CFG_INITRD_ADDR 0x0a
+#define FW_CFG_INITRD_SIZE 0x0b
+#define FW_CFG_BOOT_DEVICE 0x0c
+#define FW_CFG_NUMA 0x0d
+#define FW_CFG_BOOT_MENU 0x0e
+#define FW_CFG_MAX_CPUS 0x0f
+#define FW_CFG_KERNEL_ENTRY 0x10
+#define FW_CFG_KERNEL_DATA 0x11
+#define FW_CFG_INITRD_DATA 0x12
+#define FW_CFG_CMDLINE_ADDR 0x13
+#define FW_CFG_CMDLINE_SIZE 0x14
+#define FW_CFG_CMDLINE_DATA 0x15
+#define FW_CFG_SETUP_ADDR 0x16
+#define FW_CFG_SETUP_SIZE 0x17
+#define FW_CFG_SETUP_DATA 0x18
+#define FW_CFG_FILE_DIR 0x19
+
+#define FW_CFG_FILE_FIRST 0x20
+#define FW_CFG_FILE_SLOTS_MIN 0x10
+
+#define FW_CFG_WRITE_CHANNEL 0x4000
+#define FW_CFG_ARCH_LOCAL 0x8000
+#define FW_CFG_ENTRY_MASK (~(FW_CFG_WRITE_CHANNEL | FW_CFG_ARCH_LOCAL))
+
+#define FW_CFG_INVALID 0xffff
+
+/* width in bytes of fw_cfg control register */
+#define FW_CFG_CTL_SIZE 0x02
+
+/* fw_cfg "file name" is up to 56 characters (including terminating nul) */
+#define FW_CFG_MAX_FILE_PATH 56
+
+/* size in bytes of fw_cfg signature */
+#define FW_CFG_SIG_SIZE 4
+
+/* FW_CFG_ID bits */
+#define FW_CFG_VERSION 0x01
+#define FW_CFG_VERSION_DMA 0x02
+
+/* fw_cfg file directory entry type */
+struct fw_cfg_file
+{
+ rt_be32_t size;
+ rt_be16_t select;
+ rt_le16_t reserved;
+ char name[FW_CFG_MAX_FILE_PATH];
+};
+
+/* FW_CFG_DMA_CONTROL bits */
+#define FW_CFG_DMA_CTL_ERROR 0x01
+#define FW_CFG_DMA_CTL_READ 0x02
+#define FW_CFG_DMA_CTL_SKIP 0x04
+#define FW_CFG_DMA_CTL_SELECT 0x08
+#define FW_CFG_DMA_CTL_WRITE 0x10
+
+#define FW_CFG_DMA_SIGNATURE 0x51454d5520434647ULL /* "QEMU CFG" */
+
+/* Control as first field allows for different structures selected by this
+ * field, which might be useful in the future
+ */
+rt_packed(struct fw_cfg_dma_access
+{
+ rt_be32_t control;
+ rt_be32_t length;
+ rt_be64_t address;
+});
+
+#define FW_CFG_RAMFB_FILENAME "etc/ramfb"
+
+rt_packed(struct fw_cfg_ram_fb
+{
+ rt_be64_t addr;
+ rt_be32_t fourcc;
+ rt_be32_t flags;
+ rt_be32_t width;
+ rt_be32_t height;
+ rt_be32_t stride;
+});
+
+#endif /* __QEMU_FW_CFG_H__ */