On Sat, 2026-06-27 at 23:42 +0800, Bin Meng wrote:
> Real-board ZCU102 DTBs describe the UART, SDHCI and GQSPI nodes
> in terms of ZynqMP PM firmware providers for clocks, resets, power
> domains and pin control. QEMU does not model those runtime firmware
> providers, so using such a DTB with -kernel currently requires users
> to edit the DTB before Linux can probe the boot-critical devices.
> 
> Add a board DTB fix-up for direct kernel boot that removes those
> provider-only properties from the boot-critical nodes and rewrites
> their clocks to fixed reference clocks already present in the DTB.
> This keeps the user command line using the Buildroot-generated DTB
> while limiting the compatibility adjustment to devices modeled by
> QEMU.
> 
> Document the direct boot flow, the SD and QSPI image layout details,
> and the remaining firmware and real-board peripheral limitations.
> 
> Signed-off-by: Bin Meng <[email protected]>
> 
> ---
> 
>  docs/system/arm/xlnx-zcu102.rst | 139
> +++++++++++++++++++++++++++++++-
>  hw/arm/xlnx-zcu102.c            | 109 +++++++++++++++++++++++++
>  2 files changed, 247 insertions(+), 1 deletion(-)
> 
> diff --git a/docs/system/arm/xlnx-zcu102.rst b/docs/system/arm/xlnx-
> zcu102.rst
> index 534cd1dc88..41ddd15592 100644
> --- a/docs/system/arm/xlnx-zcu102.rst
> +++ b/docs/system/arm/xlnx-zcu102.rst
> @@ -4,8 +4,30 @@ Xilinx ZynqMP ZCU102 (``xlnx-zcu102``)
>  The ``xlnx-zcu102`` board models the Xilinx ZynqMP ZCU102 board.
>  This board has 4 Cortex-A53 CPUs and 2 Cortex-R5F CPUs.
>  
> +Supported devices
> +-----------------
> +
> +The machine is based on QEMU's Xilinx ZynqMP SoC model and includes
> +the following Processing System devices:
> +
> + * Cortex-A53 APU and Cortex-R5F RPU CPU cores
> + * Generic Interrupt Controller
> + * DDR memory
> + * On-chip memory
> + * Cadence UARTs
> + * Cadence GEM Ethernet controllers
> + * SDHCI controllers
> + * SPI controllers
> + * GQSPI controller
> + * RTC
> + * CAN controllers
> + * USB controllers
> + * SATA controller
> + * DMA controllers
> + * BBRAM and eFUSE devices
> +
>  Machine-specific options
> -""""""""""""""""""""""""
> +------------------------
>  
>  The following machine-specific options are supported:
>  
> @@ -17,3 +39,118 @@ virtualization
>    Set ``on``/``off`` to enable/disable emulating a guest CPU which
> implements the
>    Arm Virtualization Extensions. The default is ``off``.
>  
> +Boot options
> +------------
> +
> +The ``xlnx-zcu102`` machine can start a Linux kernel directly with
> +``-kernel``. It does not provide a ZynqMP BootROM flow for loading a
> +Xilinx boot image from SD or QSPI flash, so images such as
> ``boot.bin``
> +or ``qspi.bin`` cannot be passed alone and expected to boot like
> they
> +would on real hardware.
> +
> +QEMU does not generate a built-in device tree for this machine. Pass
> a DTB
> +with ``-dtb``. For direct Linux boot with a real-board ZCU102 DTB,
> QEMU
> +applies compatibility adjustments for the boot-critical UART, SDHCI
> and
> +GQSPI nodes so that Linux can use the devices modeled by QEMU
> without
> +requiring user-side DTB edits.
> +
> +This is not the same as emulating the complete ZynqMP firmware
> stack. The
> +DTB can still contain non-boot devices that depend on board
> peripherals,
> +external clock chips, power domains, pin control, or firmware
> services
> +that QEMU does not model.
> +
> +Direct Linux boot with Buildroot
> +--------------------------------
> +
> +Buildroot has a ZCU102 defconfig. Buildroot 2026.05 release is
> tested at the
> +time of writing. From the Buildroot source tree:
> +
> +.. code-block:: bash
> +
> +  $ make zynqmp_zcu102_defconfig
> +  $ make
> +
> +The generated files are in ``output/images/``. The examples below
> use:
> +
> + * ``Image``
> + * ``zynqmp-zcu102-rev1.0.dtb``
> + * ``sdcard.img``
> + * ``qspi.bin`` if QSPI flash probing is desired
> +
> +QEMU's SD card model requires a power-of-two image size. Work on a
> copy
> +of the Buildroot SD image and resize the copy, not the original
> output:
> +
> +.. code-block:: bash
> +
> +  $ cp output/images/sdcard.img sdcard-qemu.img
> +  $ qemu-img resize -f raw sdcard-qemu.img 128M
> +
> +The real-board DTB enables the SDHCI controller at ``mmc@ff170000``.
> +In QEMU this is SD index 1, so attach the SD card with
> +``if=sd,index=1``.
> +
> +Boot Linux with the Buildroot kernel and the native Buildroot DTB:
> +
> +.. code-block:: bash
> +
> +  $ qemu-system-aarch64 -M xlnx-zcu102 -m 2G -nographic \
> +      -kernel output/images/Image \
> +      -dtb output/images/zynqmp-zcu102-rev1.0.dtb \
> +      -append "earlycon=cdns,mmio,0xff000000,115200n8 \
> +               console=ttyPS0,115200 root=/dev/mmcblk0p2 rw
> rootwait" \
> +      -drive file=sdcard-qemu.img,if=sd,index=1,format=raw
> +
> +This should get the kernel booting all the way to the Buildroot
> login
> +prompt on ``ttyPS0``.
> +
> +QSPI flash
> +----------
> +
> +The board creates two PS SPI NOR flashes first and the ZynqMP GQSPI
> +flashes after them. Therefore the first GQSPI flash is
> +``if=mtd,index=2``. The remaining GQSPI flash backends use indices
> 3, 4
> +and 5.
> +
> +The Buildroot ``qspi.bin`` image is smaller than the 64 MiB
> +``n25q512a11`` flash model. To attach it as the first GQSPI flash,
> +place it in a 64 MiB raw backing file:
> +
> +.. code-block:: bash
> +
> +  $ qemu-img create -f raw qspi-cs0.img 64M
> +  $ dd if=output/images/qspi.bin of=qspi-cs0.img bs=1M conv=notrunc
> +
> +Add the following drive to the direct Linux boot command above:
> +
> +.. code-block:: bash
> +
> +      -drive file=qspi-cs0.img,if=mtd,index=2,format=raw
> +
> +With this drive attached, Linux should probe the ZynqMP GQSPI
> +controller and register the SPI NOR partitions from the DTB, for
> +example ``qspi-fsbl-uboot``, ``qspi-linux``, ``qspi-device-tree``
> and
> +``qspi-rootfs``.
> +
> +The default Buildroot ``qspi.bin`` is a boot image, not a root
> +filesystem image. The default DTB's ``qspi-rootfs`` partition is
> also
> +too small for Buildroot's ``rootfs.ext2``. Booting with the root
> +filesystem in QSPI flash therefore requires a custom image layout
> and
> +matching DTB changes.
> +
> +Known limitations
> +-----------------
> +
> +The machine does not emulate the complete real-board firmware stack.
> +In particular:
> +
> + * ZynqMP BootROM boot-mode selection is not modeled.
> + * Passing the Buildroot ``boot.bin`` with ``-bios`` does not boot
> Linux.
> + * Attaching ``qspi.bin`` as an MTD drive without ``-kernel`` does
> not
> +   boot from QSPI flash.
> + * ZynqMP PM firmware services are not modeled. For direct Linux
> boot,
> +   QEMU adjusts the supplied DTB only for the boot-critical UART,
> SDHCI
> +   and GQSPI nodes.
> + * Other real-board DTB nodes can still defer or fail probing when
> they
> +   depend on devices that are not present in the QEMU model, such as
> +   external I2C clock generators, board sensors, some GPIO
> consumers,
> +   PHYs, or non-boot DMA/display paths.
> diff --git a/hw/arm/xlnx-zcu102.c b/hw/arm/xlnx-zcu102.c
> index 4e48970274..839e4e90d5 100644
> --- a/hw/arm/xlnx-zcu102.c
> +++ b/hw/arm/xlnx-zcu102.c
> @@ -26,6 +26,7 @@
>  #include "system/device_tree.h"
>  #include "qom/object.h"
>  #include "net/can_emu.h"
> +#include <libfdt.h>
>  
>  struct XlnxZCU102 {
>      MachineState parent_obj;
> @@ -43,6 +44,112 @@ struct XlnxZCU102 {
>  #define TYPE_ZCU102_MACHINE   MACHINE_TYPE_NAME("xlnx-zcu102")
>  OBJECT_DECLARE_SIMPLE_TYPE(XlnxZCU102, ZCU102_MACHINE)
>  
> +static bool zcu102_fdt_get_phandle(void *fdt, const char *node_path,
> +                                   uint32_t *phandle)
> +{
> +    int offset;
> +
> +    offset = fdt_path_offset(fdt, node_path);
> +    if (offset < 0) {
> +        return false;
> +    }
> +
> +    *phandle = fdt_get_phandle(fdt, offset);
> +    return *phandle != 0;
> +}
> +
> +static void zcu102_fdt_nop_prop(void *fdt, const char *node_path,
> +                                const char *prop)
> +{
> +    int ret;
> +    int offset;
> +
> +    offset = fdt_path_offset(fdt, node_path);
> +    if (offset < 0) {
> +        return;
> +    }
> +
> +    ret = fdt_nop_property(fdt, offset, prop);
> +    if (ret < 0 && ret != -FDT_ERR_NOTFOUND) {
> +        error_report("%s: Couldn't nop %s/%s: %s", __func__,
> node_path,
> +                     prop, fdt_strerror(ret));
> +        exit(1);
> +    }
> +}
> +
> +static void zcu102_fdt_fixup_clocks(void *fdt, const char
> *node_path,
> +                                    uint32_t phandle)
> +{
> +    if (fdt_path_offset(fdt, node_path) < 0) {
> +        return;
> +    }
> +
> +    qemu_fdt_setprop_cells(fdt, node_path, "clocks", phandle,
> phandle);
> +}
> +
> +static void zcu102_fdt_fixup_qemu_direct_boot_nodes(void *fdt)
> +{
> +    uint32_t pss_ref_clk;
> +    int i, j;
> +
> +    static const char * const provider_props[] = {
> +        "power-domains",
> +        "resets",
> +        "assigned-clocks",
> +        "assigned-clock-rates",
> +        "assigned-clock-parents",
> +        "pinctrl-names",
> +        "pinctrl-0",
> +    };
> +    static const char * const direct_boot_nodes[] = {
> +        "/axi/serial@ff000000",
> +        "/axi/serial@ff010000",
> +        "/axi/mmc@ff170000",
> +        "/axi/spi@ff0f0000",
> +    };
> +
> +    /*
> +     * The Linux ZCU102 DTB inherits these boot-critical nodes from
> +     * arch/arm64/boot/dts/xilinx/zynqmp.dtsi:
> +     *
> +     *   uart0: serial@ff000000
> +     *   uart1: serial@ff010000
> +     *   sdhci1: mmc@ff170000
> +     *   qspi: spi@ff0f0000
> +     *
> +     * zynqmp.dtsi, zynqmp-clk-ccf.dtsi and zynqmp-zcu102-revA.dts
> +     * wire them to PM firmware-backed clock, reset and pinctrl
> providers.
> +     * Linux reaches that firmware through zynqmp_pm_invoke_fn() in
> +     * drivers/firmware/xilinx/zynqmp-core.c; examples include
> +     * zynqmp_pm_query_data() for drivers/clk/zynqmp/clkc.c,
> +     * zynqmp_pm_reset_assert() for drivers/reset/reset-zynqmp.c,
> and
> +     * zynqmp_pm_pinctrl_request() for drivers/pinctrl/pinctrl-
> zynqmp.c.
> +     *
> +     * Direct kernel boot has no PM firmware stage for those calls.
> Bypass the
> +     * firmware-backed providers for just these nodes and describe
> their clocks
> +     * as fixed inputs, avoiding a QEMU model of the runtime PM
> firmware.
> +     *
> +     * The Linux ZynqMP clock description defines pss-ref-clk as a
> 33.333333 MHz
> +     * fixed clock with #clock-cells = <0>. Use it as a conservative
> direct-boot
> +     * fallback for both clock inputs of these devices. That
> satisfies the
> +     * existing two clock-names entries while keeping the DTB
> independent from
> +     * the firmware-backed ZynqMP clock controller.
> +     */
> +    if (!zcu102_fdt_get_phandle(fdt, "/pss-ref-clk", &pss_ref_clk))
> {
> +        return;
> +    }
> +
> +    for (i = 0; i < ARRAY_SIZE(direct_boot_nodes); i++) {
> +        for (j = 0; j < ARRAY_SIZE(provider_props); j++) {
> +            zcu102_fdt_nop_prop(fdt, direct_boot_nodes[i],
> provider_props[j]);
> +        }
> +    }
> +
> +    zcu102_fdt_fixup_clocks(fdt, "/axi/serial@ff000000",
> pss_ref_clk);
> +    zcu102_fdt_fixup_clocks(fdt, "/axi/serial@ff010000",
> pss_ref_clk);
> +    zcu102_fdt_fixup_clocks(fdt, "/axi/mmc@ff170000", pss_ref_clk);
> +    zcu102_fdt_fixup_clocks(fdt, "/axi/spi@ff0f0000", pss_ref_clk);

I don't think this is something QEMU should be doing. Modifying the DTB
on the fly just seems very brittle and prone to breakage and confusion.

Alistair

> +}
>  
>  static bool zcu102_get_secure(Object *obj, Error **errp)
>  {
> @@ -98,6 +205,8 @@ static void zcu102_modify_dtb(const struct
> arm_boot_info *binfo, void *fdt)
>          }
>          g_strfreev(node_path);
>      }
> +
> +    zcu102_fdt_fixup_qemu_direct_boot_nodes(fdt);
>  }
>  
>  static void bbram_attach_drive(XlnxBBRam *dev)

Reply via email to