QEMU-Arm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support
@ 2026-09-03 11:24 Bin Meng
  2026-09-03 11:24 ` [PATCH 01/33] target/arm: Add Phytium FTC310 and FTC664 CPU models Bin Meng
                   ` (28 more replies)
  0 siblings, 29 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU
  Cc: Alistair Francis, Bin Meng, Fabiano Rosas, Hanna Reitz,
	Kevin Wolf, Laurent Vivier, Markus Armbruster, Paolo Bonzini,
	Peter Maydell, Philippe Mathieu-Daudé,
	Philippe Mathieu-Daudé, Pierrick Bouvier, Thomas Huth,
	qemu-arm, qemu-block


The Phytium E2000Q is a heterogeneous Armv8 SoC with two FTC664 cores
and two FTC310 cores. This series adds CPU definitions and a shared
SoC model, then uses it for two machines:

  - phytium-pi, with firmware and Linux on SD0
  - phytium-e2000-come, with firmware in QSPI NOR and Linux on an
    AHCI1 SATA disk

The machines support the vendor firmware path from the on-chip
Phytium Boot ROM (PBR) handoff through Processor Base Firmware (PBF),
TF-A, OP-TEE, U-Boot, and Linux. They can also directly boot SDK Linux
kernels when supplied with matching board device trees.

The PBR model does not execute undocumented ROM code. It parses the
selected fip-all.bin, validates the PBF, TF-A FIP, and platform
parameter records, and reconstructs the observed handoff state. The
MHU model handles the private SCMI power-domain requests needed to
release secondary CPUs and exposes a separate Base-protocol channel
to Linux.

The shared E2000Q machine provides GICv3/ITS, UARTs, GPEX PCIe,
SMMUv3, four Cadence GEM controllers, two DesignWare-derived MCI
controllers, QSPI, I2C, xHCI, AHCI, a random generator, and the
firmware-facing DDR and control regions. Unimplemented register
windows remain visible to firmware without claiming unsupported
behavior.

Functional tests exercise both firmware and direct Linux boot on the
Phytium Pi with an SDK Buildroot image. Manual testing covers Phytium
Pi SD boot, COMe QSPI-to-SATA boot, all four CPUs, and network access.

Known limitations are documented. In particular, the PBR handoff is
a behavioral model validated against the available SDK firmware
samples.


Bin Meng (33):
  target/arm: Add Phytium FTC310 and FTC664 CPU models
  hw/arm: Add basic Phytium Pi machine
  hw/arm: phytium: Add Phytium E2000 PCIe host
  hw/sd: Add Synopsys DesignWare MCI controller
  hw/sd: Add Phytium E2000 MCI controller
  hw/arm: phytium: Connect Phytium E2000 MCI controllers
  tests/qtest: Add Synopsys DesignWare MCI coverage
  hw/arm: phytium: Connect Phytium E2000 GEM controllers
  hw/misc: Add Phytium E2000 DDR status
  hw/arm: phytium: Connect the Phytium E2000 DDR status
  hw/misc: Add Phytium E2000 MHU doorbell
  hw/arm: phytium: Connect the Phytium E2000 MHU
  hw/ssi: Add Phytium E2000 QSPI controller
  hw/arm: phytium: Connect the Phytium E2000 QSPI controller
  hw/misc: Add Phytium E2000 PBR model
  hw/arm: phytium: Integrate the Phytium E2000 PBR
  hw/arm: phytium: Add Phytium E2000 control region placeholders
  hw/misc: Support Phytium E2000 SCMI CPU power control
  hw/arm: phytium: Select the Phytium E2000 PBR boot medium
  hw/arm: phytium: Connect the Phytium E2000 I2C controller
  hw/arm: phytium: Add Phytium E2000 xHCI controllers
  hw/misc: Model the Phytium E2000 random generator
  hw/arm: phytium: Connect the Phytium E2000 random generator
  hw/arm: phytium: Support Phytium E2000 direct Linux boot
  hw/arm: phytium: Add Phytium E2000Q COMe machine
  hw/block: m25p80: Add GigaDevice GD25Q128 flash
  hw/arm: phytium: Connect the Phytium E2000Q COMe QSPI flash
  hw/arm: phytium: Add Phytium E2000 AHCI controllers
  hw/arm: Add Phytium E2000 Linux SCMI channel
  hw/arm: phytium: Connect the Phytium E2000 SMMUv3
  docs/system/arm: Document Phytium E2000 machines
  tests/functional/aarch64: Add Phytium Pi boot tests
  MAINTAINERS: Add Phytium E2000Q machines

 MAINTAINERS                                 |   17 +
 docs/system/arm/phytium_e2000.rst           |  412 ++++++
 docs/system/target-arm.rst                  |    1 +
 hw/arm/Kconfig                              |   22 +
 hw/arm/meson.build                          |    1 +
 hw/arm/phytium_e2000.c                      | 1181 ++++++++++++++++
 hw/block/m25p80.c                           |    1 +
 hw/misc/meson.build                         |    4 +
 hw/misc/phytium_e2000_ddr.c                 |  197 +++
 hw/misc/phytium_e2000_mhu.c                 |  818 +++++++++++
 hw/misc/phytium_e2000_pbr.c                 | 1366 ++++++++++++++++++
 hw/misc/phytium_e2000_rng.c                 |  182 +++
 hw/sd/Kconfig                               |    5 +
 hw/sd/dw_mci.c                              | 1382 +++++++++++++++++++
 hw/sd/meson.build                           |    2 +
 hw/sd/phytium_e2000_mci.c                   |   95 ++
 hw/ssi/Kconfig                              |    5 +
 hw/ssi/meson.build                          |    1 +
 hw/ssi/phytium_qspi.c                       |  398 ++++++
 include/hw/misc/phytium_e2000_ddr.h         |   23 +
 include/hw/misc/phytium_e2000_mhu.h         |   34 +
 include/hw/misc/phytium_e2000_pbr.h         |   62 +
 include/hw/misc/phytium_e2000_rng.h         |   23 +
 include/hw/sd/dw_mci.h                      |   74 +
 include/hw/sd/phytium_e2000_mci.h           |   21 +
 include/hw/ssi/phytium_qspi.h               |   23 +
 target/arm/tcg/cpu64.c                      |   81 ++
 tests/functional/aarch64/meson.build        |    2 +
 tests/functional/aarch64/test_phytium_pi.py |   77 ++
 tests/qtest/dw_mci-test.c                   |  417 ++++++
 tests/qtest/meson.build                     |    1 +
 31 files changed, 6928 insertions(+)
 create mode 100644 docs/system/arm/phytium_e2000.rst
 create mode 100644 hw/arm/phytium_e2000.c
 create mode 100644 hw/misc/phytium_e2000_ddr.c
 create mode 100644 hw/misc/phytium_e2000_mhu.c
 create mode 100644 hw/misc/phytium_e2000_pbr.c
 create mode 100644 hw/misc/phytium_e2000_rng.c
 create mode 100644 hw/sd/dw_mci.c
 create mode 100644 hw/sd/phytium_e2000_mci.c
 create mode 100644 hw/ssi/phytium_qspi.c
 create mode 100644 include/hw/misc/phytium_e2000_ddr.h
 create mode 100644 include/hw/misc/phytium_e2000_mhu.h
 create mode 100644 include/hw/misc/phytium_e2000_pbr.h
 create mode 100644 include/hw/misc/phytium_e2000_rng.h
 create mode 100644 include/hw/sd/dw_mci.h
 create mode 100644 include/hw/sd/phytium_e2000_mci.h
 create mode 100644 include/hw/ssi/phytium_qspi.h
 create mode 100644 tests/functional/aarch64/test_phytium_pi.py
 create mode 100644 tests/qtest/dw_mci-test.c

---
base-commit: 61d82640e03f5fdbeb70c455e2b3cbff539600ec
branch: phytium

-- 
2.53.0



^ permalink raw reply	[flat|nested] 45+ messages in thread

* [PATCH 01/33] target/arm: Add Phytium FTC310 and FTC664 CPU models
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 14:45   ` Alex Bennée
  2026-09-03 11:24 ` [PATCH 02/33] hw/arm: Add basic Phytium Pi machine Bin Meng
                   ` (27 subsequent siblings)
  28 siblings, 1 reply; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

The Phytium E2000Q combines two FTC310 cores and two FTC664 cores.
Its EL3 firmware accesses implementation-defined system registers
while setting up the cores. Using generic Cortex-A72 CPUs leaves
these registers undefined and traps firmware before it can reach
U-Boot.

Add FTC310 and FTC664 CPU types using Cortex-A72 as their common
execution base. Provide conservative RAZ/WI and NOP stubs for the
firmware-visible E2000 controls that have no modeled behavior.

Set each CPU DT compatible string, MIDR, instruction feature fields,
AArch32 floating-point feature fields, and CTR_EL0.L1Ip field to
values observed on an E2000Q board. The FTC310 reports a VIPT
instruction cache, while the FTC664 reports a PIPT instruction cache.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 target/arm/tcg/cpu64.c | 81 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 81 insertions(+)

diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c
index affd87a3ae..0fd2f79bbc 100644
--- a/target/arm/tcg/cpu64.c
+++ b/target/arm/tcg/cpu64.c
@@ -336,6 +336,85 @@ static void aarch64_a72_initfn(Object *obj)
     define_cortex_a72_a57_a53_cp_reginfo(cpu);
 }
 
+static const ARMCPRegInfo phytium_e2000_cp_reginfo[] = {
+    /*
+     * The E2000 EL3 firmware touches implementation-defined CPU registers
+     * during the PBF/BL1 cache and core setup. QEMU does not model these
+     * controls, so expose conservative RAZ/WI stubs for the boot firmware.
+     *
+     * PBF reads these identification and cluster controls after writing
+     * them. Returning zero preserves the reset state without claiming that
+     * QEMU implements the associated cache or coherency controls.
+     */
+    { .name = "E2000_CPUID_CTL1", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 1, .crn = 15, .crm = 1, .opc2 = 0,
+      .access = PL1_RW, .type = ARM_CP_CONST | ARM_CP_NO_RAW,
+      .resetvalue = 0 },
+    { .name = "E2000_CLUSTER_CTL", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 1, .crn = 11, .crm = 8, .opc2 = 6,
+      .access = PL1_RW, .type = ARM_CP_CONST | ARM_CP_NO_RAW,
+      .resetvalue = 0 },
+    /*
+     * The remaining controls are only programmed as part of firmware setup.
+     * Accept the writes without retaining state because no modeled CPU
+     * behavior depends on their values.
+     */
+    { .name = "E2000_EL1_CTL", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 2, .crn = 15, .crm = 15, .opc2 = 0,
+      .access = PL1_RW, .type = ARM_CP_NOP | ARM_CP_NO_RAW },
+    { .name = "E2000_EL2_CTL", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 4, .crn = 15, .crm = 15, .opc2 = 0,
+      .access = PL2_RW, .type = ARM_CP_NOP | ARM_CP_NO_RAW },
+    { .name = "E2000_EL2_CTL2", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 4, .crn = 15, .crm = 2, .opc2 = 4,
+      .access = PL2_RW, .type = ARM_CP_NOP | ARM_CP_NO_RAW },
+    { .name = "E2000_EL3_CTL", .state = ARM_CP_STATE_AA64,
+      .opc0 = 3, .opc1 = 6, .crn = 15, .crm = 15, .opc2 = 0,
+      .access = PL3_RW, .type = ARM_CP_NOP | ARM_CP_NO_RAW },
+};
+
+/*
+ * Use the Cortex-A72 execution model as the common E2000 TCG base, then
+ * replace the architected identity fields that differ between the two
+ * physical core types.
+ */
+static void aarch64_phytium_e2000_base_initfn(Object *obj)
+{
+    ARMCPU *cpu = ARM_CPU(obj);
+
+    aarch64_a72_initfn(obj);
+    define_arm_cp_regs(cpu, phytium_e2000_cp_reginfo);
+}
+
+static void aarch64_phytium_ftc310_initfn(Object *obj)
+{
+    ARMCPU *cpu = ARM_CPU(obj);
+    ARMISARegisters *isar = &cpu->isar;
+
+    aarch64_phytium_e2000_base_initfn(obj);
+
+    /* FTC310 cores identify with the FTC303 part number */
+    cpu->dtb_compatible = "phytium,ftc310";
+    cpu->midr = 0x700f3034;
+    SET_IDREG(isar, ID_AA64ISAR0, 0x00011100012120);
+    cpu->isar.mvfr0 = 0x10110222;
+    cpu->ctr = FIELD_DP64(cpu->ctr, CTR_EL0, L1IP, 2); /* VIPT */
+}
+
+static void aarch64_phytium_ftc664_initfn(Object *obj)
+{
+    ARMCPU *cpu = ARM_CPU(obj);
+    ARMISARegisters *isar = &cpu->isar;
+
+    aarch64_phytium_e2000_base_initfn(obj);
+
+    cpu->dtb_compatible = "phytium,ftc664";
+    cpu->midr = 0x701f6643;
+    SET_IDREG(isar, ID_AA64ISAR0, 0x00000100012120);
+    cpu->isar.mvfr0 = 0x10111222;
+    cpu->ctr = FIELD_DP64(cpu->ctr, CTR_EL0, L1IP, 3); /* PIPT */
+}
+
 static void aarch64_a76_initfn(Object *obj)
 {
     ARMCPU *cpu = ARM_CPU(obj);
@@ -1533,6 +1612,8 @@ static const ARMCPUInfo aarch64_cpus[] = {
     { .name = "cortex-a55",         .initfn = aarch64_a55_initfn },
     { .name = "cortex-a72",         .initfn = aarch64_a72_initfn },
     { .name = "cortex-a76",         .initfn = aarch64_a76_initfn },
+    { .name = "phytium-ftc310",     .initfn = aarch64_phytium_ftc310_initfn },
+    { .name = "phytium-ftc664",     .initfn = aarch64_phytium_ftc664_initfn },
     /*
      * The Cortex-A78AE differs slightly from the plain Cortex-A78. We don't
      * currently model the latter.
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
  2026-09-03 11:24 ` [PATCH 01/33] target/arm: Add Phytium FTC310 and FTC664 CPU models Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 16:56   ` Philippe Mathieu-Daudé
  2026-09-03 11:24 ` [PATCH 03/33] hw/arm: phytium: Add Phytium E2000 PCIe host Bin Meng
                   ` (26 subsequent siblings)
  28 siblings, 1 reply; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Add the base topology for the Phytium Pi board built around the
E2000Q SoC, with two FTC310 and two FTC664 CPUs, RAM, GICv3/ITS,
and PL011 UARTs.

Describe the CPUs as three non-uniform clusters matching the vendor
Linux device tree. Preserve the board CPU slot order so firmware
MPIDR 0x200 maps to QEMU CPU index 2.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |   8 +
 hw/arm/meson.build     |   1 +
 hw/arm/phytium_e2000.c | 406 +++++++++++++++++++++++++++++++++++++++++
 3 files changed, 415 insertions(+)
 create mode 100644 hw/arm/phytium_e2000.c

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 5db7ce057f..20863b5847 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -126,6 +126,14 @@ config OMAP
     select SD
     select SERIAL_MM
 
+config PHYTIUM_E2000
+    bool
+    default y
+    depends on TCG && AARCH64
+    select ARM_GIC
+    select PL011
+    select UNIMP
+
 config REALVIEW
     bool
     default y
diff --git a/hw/arm/meson.build b/hw/arm/meson.build
index 78acb04c8b..6f685b414d 100644
--- a/hw/arm/meson.build
+++ b/hw/arm/meson.build
@@ -10,6 +10,7 @@ arm_common_ss.add(when: 'CONFIG_NETDUINOPLUS2', if_true: files('netduinoplus2.c'
 arm_common_ss.add(when: 'CONFIG_OLIMEX_STM32_H405', if_true: files('olimex-stm32-h405.c'))
 arm_common_ss.add(when: 'CONFIG_NPCM7XX', if_true: files('npcm7xx.c', 'npcm7xx_boards.c'))
 arm_common_ss.add(when: 'CONFIG_NPCM8XX', if_true: files('npcm8xx.c', 'npcm8xx_boards.c'))
+arm_common_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000.c'))
 arm_common_ss.add(when: 'CONFIG_REALVIEW', if_true: files('realview.c'))
 arm_common_ss.add(when: 'CONFIG_SBSA_REF', if_true: files('sbsa-ref.c'))
 arm_common_ss.add(when: 'CONFIG_STELLARIS', if_true: files('stellaris.c'))
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
new file mode 100644
index 0000000000..defc265a5d
--- /dev/null
+++ b/hw/arm/phytium_e2000.c
@@ -0,0 +1,406 @@
+/*
+ * Phytium E2000 board models
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/error-report.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "system/address-spaces.h"
+#include "system/kvm.h"
+#include "system/system.h"
+#include "exec/hwaddr.h"
+#include "hw/arm/boot.h"
+#include "hw/arm/bsa.h"
+#include "hw/arm/machines-qom.h"
+#include "hw/char/pl011.h"
+#include "hw/core/boards.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/intc/arm_gicv3_common.h"
+#include "hw/intc/arm_gicv3_its_common.h"
+#include "hw/misc/unimp.h"
+#include "qobject/qlist.h"
+#include "qom/object.h"
+#include "target/arm/cpu.h"
+#include "target/arm/cpu-qom.h"
+#include "target/arm/gtimer.h"
+
+#define TYPE_PHYTIUM_PI MACHINE_TYPE_NAME("phytium-pi")
+OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000State, PHYTIUM_PI)
+
+#define PHYTIUM_E2000_NUM_CPUS        4
+#define PHYTIUM_E2000_NUM_IRQS        256
+
+#define PHYTIUM_E2000_NUM_UARTS       7
+
+#define PHYTIUM_E2000_GTIMER_HZ       50000000
+
+enum {
+    PHYTIUM_E2000_LOW_PERIPH,
+    PHYTIUM_E2000_UART0,
+    PHYTIUM_E2000_UART1,
+    PHYTIUM_E2000_UART2,
+    PHYTIUM_E2000_UART3,
+    PHYTIUM_E2000_UART4,
+    PHYTIUM_E2000_UART5,
+    PHYTIUM_E2000_UART6,
+    PHYTIUM_E2000_GIC_DIST,
+    PHYTIUM_E2000_GIC_ITS,
+    PHYTIUM_E2000_GIC_REDIST,
+    PHYTIUM_E2000_BOOT_SRAM,
+    PHYTIUM_E2000_BOARD_CTRL,
+    PHYTIUM_E2000_BOOT_IACC,
+    PHYTIUM_E2000_RAM,
+    PHYTIUM_E2000_RAM_HIGH,
+};
+
+struct PhytiumE2000State {
+    MachineState parent;
+    struct arm_boot_info bootinfo;
+    DeviceState *gic;
+    MemoryRegion ram_low;
+    MemoryRegion ram_high;
+};
+
+/*
+ * Keep the physical addresses used by the vendor firmware even before every
+ * device behind them is modeled. In particular, boot SRAM carries PBR/PBF
+ * handoff data and IACC is the fixed execution window for system firmware.
+ */
+static const MemMapEntry phytium_e2000_memmap[] = {
+    [PHYTIUM_E2000_LOW_PERIPH] =     { 0x28000000, 0x00100000 },
+    [PHYTIUM_E2000_UART0] =          { 0x2800c000, 0x00001000 },
+    [PHYTIUM_E2000_UART1] =          { 0x2800d000, 0x00001000 },
+    [PHYTIUM_E2000_UART2] =          { 0x2800e000, 0x00001000 },
+    [PHYTIUM_E2000_UART3] =          { 0x2800f000, 0x00001000 },
+    [PHYTIUM_E2000_UART4] =          { 0x28014000, 0x00001000 },
+    [PHYTIUM_E2000_UART5] =          { 0x2802a000, 0x00001000 },
+    [PHYTIUM_E2000_UART6] =          { 0x28032000, 0x00001000 },
+    [PHYTIUM_E2000_GIC_DIST] =       { 0x30800000, 0x00020000 },
+    [PHYTIUM_E2000_GIC_ITS] =        { 0x30820000, 0x00020000 },
+    [PHYTIUM_E2000_GIC_REDIST] =     { 0x30880000, 0x00080000 },
+    [PHYTIUM_E2000_BOOT_SRAM] =      { 0x30c00000, 0x00100000 },
+    [PHYTIUM_E2000_BOARD_CTRL] =     { 0x31800000, 0x01400000 },
+    [PHYTIUM_E2000_BOOT_IACC] =      { 0x38000000, 0x08000000 },
+    [PHYTIUM_E2000_RAM] =            { 0x80000000, 0x80000000 },
+    [PHYTIUM_E2000_RAM_HIGH] =       { 0x2000000000ULL, 0x180000000ULL },
+};
+
+static const int phytium_e2000_uart_irqmap[] = {
+    [0] = 83,
+    [1] = 84,
+    [2] = 85,
+    [3] = 86,
+    [4] = 92,
+    [5] = 103,
+    [6] = 107,
+};
+
+typedef struct PhytiumE2000CPUConfig {
+    const char *type;
+    uint64_t mp_affinity;
+    int64_t cluster_id;
+    int64_t core_id;
+} PhytiumE2000CPUConfig;
+
+static const PhytiumE2000CPUConfig
+phytium_e2000_cpu_config[PHYTIUM_E2000_NUM_CPUS] = {
+    {
+        .type = ARM_CPU_TYPE_NAME("phytium-ftc664"),
+        .mp_affinity = 0x000,
+        .cluster_id = 0,
+        .core_id = 0,
+    },
+    {
+        .type = ARM_CPU_TYPE_NAME("phytium-ftc664"),
+        .mp_affinity = 0x100,
+        .cluster_id = 1,
+        .core_id = 0,
+    },
+    {
+        .type = ARM_CPU_TYPE_NAME("phytium-ftc310"),
+        .mp_affinity = 0x200,
+        .cluster_id = 2,
+        .core_id = 0,
+    },
+    {
+        .type = ARM_CPU_TYPE_NAME("phytium-ftc310"),
+        .mp_affinity = 0x201,
+        .cluster_id = 2,
+        .core_id = 1,
+    },
+};
+
+static void phytium_e2000_create_its(PhytiumE2000State *s)
+{
+    DeviceState *dev = qdev_new(its_class_name());
+
+    object_property_set_link(OBJECT(dev), "parent-gicv3", OBJECT(s->gic),
+                             &error_abort);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0,
+                    phytium_e2000_memmap[PHYTIUM_E2000_GIC_ITS].base);
+}
+
+static void phytium_e2000_create_gic(PhytiumE2000State *s)
+{
+    MachineState *ms = MACHINE(s);
+    SysBusDevice *gicbusdev;
+    QList *redist_region_count;
+    int i;
+
+    s->gic = qdev_new(gicv3_class_name());
+    qdev_prop_set_uint32(s->gic, "revision", 3);
+    qdev_prop_set_uint32(s->gic, "num-cpu", ms->smp.cpus);
+    qdev_prop_set_uint32(s->gic, "num-irq", PHYTIUM_E2000_NUM_IRQS + 32);
+    qdev_prop_set_bit(s->gic, "has-security-extensions", true);
+    qdev_prop_set_bit(s->gic, "has-lpi", true);
+
+    redist_region_count = qlist_new();
+    qlist_append_int(redist_region_count, ms->smp.cpus);
+    qdev_prop_set_array(s->gic, "redist-region-count", redist_region_count);
+
+    object_property_set_link(OBJECT(s->gic), "sysmem",
+                             OBJECT(get_system_memory()), &error_fatal);
+
+    gicbusdev = SYS_BUS_DEVICE(s->gic);
+    sysbus_realize_and_unref(gicbusdev, &error_fatal);
+    sysbus_mmio_map(gicbusdev, 0,
+                    phytium_e2000_memmap[PHYTIUM_E2000_GIC_DIST].base);
+    sysbus_mmio_map(gicbusdev, 1,
+                    phytium_e2000_memmap[PHYTIUM_E2000_GIC_REDIST].base);
+
+    for (i = 0; i < ms->smp.cpus; i++) {
+        DeviceState *cpudev = DEVICE(qemu_get_cpu(i));
+        int intidbase = PHYTIUM_E2000_NUM_IRQS + i * GIC_INTERNAL;
+        static const int timer_irq[] = {
+            [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ,
+            [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ,
+            [GTIMER_HYP]  = ARCH_TIMER_NS_EL2_IRQ,
+            [GTIMER_SEC]  = ARCH_TIMER_S_EL1_IRQ,
+        };
+
+        for (int irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) {
+            qdev_connect_gpio_out(cpudev, irq,
+                qdev_get_gpio_in(s->gic, intidbase + timer_irq[irq]));
+        }
+        qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", 0,
+            qdev_get_gpio_in(s->gic, intidbase + ARCH_GIC_MAINT_IRQ));
+        qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0,
+            qdev_get_gpio_in(s->gic, intidbase + VIRTUAL_PMU_IRQ));
+
+        sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
+        sysbus_connect_irq(gicbusdev, i + ms->smp.cpus,
+                           qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
+        sysbus_connect_irq(gicbusdev, i + 2 * ms->smp.cpus,
+                           qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ));
+        sysbus_connect_irq(gicbusdev, i + 3 * ms->smp.cpus,
+                           qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ));
+    }
+
+    phytium_e2000_create_its(s);
+}
+
+static void phytium_e2000_create_uart(PhytiumE2000State *s, int index)
+{
+    int map_idx = PHYTIUM_E2000_UART0 + index;
+    DeviceState *dev = qdev_new(TYPE_PL011);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    qdev_prop_set_chr(dev, "chardev", serial_hd(index));
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map(sbd, 0, phytium_e2000_memmap[map_idx].base);
+    sysbus_connect_irq(sbd, 0,
+        qdev_get_gpio_in(s->gic, phytium_e2000_uart_irqmap[index]));
+}
+
+static void phytium_e2000_create_unimplemented(void)
+{
+    /*
+     * Preserve the SoC address map while individual boot-critical devices are
+     * introduced. More specific devices may overlap these low-priority
+     * catch-all regions without silently accepting accesses elsewhere.
+     */
+    create_unimplemented_device(
+        "phytium-e2000.low-peripheral",
+        phytium_e2000_memmap[PHYTIUM_E2000_LOW_PERIPH].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_LOW_PERIPH].size);
+    create_unimplemented_device(
+        "phytium-e2000.board-control",
+        phytium_e2000_memmap[PHYTIUM_E2000_BOARD_CTRL].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_BOARD_CTRL].size);
+}
+
+static void phytium_e2000_create_ram(PhytiumE2000State *s)
+{
+    MachineState *ms = MACHINE(s);
+    uint64_t low_size =
+        MIN(ms->ram_size, phytium_e2000_memmap[PHYTIUM_E2000_RAM].size);
+    uint64_t high_size = ms->ram_size - low_size;
+
+    /*
+     * Keep the machine RAMBlock contiguous for migration, but expose it in
+     * the two physical windows implemented by E2000. The high alias resumes
+     * at low_size, so the intervening PCIe hole consumes no guest RAM.
+     */
+    memory_region_init_alias(&s->ram_low, OBJECT(s),
+        "phytium-e2000.ram-low", ms->ram, 0, low_size);
+    memory_region_add_subregion(get_system_memory(),
+        phytium_e2000_memmap[PHYTIUM_E2000_RAM].base, &s->ram_low);
+
+    if (!high_size) {
+        return;
+    }
+
+    memory_region_init_alias(&s->ram_high, OBJECT(s),
+        "phytium-e2000.ram-high", ms->ram, low_size, high_size);
+    memory_region_add_subregion(get_system_memory(),
+        phytium_e2000_memmap[PHYTIUM_E2000_RAM_HIGH].base, &s->ram_high);
+}
+
+static void phytium_e2000_create_cpus(PhytiumE2000State *s)
+{
+    MachineState *ms = MACHINE(s);
+    const CPUArchIdList *possible_cpus;
+    int i;
+
+    possible_cpus = MACHINE_GET_CLASS(ms)->possible_cpu_arch_ids(ms);
+
+    for (i = 0; i < ms->smp.cpus; i++) {
+        Object *cpuobj = object_new(possible_cpus->cpus[i].type);
+        CPUState *cs;
+
+        object_property_set_int(cpuobj, "mp-affinity",
+                                possible_cpus->cpus[i].arch_id,
+                                &error_abort);
+        object_property_set_int(cpuobj, "cntfrq", PHYTIUM_E2000_GTIMER_HZ,
+                                &error_abort);
+        if (object_property_find(cpuobj, "has_el3")) {
+            /*
+             * The generic-loader U-Boot path starts after the EL3 firmware
+             * stages that normally provide the Phytium SMC services.
+             */
+            object_property_set_bool(cpuobj, "has_el3", false, &error_abort);
+        }
+        object_property_set_link(cpuobj, "memory", OBJECT(get_system_memory()),
+                                 &error_abort);
+        cs = CPU(cpuobj);
+        cs->cpu_index = i;
+        qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
+        object_unref(cpuobj);
+    }
+}
+
+static void phytium_pi_init(MachineState *ms)
+{
+    PhytiumE2000State *s = PHYTIUM_PI(ms);
+    int i;
+
+    if (kvm_enabled()) {
+        error_report("phytium-pi: KVM is not supported");
+        exit(1);
+    }
+
+    if (ms->smp.cpus > PHYTIUM_E2000_NUM_CPUS) {
+        error_report("phytium-pi supports at most %d CPUs",
+                     PHYTIUM_E2000_NUM_CPUS);
+        exit(1);
+    }
+
+    if (ms->ram_size >
+        phytium_e2000_memmap[PHYTIUM_E2000_RAM].size +
+        phytium_e2000_memmap[PHYTIUM_E2000_RAM_HIGH].size) {
+        error_report("phytium-pi supports at most 8 GiB RAM");
+        exit(1);
+    }
+
+    phytium_e2000_create_ram(s);
+    phytium_e2000_create_unimplemented();
+
+    phytium_e2000_create_cpus(s);
+    phytium_e2000_create_gic(s);
+
+    for (i = 0; i < PHYTIUM_E2000_NUM_UARTS; i++) {
+        phytium_e2000_create_uart(s, i);
+    }
+
+    s->bootinfo.ram_size = ms->ram_size;
+    s->bootinfo.board_id = -1;
+    s->bootinfo.loader_start = phytium_e2000_memmap[PHYTIUM_E2000_RAM].base;
+    s->bootinfo.psci_conduit = QEMU_PSCI_CONDUIT_SMC;
+    s->bootinfo.firmware_loaded = false;
+    arm_load_kernel(ARM_CPU(first_cpu), ms, &s->bootinfo);
+}
+
+static const CPUArchIdList *phytium_e2000_possible_cpu_arch_ids(MachineState *ms)
+{
+    int i;
+
+    if (ms->possible_cpus) {
+        return ms->possible_cpus;
+    }
+
+    ms->possible_cpus = g_malloc0(sizeof(CPUArchIdList) +
+                                  sizeof(CPUArchId) * ms->smp.max_cpus);
+    ms->possible_cpus->len = ms->smp.max_cpus;
+
+    for (i = 0; i < ms->possible_cpus->len; i++) {
+        const PhytiumE2000CPUConfig *config = &phytium_e2000_cpu_config[i];
+
+        ms->possible_cpus->cpus[i].type = config->type;
+        ms->possible_cpus->cpus[i].arch_id = config->mp_affinity;
+        ms->possible_cpus->cpus[i].props.has_cluster_id = true;
+        ms->possible_cpus->cpus[i].props.cluster_id = config->cluster_id;
+        ms->possible_cpus->cpus[i].props.has_core_id = true;
+        ms->possible_cpus->cpus[i].props.core_id = config->core_id;
+        ms->possible_cpus->cpus[i].props.has_thread_id = true;
+        ms->possible_cpus->cpus[i].props.thread_id = 0;
+    }
+
+    return ms->possible_cpus;
+}
+
+static void phytium_pi_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+    static const char * const valid_cpu_types[] = {
+        /* The machine assigns the FTC310 slots independently */
+        ARM_CPU_TYPE_NAME("phytium-ftc664"),
+        NULL,
+    };
+
+    mc->init = phytium_pi_init;
+    mc->desc = "Phytium Pi board (Phytium E2000Q)";
+    mc->default_cpu_type = ARM_CPU_TYPE_NAME("phytium-ftc664");
+    mc->valid_cpu_types = valid_cpu_types;
+    mc->max_cpus = PHYTIUM_E2000_NUM_CPUS;
+    mc->default_cpus = PHYTIUM_E2000_NUM_CPUS;
+    mc->default_ram_size = 1 * GiB;
+    mc->default_ram_id = "phytium-e2000.ram";
+    mc->minimum_page_bits = 12;
+    mc->block_default_type = IF_SD;
+    mc->no_cdrom = 1;
+    mc->possible_cpu_arch_ids = phytium_e2000_possible_cpu_arch_ids;
+}
+
+static const TypeInfo phytium_pi_info = {
+    .name = TYPE_PHYTIUM_PI,
+    .parent = TYPE_MACHINE,
+    .class_init = phytium_pi_class_init,
+    .instance_size = sizeof(PhytiumE2000State),
+    .interfaces = aarch64_machine_interfaces,
+};
+
+static void phytium_pi_machine_init(void)
+{
+    type_register_static(&phytium_pi_info);
+}
+
+type_init(phytium_pi_machine_init);
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 03/33] hw/arm: phytium: Add Phytium E2000 PCIe host
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
  2026-09-03 11:24 ` [PATCH 01/33] target/arm: Add Phytium FTC310 and FTC664 CPU models Bin Meng
  2026-09-03 11:24 ` [PATCH 02/33] hw/arm: Add basic Phytium Pi machine Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 05/33] hw/sd: Add Phytium E2000 MCI controller Bin Meng
                   ` (25 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Add the E2000 GPEX host with ECAM, PIO, and 32-bit and 64-bit MMIO
windows. Connect MSI through the ITS and route the four INTx outputs
to GIC SPI 4 through 7.

The vendor device tree masks out the slot number and maps each
interrupt pin directly to the corresponding SPI. Override the GPEX
root-bus IRQ mapper so it selects an output solely by pin. Standard
PCI-to-PCI bridge swizzling remains unchanged.

Expose the SoC-specific PCIe port and PHY control apertures as
low-priority unimplemented regions. Firmware programs these windows
before enumerating ECAM, but their state does not affect the generic
host bridge.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |   3 ++
 hw/arm/phytium_e2000.c | 118 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 121 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 20863b5847..e68f137a66 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -130,7 +130,10 @@ config PHYTIUM_E2000
     bool
     default y
     depends on TCG && AARCH64
+    imply PCI_DEVICES
     select ARM_GIC
+    select PCI_EXPRESS
+    select PCI_EXPRESS_GENERIC_BRIDGE
     select PL011
     select UNIMP
 
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index defc265a5d..aac798aa07 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -26,6 +26,8 @@
 #include "hw/intc/arm_gicv3_common.h"
 #include "hw/intc/arm_gicv3_its_common.h"
 #include "hw/misc/unimp.h"
+#include "hw/pci/pci.h"
+#include "hw/pci-host/gpex.h"
 #include "qobject/qlist.h"
 #include "qom/object.h"
 #include "target/arm/cpu.h"
@@ -55,9 +57,15 @@ enum {
     PHYTIUM_E2000_GIC_ITS,
     PHYTIUM_E2000_GIC_REDIST,
     PHYTIUM_E2000_BOOT_SRAM,
+    PHYTIUM_E2000_PCIE_CTRL,
+    PHYTIUM_E2000_PCIE_PHY_CTRL,
     PHYTIUM_E2000_BOARD_CTRL,
     PHYTIUM_E2000_BOOT_IACC,
+    PHYTIUM_E2000_PCIE_ECAM,
+    PHYTIUM_E2000_PCIE_PIO,
+    PHYTIUM_E2000_PCIE_MMIO,
     PHYTIUM_E2000_RAM,
+    PHYTIUM_E2000_PCIE_MMIO_HIGH,
     PHYTIUM_E2000_RAM_HIGH,
 };
 
@@ -87,9 +95,15 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_GIC_ITS] =        { 0x30820000, 0x00020000 },
     [PHYTIUM_E2000_GIC_REDIST] =     { 0x30880000, 0x00080000 },
     [PHYTIUM_E2000_BOOT_SRAM] =      { 0x30c00000, 0x00100000 },
+    [PHYTIUM_E2000_PCIE_CTRL] =      { 0x31000000, 0x00200000 },
+    [PHYTIUM_E2000_PCIE_PHY_CTRL] =  { 0x31500000, 0x00001000 },
     [PHYTIUM_E2000_BOARD_CTRL] =     { 0x31800000, 0x01400000 },
     [PHYTIUM_E2000_BOOT_IACC] =      { 0x38000000, 0x08000000 },
+    [PHYTIUM_E2000_PCIE_ECAM] =      { 0x40000000, 0x10000000 },
+    [PHYTIUM_E2000_PCIE_PIO] =       { 0x50000000, 0x00f00000 },
+    [PHYTIUM_E2000_PCIE_MMIO] =      { 0x58000000, 0x28000000 },
     [PHYTIUM_E2000_RAM] =            { 0x80000000, 0x80000000 },
+    [PHYTIUM_E2000_PCIE_MMIO_HIGH] = { 0x1000000000ULL, 0x1000000000ULL },
     [PHYTIUM_E2000_RAM_HIGH] =       { 0x2000000000ULL, 0x180000000ULL },
 };
 
@@ -103,6 +117,13 @@ static const int phytium_e2000_uart_irqmap[] = {
     [6] = 107,
 };
 
+static const int phytium_e2000_pcie_irqmap[PCI_NUM_PINS] = {
+    [0] = 4,
+    [1] = 5,
+    [2] = 6,
+    [3] = 7,
+};
+
 typedef struct PhytiumE2000CPUConfig {
     const char *type;
     uint64_t mp_affinity;
@@ -221,6 +242,88 @@ static void phytium_e2000_create_uart(PhytiumE2000State *s, int index)
         qdev_get_gpio_in(s->gic, phytium_e2000_uart_irqmap[index]));
 }
 
+/* The vendor DT maps root-bus INTx solely by pin */
+static int phytium_e2000_pcie_map_irq(PCIDevice *pdev, int pin)
+{
+    return pin;
+}
+
+static void phytium_e2000_create_pcie(PhytiumE2000State *s)
+{
+    DeviceState *dev = qdev_new(TYPE_GPEX_HOST);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    MemoryRegion *ecam_alias;
+    MemoryRegion *ecam_reg;
+    MemoryRegion *mmio_alias;
+    MemoryRegion *mmio_high_alias;
+    MemoryRegion *mmio_reg;
+    int i;
+
+    /*
+     * GPEX owns generic ECAM, PIO, and MMIO containers. The aliases below
+     * place those containers at the E2000 physical windows that U-Boot scans
+     * and that the SDK device tree publishes to Linux.
+     *
+     * The MMIO container is indexed by PCI bus address, so each alias uses
+     * the physical window base as its source offset to preserve a 1:1 mapping
+     * between CPU and PCI addresses.
+     */
+    qdev_prop_set_uint64(dev, PCI_HOST_ECAM_BASE,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_ECAM].base);
+    qdev_prop_set_uint64(dev, PCI_HOST_ECAM_SIZE,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_ECAM].size);
+    qdev_prop_set_uint64(dev, PCI_HOST_PIO_BASE,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_PIO].base);
+    qdev_prop_set_uint64(dev, PCI_HOST_PIO_SIZE,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_PIO].size);
+    qdev_prop_set_uint64(dev, PCI_HOST_BELOW_4G_MMIO_BASE,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO].base);
+    qdev_prop_set_uint64(dev, PCI_HOST_BELOW_4G_MMIO_SIZE,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO].size);
+    qdev_prop_set_uint64(dev, PCI_HOST_ABOVE_4G_MMIO_BASE,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO_HIGH].base);
+    qdev_prop_set_uint64(dev, PCI_HOST_ABOVE_4G_MMIO_SIZE,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO_HIGH].size);
+
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    pci_bus_map_irqs(PCI_HOST_BRIDGE(dev)->bus,
+                     phytium_e2000_pcie_map_irq);
+
+    ecam_alias = g_new0(MemoryRegion, 1);
+    ecam_reg = sysbus_mmio_get_region(sbd, 0);
+    memory_region_init_alias(ecam_alias, OBJECT(dev), "phytium-pcie-ecam",
+        ecam_reg, 0, phytium_e2000_memmap[PHYTIUM_E2000_PCIE_ECAM].size);
+    memory_region_add_subregion(get_system_memory(),
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_ECAM].base, ecam_alias);
+
+    mmio_alias = g_new0(MemoryRegion, 1);
+    mmio_reg = sysbus_mmio_get_region(sbd, 1);
+    memory_region_init_alias(mmio_alias, OBJECT(dev), "phytium-pcie-mmio",
+        mmio_reg,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO].size);
+    memory_region_add_subregion(get_system_memory(),
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO].base,
+        mmio_alias);
+
+    mmio_high_alias = g_new0(MemoryRegion, 1);
+    memory_region_init_alias(mmio_high_alias, OBJECT(dev),
+        "phytium-pcie-mmio-high", mmio_reg,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO_HIGH].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO_HIGH].size);
+    memory_region_add_subregion(get_system_memory(),
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_MMIO_HIGH].base,
+        mmio_high_alias);
+
+    sysbus_mmio_map(sbd, 2, phytium_e2000_memmap[PHYTIUM_E2000_PCIE_PIO].base);
+
+    for (i = 0; i < PCI_NUM_PINS; i++) {
+        sysbus_connect_irq(sbd, i,
+            qdev_get_gpio_in(s->gic, phytium_e2000_pcie_irqmap[i]));
+        gpex_set_irq_num(GPEX_HOST(dev), i, phytium_e2000_pcie_irqmap[i]);
+    }
+}
+
 static void phytium_e2000_create_unimplemented(void)
 {
     /*
@@ -232,6 +335,19 @@ static void phytium_e2000_create_unimplemented(void)
         "phytium-e2000.low-peripheral",
         phytium_e2000_memmap[PHYTIUM_E2000_LOW_PERIPH].base,
         phytium_e2000_memmap[PHYTIUM_E2000_LOW_PERIPH].size);
+    /*
+     * PBF programs SoC-specific PCIe PHY and port controls before U-Boot
+     * enumerates ECAM. Their values do not affect the generic host bridge, so
+     * keep this control aperture visible without inventing register behavior.
+     */
+    create_unimplemented_device(
+        "phytium-e2000.pcie-control",
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_CTRL].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_CTRL].size);
+    create_unimplemented_device(
+        "phytium-e2000.pcie-phy-control",
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_PHY_CTRL].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_PCIE_PHY_CTRL].size);
     create_unimplemented_device(
         "phytium-e2000.board-control",
         phytium_e2000_memmap[PHYTIUM_E2000_BOARD_CTRL].base,
@@ -331,6 +447,8 @@ static void phytium_pi_init(MachineState *ms)
         phytium_e2000_create_uart(s, i);
     }
 
+    phytium_e2000_create_pcie(s);
+
     s->bootinfo.ram_size = ms->ram_size;
     s->bootinfo.board_id = -1;
     s->bootinfo.loader_start = phytium_e2000_memmap[PHYTIUM_E2000_RAM].base;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 05/33] hw/sd: Add Phytium E2000 MCI controller
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (2 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 03/33] hw/arm: phytium: Add Phytium E2000 PCIe host Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 06/33] hw/arm: phytium: Connect Phytium E2000 MCI controllers Bin Meng
                   ` (24 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Model the E2000 SD/MMC controller as a subclass of the reusable
DesignWare MCI implementation. Supply the E2000 hardware configuration,
FIFO depth, data window, clock-ready behavior, and vendor divider
register while inheriting the standard command and DMA paths.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig                    |  1 +
 hw/sd/meson.build                 |  1 +
 hw/sd/phytium_e2000_mci.c         | 95 +++++++++++++++++++++++++++++++
 include/hw/sd/phytium_e2000_mci.h | 21 +++++++
 4 files changed, 118 insertions(+)
 create mode 100644 hw/sd/phytium_e2000_mci.c
 create mode 100644 include/hw/sd/phytium_e2000_mci.h

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index e68f137a66..99a5799b22 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -132,6 +132,7 @@ config PHYTIUM_E2000
     depends on TCG && AARCH64
     imply PCI_DEVICES
     select ARM_GIC
+    select DW_MCI
     select PCI_EXPRESS
     select PCI_EXPRESS_GENERIC_BRIDGE
     select PL011
diff --git a/hw/sd/meson.build b/hw/sd/meson.build
index d7b97cdca4..5792d0c923 100644
--- a/hw/sd/meson.build
+++ b/hw/sd/meson.build
@@ -6,6 +6,7 @@ system_ss.add(when: 'CONFIG_SDHCI_PCI', if_true: files('sdhci-pci.c'))
 system_ss.add(when: 'CONFIG_SSI_SD', if_true: files('ssi-sd.c'))
 
 system_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_mmc.c'))
+system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_mci.c'))
 system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_sdhost.c'))
 system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files('aspeed_sdhci.c'))
 system_ss.add(when: 'CONFIG_ALLWINNER_H3', if_true: files('allwinner-sdhost.c'))
diff --git a/hw/sd/phytium_e2000_mci.c b/hw/sd/phytium_e2000_mci.c
new file mode 100644
index 0000000000..84cfc213de
--- /dev/null
+++ b/hw/sd/phytium_e2000_mci.c
@@ -0,0 +1,95 @@
+/*
+ * Phytium E2000 extension to the Synopsys DesignWare MCI
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sd/phytium_e2000_mci.h"
+
+#include "qemu/bitops.h"
+#include "qemu/module.h"
+
+#define PHYTIUM_E2000_MCI_CCLK_RDY       0x058
+#define PHYTIUM_E2000_MCI_CLK_DIVIDER    0x114
+#define PHYTIUM_E2000_MCI_VERID          0x280a
+#define PHYTIUM_E2000_MCI_HCON           (BIT(27) | BIT(7))
+#define PHYTIUM_E2000_MCI_DATA_OFFSET    0x200
+#define PHYTIUM_E2000_MCI_FIFO_DEPTH     512
+
+struct PhytiumE2000MciState {
+    DwMciState parent_obj;
+};
+
+/*
+ * The vendor driver polls CCLK_RDY after programming its private divider.
+ * QEMU has no controller clock tree, so completion is immediate while the
+ * divider remains ordinary storage for firmware readback.
+ */
+static bool phytium_e2000_mci_read(DwMciState *s, hwaddr offset,
+                                   uint64_t *value, unsigned size)
+{
+    if (size != sizeof(uint32_t)) {
+        return false;
+    }
+
+    switch (offset) {
+    case PHYTIUM_E2000_MCI_CCLK_RDY:
+        *value = 1;
+        return true;
+    case PHYTIUM_E2000_MCI_CLK_DIVIDER:
+        *value = s->regs[offset / sizeof(uint32_t)];
+        return true;
+    default:
+        return false;
+    }
+}
+
+static bool phytium_e2000_mci_write(DwMciState *s, hwaddr offset,
+                                    uint64_t value, unsigned size)
+{
+    if (size != sizeof(uint32_t)) {
+        return false;
+    }
+
+    switch (offset) {
+    case PHYTIUM_E2000_MCI_CCLK_RDY:
+        return true;
+    case PHYTIUM_E2000_MCI_CLK_DIVIDER:
+        s->regs[offset / sizeof(uint32_t)] = value;
+        return true;
+    default:
+        return false;
+    }
+}
+
+static void phytium_e2000_mci_class_init(ObjectClass *klass, const void *data)
+{
+    DwMciClass *dmc = DW_MCI_CLASS(klass);
+
+    dmc->verid = PHYTIUM_E2000_MCI_VERID;
+    dmc->hcon = PHYTIUM_E2000_MCI_HCON;
+    dmc->data_offset = PHYTIUM_E2000_MCI_DATA_OFFSET;
+    dmc->fifo_depth = PHYTIUM_E2000_MCI_FIFO_DEPTH;
+    dmc->vendor_read = phytium_e2000_mci_read;
+    dmc->vendor_write = phytium_e2000_mci_write;
+}
+
+static const TypeInfo phytium_e2000_mci_info = {
+    .name = TYPE_PHYTIUM_E2000_MCI,
+    .parent = TYPE_DW_MCI,
+    .instance_size = sizeof(PhytiumE2000MciState),
+    .class_init = phytium_e2000_mci_class_init,
+};
+
+static void phytium_e2000_mci_register_types(void)
+{
+    type_register_static(&phytium_e2000_mci_info);
+}
+
+type_init(phytium_e2000_mci_register_types)
diff --git a/include/hw/sd/phytium_e2000_mci.h b/include/hw/sd/phytium_e2000_mci.h
new file mode 100644
index 0000000000..cc6fce06d0
--- /dev/null
+++ b/include/hw/sd/phytium_e2000_mci.h
@@ -0,0 +1,21 @@
+/*
+ * Phytium E2000 SD/MMC controller
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SD_PHYTIUM_E2000_MCI_H
+#define HW_SD_PHYTIUM_E2000_MCI_H
+
+#include "hw/sd/dw_mci.h"
+#include "qom/object.h"
+
+#define TYPE_PHYTIUM_E2000_MCI "phytium-e2000-mci"
+OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000MciState, PHYTIUM_E2000_MCI)
+
+#endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 06/33] hw/arm: phytium: Connect Phytium E2000 MCI controllers
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (3 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 05/33] hw/sd: Add Phytium E2000 MCI controller Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 08/33] hw/arm: phytium: Connect Phytium E2000 GEM controllers Bin Meng
                   ` (23 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Instantiate both E2000 MCI controllers at their low-peripheral
addresses, route their interrupts to the GIC, and create an SD
card on each exported bus. Map legacy IF_SD backends by controller
index so firmware and Linux use the same board-visible media topology.

Keep the broad unimplemented aperture underneath the concrete
devices and make SD the machine's default block interface.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |  1 +
 hw/arm/phytium_e2000.c | 65 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 99a5799b22..9358a47b8a 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -136,6 +136,7 @@ config PHYTIUM_E2000
     select PCI_EXPRESS
     select PCI_EXPRESS_GENERIC_BRIDGE
     select PL011
+    select SD
     select UNIMP
 
 config REALVIEW
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index aac798aa07..88307184e9 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -28,6 +28,8 @@
 #include "hw/misc/unimp.h"
 #include "hw/pci/pci.h"
 #include "hw/pci-host/gpex.h"
+#include "hw/sd/phytium_e2000_mci.h"
+#include "hw/sd/sd.h"
 #include "qobject/qlist.h"
 #include "qom/object.h"
 #include "target/arm/cpu.h"
@@ -40,12 +42,15 @@ OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000State, PHYTIUM_PI)
 #define PHYTIUM_E2000_NUM_CPUS        4
 #define PHYTIUM_E2000_NUM_IRQS        256
 
+#define PHYTIUM_E2000_NUM_MCIS        2
 #define PHYTIUM_E2000_NUM_UARTS       7
 
 #define PHYTIUM_E2000_GTIMER_HZ       50000000
 
 enum {
     PHYTIUM_E2000_LOW_PERIPH,
+    PHYTIUM_E2000_MCI0,
+    PHYTIUM_E2000_MCI1,
     PHYTIUM_E2000_UART0,
     PHYTIUM_E2000_UART1,
     PHYTIUM_E2000_UART2,
@@ -73,6 +78,7 @@ struct PhytiumE2000State {
     MachineState parent;
     struct arm_boot_info bootinfo;
     DeviceState *gic;
+    PhytiumE2000MciState *mci[PHYTIUM_E2000_NUM_MCIS];
     MemoryRegion ram_low;
     MemoryRegion ram_high;
 };
@@ -84,6 +90,8 @@ struct PhytiumE2000State {
  */
 static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_LOW_PERIPH] =     { 0x28000000, 0x00100000 },
+    [PHYTIUM_E2000_MCI0] =           { 0x28000000, 0x00001000 },
+    [PHYTIUM_E2000_MCI1] =           { 0x28001000, 0x00001000 },
     [PHYTIUM_E2000_UART0] =          { 0x2800c000, 0x00001000 },
     [PHYTIUM_E2000_UART1] =          { 0x2800d000, 0x00001000 },
     [PHYTIUM_E2000_UART2] =          { 0x2800e000, 0x00001000 },
@@ -107,6 +115,11 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_RAM_HIGH] =       { 0x2000000000ULL, 0x180000000ULL },
 };
 
+static const int phytium_e2000_mci_irqmap[] = {
+    [0] = 72,
+    [1] = 73,
+};
+
 static const int phytium_e2000_uart_irqmap[] = {
     [0] = 83,
     [1] = 84,
@@ -324,6 +337,55 @@ static void phytium_e2000_create_pcie(PhytiumE2000State *s)
     }
 }
 
+static BlockBackend *phytium_e2000_sd_blk(int index)
+{
+    DriveInfo *dinfo = drive_get(IF_SD, 0, index);
+
+    return dinfo ? blk_by_legacy_dinfo(dinfo) : NULL;
+}
+
+static void phytium_e2000_attach_sd_card(DwMciState *mci, int index)
+{
+    BlockBackend *blk = phytium_e2000_sd_blk(index);
+    BusState *bus = BUS(dw_mci_get_bus(mci));
+    DeviceState *card;
+
+    /*
+     * Always instantiate the socket-level card object. A missing backend then
+     * behaves as an empty slot, while if=sd,index=N gives firmware a real SD
+     * card on the matching physical MCI controller.
+     */
+    card = qdev_new(TYPE_SD_CARD);
+    qdev_prop_set_drive_err(card, "drive", blk, &error_fatal);
+    qdev_realize_and_unref(card, bus, &error_fatal);
+}
+
+static void phytium_e2000_create_mci(PhytiumE2000State *s, int index)
+{
+    PhytiumE2000MciState *mci =
+        PHYTIUM_E2000_MCI(qdev_new(TYPE_PHYTIUM_E2000_MCI));
+    DeviceState *dev = DEVICE(mci);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    MemoryRegion *iomem;
+    int map_idx = PHYTIUM_E2000_MCI0 + index;
+    g_autofree char *name = g_strdup_printf("mci%d", index);
+
+    /*
+     * MCI0 and MCI1 occupy the first two pages of the broad low-peripheral
+     * placeholder. Use a higher overlap priority so real command and data
+     * accesses reach the controller model.
+     */
+    s->mci[index] = mci;
+    object_property_add_child(OBJECT(s), name, OBJECT(mci));
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    iomem = sysbus_mmio_get_region(sbd, 0);
+    memory_region_add_subregion_overlap(
+        get_system_memory(), phytium_e2000_memmap[map_idx].base, iomem, 1);
+    sysbus_connect_irq(sbd, 0,
+        qdev_get_gpio_in(s->gic, phytium_e2000_mci_irqmap[index]));
+    phytium_e2000_attach_sd_card(DW_MCI(mci), index);
+}
+
 static void phytium_e2000_create_unimplemented(void)
 {
     /*
@@ -443,6 +505,9 @@ static void phytium_pi_init(MachineState *ms)
     phytium_e2000_create_cpus(s);
     phytium_e2000_create_gic(s);
 
+    for (i = 0; i < PHYTIUM_E2000_NUM_MCIS; i++) {
+        phytium_e2000_create_mci(s, i);
+    }
     for (i = 0; i < PHYTIUM_E2000_NUM_UARTS; i++) {
         phytium_e2000_create_uart(s, i);
     }
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 08/33] hw/arm: phytium: Connect Phytium E2000 GEM controllers
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (4 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 06/33] hw/arm: phytium: Connect Phytium E2000 MCI controllers Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 09/33] hw/misc: Add Phytium E2000 DDR status Bin Meng
                   ` (22 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Instantiate all four standard Cadence GEM controllers described by the
E2000 SoC device tree. The generic model implements the first 0x800
bytes of each 0x2000-byte E2000 aperture, so cover the complete window
with a low-priority unimplemented region and map the Cadence registers
on top. This accepts accesses to the SoC-specific clock and SerDes
selectors without inventing their behavior.

Connect eight queue interrupts for GEM0 and four for GEM1 through GEM3,
and configure the PCS, MDIO PHY address and 16360-byte jumbo limit
expected by the SDK drivers.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |  1 +
 hw/arm/phytium_e2000.c | 62 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 63 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 9358a47b8a..08128232ca 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -132,6 +132,7 @@ config PHYTIUM_E2000
     depends on TCG && AARCH64
     imply PCI_DEVICES
     select ARM_GIC
+    select CADENCE
     select DW_MCI
     select PCI_EXPRESS
     select PCI_EXPRESS_GENERIC_BRIDGE
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 88307184e9..c68a565330 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -26,10 +26,12 @@
 #include "hw/intc/arm_gicv3_common.h"
 #include "hw/intc/arm_gicv3_its_common.h"
 #include "hw/misc/unimp.h"
+#include "hw/net/cadence_gem.h"
 #include "hw/pci/pci.h"
 #include "hw/pci-host/gpex.h"
 #include "hw/sd/phytium_e2000_mci.h"
 #include "hw/sd/sd.h"
+#include "net/net.h"
 #include "qobject/qlist.h"
 #include "qom/object.h"
 #include "target/arm/cpu.h"
@@ -44,6 +46,7 @@ OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000State, PHYTIUM_PI)
 
 #define PHYTIUM_E2000_NUM_MCIS        2
 #define PHYTIUM_E2000_NUM_UARTS       7
+#define PHYTIUM_E2000_NUM_GEMS        4
 
 #define PHYTIUM_E2000_GTIMER_HZ       50000000
 
@@ -65,6 +68,10 @@ enum {
     PHYTIUM_E2000_PCIE_CTRL,
     PHYTIUM_E2000_PCIE_PHY_CTRL,
     PHYTIUM_E2000_BOARD_CTRL,
+    PHYTIUM_E2000_GEM0,
+    PHYTIUM_E2000_GEM1,
+    PHYTIUM_E2000_GEM2,
+    PHYTIUM_E2000_GEM3,
     PHYTIUM_E2000_BOOT_IACC,
     PHYTIUM_E2000_PCIE_ECAM,
     PHYTIUM_E2000_PCIE_PIO,
@@ -79,6 +86,7 @@ struct PhytiumE2000State {
     struct arm_boot_info bootinfo;
     DeviceState *gic;
     PhytiumE2000MciState *mci[PHYTIUM_E2000_NUM_MCIS];
+    CadenceGEMState *gem[PHYTIUM_E2000_NUM_GEMS];
     MemoryRegion ram_low;
     MemoryRegion ram_high;
 };
@@ -106,6 +114,10 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_PCIE_CTRL] =      { 0x31000000, 0x00200000 },
     [PHYTIUM_E2000_PCIE_PHY_CTRL] =  { 0x31500000, 0x00001000 },
     [PHYTIUM_E2000_BOARD_CTRL] =     { 0x31800000, 0x01400000 },
+    [PHYTIUM_E2000_GEM0] =           { 0x3200c000, 0x00002000 },
+    [PHYTIUM_E2000_GEM1] =           { 0x3200e000, 0x00002000 },
+    [PHYTIUM_E2000_GEM2] =           { 0x32010000, 0x00002000 },
+    [PHYTIUM_E2000_GEM3] =           { 0x32012000, 0x00002000 },
     [PHYTIUM_E2000_BOOT_IACC] =      { 0x38000000, 0x08000000 },
     [PHYTIUM_E2000_PCIE_ECAM] =      { 0x40000000, 0x10000000 },
     [PHYTIUM_E2000_PCIE_PIO] =       { 0x50000000, 0x00f00000 },
@@ -130,6 +142,16 @@ static const int phytium_e2000_uart_irqmap[] = {
     [6] = 107,
 };
 
+static const uint8_t phytium_e2000_gem_num_queues[] = { 8, 4, 4, 4 };
+
+static const int phytium_e2000_gem_irqmap[PHYTIUM_E2000_NUM_GEMS]
+                                               [MAX_PRIORITY_QUEUES] = {
+    [0] = { 55, 56, 57, 58, 28, 29, 30, 31 },
+    [1] = { 59, 60, 61, 62 },
+    [2] = { 64, 65, 66, 67 },
+    [3] = { 68, 69, 70, 71 },
+};
+
 static const int phytium_e2000_pcie_irqmap[PCI_NUM_PINS] = {
     [0] = 4,
     [1] = 5,
@@ -255,6 +277,43 @@ static void phytium_e2000_create_uart(PhytiumE2000State *s, int index)
         qdev_get_gpio_in(s->gic, phytium_e2000_uart_irqmap[index]));
 }
 
+static void phytium_e2000_create_gem(PhytiumE2000State *s, int index)
+{
+    DeviceState *dev = qdev_new(TYPE_CADENCE_GEM);
+    CadenceGEMState *gem = CADENCE_GEM(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    g_autofree char *name = g_strdup_printf("gem%d", index);
+    g_autofree char *unimp_name =
+        g_strdup_printf("phytium-e2000.gem%d-unimplemented", index);
+    int map_idx = PHYTIUM_E2000_GEM0 + index;
+    hwaddr base = phytium_e2000_memmap[map_idx].base;
+    int i;
+
+    s->gem[index] = gem;
+    object_property_add_child(OBJECT(s), name, OBJECT(dev));
+
+    qemu_configure_nic_device(dev, true, name);
+    qdev_prop_set_uint8(dev, "phy-addr", 0);
+    qdev_prop_set_uint8(dev, "num-priority-queues",
+                        phytium_e2000_gem_num_queues[index]);
+    qdev_prop_set_uint16(dev, "jumbo-max-len", 16360);
+    qdev_prop_set_bit(dev, "pcs-enabled", true);
+
+    /*
+     * The E2000 exposes a 0x2000-byte aperture, while the generic Cadence
+     * model implements the first 0x800 bytes. Catch accesses to the remaining
+     * SoC-specific registers without inventing their clock and SerDes effects.
+     */
+    create_unimplemented_device(unimp_name, base,
+                                phytium_e2000_memmap[map_idx].size);
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map(sbd, 0, base);
+    for (i = 0; i < phytium_e2000_gem_num_queues[index]; i++) {
+        sysbus_connect_irq(sbd, i,
+            qdev_get_gpio_in(s->gic, phytium_e2000_gem_irqmap[index][i]));
+    }
+}
+
 /* The vendor DT maps root-bus INTx solely by pin */
 static int phytium_e2000_pcie_map_irq(PCIDevice *pdev, int pin)
 {
@@ -511,6 +570,9 @@ static void phytium_pi_init(MachineState *ms)
     for (i = 0; i < PHYTIUM_E2000_NUM_UARTS; i++) {
         phytium_e2000_create_uart(s, i);
     }
+    for (i = 0; i < PHYTIUM_E2000_NUM_GEMS; i++) {
+        phytium_e2000_create_gem(s, i);
+    }
 
     phytium_e2000_create_pcie(s);
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 09/33] hw/misc: Add Phytium E2000 DDR status
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (5 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 08/33] hw/arm: phytium: Connect Phytium E2000 GEM controllers Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 10/33] hw/arm: phytium: Connect the " Bin Meng
                   ` (21 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: qemu-arm

Move the E2000 DDR initialization and training compatibility window into
a first-class registerinfo device. Keep ordinary aligned registers as
persistent storage and implement the indirect selector/value interface
used by the vendor PBF.

Return the expected initialization, PHY training, update, and BIST
results, including the training-error range. This models the firmware
contract without attempting a timing-accurate DDR PHY.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/misc/meson.build                 |   1 +
 hw/misc/phytium_e2000_ddr.c         | 197 ++++++++++++++++++++++++++++
 include/hw/misc/phytium_e2000_ddr.h |  23 ++++
 3 files changed, 221 insertions(+)
 create mode 100644 hw/misc/phytium_e2000_ddr.c
 create mode 100644 include/hw/misc/phytium_e2000_ddr.h

diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 54e07aacda..898cf6ea87 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -16,6 +16,7 @@ system_ss.add(when: 'CONFIG_PL310', if_true: files('arm_l2x0.c'))
 system_ss.add(when: 'CONFIG_INTEGRATOR_DEBUG', if_true: files('arm_integrator_debug.c'))
 system_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
 system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
+system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_ddr.c'))
 
 system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
 
diff --git a/hw/misc/phytium_e2000_ddr.c b/hw/misc/phytium_e2000_ddr.c
new file mode 100644
index 0000000000..ab46a46961
--- /dev/null
+++ b/hw/misc/phytium_e2000_ddr.c
@@ -0,0 +1,197 @@
+/*
+ * Phytium E2000 DDR training status
+ *
+ * The vendor firmware accesses controller and PHY state through an indexed
+ * selector/value window. This functional model reproduces the completion and
+ * error predicates used during boot; it does not simulate analog DDR training
+ * or generate physical calibration results.
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/phytium_e2000_ddr.h"
+
+#include "hw/core/register.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+
+REG32(SELECTOR, 0x80)
+REG32(VALUE, 0x84)
+
+#define PHYTIUM_E2000_DDR_R_MAX                  \
+    (PHYTIUM_E2000_DDR_MMIO_SIZE / sizeof(uint32_t))
+/*
+ * Selectors encode a register index as a byte offset. PBF uses separate
+ * direct, indirect, and training banks distinguished by 0x800 and 0x1000
+ * index biases.
+ */
+#define PHYTIUM_E2000_DDR_SELECTOR(reg)          ((reg) * 4)
+#define PHYTIUM_E2000_DDR_INDIRECT_SELECTOR(reg) (((reg) + 0x800) * 4)
+#define PHYTIUM_E2000_DDR_TRAINING_SELECTOR(reg) (((reg) + 0x1000) * 4)
+#define PHYTIUM_E2000_DDR_TRAINING_ERR_FIRST     0xc72
+#define PHYTIUM_E2000_DDR_TRAINING_ERR_LAST      0xc79
+
+struct PhytiumE2000DDRState {
+    SysBusDevice parent_obj;
+
+    uint32_t regs[PHYTIUM_E2000_DDR_R_MAX];
+    RegisterInfo regs_info[PHYTIUM_E2000_DDR_R_MAX];
+    RegisterAccessInfo regs_access_info[PHYTIUM_E2000_DDR_R_MAX];
+};
+
+static uint64_t phytium_e2000_ddr_value_post_read(RegisterInfo *reg,
+                                                  uint64_t value)
+{
+    PhytiumE2000DDRState *s = PHYTIUM_E2000_DDR(reg->opaque);
+    uint32_t selector = s->regs[R_SELECTOR];
+
+    /*
+     * These fixed selectors are controller/PHY polls observed in the tested
+     * firmware. Return only the ready and completion bits that its loops
+     * require, without assigning behavior to the surrounding register bank.
+     */
+    switch (selector) {
+    case PHYTIUM_E2000_DDR_INDIRECT_SELECTOR(0xdc):
+        return BIT(0);
+    case PHYTIUM_E2000_DDR_SELECTOR(0x256):
+        return BIT(0) | BIT(25);
+    case PHYTIUM_E2000_DDR_INDIRECT_SELECTOR(0x76):
+        return BIT(0) | BIT(14) | BIT(27);
+    case PHYTIUM_E2000_DDR_SELECTOR(0x10f):
+        return 0x40U << 24;
+    case PHYTIUM_E2000_DDR_SELECTOR(0x229):
+        return BIT(0);
+    case PHYTIUM_E2000_DDR_SELECTOR(0x257):
+        return BIT(3);
+    case PHYTIUM_E2000_DDR_SELECTOR(0x255):
+        return BIT(16);
+    case PHYTIUM_E2000_DDR_SELECTOR(0x1d5):
+        return BIT(16) | BIT(24) | BIT(25);
+    default:
+        /*
+         * The training bank is read repeatedly for lane state and error
+         * summaries. Error selectors report no failure; the remaining values
+         * are deterministic so repeated sweeps see stable lane data.
+         */
+        if (selector >= PHYTIUM_E2000_DDR_TRAINING_SELECTOR(0) &&
+            selector < PHYTIUM_E2000_DDR_TRAINING_SELECTOR(0x1000)) {
+            uint32_t index = selector / sizeof(uint32_t) - 0x1000;
+
+            if (index >= PHYTIUM_E2000_DDR_TRAINING_ERR_FIRST &&
+                index <= PHYTIUM_E2000_DDR_TRAINING_ERR_LAST) {
+                return 0;
+            }
+            /*
+             * Preserve the masks and flag combinations checked by the PBF
+             * training algorithm for these recurring low-byte selectors.
+             */
+            if ((index & 0xff) == 0x34) {
+                return 0x00000fff;
+            }
+            if ((index & 0xff) == 0x38) {
+                return BIT(4) | BIT(5);
+            }
+            if ((index & 0xff) == 0x83) {
+                return 0;
+            }
+            if ((index & 0xff) == 0x3b) {
+                return 0x01234567U | BIT(26) | BIT(27);
+            }
+            return 0x01234567U;
+        }
+        /*
+         * Unclassified offsets retain ordinary register storage. This keeps
+         * setup writes readable without pretending they affect DDR timing.
+         */
+        return value;
+    }
+}
+
+static const MemoryRegionOps phytium_e2000_ddr_ops = {
+    .read = register_read_memory,
+    .write = register_write_memory,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void phytium_e2000_ddr_reset(DeviceState *dev)
+{
+    PhytiumE2000DDRState *s = PHYTIUM_E2000_DDR(dev);
+    int i;
+
+    for (i = 0; i < PHYTIUM_E2000_DDR_R_MAX; i++) {
+        register_reset(&s->regs_info[i]);
+    }
+}
+
+static void phytium_e2000_ddr_init(Object *obj)
+{
+    PhytiumE2000DDRState *s = PHYTIUM_E2000_DDR(obj);
+    RegisterInfoArray *reg_array;
+    int i;
+
+    /*
+     * Firmware may touch any word in this compact window before selecting a
+     * status source, so create registerinfo metadata for the full aperture and
+     * specialize only SELECTOR and VALUE.
+     */
+    for (i = 0; i < PHYTIUM_E2000_DDR_R_MAX; i++) {
+        s->regs_access_info[i].name = "DDR_STATUS";
+        s->regs_access_info[i].addr = i * sizeof(uint32_t);
+    }
+    s->regs_access_info[R_SELECTOR].name = "SELECTOR";
+    s->regs_access_info[R_VALUE].name = "VALUE";
+    s->regs_access_info[R_VALUE].ro = UINT32_MAX;
+    s->regs_access_info[R_VALUE].post_read =
+        phytium_e2000_ddr_value_post_read;
+
+    reg_array = register_init_block32(
+        DEVICE(obj), s->regs_access_info, PHYTIUM_E2000_DDR_R_MAX,
+        s->regs_info, s->regs, &phytium_e2000_ddr_ops, false,
+        PHYTIUM_E2000_DDR_MMIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &reg_array->mem);
+}
+
+static const VMStateDescription phytium_e2000_ddr_vmsd = {
+    .name = TYPE_PHYTIUM_E2000_DDR,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, PhytiumE2000DDRState,
+                             PHYTIUM_E2000_DDR_R_MAX),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void phytium_e2000_ddr_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd = &phytium_e2000_ddr_vmsd;
+    device_class_set_legacy_reset(dc, phytium_e2000_ddr_reset);
+}
+
+static const TypeInfo phytium_e2000_ddr_info = {
+    .name = TYPE_PHYTIUM_E2000_DDR,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(PhytiumE2000DDRState),
+    .instance_init = phytium_e2000_ddr_init,
+    .class_init = phytium_e2000_ddr_class_init,
+};
+
+static void phytium_e2000_ddr_register_types(void)
+{
+    type_register_static(&phytium_e2000_ddr_info);
+}
+
+type_init(phytium_e2000_ddr_register_types)
diff --git a/include/hw/misc/phytium_e2000_ddr.h b/include/hw/misc/phytium_e2000_ddr.h
new file mode 100644
index 0000000000..91dc661106
--- /dev/null
+++ b/include/hw/misc/phytium_e2000_ddr.h
@@ -0,0 +1,23 @@
+/*
+ * Phytium E2000 DDR training status
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_PHYTIUM_E2000_DDR_H
+#define HW_MISC_PHYTIUM_E2000_DDR_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_PHYTIUM_E2000_DDR "phytium-e2000-ddr"
+OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000DDRState, PHYTIUM_E2000_DDR)
+
+#define PHYTIUM_E2000_DDR_MMIO_SIZE 0x400
+
+#endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 10/33] hw/arm: phytium: Connect the Phytium E2000 DDR status
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (6 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 09/33] hw/misc: Add Phytium E2000 DDR status Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 11/33] hw/misc: Add Phytium E2000 MHU doorbell Bin Meng
                   ` (20 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

Instantiate the E2000 DDR status device at 0x32b33000 and map it over
the board-control placeholder. PBF and early U-Boot poll this selector
window while firmware coordinates DRAM initialization from EL3.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index c68a565330..f586184e51 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -25,6 +25,7 @@
 #include "hw/core/qdev-properties.h"
 #include "hw/intc/arm_gicv3_common.h"
 #include "hw/intc/arm_gicv3_its_common.h"
+#include "hw/misc/phytium_e2000_ddr.h"
 #include "hw/misc/unimp.h"
 #include "hw/net/cadence_gem.h"
 #include "hw/pci/pci.h"
@@ -48,6 +49,8 @@ OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000State, PHYTIUM_PI)
 #define PHYTIUM_E2000_NUM_UARTS       7
 #define PHYTIUM_E2000_NUM_GEMS        4
 
+#define PHYTIUM_E2000_DDR_STATUS_BASE 0x32b33000
+
 #define PHYTIUM_E2000_GTIMER_HZ       50000000
 
 enum {
@@ -445,6 +448,21 @@ static void phytium_e2000_create_mci(PhytiumE2000State *s, int index)
     phytium_e2000_attach_sd_card(DW_MCI(mci), index);
 }
 
+static void phytium_e2000_create_ddr_status(PhytiumE2000State *s)
+{
+    DeviceState *dev = qdev_new(TYPE_PHYTIUM_E2000_DDR);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    /*
+     * The selector window is embedded in the board-control aperture. It must
+     * override the placeholder because early U-Boot polls it while PBF is
+     * still coordinating DRAM initialization from EL3.
+     */
+    object_property_add_child(OBJECT(s), "ddr-status", OBJECT(dev));
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map_overlap(sbd, 0, PHYTIUM_E2000_DDR_STATUS_BASE, 2);
+}
+
 static void phytium_e2000_create_unimplemented(void)
 {
     /*
@@ -564,6 +582,8 @@ static void phytium_pi_init(MachineState *ms)
     phytium_e2000_create_cpus(s);
     phytium_e2000_create_gic(s);
 
+    phytium_e2000_create_ddr_status(s);
+
     for (i = 0; i < PHYTIUM_E2000_NUM_MCIS; i++) {
         phytium_e2000_create_mci(s, i);
     }
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 11/33] hw/misc: Add Phytium E2000 MHU doorbell
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (7 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 10/33] hw/arm: phytium: Connect the " Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 12/33] hw/arm: phytium: Connect the Phytium E2000 MHU Bin Meng
                   ` (19 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: qemu-arm

Add a model for the E2000 AP OS MHU channel used by vendor firmware.
Model its status, set and clear registers and complete nonzero
notifications by updating the shared SCMI mailbox.

The device also provides the mailbox seeding helper required by
the later PBR handoff. Board address mapping and SCP SRAM ownership
are intentionally left to a following machine-integration commit.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/misc/meson.build                 |   1 +
 hw/misc/phytium_e2000_mhu.c         | 185 ++++++++++++++++++++++++++++
 include/hw/misc/phytium_e2000_mhu.h |  25 ++++
 3 files changed, 211 insertions(+)
 create mode 100644 hw/misc/phytium_e2000_mhu.c
 create mode 100644 include/hw/misc/phytium_e2000_mhu.h

diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 898cf6ea87..a2ea120dd4 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -17,6 +17,7 @@ system_ss.add(when: 'CONFIG_INTEGRATOR_DEBUG', if_true: files('arm_integrator_de
 system_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
 system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
 system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_ddr.c'))
+system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_mhu.c'))
 
 system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
 
diff --git a/hw/misc/phytium_e2000_mhu.c b/hw/misc/phytium_e2000_mhu.c
new file mode 100644
index 0000000000..4ea23af900
--- /dev/null
+++ b/hw/misc/phytium_e2000_mhu.c
@@ -0,0 +1,185 @@
+/*
+ * Phytium E2000 MHU/SCMI doorbell
+ *
+ * This is a boot-oriented SCMI transport proxy. It acknowledges requests in
+ * shared SRAM so PBF can complete clock and platform setup; it is not a full
+ * SCMI protocol server.
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/phytium_e2000_mhu.h"
+
+#include "hw/core/register.h"
+#include "migration/vmstate.h"
+#include "qemu/module.h"
+#include "system/address-spaces.h"
+
+#define PHYTIUM_E2000_PBF_SCMI_MBOX_BASE  0x32a10400
+#define PHYTIUM_E2000_SCMI_STATUS_OFFSET  0x04
+#define PHYTIUM_E2000_SCMI_LEN_OFFSET     0x14
+#define PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET 0x1c
+#define PHYTIUM_E2000_SCMI_STATUS_FREE    BIT(0)
+
+/*
+ * The SDK defines AP OS status/set/clear at 0x100/0x108/0x110 within a
+ * channel. PBF selects the channel at MHU offset 0x200, producing the global
+ * offsets below. Writes to AP_OS_SET are the request notification.
+ */
+REG32(AP_OS_STAT, 0x300)
+REG32(AP_OS_SET, 0x308)
+REG32(AP_OS_CLR, 0x310)
+
+#define PHYTIUM_E2000_MHU_R_MAX \
+    (PHYTIUM_E2000_MHU_MMIO_SIZE / sizeof(uint32_t))
+
+struct PhytiumE2000MHUState {
+    SysBusDevice parent_obj;
+
+    uint32_t regs[PHYTIUM_E2000_MHU_R_MAX];
+    RegisterInfo regs_info[PHYTIUM_E2000_MHU_R_MAX];
+};
+
+static void phytium_e2000_mhu_complete_scmi(void)
+{
+    uint8_t buf[sizeof(uint32_t)];
+    uint32_t len;
+
+    /*
+     * Preserve the caller's message length, but reserve one status word for
+     * the minimal success response returned in the payload.
+     */
+    address_space_read(&address_space_memory,
+                       PHYTIUM_E2000_PBF_SCMI_MBOX_BASE +
+                       PHYTIUM_E2000_SCMI_LEN_OFFSET,
+                       MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
+    len = MAX(ldl_le_p(buf), (uint32_t)sizeof(uint32_t));
+
+    stl_le_p(buf, 0);
+    address_space_write(&address_space_memory,
+                        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE +
+                        PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET,
+                        MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
+    stl_le_p(buf, len);
+    address_space_write(&address_space_memory,
+                        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE +
+                        PHYTIUM_E2000_SCMI_LEN_OFFSET,
+                        MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
+    stl_le_p(buf, PHYTIUM_E2000_SCMI_STATUS_FREE);
+    /*
+     * Publish the free bit last. PBF polls this field as the ownership handoff
+     * and may consume the response immediately after observing it.
+     */
+    address_space_write(&address_space_memory,
+                        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE +
+                        PHYTIUM_E2000_SCMI_STATUS_OFFSET,
+                        MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
+}
+
+void phytium_e2000_mhu_seed_mailbox(void)
+{
+    /*
+     * PBR leaves the shared channel available before releasing PBF. Seed the
+     * same ownership and success state even before the first doorbell write.
+     */
+    phytium_e2000_mhu_complete_scmi();
+}
+
+static void phytium_e2000_mhu_doorbell_post_write(RegisterInfo *reg,
+                                                  uint64_t value)
+{
+    /*
+     * Complete requests synchronously because no separate SCP CPU executes in
+     * this model. Zero writes only update doorbell storage.
+     */
+    if (value) {
+        phytium_e2000_mhu_complete_scmi();
+    }
+}
+
+static const RegisterAccessInfo phytium_e2000_mhu_regs_info[] = {
+    /*
+     * The functional transport does not model an SCP interrupt line. STAT is
+     * therefore idle, SET completes the shared-memory transaction, and CLR
+     * remains ordinary register storage for the firmware acknowledge path.
+     */
+    { .name = "AP_OS_STAT", .addr = A_AP_OS_STAT,
+      .ro = UINT32_MAX },
+    { .name = "AP_OS_SET", .addr = A_AP_OS_SET,
+      .post_write = phytium_e2000_mhu_doorbell_post_write },
+    { .name = "AP_OS_CLR", .addr = A_AP_OS_CLR },
+};
+
+static const MemoryRegionOps phytium_e2000_mhu_ops = {
+    .read = register_read_memory,
+    .write = register_write_memory,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void phytium_e2000_mhu_reset(DeviceState *dev)
+{
+    PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(dev);
+    int i;
+
+    for (i = 0; i < ARRAY_SIZE(phytium_e2000_mhu_regs_info); i++) {
+        register_reset(&s->regs_info[
+            phytium_e2000_mhu_regs_info[i].addr / sizeof(uint32_t)]);
+    }
+}
+
+static void phytium_e2000_mhu_init(Object *obj)
+{
+    PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(obj);
+    RegisterInfoArray *reg_array;
+
+    reg_array = register_init_block32(
+        DEVICE(obj), phytium_e2000_mhu_regs_info,
+        ARRAY_SIZE(phytium_e2000_mhu_regs_info), s->regs_info, s->regs,
+        &phytium_e2000_mhu_ops, false, PHYTIUM_E2000_MHU_MMIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &reg_array->mem);
+}
+
+static const VMStateDescription phytium_e2000_mhu_vmsd = {
+    .name = TYPE_PHYTIUM_E2000_MHU,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, PhytiumE2000MHUState,
+                             PHYTIUM_E2000_MHU_R_MAX),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void phytium_e2000_mhu_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd = &phytium_e2000_mhu_vmsd;
+    device_class_set_legacy_reset(dc, phytium_e2000_mhu_reset);
+}
+
+static const TypeInfo phytium_e2000_mhu_info = {
+    .name = TYPE_PHYTIUM_E2000_MHU,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(PhytiumE2000MHUState),
+    .instance_init = phytium_e2000_mhu_init,
+    .class_init = phytium_e2000_mhu_class_init,
+};
+
+static void phytium_e2000_mhu_register_types(void)
+{
+    type_register_static(&phytium_e2000_mhu_info);
+}
+
+type_init(phytium_e2000_mhu_register_types)
diff --git a/include/hw/misc/phytium_e2000_mhu.h b/include/hw/misc/phytium_e2000_mhu.h
new file mode 100644
index 0000000000..0527d6fe8d
--- /dev/null
+++ b/include/hw/misc/phytium_e2000_mhu.h
@@ -0,0 +1,25 @@
+/*
+ * Phytium E2000 MHU/SCMI doorbell
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_PHYTIUM_E2000_MHU_H
+#define HW_MISC_PHYTIUM_E2000_MHU_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_PHYTIUM_E2000_MHU "phytium-e2000-mhu"
+OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000MHUState, PHYTIUM_E2000_MHU)
+
+#define PHYTIUM_E2000_MHU_MMIO_SIZE 0x1000
+
+void phytium_e2000_mhu_seed_mailbox(void);
+
+#endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 12/33] hw/arm: phytium: Connect the Phytium E2000 MHU
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (8 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 11/33] hw/misc: Add Phytium E2000 MHU doorbell Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 13/33] hw/ssi: Add Phytium E2000 QSPI controller Bin Meng
                   ` (18 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

Add the E2000 SCP SRAM window used for SCMI messages and map the MHU
doorbell into the board-control aperture.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index f586184e51..ff8dd3354b 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -26,6 +26,7 @@
 #include "hw/intc/arm_gicv3_common.h"
 #include "hw/intc/arm_gicv3_its_common.h"
 #include "hw/misc/phytium_e2000_ddr.h"
+#include "hw/misc/phytium_e2000_mhu.h"
 #include "hw/misc/unimp.h"
 #include "hw/net/cadence_gem.h"
 #include "hw/pci/pci.h"
@@ -49,6 +50,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000State, PHYTIUM_PI)
 #define PHYTIUM_E2000_NUM_UARTS       7
 #define PHYTIUM_E2000_NUM_GEMS        4
 
+#define PHYTIUM_E2000_MHU_BASE        0x32a00000
+#define PHYTIUM_E2000_SCP_SRAM_BASE   0x32a10000
+#define PHYTIUM_E2000_SCP_SRAM_SIZE   0x2000
 #define PHYTIUM_E2000_DDR_STATUS_BASE 0x32b33000
 
 #define PHYTIUM_E2000_GTIMER_HZ       50000000
@@ -90,6 +94,7 @@ struct PhytiumE2000State {
     DeviceState *gic;
     PhytiumE2000MciState *mci[PHYTIUM_E2000_NUM_MCIS];
     CadenceGEMState *gem[PHYTIUM_E2000_NUM_GEMS];
+    MemoryRegion scp_sram;
     MemoryRegion ram_low;
     MemoryRegion ram_high;
 };
@@ -463,6 +468,33 @@ static void phytium_e2000_create_ddr_status(PhytiumE2000State *s)
     sysbus_mmio_map_overlap(sbd, 0, PHYTIUM_E2000_DDR_STATUS_BASE, 2);
 }
 
+static void phytium_e2000_create_scp_sram(PhytiumE2000State *s)
+{
+    /*
+     * PBF exchanges SCMI messages and platform parameters through this SCP
+     * SRAM window. Map writable RAM over the board-control placeholder before
+     * the MHU doorbell starts completing requests in it.
+     */
+    memory_region_init_ram(&s->scp_sram, NULL, "phytium-e2000.scp-sram",
+        PHYTIUM_E2000_SCP_SRAM_SIZE, &error_fatal);
+    memory_region_add_subregion_overlap(get_system_memory(),
+        PHYTIUM_E2000_SCP_SRAM_BASE, &s->scp_sram, 1);
+}
+
+static void phytium_e2000_create_mhu(PhytiumE2000State *s)
+{
+    DeviceState *dev = qdev_new(TYPE_PHYTIUM_E2000_MHU);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    /*
+     * MHU is the notification side of the SCMI transport. The message body
+     * remains in SCP SRAM, so this device only owns the doorbell aperture.
+     */
+    object_property_add_child(OBJECT(s), "mhu", OBJECT(dev));
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map_overlap(sbd, 0, PHYTIUM_E2000_MHU_BASE, 2);
+}
+
 static void phytium_e2000_create_unimplemented(void)
 {
     /*
@@ -582,6 +614,8 @@ static void phytium_pi_init(MachineState *ms)
     phytium_e2000_create_cpus(s);
     phytium_e2000_create_gic(s);
 
+    phytium_e2000_create_scp_sram(s);
+    phytium_e2000_create_mhu(s);
     phytium_e2000_create_ddr_status(s);
 
     for (i = 0; i < PHYTIUM_E2000_NUM_MCIS; i++) {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 13/33] hw/ssi: Add Phytium E2000 QSPI controller
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (9 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 12/33] hw/arm: phytium: Connect the Phytium E2000 MHU Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 14/33] hw/arm: phytium: Connect the " Bin Meng
                   ` (17 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Alistair Francis, Paolo Bonzini, qemu-arm

Phytium E2000Q integrates a QSPI controller which seems to be an
in-house IP. This adds a model for it with command-port and
direct-map paths, and exposes an SSI bus and chip-select output.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/ssi/Kconfig                |   5 +
 hw/ssi/meson.build            |   1 +
 hw/ssi/phytium_qspi.c         | 398 ++++++++++++++++++++++++++++++++++
 include/hw/ssi/phytium_qspi.h |  23 ++
 4 files changed, 427 insertions(+)
 create mode 100644 hw/ssi/phytium_qspi.c
 create mode 100644 include/hw/ssi/phytium_qspi.h

diff --git a/hw/ssi/Kconfig b/hw/ssi/Kconfig
index 1bd56463c1..202c81c5ac 100644
--- a/hw/ssi/Kconfig
+++ b/hw/ssi/Kconfig
@@ -9,6 +9,11 @@ config SIFIVE_SPI
 config SSI
     bool
 
+config PHYTIUM_E2000_QSPI
+    bool
+    select REGISTER
+    select SSI
+
 config XILINX_SPI
     bool
     select SSI
diff --git a/hw/ssi/meson.build b/hw/ssi/meson.build
index 6afb1ea200..c76945ddf4 100644
--- a/hw/ssi/meson.build
+++ b/hw/ssi/meson.build
@@ -13,3 +13,4 @@ system_ss.add(when: 'CONFIG_IMX', if_true: files('imx_spi.c'))
 system_ss.add(when: 'CONFIG_IBEX', if_true: files('ibex_spi_host.c'))
 system_ss.add(when: 'CONFIG_BCM2835_SPI', if_true: files('bcm2835_spi.c'))
 system_ss.add(when: 'CONFIG_PNV_SPI', if_true: files('pnv_spi.c'))
+system_ss.add(when: 'CONFIG_PHYTIUM_E2000_QSPI', if_true: files('phytium_qspi.c'))
diff --git a/hw/ssi/phytium_qspi.c b/hw/ssi/phytium_qspi.c
new file mode 100644
index 0000000000..da7dce1d46
--- /dev/null
+++ b/hw/ssi/phytium_qspi.c
@@ -0,0 +1,398 @@
+/*
+ * Phytium E2000 QSPI controller
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/ssi/phytium_qspi.h"
+
+#include "hw/core/register.h"
+#include "hw/core/irq.h"
+#include "hw/ssi/ssi.h"
+#include "migration/vmstate.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+REG32(FLASH_CAP, 0x00)
+REG32(RD_CFG, 0x04)
+    FIELD(RD_CFG, CMD, 24, 8)
+    FIELD(RD_CFG, ADDR_SEL, 19, 1)
+    FIELD(RD_CFG, LATENCY, 18, 1)
+    FIELD(RD_CFG, DUMMY, 4, 5)
+REG32(WR_CFG, 0x08)
+    FIELD(WR_CFG, CMD, 24, 8)
+    FIELD(WR_CFG, ADDR_SEL, 4, 1)
+    FIELD(WR_CFG, MODE, 3, 1)
+REG32(FLUSH, 0x0c)
+REG32(CMD_PORT, 0x10)
+    FIELD(CMD_PORT, CMD, 24, 8)
+    FIELD(CMD_PORT, CS, 19, 2)
+    FIELD(CMD_PORT, CMD_ADDR, 15, 1)
+    FIELD(CMD_PORT, LATENCY, 14, 1)
+    FIELD(CMD_PORT, DATA_XFER, 13, 1)
+    FIELD(CMD_PORT, ADDR_SEL, 12, 1)
+    FIELD(CMD_PORT, DUMMY, 7, 5)
+    FIELD(CMD_PORT, RW_NUM, 3, 3)
+REG32(ADDR_PORT, 0x14)
+REG32(HD_PORT, 0x18)
+REG32(LD_PORT, 0x1c)
+REG32(FUN_SET, 0x20)
+REG32(WIP, 0x24)
+REG32(WP, 0x28)
+REG32(MODE, 0x2c)
+REG32(LEGACY_CTRL, 0x30)
+
+#define PHYTIUM_E2000_QSPI_R_MAX     (A_LEGACY_CTRL / sizeof(uint32_t) + 1)
+#define PHYTIUM_E2000_QSPI_PAGE_SIZE 256
+
+struct PhytiumE2000QSPIState {
+    SysBusDevice parent_obj;
+
+    uint32_t regs[PHYTIUM_E2000_QSPI_R_MAX];
+    RegisterInfo regs_info[PHYTIUM_E2000_QSPI_R_MAX];
+    MemoryRegion direct_mr;
+    SSIBus *spi;
+    qemu_irq cs;
+    uint8_t write_buffer[PHYTIUM_E2000_QSPI_PAGE_SIZE];
+    uint32_t write_address;
+    uint16_t write_len;
+    bool write_pending;
+    bool resetting;
+};
+
+static void phytium_e2000_qspi_select(PhytiumE2000QSPIState *s)
+{
+    qemu_irq_lower(s->cs);
+}
+
+static void phytium_e2000_qspi_deselect(PhytiumE2000QSPIState *s)
+{
+    qemu_irq_raise(s->cs);
+}
+
+static void phytium_e2000_qspi_send_address(PhytiumE2000QSPIState *s,
+                                            uint32_t address,
+                                            unsigned int length)
+{
+    int shift;
+
+    for (shift = (length - 1) * 8; shift >= 0; shift -= 8) {
+        ssi_transfer(s->spi, extract32(address, shift, 8));
+    }
+}
+
+static void phytium_e2000_qspi_send_dummy(PhytiumE2000QSPIState *s,
+                                          unsigned int cycles)
+{
+    unsigned int i;
+
+    for (i = 0; i < DIV_ROUND_UP(cycles, 8); i++) {
+        ssi_transfer(s->spi, 0);
+    }
+}
+
+static void phytium_e2000_qspi_exec_port(PhytiumE2000QSPIState *s)
+{
+    uint32_t cfg = s->regs[R_CMD_PORT];
+    uint64_t tx = s->regs[R_LD_PORT] |
+                  ((uint64_t)s->regs[R_HD_PORT] << 32);
+    uint64_t rx = 0;
+    unsigned int cs = FIELD_EX32(cfg, CMD_PORT, CS);
+    unsigned int length;
+    unsigned int i;
+
+    if (cs != 0) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: command selects unwired CS%u\n",
+                      TYPE_PHYTIUM_E2000_QSPI, cs);
+        s->regs[R_LD_PORT] = 0;
+        s->regs[R_HD_PORT] = 0;
+        return;
+    }
+
+    phytium_e2000_qspi_select(s);
+    ssi_transfer(s->spi, FIELD_EX32(cfg, CMD_PORT, CMD));
+
+    if (FIELD_EX32(cfg, CMD_PORT, CMD_ADDR)) {
+        length = FIELD_EX32(cfg, CMD_PORT, ADDR_SEL) ? 4 : 3;
+        phytium_e2000_qspi_send_address(s, s->regs[R_ADDR_PORT], length);
+    }
+
+    if (FIELD_EX32(cfg, CMD_PORT, LATENCY)) {
+        phytium_e2000_qspi_send_dummy(
+            s, FIELD_EX32(cfg, CMD_PORT, DUMMY) + 1);
+    }
+
+    if (FIELD_EX32(cfg, CMD_PORT, DATA_XFER)) {
+        length = FIELD_EX32(cfg, CMD_PORT, RW_NUM) + 1;
+        for (i = 0; i < length; i++) {
+            rx = deposit64(rx, i * 8, 8,
+                           ssi_transfer(s->spi, extract64(tx, i * 8, 8)));
+        }
+    }
+
+    phytium_e2000_qspi_deselect(s);
+    s->regs[R_LD_PORT] = extract64(rx, 0, 32);
+    s->regs[R_HD_PORT] = extract64(rx, 32, 32);
+}
+
+static void phytium_e2000_qspi_ld_post_write(RegisterInfo *reg,
+                                             uint64_t value)
+{
+    PhytiumE2000QSPIState *s = PHYTIUM_E2000_QSPI(reg->opaque);
+
+    /*
+     * LD_PORT is the final staging write and commits the command assembled in
+     * CMD_PORT, ADDR_PORT, HD_PORT, and LD_PORT. register_reset() invokes the
+     * same callback, so suppress SPI side effects while installing defaults.
+     */
+    if (!s->resetting) {
+        phytium_e2000_qspi_exec_port(s);
+    }
+}
+
+static void phytium_e2000_qspi_flush_write_buffer(PhytiumE2000QSPIState *s)
+{
+    uint32_t cfg = s->regs[R_WR_CFG];
+    unsigned int address_len;
+    unsigned int i;
+
+    if (!s->write_pending) {
+        return;
+    }
+    if (!FIELD_EX32(cfg, WR_CFG, MODE)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: FLUSH with direct writes disabled\n",
+                      TYPE_PHYTIUM_E2000_QSPI);
+        return;
+    }
+
+    phytium_e2000_qspi_select(s);
+    ssi_transfer(s->spi, FIELD_EX32(cfg, WR_CFG, CMD));
+    address_len = FIELD_EX32(cfg, WR_CFG, ADDR_SEL) ? 4 : 3;
+    phytium_e2000_qspi_send_address(s, s->write_address, address_len);
+    for (i = 0; i < s->write_len; i++) {
+        ssi_transfer(s->spi, s->write_buffer[i]);
+    }
+    phytium_e2000_qspi_deselect(s);
+
+    s->write_pending = false;
+    s->write_address = 0;
+    s->write_len = 0;
+}
+
+static uint64_t phytium_e2000_qspi_flush_pre_write(RegisterInfo *reg,
+                                                   uint64_t value)
+{
+    PhytiumE2000QSPIState *s = PHYTIUM_E2000_QSPI(reg->opaque);
+
+    if (value & BIT(0)) {
+        phytium_e2000_qspi_flush_write_buffer(s);
+    }
+    return 0;
+}
+
+static const RegisterAccessInfo phytium_e2000_qspi_regs_info[] = {
+    { .name = "FLASH_CAP", .addr = A_FLASH_CAP,
+      .rsvd = 0xfffc0000 },
+    { .name = "RD_CFG", .addr = A_RD_CFG },
+    { .name = "WR_CFG", .addr = A_WR_CFG,
+      .rsvd = 0x00fffc00 },
+    { .name = "FLUSH", .addr = A_FLUSH,
+      .rsvd = 0xfffffffe,
+      .pre_write = phytium_e2000_qspi_flush_pre_write },
+    { .name = "CMD_PORT", .addr = A_CMD_PORT,
+      .rsvd = BIT(23) },
+    { .name = "ADDR_PORT", .addr = A_ADDR_PORT },
+    { .name = "HD_PORT", .addr = A_HD_PORT },
+    { .name = "LD_PORT", .addr = A_LD_PORT,
+      .post_write = phytium_e2000_qspi_ld_post_write },
+    { .name = "FUN_SET", .addr = A_FUN_SET },
+    { .name = "WIP", .addr = A_WIP,
+      .rsvd = 0x00ffffe0 },
+    { .name = "WP", .addr = A_WP,
+      .rsvd = 0xfffc0000 },
+    { .name = "MODE", .addr = A_MODE,
+      .rsvd = 0xffff0000 },
+    { .name = "LEGACY_CTRL", .addr = A_LEGACY_CTRL,
+      .rsvd = 0xfffffeff },
+};
+
+static const MemoryRegionOps phytium_e2000_qspi_regs_ops = {
+    .read = register_read_memory,
+    .write = register_write_memory,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static uint64_t phytium_e2000_qspi_direct_read(void *opaque, hwaddr address,
+                                               unsigned int size)
+{
+    PhytiumE2000QSPIState *s = opaque;
+    uint32_t cfg = s->regs[R_RD_CFG];
+    uint64_t value = 0;
+    unsigned int address_len;
+    unsigned int i;
+
+    phytium_e2000_qspi_select(s);
+    ssi_transfer(s->spi, FIELD_EX32(cfg, RD_CFG, CMD));
+    address_len = FIELD_EX32(cfg, RD_CFG, ADDR_SEL) ? 4 : 3;
+    phytium_e2000_qspi_send_address(s, address, address_len);
+
+    if (FIELD_EX32(cfg, RD_CFG, LATENCY)) {
+        phytium_e2000_qspi_send_dummy(
+            s, FIELD_EX32(cfg, RD_CFG, DUMMY) + 1);
+    }
+
+    for (i = 0; i < size; i++) {
+        value = deposit64(value, i * 8, 8, ssi_transfer(s->spi, 0));
+    }
+    phytium_e2000_qspi_deselect(s);
+
+    return value;
+}
+
+static void phytium_e2000_qspi_direct_write(void *opaque, hwaddr address,
+                                            uint64_t value,
+                                            unsigned int size)
+{
+    PhytiumE2000QSPIState *s = opaque;
+    uint32_t cfg = s->regs[R_WR_CFG];
+    unsigned int i;
+
+    if (!FIELD_EX32(cfg, WR_CFG, MODE)) {
+        return;
+    }
+
+    /*
+     * The direct aperture accepts multiple CPU stores for one page program.
+     * Buffer them until FLUSH supplies the transaction boundary required by
+     * the flash command.
+     */
+    if (!s->write_pending) {
+        if ((address & ~(PHYTIUM_E2000_QSPI_PAGE_SIZE - 1)) !=
+            ((address + size - 1) &
+             ~(PHYTIUM_E2000_QSPI_PAGE_SIZE - 1))) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: direct write crosses a flash page\n",
+                          TYPE_PHYTIUM_E2000_QSPI);
+            return;
+        }
+        s->write_pending = true;
+        s->write_address = address;
+        s->write_len = 0;
+    } else if (address != s->write_address + s->write_len ||
+               (s->write_address &
+                (PHYTIUM_E2000_QSPI_PAGE_SIZE - 1)) +
+               s->write_len + size > PHYTIUM_E2000_QSPI_PAGE_SIZE) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: non-contiguous direct writes before FLUSH\n",
+                      TYPE_PHYTIUM_E2000_QSPI);
+        return;
+    }
+
+    for (i = 0; i < size; i++) {
+        s->write_buffer[s->write_len++] = extract64(value, i * 8, 8);
+    }
+}
+
+static const MemoryRegionOps phytium_e2000_qspi_direct_ops = {
+    .read = phytium_e2000_qspi_direct_read,
+    .write = phytium_e2000_qspi_direct_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        .min_access_size = 1,
+        .max_access_size = 8,
+        .unaligned = true,
+    },
+};
+
+static void phytium_e2000_qspi_reset(DeviceState *dev)
+{
+    PhytiumE2000QSPIState *s = PHYTIUM_E2000_QSPI(dev);
+    size_t i;
+
+    s->resetting = true;
+    for (i = 0; i < ARRAY_SIZE(phytium_e2000_qspi_regs_info); i++) {
+        register_reset(&s->regs_info[
+            phytium_e2000_qspi_regs_info[i].addr / sizeof(uint32_t)]);
+    }
+    memset(s->write_buffer, 0, sizeof(s->write_buffer));
+    s->write_address = 0;
+    s->write_len = 0;
+    s->write_pending = false;
+    s->resetting = false;
+    phytium_e2000_qspi_deselect(s);
+}
+
+static void phytium_e2000_qspi_init(Object *obj)
+{
+    PhytiumE2000QSPIState *s = PHYTIUM_E2000_QSPI(obj);
+    RegisterInfoArray *reg_array;
+
+    s->spi = ssi_create_bus(DEVICE(obj), "spi");
+    qdev_init_gpio_out_named(DEVICE(obj), &s->cs, "cs", 1);
+
+    reg_array = register_init_block32(
+        DEVICE(obj), phytium_e2000_qspi_regs_info,
+        ARRAY_SIZE(phytium_e2000_qspi_regs_info), s->regs_info, s->regs,
+        &phytium_e2000_qspi_regs_ops, false,
+        PHYTIUM_E2000_QSPI_REG_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &reg_array->mem);
+
+    memory_region_init_io(&s->direct_mr, obj,
+                          &phytium_e2000_qspi_direct_ops, s,
+                          TYPE_PHYTIUM_E2000_QSPI ".direct",
+                          PHYTIUM_E2000_QSPI_DIRECT_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->direct_mr);
+}
+
+static const VMStateDescription phytium_e2000_qspi_vmsd = {
+    .name = TYPE_PHYTIUM_E2000_QSPI,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, PhytiumE2000QSPIState,
+                             PHYTIUM_E2000_QSPI_R_MAX),
+        VMSTATE_UINT8_ARRAY(write_buffer, PhytiumE2000QSPIState,
+                            PHYTIUM_E2000_QSPI_PAGE_SIZE),
+        VMSTATE_UINT32(write_address, PhytiumE2000QSPIState),
+        VMSTATE_UINT16(write_len, PhytiumE2000QSPIState),
+        VMSTATE_BOOL(write_pending, PhytiumE2000QSPIState),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void phytium_e2000_qspi_class_init(ObjectClass *klass,
+                                          const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+
+    dc->vmsd = &phytium_e2000_qspi_vmsd;
+    device_class_set_legacy_reset(dc, phytium_e2000_qspi_reset);
+}
+
+static const TypeInfo phytium_e2000_qspi_info = {
+    .name = TYPE_PHYTIUM_E2000_QSPI,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(PhytiumE2000QSPIState),
+    .instance_init = phytium_e2000_qspi_init,
+    .class_init = phytium_e2000_qspi_class_init,
+};
+
+static void phytium_e2000_qspi_register_types(void)
+{
+    type_register_static(&phytium_e2000_qspi_info);
+}
+
+type_init(phytium_e2000_qspi_register_types)
diff --git a/include/hw/ssi/phytium_qspi.h b/include/hw/ssi/phytium_qspi.h
new file mode 100644
index 0000000000..c6534af16d
--- /dev/null
+++ b/include/hw/ssi/phytium_qspi.h
@@ -0,0 +1,23 @@
+/*
+ * Phytium E2000 QSPI controller
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_SSI_PHYTIUM_QSPI_H
+#define HW_SSI_PHYTIUM_QSPI_H
+
+#include "hw/core/sysbus.h"
+
+#define TYPE_PHYTIUM_E2000_QSPI "phytium-e2000-qspi"
+OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000QSPIState, PHYTIUM_E2000_QSPI)
+
+#define PHYTIUM_E2000_QSPI_REG_SIZE    0x1000
+#define PHYTIUM_E2000_QSPI_DIRECT_SIZE 0x10000000
+
+#endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 14/33] hw/arm: phytium: Connect the Phytium E2000 QSPI controller
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (10 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 13/33] hw/ssi: Add Phytium E2000 QSPI controller Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 15/33] hw/misc: Add Phytium E2000 PBR model Bin Meng
                   ` (16 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Instantiate QSPI0 at its command and direct-map windows so accesses
from E2000 firmware reach the controller model.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |  1 +
 hw/arm/phytium_e2000.c | 20 ++++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 08128232ca..891d09e86b 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -136,6 +136,7 @@ config PHYTIUM_E2000
     select DW_MCI
     select PCI_EXPRESS
     select PCI_EXPRESS_GENERIC_BRIDGE
+    select PHYTIUM_E2000_QSPI
     select PL011
     select SD
     select UNIMP
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index ff8dd3354b..c6d789bb04 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -33,6 +33,7 @@
 #include "hw/pci-host/gpex.h"
 #include "hw/sd/phytium_e2000_mci.h"
 #include "hw/sd/sd.h"
+#include "hw/ssi/phytium_qspi.h"
 #include "net/net.h"
 #include "qobject/qlist.h"
 #include "qom/object.h"
@@ -58,9 +59,11 @@ OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000State, PHYTIUM_PI)
 #define PHYTIUM_E2000_GTIMER_HZ       50000000
 
 enum {
+    PHYTIUM_E2000_QSPI_DIRECT,
     PHYTIUM_E2000_LOW_PERIPH,
     PHYTIUM_E2000_MCI0,
     PHYTIUM_E2000_MCI1,
+    PHYTIUM_E2000_QSPI_REGS,
     PHYTIUM_E2000_UART0,
     PHYTIUM_E2000_UART1,
     PHYTIUM_E2000_UART2,
@@ -92,6 +95,7 @@ struct PhytiumE2000State {
     MachineState parent;
     struct arm_boot_info bootinfo;
     DeviceState *gic;
+    DeviceState *qspi;
     PhytiumE2000MciState *mci[PHYTIUM_E2000_NUM_MCIS];
     CadenceGEMState *gem[PHYTIUM_E2000_NUM_GEMS];
     MemoryRegion scp_sram;
@@ -105,9 +109,11 @@ struct PhytiumE2000State {
  * handoff data and IACC is the fixed execution window for system firmware.
  */
 static const MemMapEntry phytium_e2000_memmap[] = {
+    [PHYTIUM_E2000_QSPI_DIRECT] =    { 0x00000000, 0x10000000 },
     [PHYTIUM_E2000_LOW_PERIPH] =     { 0x28000000, 0x00100000 },
     [PHYTIUM_E2000_MCI0] =           { 0x28000000, 0x00001000 },
     [PHYTIUM_E2000_MCI1] =           { 0x28001000, 0x00001000 },
+    [PHYTIUM_E2000_QSPI_REGS] =      { 0x28008000, 0x00001000 },
     [PHYTIUM_E2000_UART0] =          { 0x2800c000, 0x00001000 },
     [PHYTIUM_E2000_UART1] =          { 0x2800d000, 0x00001000 },
     [PHYTIUM_E2000_UART2] =          { 0x2800e000, 0x00001000 },
@@ -453,6 +459,18 @@ static void phytium_e2000_create_mci(PhytiumE2000State *s, int index)
     phytium_e2000_attach_sd_card(DW_MCI(mci), index);
 }
 
+static void phytium_e2000_create_qspi(PhytiumE2000State *s)
+{
+    DeviceState *controller = qdev_new(TYPE_PHYTIUM_E2000_QSPI);
+
+    s->qspi = controller;
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(controller), &error_fatal);
+    sysbus_mmio_map_overlap(SYS_BUS_DEVICE(controller), 0,
+        phytium_e2000_memmap[PHYTIUM_E2000_QSPI_REGS].base, 2);
+    sysbus_mmio_map_overlap(SYS_BUS_DEVICE(controller), 1,
+        phytium_e2000_memmap[PHYTIUM_E2000_QSPI_DIRECT].base, 2);
+}
+
 static void phytium_e2000_create_ddr_status(PhytiumE2000State *s)
 {
     DeviceState *dev = qdev_new(TYPE_PHYTIUM_E2000_DDR);
@@ -611,6 +629,8 @@ static void phytium_pi_init(MachineState *ms)
     phytium_e2000_create_ram(s);
     phytium_e2000_create_unimplemented();
 
+    phytium_e2000_create_qspi(s);
+
     phytium_e2000_create_cpus(s);
     phytium_e2000_create_gic(s);
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 15/33] hw/misc: Add Phytium E2000 PBR model
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (11 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 14/33] hw/arm: phytium: Connect the " Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 16/33] hw/arm: phytium: Integrate the Phytium E2000 PBR Bin Meng
                   ` (15 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: qemu-arm

Model the E2000 Phytium Boot ROM as a standalone device. Parse the
selected fip-all.bin medium and validate its PBF, TF-A FIP, and
platform parameter records.

Use the common PBF parameter header to validate exact E2000 PLL, PCIe,
DDR/MCU, and COMMON records, their declared sizes, and slot bounds.
Stage the opaque records without interpreting service-specific
payloads or selecting handoff addresses from their versions.

Locate the embedded TF-A FIP and memmap drivers from their
data-structure relationships. Validate the documented vendor-private
handoff relations instead of maintaining unexplained firmware-version
address tables.

Expose the board strap as a PBR boot-mode property and derive the
firmware-visible boot-media status inside the device. Stage IACC,
relocate BL1, build the firmware handoff state, and release the
primary CPU selected by the firmware. Keep status, boot SRAM, IACC,
reset sequencing, and migration state inside the device so board
code only supplies topology and the selected boot backend.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/misc/meson.build                 |    1 +
 hw/misc/phytium_e2000_pbr.c         | 1221 +++++++++++++++++++++++++++
 include/hw/misc/phytium_e2000_pbr.h |   55 ++
 3 files changed, 1277 insertions(+)
 create mode 100644 hw/misc/phytium_e2000_pbr.c
 create mode 100644 include/hw/misc/phytium_e2000_pbr.h

diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index a2ea120dd4..8db55e3ef9 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -18,6 +18,7 @@ system_ss.add(when: 'CONFIG_A9SCU', if_true: files('a9scu.c'))
 system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
 system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_ddr.c'))
 system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_mhu.c'))
+system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_pbr.c'))
 
 system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
 
diff --git a/hw/misc/phytium_e2000_pbr.c b/hw/misc/phytium_e2000_pbr.c
new file mode 100644
index 0000000000..4485f073e1
--- /dev/null
+++ b/hw/misc/phytium_e2000_pbr.c
@@ -0,0 +1,1221 @@
+/*
+ * Phytium E2000 PBR (Phytium Boot ROM) model
+ *
+ * The on-chip PBR does not execute Arm instructions in QEMU. This device
+ * reproduces its boot-medium loading and handoff behavior before releasing
+ * PBF on the primary CPU selected by the firmware container.
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/phytium_e2000_pbr.h"
+
+#include "hw/core/cpu.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/register.h"
+#include "hw/misc/phytium_e2000_mhu.h"
+#include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/cutils.h"
+#include "qemu/host-utils.h"
+#include "qemu/module.h"
+#include "qemu/units.h"
+#include "system/address-spaces.h"
+#include "system/block-backend-io.h"
+
+REG32(RESET_SOURCE, 0x00)
+REG32(SCP_READY, 0x04)
+REG32(BOOT_MEDIA, 0x08)
+REG32(ETH_TRAINING_STATUS, 0x60)
+
+#define PHYTIUM_E2000_PBR_R_MAX \
+    (PHYTIUM_E2000_PBR_MMIO_SIZE / sizeof(uint32_t))
+
+#define PHYTIUM_E2000_FIP_ALL_HEADER_SIZE           0x000f4c10
+#define PHYTIUM_E2000_FIP_ALL_PAYLOAD_INFO_OFFSET   0x000f4c00
+#define PHYTIUM_E2000_FIP_ALL_MIN_SIZE              0x00100000
+#define PHYTIUM_E2000_FIP_ALL_MAX_SIZE              (4 * MiB)
+#define PHYTIUM_E2000_TFA_FIP_OFFSET                0x000d0000
+#define PHYTIUM_E2000_TFA_FIP_MAGIC                 0xaa640001
+#define PHYTIUM_E2000_TFA_FIP_HEADER_SIZE           16
+#define PHYTIUM_E2000_TFA_FIP_UUID_SIZE             16
+#define PHYTIUM_E2000_TFA_FIP_ENTRY_SIZE            40
+#define PHYTIUM_E2000_TFA_FIP_MAX_ENTRIES           64
+
+#define PHYTIUM_E2000_PBF_PARAMETER_OFFSET          0x000f4000
+#define PHYTIUM_E2000_PBF_MAGIC                     0x54460000
+#define PHYTIUM_E2000_PBF_PRIMARY_CORE_OFFSET       0x1c
+#define PHYTIUM_E2000_BL1_FLASH_OFFSET              0x00040000
+#define PHYTIUM_E2000_BL1_SIZE                      0x00090000
+#define PHYTIUM_E2000_PBR_BL1_RUNTIME_BASE          0xf8c40000
+
+#define PHYTIUM_E2000_PBR_ROOT_OFFSET 0x00000f00
+#define PHYTIUM_E2000_PBR_ROOT        \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x1000)
+#define PHYTIUM_E2000_PBR_PARAM_NODE  \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x10a0)
+#define PHYTIUM_E2000_PBR_PARAM_SLOT  \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x1200)
+#define PHYTIUM_E2000_PBR_PLL_DESC    \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x1220)
+#define PHYTIUM_E2000_PBR_MCU_DESC    \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x1240)
+#define PHYTIUM_E2000_PBR_PCIE_DESC   \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x1260)
+#define PHYTIUM_E2000_PBR_COMMON_DESC \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x1280)
+
+#define PHYTIUM_E2000_PBR_SCP_PARAM_BASE  0x32a10c00
+#define PHYTIUM_E2000_PBR_PLL_PAYLOAD     \
+    (PHYTIUM_E2000_PBR_SCP_PARAM_BASE + 0x000)
+#define PHYTIUM_E2000_PBR_MCU_PAYLOAD     \
+    (PHYTIUM_E2000_PBR_SCP_PARAM_BASE + 0x100)
+#define PHYTIUM_E2000_PBR_PCIE_PAYLOAD    \
+    (PHYTIUM_E2000_PBR_SCP_PARAM_BASE + 0x200)
+#define PHYTIUM_E2000_PBR_COMMON_PAYLOAD  \
+    (PHYTIUM_E2000_PBR_SCP_PARAM_BASE + 0x300)
+#define PHYTIUM_E2000_PBR_PARAM_COPY      0xf1801300
+#define PHYTIUM_E2000_PBR_PARAM_COPY_SIZE 0x20
+
+#define PHYTIUM_E2000_PARAM_BASE      0x000f5000
+#define PHYTIUM_E2000_PARAM_SLOT_SIZE 0x100
+#define PHYTIUM_PBF_PARAM_SIZE_OFFSET 0x08
+#define PHYTIUM_PBF_PARAM_HEADER_SIZE 0x10
+
+typedef enum PhytiumE2000PbfParameter {
+    PHYTIUM_E2000_PBF_PARAM_PLL,
+    PHYTIUM_E2000_PBF_PARAM_DDR,
+    PHYTIUM_E2000_PBF_PARAM_PCIE,
+    PHYTIUM_E2000_PBF_PARAM_COMMON,
+    PHYTIUM_E2000_PBF_PARAM_COUNT,
+} PhytiumE2000PbfParameter;
+
+typedef struct PhytiumE2000PbfParameterSpec {
+    const char *name;
+    size_t source_offset;
+    hwaddr destination;
+    uint32_t magic;
+    uint32_t minimum_size;
+    uint32_t slot_size;
+} PhytiumE2000PbfParameterSpec;
+
+/*
+ * The PBF interface specification publishes the common parameter-record
+ * header, but not this E2000 fip-all.bin placement or the PBR-owned SCP SRAM
+ * handoff.  Keep the documented record shape separate from this
+ * firmware-derived E2000 profile.
+ *
+ * The specification's older PLL/PCIe/DDR magics are 0x54460010,
+ * 0x54460011, and 0x54460014.  Every inspected E2000 image instead uses the
+ * exact values below.  Do not infer compatibility from the common prefix:
+ * recognizing a record class does not prove that this PBF accepts its
+ * payload format.
+ */
+static const PhytiumE2000PbfParameterSpec
+phytium_e2000_pbf_parameters[PHYTIUM_E2000_PBF_PARAM_COUNT] = {
+    [PHYTIUM_E2000_PBF_PARAM_PLL] = {
+        .name = "PLL",
+        .source_offset = PHYTIUM_E2000_PARAM_BASE,
+        .destination = PHYTIUM_E2000_PBR_PLL_PAYLOAD,
+        .magic = 0x54460020,
+        .minimum_size = PHYTIUM_PBF_PARAM_HEADER_SIZE,
+        .slot_size = PHYTIUM_E2000_PARAM_SLOT_SIZE,
+    },
+    [PHYTIUM_E2000_PBF_PARAM_DDR] = {
+        /* MCU is Memory Controller Unit, not a microcontroller */
+        .name = "DDR/MCU",
+        .source_offset = PHYTIUM_E2000_PARAM_BASE + 0x300,
+        .destination = PHYTIUM_E2000_PBR_MCU_PAYLOAD,
+        .magic = 0x54460024,
+        .minimum_size = PHYTIUM_PBF_PARAM_HEADER_SIZE,
+        .slot_size = PHYTIUM_E2000_PARAM_SLOT_SIZE,
+    },
+    [PHYTIUM_E2000_PBF_PARAM_PCIE] = {
+        .name = "PCIe",
+        .source_offset = PHYTIUM_E2000_PARAM_BASE + 0x100,
+        .destination = PHYTIUM_E2000_PBR_PCIE_PAYLOAD,
+        .magic = 0x54460021,
+        .minimum_size = PHYTIUM_PBF_PARAM_HEADER_SIZE,
+        .slot_size = PHYTIUM_E2000_PARAM_SLOT_SIZE,
+    },
+    [PHYTIUM_E2000_PBF_PARAM_COMMON] = {
+        .name = "COMMON",
+        .source_offset = PHYTIUM_E2000_PARAM_BASE + 0x200,
+        .destination = PHYTIUM_E2000_PBR_COMMON_PAYLOAD,
+        .magic = 0x54460013,
+        .minimum_size = PHYTIUM_PBF_PARAM_HEADER_SIZE,
+        .slot_size = PHYTIUM_E2000_PARAM_SLOT_SIZE,
+    },
+};
+
+static const PhytiumE2000PbfParameterSpec
+phytium_e2000_pbf_summary = {
+    /*
+     * The public service specification does not describe this container
+     * summary.  All inspected E2000 images nevertheless use the same common
+     * header, followed by the PBR-to-PBF fields consumed below.
+     */
+    .name = "PBF",
+    .source_offset = PHYTIUM_E2000_PBF_PARAMETER_OFFSET,
+    .magic = PHYTIUM_E2000_PBF_MAGIC,
+    .minimum_size = PHYTIUM_E2000_PBR_PARAM_COPY_SIZE,
+    .slot_size = PHYTIUM_E2000_PARAM_SLOT_SIZE,
+};
+
+/*
+ * TF-A v2.3 represents an I/O driver as an io_dev_connector_t followed by
+ * io_dev_funcs_t.  The former contains one pointer and the latter contains
+ * nine function pointers on AArch64.  PBF embeds a FIP driver, a memmap
+ * driver, and the memmap driver's static io_dev_info_t consecutively.
+ *
+ * These sizes come from the public TF-A v2.3 io_driver.h ABI.  They are not
+ * Phytium guesses and must stay expressed in terms of the TF-A types instead
+ * of unexplained offsets such as 0x50, 0xa0, and 0xb0.
+ */
+#define PHYTIUM_E2000_TFA_IO_CONNECTOR_SIZE      sizeof(uint64_t)
+#define PHYTIUM_E2000_TFA_IO_FUNC_COUNT          9
+#define PHYTIUM_E2000_TFA_IO_FUNCS_SIZE          \
+    (PHYTIUM_E2000_TFA_IO_FUNC_COUNT * sizeof(uint64_t))
+#define PHYTIUM_E2000_TFA_IO_DRIVER_SIZE         \
+    (PHYTIUM_E2000_TFA_IO_CONNECTOR_SIZE + PHYTIUM_E2000_TFA_IO_FUNCS_SIZE)
+#define PHYTIUM_E2000_TFA_FIP_DRIVER_OFFSET      0
+#define PHYTIUM_E2000_TFA_MEMMAP_DRIVER_OFFSET   \
+    PHYTIUM_E2000_TFA_IO_DRIVER_SIZE
+#define PHYTIUM_E2000_TFA_MEMMAP_DEV_INFO_OFFSET \
+    (2 * PHYTIUM_E2000_TFA_IO_DRIVER_SIZE)
+#define PHYTIUM_E2000_TFA_IO_DEV_INFO_SIZE       (2 * sizeof(uint64_t))
+#define PHYTIUM_E2000_TFA_IO_DRIVER_GROUP_SIZE   \
+    (PHYTIUM_E2000_TFA_MEMMAP_DEV_INFO_OFFSET +  \
+     PHYTIUM_E2000_TFA_IO_DEV_INFO_SIZE)
+
+/*
+ * A TF-A platform I/O policy entry contains dev_handle, image_spec, and
+ * check.  Three entries are sufficient to distinguish the leading memmap
+ * policy from the following FIP policies in every inspected PBF.
+ */
+#define PHYTIUM_E2000_TFA_IO_POLICY_ENTRY_SIZE  (3 * sizeof(uint64_t))
+#define PHYTIUM_E2000_TFA_IO_POLICY_PREFIX_SIZE \
+    (3 * PHYTIUM_E2000_TFA_IO_POLICY_ENTRY_SIZE)
+
+/*
+ * The address of fip_dev_info is not present as data in the PBF image.  PBR
+ * obtains it by calling the FIP driver's dev_open callback.  Disassembly of
+ * that callback in the 2 GiB and 4 GiB Phytium Pi SDK PBFs and the COMe SDK
+ * PBF shows the compiler-emitted object 0xb0 bytes before the fip_dev_con
+ * slot.  This is a property of those firmware builds, not a TF-A or hardware
+ * ABI.
+ *
+ * Executing an arbitrary PBF callback inside QEMU's reset path is neither a
+ * safe parser nor an emulation of the ROM.  Keep this one empirical relation
+ * explicit and reject images whose surrounding TF-A structures do not match,
+ * rather than silently selecting an address from the DDR/MCU record version.
+ */
+#define PHYTIUM_E2000_PBR_FIP_DEV_INFO_BACKOFF 0xb0
+#define PHYTIUM_E2000_PBR_FIP_DEV_STATE_OFFSET 0x10
+
+typedef struct PhytiumE2000TfaIoHandoff {
+    hwaddr fip_connector_slot;
+    hwaddr fip_handle_slot;
+    hwaddr fip_dev_info;
+    hwaddr fip_dev_state;
+    hwaddr fip_connector;
+    hwaddr fip_funcs;
+    hwaddr memmap_connector_slot;
+    hwaddr memmap_handle_slot;
+    hwaddr memmap_connector;
+    hwaddr memmap_dev_info;
+} PhytiumE2000TfaIoHandoff;
+
+struct PhytiumE2000PBRState {
+    SysBusDevice parent_obj;
+
+    uint32_t regs[PHYTIUM_E2000_PBR_R_MAX];
+    RegisterInfo regs_info[PHYTIUM_E2000_PBR_R_MAX];
+    MemoryRegion boot_sram;
+    MemoryRegion iacc;
+    uint32_t boot_media;
+    BlockBackend *boot_blk;
+    char *boot_mode;
+    hwaddr ram_base;
+    uint64_t ram_size;
+    uint64_t cpu_mpidrs[PHYTIUM_E2000_PBR_MAX_CPUS];
+    CPUState *cpus[PHYTIUM_E2000_PBR_MAX_CPUS];
+    unsigned int num_cpus;
+    bool firmware_loaded;
+    int32_t primary_cpu;
+    uint32_t parameter_sizes[PHYTIUM_E2000_PBF_PARAM_COUNT];
+    PhytiumE2000TfaIoHandoff tfa_io;
+    uint8_t *iacc_image;
+    size_t iacc_image_size;
+    uint8_t *bl1;
+    size_t bl1_size;
+};
+
+static const char *phytium_e2000_pbr_boot_medium(PhytiumE2000PBRState *s)
+{
+    if (!strcmp(s->boot_mode, PHYTIUM_E2000_PBR_BOOT_MODE_QSPI)) {
+        return "QSPI";
+    }
+
+    return "SD";
+}
+
+static const RegisterAccessInfo phytium_e2000_pbr_regs_info[] = {
+    /*
+     * These reset values are the boot contract observed by the vendor PBF:
+     * reset source 1 selects the power-on path, 0xabcdef releases the SCP
+     * ready wait, boot media reflects the board strap selected by PBR, and
+     * 0x0c reports the Ethernet training state expected by the tested
+     * firmware.
+     *
+     * They remain read-only because software is consuming PBR-owned status,
+     * not configuring a live peripheral.
+     */
+    { .name = "RESET_SOURCE", .addr = A_RESET_SOURCE,
+      .reset = 0x1, .ro = UINT32_MAX },
+    { .name = "SCP_READY", .addr = A_SCP_READY,
+      .reset = 0x00abcdef, .ro = UINT32_MAX },
+    { .name = "BOOT_MEDIA", .addr = A_BOOT_MEDIA,
+      .ro = UINT32_MAX },
+    { .name = "ETH_TRAINING_STATUS", .addr = A_ETH_TRAINING_STATUS,
+      .reset = 0x0c, .ro = UINT32_MAX },
+};
+
+static const MemoryRegionOps phytium_e2000_pbr_ops = {
+    .read = register_read_memory,
+    .write = register_write_memory,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .valid = {
+        /*
+         * PBF byte-copies the complete status structure into SCP SRAM,
+         * including reserved holes such as base + 0x10. Accept subword and
+         * unaligned reads even though the defined status fields are 32-bit.
+         */
+        .min_access_size = 1,
+        .max_access_size = 4,
+        .unaligned = true,
+    },
+};
+
+static bool phytium_e2000_pbr_ranges_overlap(uint64_t first_offset,
+                                             uint64_t first_size,
+                                             uint64_t second_offset,
+                                             uint64_t second_size)
+{
+    if (!first_size || !second_size) {
+        return false;
+    }
+
+    return first_offset < second_offset + second_size &&
+           second_offset < first_offset + first_size;
+}
+
+static bool phytium_e2000_pbr_fip_extent(const uint8_t *header,
+                                         uint64_t medium_size,
+                                         size_t *extent, Error **errp)
+{
+    const uint8_t *info =
+        header + PHYTIUM_E2000_FIP_ALL_PAYLOAD_INFO_OFFSET;
+    uint32_t bl33_offset = ldl_le_p(info);
+    uint32_t bl33_size = ldl_le_p(info + 4);
+    uint32_t bl32_offset = ldl_le_p(info + 8);
+    uint32_t bl32_size = ldl_le_p(info + 12);
+    uint32_t bl33_end;
+    uint32_t bl32_end;
+    uint64_t image_extent;
+
+    if (ldl_le_p(header + PHYTIUM_E2000_PBF_PARAMETER_OFFSET) !=
+        PHYTIUM_E2000_PBF_MAGIC) {
+        error_setg(errp, "PBF magic at 0x%x is not 0x%08x",
+                   PHYTIUM_E2000_PBF_PARAMETER_OFFSET,
+                   PHYTIUM_E2000_PBF_MAGIC);
+        return false;
+    }
+
+    if (uadd32_overflow(bl33_offset, bl33_size, &bl33_end) ||
+        uadd32_overflow(bl32_offset, bl32_size, &bl32_end)) {
+        error_setg(errp, "BL32/BL33 metadata overflows its address range");
+        return false;
+    }
+
+    if (bl33_end > medium_size || bl32_end > medium_size) {
+        error_setg(errp, "BL32/BL33 range exceeds the boot medium");
+        return false;
+    }
+
+    if (phytium_e2000_pbr_ranges_overlap(bl33_offset, bl33_size,
+                                         bl32_offset, bl32_size)) {
+        error_setg(errp, "BL32 and BL33 payload ranges overlap");
+        return false;
+    }
+
+    image_extent = MAX((uint64_t)PHYTIUM_E2000_FIP_ALL_MIN_SIZE,
+                       MAX(bl33_end, bl32_end));
+    if (image_extent > medium_size) {
+        error_setg(errp, "computed fip-all.bin extent 0x%" PRIx64
+                   " exceeds the boot medium size 0x%" PRIx64,
+                   image_extent, medium_size);
+        return false;
+    }
+    if (image_extent > PHYTIUM_E2000_FIP_ALL_MAX_SIZE) {
+        error_setg(errp, "computed fip-all.bin extent 0x%" PRIx64
+                   " exceeds the 4 MiB firmware region", image_extent);
+        return false;
+    }
+
+    *extent = image_extent;
+    return true;
+}
+
+static bool phytium_e2000_pbr_tfa_fip_valid(const uint8_t *data, size_t size,
+                                            Error **errp)
+{
+    const uint8_t *fip = data + PHYTIUM_E2000_TFA_FIP_OFFSET;
+    uint64_t max_payload_end = 0;
+    uint64_t min_payload_offset = UINT64_MAX;
+    uint64_t available;
+    size_t entry_offset;
+    unsigned int i;
+    bool has_entry = false;
+
+    if (size < PHYTIUM_E2000_TFA_FIP_OFFSET +
+               PHYTIUM_E2000_TFA_FIP_HEADER_SIZE +
+               PHYTIUM_E2000_TFA_FIP_ENTRY_SIZE) {
+        error_setg(errp, "image is too small for the TF-A FIP header");
+        return false;
+    }
+    if (ldl_le_p(fip) != PHYTIUM_E2000_TFA_FIP_MAGIC) {
+        error_setg(errp, "TF-A FIP magic at 0x%x is not 0x%08x",
+                   PHYTIUM_E2000_TFA_FIP_OFFSET,
+                   PHYTIUM_E2000_TFA_FIP_MAGIC);
+        return false;
+    }
+    if (!ldl_le_p(fip + sizeof(uint32_t))) {
+        error_setg(errp, "TF-A FIP serial number is zero");
+        return false;
+    }
+
+    available = size - PHYTIUM_E2000_TFA_FIP_OFFSET;
+    entry_offset = PHYTIUM_E2000_TFA_FIP_HEADER_SIZE;
+    for (i = 0; i < PHYTIUM_E2000_TFA_FIP_MAX_ENTRIES; i++) {
+        const uint8_t *entry;
+        uint64_t payload_offset;
+        uint64_t payload_size;
+        uint64_t payload_end;
+        uint64_t payload_flags;
+
+        if (entry_offset + PHYTIUM_E2000_TFA_FIP_ENTRY_SIZE > available) {
+            error_setg(errp, "TF-A FIP TOC has no in-bounds terminator");
+            return false;
+        }
+
+        entry = fip + entry_offset;
+        payload_offset = ldq_le_p(entry + PHYTIUM_E2000_TFA_FIP_UUID_SIZE);
+        payload_size = ldq_le_p(entry +
+                                PHYTIUM_E2000_TFA_FIP_UUID_SIZE + 8);
+        payload_flags = ldq_le_p(entry +
+                                 PHYTIUM_E2000_TFA_FIP_UUID_SIZE + 16);
+        if (buffer_is_zero(entry, PHYTIUM_E2000_TFA_FIP_UUID_SIZE)) {
+            uint64_t toc_end = entry_offset +
+                               PHYTIUM_E2000_TFA_FIP_ENTRY_SIZE;
+
+            /*
+             * A zero UUID terminates the TF-A TOC. Its offset records the end
+             * of the package rather than another payload start, so all prior
+             * payloads and the TOC itself must fit below that boundary.
+             */
+            if (!has_entry) {
+                error_setg(errp, "TF-A FIP TOC has no payload entries");
+                return false;
+            }
+            if (payload_size || payload_flags || payload_offset < toc_end ||
+                payload_offset > available ||
+                max_payload_end > payload_offset ||
+                min_payload_offset < toc_end) {
+                error_setg(errp, "TF-A FIP TOC terminator is out of bounds");
+                return false;
+            }
+            return true;
+        }
+
+        if (uadd64_overflow(payload_offset, payload_size, &payload_end) ||
+            payload_offset < PHYTIUM_E2000_TFA_FIP_HEADER_SIZE ||
+            payload_end > available) {
+            error_setg(errp, "TF-A FIP payload range is out of bounds");
+            return false;
+        }
+        has_entry = true;
+        min_payload_offset = MIN(min_payload_offset, payload_offset);
+        max_payload_end = MAX(max_payload_end, payload_end);
+        entry_offset += PHYTIUM_E2000_TFA_FIP_ENTRY_SIZE;
+    }
+
+    error_setg(errp, "TF-A FIP TOC exceeds %u entries",
+               PHYTIUM_E2000_TFA_FIP_MAX_ENTRIES);
+    return false;
+}
+
+static bool phytium_e2000_pbr_parameter_valid(
+    const uint8_t *data, size_t size,
+    const PhytiumE2000PbfParameterSpec *spec, uint32_t *validated_size,
+    Error **errp)
+{
+    const uint8_t *parameter;
+    uint32_t record_size;
+
+    if (spec->source_offset > size ||
+        size - spec->source_offset < PHYTIUM_PBF_PARAM_HEADER_SIZE) {
+        error_setg(errp, "%s parameter record at 0x%zx is out of bounds",
+                   spec->name, spec->source_offset);
+        return false;
+    }
+
+    parameter = data + spec->source_offset;
+    if (ldl_le_p(parameter) != spec->magic) {
+        error_setg(errp, "%s parameter magic at 0x%zx is not 0x%08x",
+                   spec->name, spec->source_offset, spec->magic);
+        return false;
+    }
+
+    record_size = ldl_le_p(parameter + PHYTIUM_PBF_PARAM_SIZE_OFFSET);
+    if (record_size < spec->minimum_size ||
+        record_size > spec->slot_size ||
+        record_size > size - spec->source_offset) {
+        error_setg(errp, "%s parameter size 0x%x is outside the "
+                   "0x%x-byte parameter slot", spec->name, record_size,
+                   spec->slot_size);
+        return false;
+    }
+
+    /*
+     * The public PBF ABI defines magic, version, size, and reserved as a
+     * common 16-byte header for PLL, PCIe, DDR, and COMMON parameters.  It
+     * passes each service-specific payload to PBF by address rather than
+     * defining it as part of a PBR layout.  In particular, the DDR record
+     * contains firmware-owned SPD and training data.
+     *
+     * GET_PARAMETER_VERSION reports a separate version for each PBF service.
+     * Consequently, a record version describes that service's payload, not
+     * the PBR handoff graph or TF-A object placement.  Preserve the payload
+     * and reserved word verbatim and accept an otherwise valid future
+     * version instead of inventing version-specific PBR layouts.
+     */
+    if (validated_size) {
+        *validated_size = record_size;
+    }
+
+    return true;
+}
+
+static bool phytium_e2000_pbr_pbf_code_pointer(uint64_t value)
+{
+    return value >= PHYTIUM_E2000_PBR_IACC_BASE +
+                    PHYTIUM_E2000_BL1_FLASH_OFFSET &&
+           value < PHYTIUM_E2000_PBR_IACC_BASE +
+                   PHYTIUM_E2000_TFA_FIP_OFFSET &&
+           !(value & 3);
+}
+
+static bool phytium_e2000_pbr_boot_sram_qword(uint64_t value)
+{
+    return value >= PHYTIUM_E2000_PBR_BOOT_SRAM_BASE &&
+           value <= PHYTIUM_E2000_PBR_BOOT_SRAM_BASE +
+                    PHYTIUM_E2000_PBR_BOOT_SRAM_SIZE - sizeof(uint64_t) &&
+           !(value & 7);
+}
+
+static bool phytium_e2000_pbr_tfa_io_driver_candidate(const uint8_t *data,
+                                                      size_t offset)
+{
+    unsigned int implemented = 0;
+    unsigned int i;
+
+    /*
+     * connector->dev_open is mandatory.  Individual io_dev_funcs_t entries
+     * may be NULL: for example, TF-A's FIP driver has no size/write methods
+     * and its memmap driver has no dev_close method in the inspected builds.
+     * Requiring every slot to be non-NULL would encode one implementation,
+     * while accepting non-code values would make the scan match random data.
+     */
+    if (!phytium_e2000_pbr_pbf_code_pointer(ldq_le_p(data + offset))) {
+        return false;
+    }
+
+    for (i = 0; i < PHYTIUM_E2000_TFA_IO_FUNC_COUNT; i++) {
+        uint64_t function = ldq_le_p(
+            data + offset + PHYTIUM_E2000_TFA_IO_CONNECTOR_SIZE +
+            i * sizeof(uint64_t));
+
+        if (!function) {
+            continue;
+        }
+        if (!phytium_e2000_pbr_pbf_code_pointer(function)) {
+            return false;
+        }
+        implemented++;
+    }
+
+    /*
+     * All known TF-A I/O drivers used here implement substantially more than
+     * a connector.  This lower bound is a parser discriminator, not an ABI
+     * promise about which particular callbacks must exist.
+     */
+    return implemented >= 4;
+}
+
+static bool phytium_e2000_pbr_tfa_io_driver_group_candidate(
+    const uint8_t *data, size_t offset)
+{
+    size_t memmap = offset + PHYTIUM_E2000_TFA_MEMMAP_DRIVER_OFFSET;
+    size_t dev_info = offset + PHYTIUM_E2000_TFA_MEMMAP_DEV_INFO_OFFSET;
+    uint64_t expected_funcs =
+        PHYTIUM_E2000_PBR_IACC_BASE + memmap +
+        PHYTIUM_E2000_TFA_IO_CONNECTOR_SIZE;
+
+    if (!phytium_e2000_pbr_tfa_io_driver_candidate(
+            data, offset + PHYTIUM_E2000_TFA_FIP_DRIVER_OFFSET) ||
+        !phytium_e2000_pbr_tfa_io_driver_candidate(data, memmap)) {
+        return false;
+    }
+
+    /*
+     * The memmap driver uses a static io_dev_info_t immediately after both
+     * drivers.  Its funcs member points at the memmap io_dev_funcs_t and its
+     * info member is zero.  This relation is defined by TF-A source and makes
+     * the otherwise relocatable driver group uniquely identifiable.
+     */
+    return ldq_le_p(data + dev_info) == expected_funcs &&
+           !ldq_le_p(data + dev_info + sizeof(uint64_t));
+}
+
+static bool phytium_e2000_pbr_tfa_io_policy_candidate(
+    const uint8_t *data, size_t offset, hwaddr *memmap_handle_slot,
+    hwaddr *fip_handle_slot)
+{
+    const uint8_t *memmap_policy = data + offset;
+    const uint8_t *fip_policy = memmap_policy +
+                                PHYTIUM_E2000_TFA_IO_POLICY_ENTRY_SIZE;
+    const uint8_t *second_fip_policy =
+        fip_policy + PHYTIUM_E2000_TFA_IO_POLICY_ENTRY_SIZE;
+    uint64_t memmap_handle = ldq_le_p(memmap_policy);
+    uint64_t fip_handle = ldq_le_p(fip_policy);
+    uint64_t second_fip_handle = ldq_le_p(second_fip_policy);
+    const uint8_t *policies[] = {
+        memmap_policy, fip_policy, second_fip_policy,
+    };
+    unsigned int i;
+
+    /*
+     * TF-A's plat_io_policy[] stores a pointer to each driver's handle slot,
+     * not the handle value itself.  The first policy maps the FIP container
+     * through the memmap device; image policies that follow use the FIP
+     * device.  This ordering is visible in arm_io_storage.c and is shared by
+     * all three inspected PBF images.
+     *
+     * The complete vendor table has 28 entries and contains sparse zero
+     * holes, but neither its length nor a terminator is self-describing.
+     * Depending on that sample-specific count would merely replace one magic
+     * address with a magic array length, so only the semantically meaningful
+     * three-entry prefix is used to locate the two handle slots.
+     */
+    if (!phytium_e2000_pbr_boot_sram_qword(memmap_handle) ||
+        !phytium_e2000_pbr_boot_sram_qword(fip_handle) ||
+        memmap_handle == fip_handle || second_fip_handle != fip_handle ||
+        memmap_handle < PHYTIUM_E2000_PBR_BOOT_SRAM_BASE +
+                        sizeof(uint64_t) ||
+        fip_handle < PHYTIUM_E2000_PBR_BOOT_SRAM_BASE +
+                     sizeof(uint64_t)) {
+        return false;
+    }
+
+    for (i = 0; i < ARRAY_SIZE(policies); i++) {
+        if (!phytium_e2000_pbr_pbf_code_pointer(
+                ldq_le_p(policies[i] + sizeof(uint64_t))) ||
+            !phytium_e2000_pbr_pbf_code_pointer(
+                ldq_le_p(policies[i] + 2 * sizeof(uint64_t)))) {
+            return false;
+        }
+    }
+
+    *memmap_handle_slot = memmap_handle;
+    *fip_handle_slot = fip_handle;
+    return true;
+}
+
+static bool phytium_e2000_pbr_tfa_io_handoff_valid(
+    const uint8_t *data, size_t size, PhytiumE2000TfaIoHandoff *handoff,
+    Error **errp)
+{
+    size_t scan_end = MIN(size, (size_t)PHYTIUM_E2000_TFA_FIP_OFFSET);
+    size_t driver_offset = 0;
+    hwaddr memmap_handle_slot = 0;
+    hwaddr fip_handle_slot = 0;
+    size_t offset;
+
+    /*
+     * Real PBR registers and opens the TF-A memmap and FIP I/O devices before
+     * releasing PBF.  QEMU intentionally models that ROM-visible result
+     * instead of executing undocumented PBR instructions.
+     *
+     * The PBF image contains the relocatable driver definitions and policy
+     * table, so scan for their public TF-A structural relationships.  This is
+     * why no DDR/MCU record-version address table appears here: even the two
+     * version 4 builds put these objects at different IACC offsets.
+     */
+    for (offset = PHYTIUM_E2000_BL1_FLASH_OFFSET;
+         offset + PHYTIUM_E2000_TFA_IO_DRIVER_GROUP_SIZE <= scan_end;
+         offset += sizeof(uint64_t)) {
+        if (!phytium_e2000_pbr_tfa_io_driver_group_candidate(data,
+                                                             offset)) {
+            continue;
+        }
+        if (driver_offset) {
+            error_setg(errp, "PBF contains multiple TF-A I/O driver groups");
+            return false;
+        }
+        driver_offset = offset;
+    }
+    if (!driver_offset) {
+        error_setg(errp, "PBF does not contain a valid TF-A I/O driver "
+                   "group");
+        return false;
+    }
+
+    for (offset = PHYTIUM_E2000_BL1_FLASH_OFFSET;
+         offset + PHYTIUM_E2000_TFA_IO_POLICY_PREFIX_SIZE <= scan_end;
+         offset += sizeof(uint64_t)) {
+        hwaddr candidate_memmap_handle;
+        hwaddr candidate_fip_handle;
+
+        if (!phytium_e2000_pbr_tfa_io_policy_candidate(
+                data, offset, &candidate_memmap_handle,
+                &candidate_fip_handle)) {
+            continue;
+        }
+        if (fip_handle_slot) {
+            error_setg(errp, "PBF contains multiple TF-A I/O policy "
+                       "tables");
+            return false;
+        }
+        memmap_handle_slot = candidate_memmap_handle;
+        fip_handle_slot = candidate_fip_handle;
+    }
+    if (!fip_handle_slot) {
+        error_setg(errp, "PBF does not contain a valid TF-A I/O policy "
+                   "table");
+        return false;
+    }
+
+    handoff->fip_connector_slot = fip_handle_slot - sizeof(uint64_t);
+    handoff->fip_handle_slot = fip_handle_slot;
+    if (handoff->fip_connector_slot <
+        PHYTIUM_E2000_PBR_BOOT_SRAM_BASE +
+        PHYTIUM_E2000_PBR_FIP_DEV_INFO_BACKOFF) {
+        error_setg(errp, "PBF TF-A FIP connector slot is too low for its "
+                   "device state");
+        return false;
+    }
+    handoff->fip_dev_info = handoff->fip_connector_slot -
+                            PHYTIUM_E2000_PBR_FIP_DEV_INFO_BACKOFF;
+    handoff->fip_dev_state = handoff->fip_dev_info +
+                             PHYTIUM_E2000_PBR_FIP_DEV_STATE_OFFSET;
+    handoff->memmap_connector_slot =
+        memmap_handle_slot - sizeof(uint64_t);
+    handoff->memmap_handle_slot = memmap_handle_slot;
+
+    if (!phytium_e2000_pbr_boot_sram_qword(handoff->fip_dev_info) ||
+        !phytium_e2000_pbr_boot_sram_qword(handoff->fip_dev_state) ||
+        !phytium_e2000_pbr_boot_sram_qword(
+            handoff->memmap_connector_slot)) {
+        error_setg(errp, "PBF TF-A I/O handoff objects are outside boot "
+                   "SRAM");
+        return false;
+    }
+
+    handoff->fip_connector =
+        PHYTIUM_E2000_PBR_IACC_BASE + driver_offset +
+        PHYTIUM_E2000_TFA_FIP_DRIVER_OFFSET;
+    handoff->fip_funcs =
+        handoff->fip_connector + PHYTIUM_E2000_TFA_IO_CONNECTOR_SIZE;
+    handoff->memmap_connector =
+        PHYTIUM_E2000_PBR_IACC_BASE + driver_offset +
+        PHYTIUM_E2000_TFA_MEMMAP_DRIVER_OFFSET;
+    handoff->memmap_dev_info =
+        PHYTIUM_E2000_PBR_IACC_BASE + driver_offset +
+        PHYTIUM_E2000_TFA_MEMMAP_DEV_INFO_OFFSET;
+    return true;
+}
+
+static bool phytium_e2000_pbr_parse_firmware(PhytiumE2000PBRState *s,
+                                             const uint8_t *data,
+                                             size_t size, Error **errp)
+{
+    uint32_t primary_mpidr;
+    uint64_t bl1_end;
+    int primary_cpu = -1;
+    unsigned int i;
+
+    if (size < PHYTIUM_E2000_BL1_FLASH_OFFSET +
+               PHYTIUM_E2000_BL1_SIZE) {
+        error_setg(errp, "image is too small for the PBF handoff data");
+        return false;
+    }
+
+    if (!phytium_e2000_pbr_parameter_valid(data, size,
+                                           &phytium_e2000_pbf_summary,
+                                           NULL, errp)) {
+        return false;
+    }
+
+    for (i = 0; i < PHYTIUM_E2000_PBF_PARAM_COUNT; i++) {
+        if (!phytium_e2000_pbr_parameter_valid(
+                data, size, &phytium_e2000_pbf_parameters[i],
+                &s->parameter_sizes[i], errp)) {
+            return false;
+        }
+    }
+    if (!phytium_e2000_pbr_tfa_io_handoff_valid(data, size, &s->tfa_io,
+                                                errp)) {
+        return false;
+    }
+
+    primary_mpidr = lduw_le_p(data + PHYTIUM_E2000_PBF_PARAMETER_OFFSET +
+                              PHYTIUM_E2000_PBF_PRIMARY_CORE_OFFSET);
+    /*
+     * The PBF container stores only Aff1:Aff0 in this 16-bit field. Compare
+     * the same affinities from each complete architectural MPIDR.
+     */
+    for (i = 0; i < s->num_cpus; i++) {
+        if ((s->cpu_mpidrs[i] & 0xffff) == primary_mpidr) {
+            primary_cpu = i;
+            break;
+        }
+    }
+    if (primary_cpu < 0) {
+        error_setg(errp, "PBR firmware primary core 0x%x is not present; "
+                   "use at least -smp 3", primary_mpidr);
+        return false;
+    }
+
+    bl1_end = PHYTIUM_E2000_PBR_BL1_RUNTIME_BASE +
+              PHYTIUM_E2000_BL1_SIZE;
+    if (s->ram_size < bl1_end - s->ram_base) {
+        error_setg(errp, "PBR firmware requires RAM to cover PBF runtime "
+                   "address 0x%" HWADDR_PRIx "; use -m 2G",
+                   (hwaddr)PHYTIUM_E2000_PBR_BL1_RUNTIME_BASE);
+        return false;
+    }
+
+    if (size > PHYTIUM_E2000_PBR_IACC_SIZE) {
+        error_setg(errp, "PBR firmware image does not fit in boot IACC");
+        return false;
+    }
+
+    s->firmware_loaded = true;
+    s->primary_cpu = primary_cpu;
+    s->iacc_image_size = size;
+    s->iacc_image = g_memdup2(data, size);
+    s->bl1_size = PHYTIUM_E2000_BL1_SIZE;
+    s->bl1 = g_memdup2(data + PHYTIUM_E2000_BL1_FLASH_OFFSET,
+                       s->bl1_size);
+    return true;
+}
+
+static bool phytium_e2000_pbr_load_firmware(PhytiumE2000PBRState *s,
+                                            Error **errp)
+{
+    g_autofree uint8_t *contents = NULL;
+    const char *boot_medium = phytium_e2000_pbr_boot_medium(s);
+    Error *local_err = NULL;
+    int64_t backend_size;
+    size_t extent;
+
+    if (!s->boot_blk) {
+        /*
+         * A missing backend is valid for direct -kernel boot. Leave
+         * firmware_loaded clear so the machine selects the generic Arm
+         * loader instead of the PBR reset handoff.
+         */
+        return true;
+    }
+
+    backend_size = blk_getlength(s->boot_blk);
+    if (backend_size < 0) {
+        error_setg_errno(errp, -backend_size,
+                         "failed to determine %s boot-medium size",
+                         boot_medium);
+        return false;
+    }
+    if (backend_size < PHYTIUM_E2000_FIP_ALL_HEADER_SIZE) {
+        error_setg(errp, "%s is too small for the fip-all.bin metadata",
+                   boot_medium);
+        return false;
+    }
+
+    contents = g_malloc(PHYTIUM_E2000_FIP_ALL_HEADER_SIZE);
+    if (blk_pread(s->boot_blk, 0, PHYTIUM_E2000_FIP_ALL_HEADER_SIZE,
+                  contents, 0) < 0) {
+        error_setg(errp, "failed to read fip-all.bin metadata at %s "
+                   "offset 0", boot_medium);
+        return false;
+    }
+
+    if (!phytium_e2000_pbr_fip_extent(contents, backend_size, &extent,
+                                      &local_err)) {
+        error_prepend(&local_err, "invalid fip-all.bin at %s offset 0: ",
+                      boot_medium);
+        error_propagate(errp, local_err);
+        return false;
+    }
+
+    contents = g_realloc(contents, extent);
+    if (extent > PHYTIUM_E2000_FIP_ALL_HEADER_SIZE &&
+        blk_pread(s->boot_blk, PHYTIUM_E2000_FIP_ALL_HEADER_SIZE,
+                  extent - PHYTIUM_E2000_FIP_ALL_HEADER_SIZE,
+                  contents + PHYTIUM_E2000_FIP_ALL_HEADER_SIZE, 0) < 0) {
+        error_setg(errp, "failed to read fip-all.bin extent 0x%zx from %s "
+                   "offset 0", extent, boot_medium);
+        return false;
+    }
+
+    if (!phytium_e2000_pbr_tfa_fip_valid(contents, extent, &local_err)) {
+        error_prepend(&local_err, "invalid fip-all.bin at %s offset 0: ",
+                      boot_medium);
+        error_propagate(errp, local_err);
+        return false;
+    }
+
+    if (!phytium_e2000_pbr_parse_firmware(s, contents, extent, &local_err)) {
+        error_prepend(&local_err, "invalid fip-all.bin at %s offset 0: ",
+                      boot_medium);
+        error_propagate(errp, local_err);
+        return false;
+    }
+
+    return true;
+}
+
+static void phytium_e2000_pbr_seed_shared(PhytiumE2000PBRState *s)
+{
+    const PhytiumE2000TfaIoHandoff *tfa_io = &s->tfa_io;
+    uint8_t *sram = memory_region_get_ram_ptr(&s->boot_sram);
+    uint8_t *iacc = memory_region_get_ram_ptr(&s->iacc);
+    uint8_t parameter[PHYTIUM_E2000_PARAM_SLOT_SIZE];
+    hwaddr sram_base = PHYTIUM_E2000_PBR_BOOT_SRAM_BASE;
+    unsigned int i;
+
+    /*
+     * This graph is a firmware-private PBR-to-PBF handoff, not a device-tree
+     * description or a public E2000 register ABI.  It was reconstructed by
+     * tracing the early loads performed by the 2 GiB and 4 GiB Phytium Pi SDK
+     * PBFs and the COMe SDK PBF.  All three use the same boot-SRAM
+     * descriptors, SCP-SRAM parameter slots, and 0xf1801300 parameter copy.
+     *
+     * The addresses cannot be read from fip-all.bin as a documented table.
+     * They appear as constants in vendor PBF instructions.  Teaching the
+     * parser to recognize arbitrary compiler-generated AArch64 instruction
+     * sequences would be more fragile than recording the observed ROM ABI
+     * here.  Keep the graph together and heavily annotated so that a future
+     * firmware failure is treated as missing evidence, not "fixed" by an
+     * unexplained per-version address.
+     */
+    stq_le_p(sram + PHYTIUM_E2000_PBR_ROOT_OFFSET,
+             PHYTIUM_E2000_PBR_ROOT);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_ROOT - sram_base) + 0x10,
+             PHYTIUM_E2000_PBR_PARAM_NODE);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_PARAM_NODE - sram_base) + 0x18,
+             PHYTIUM_E2000_PBR_PLL_DESC);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_PLL_DESC - sram_base) + 0x08,
+             PHYTIUM_E2000_PBR_PLL_PAYLOAD);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_PARAM_NODE - sram_base) + 0x20,
+             PHYTIUM_E2000_PBR_PCIE_DESC);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_PCIE_DESC - sram_base) + 0x08,
+             PHYTIUM_E2000_PBR_PCIE_PAYLOAD);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_PARAM_NODE - sram_base) + 0x28,
+             PHYTIUM_E2000_PBR_COMMON_DESC);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_COMMON_DESC - sram_base),
+             PHYTIUM_E2000_PBR_COMMON_PAYLOAD);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_COMMON_DESC - sram_base) + 0x08,
+             PHYTIUM_E2000_PBR_COMMON_PAYLOAD);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_PARAM_NODE - sram_base) + 0x30,
+             PHYTIUM_E2000_PBR_MCU_DESC);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_MCU_DESC - sram_base),
+             PHYTIUM_E2000_PBR_MCU_PAYLOAD);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_MCU_DESC - sram_base) + 0x08,
+             PHYTIUM_E2000_PBR_MCU_PAYLOAD);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_PARAM_NODE - sram_base) + 0x38,
+             PHYTIUM_E2000_PBR_PARAM_SLOT);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_PARAM_SLOT - sram_base) + 0x08,
+             PHYTIUM_E2000_PBR_PARAM_COPY);
+
+    /*
+     * Reproduce the result of TF-A register_io_dev_*() and dev_open():
+     *
+     *   *_dev_con       -> embedded io_dev_connector_t
+     *   *_dev_handle    -> initialized io_dev_info_t
+     *   fip_dev_info    = { &fip_dev_funcs, &fip_dev_state }
+     *
+     * The memmap driver owns a static io_dev_info_t in the PBF image, while
+     * the FIP driver creates its state in boot SRAM.  plat_io_policy[] points
+     * at the two *_dev_handle slots populated below.
+     */
+    stq_le_p(sram + (tfa_io->fip_connector_slot - sram_base),
+             tfa_io->fip_connector);
+    stq_le_p(sram + (tfa_io->fip_handle_slot - sram_base),
+             tfa_io->fip_dev_info);
+    stq_le_p(sram + (tfa_io->fip_dev_info - sram_base),
+             tfa_io->fip_funcs);
+    stq_le_p(sram + (tfa_io->fip_dev_info - sram_base) +
+             sizeof(uint64_t), tfa_io->fip_dev_state);
+    stq_le_p(sram + (tfa_io->fip_dev_state - sram_base), 0);
+    stq_le_p(sram + (tfa_io->memmap_connector_slot - sram_base),
+             tfa_io->memmap_connector);
+    stq_le_p(sram + (tfa_io->memmap_handle_slot - sram_base),
+             tfa_io->memmap_dev_info);
+
+    address_space_write(&address_space_memory,
+                        PHYTIUM_E2000_PBR_PARAM_COPY,
+                        MEMTXATTRS_UNSPECIFIED,
+                        iacc + PHYTIUM_E2000_PBF_PARAMETER_OFFSET,
+                        PHYTIUM_E2000_PBR_PARAM_COPY_SIZE);
+
+    /*
+     * PBF consumes each service parameter through the same public record
+     * header, while the payload remains service- and version-specific.  Copy
+     * every validated record without interpretation and clear the rest of
+     * its PBR-owned slot so that a shorter future format cannot expose stale
+     * reset data.
+     *
+     * This also stages PLL at 0x32a10c00.  Inspected PBFs follow the boot-SRAM
+     * PLL descriptor and require magic 0x54460020 there, just as they require
+     * the PCIe, COMMON, and DDR/MCU records in the following slots.
+     */
+    for (i = 0; i < PHYTIUM_E2000_PBF_PARAM_COUNT; i++) {
+        const PhytiumE2000PbfParameterSpec *spec =
+            &phytium_e2000_pbf_parameters[i];
+
+        memset(parameter, 0, sizeof(parameter));
+        memcpy(parameter, iacc + spec->source_offset,
+               s->parameter_sizes[i]);
+        address_space_write(&address_space_memory, spec->destination,
+                            MEMTXATTRS_UNSPECIFIED, parameter,
+                            spec->slot_size);
+    }
+}
+
+static void phytium_e2000_pbr_reset_enter(Object *obj, ResetType type)
+{
+    PhytiumE2000PBRState *s = PHYTIUM_E2000_PBR(obj);
+    int i;
+
+    (void)type;
+
+    /*
+     * Only architected handoff words have RegisterAccessInfo entries. The
+     * reserved holes stay zero-filled in the backing register array.
+     */
+    for (i = 0; i < ARRAY_SIZE(phytium_e2000_pbr_regs_info); i++) {
+        register_reset(&s->regs_info[
+            phytium_e2000_pbr_regs_info[i].addr / sizeof(uint32_t)]);
+    }
+    s->regs[R_BOOT_MEDIA] = s->boot_media;
+}
+
+static void phytium_e2000_pbr_reset_hold(Object *obj, ResetType type)
+{
+    PhytiumE2000PBRState *s = PHYTIUM_E2000_PBR(obj);
+    uint8_t *sram = memory_region_get_ram_ptr(&s->boot_sram);
+    uint8_t *iacc = memory_region_get_ram_ptr(&s->iacc);
+
+    (void)type;
+    memset(sram, 0, PHYTIUM_E2000_PBR_BOOT_SRAM_SIZE);
+    memset(iacc, 0, PHYTIUM_E2000_PBR_IACC_SIZE);
+    memory_region_set_dirty(&s->boot_sram, 0,
+                            PHYTIUM_E2000_PBR_BOOT_SRAM_SIZE);
+    memory_region_set_dirty(&s->iacc, 0, PHYTIUM_E2000_PBR_IACC_SIZE);
+
+    if (!s->firmware_loaded) {
+        return;
+    }
+
+    memcpy(iacc, s->iacc_image, s->iacc_image_size);
+    address_space_write(&address_space_memory,
+                        PHYTIUM_E2000_PBR_BL1_RUNTIME_BASE,
+                        MEMTXATTRS_UNSPECIFIED, s->bl1, s->bl1_size);
+    phytium_e2000_pbr_seed_shared(s);
+    phytium_e2000_mhu_seed_mailbox();
+}
+
+static void phytium_e2000_pbr_reset_exit(Object *obj, ResetType type)
+{
+    PhytiumE2000PBRState *s = PHYTIUM_E2000_PBR(obj);
+
+    (void)type;
+    if (!s->firmware_loaded) {
+        return;
+    }
+
+    g_assert(s->primary_cpu >= 0 && s->primary_cpu < s->num_cpus);
+    g_assert(s->cpus[s->primary_cpu]);
+    cpu_set_pc(s->cpus[s->primary_cpu],
+               PHYTIUM_E2000_PBR_BL1_RUNTIME_BASE);
+}
+
+void phytium_e2000_pbr_configure(PhytiumE2000PBRState *s,
+                                 BlockBackend *boot_blk,
+                                 hwaddr ram_base, uint64_t ram_size,
+                                 const uint64_t *cpu_mpidrs,
+                                 unsigned int num_cpus)
+{
+    g_assert(!DEVICE(s)->realized);
+    g_assert(num_cpus <= PHYTIUM_E2000_PBR_MAX_CPUS);
+
+    s->boot_blk = boot_blk;
+    s->ram_base = ram_base;
+    s->ram_size = ram_size;
+    s->num_cpus = num_cpus;
+    memcpy(s->cpu_mpidrs, cpu_mpidrs,
+           num_cpus * sizeof(s->cpu_mpidrs[0]));
+}
+
+bool phytium_e2000_pbr_firmware_loaded(PhytiumE2000PBRState *s)
+{
+    return s->firmware_loaded;
+}
+
+int phytium_e2000_pbr_primary_cpu(PhytiumE2000PBRState *s)
+{
+    g_assert(s->firmware_loaded);
+    return s->primary_cpu;
+}
+
+void phytium_e2000_pbr_connect_cpu(PhytiumE2000PBRState *s,
+                                   unsigned int index, CPUState *cpu)
+{
+    g_assert(index < s->num_cpus);
+    g_assert(cpu);
+    g_assert(!s->cpus[index]);
+    object_ref(OBJECT(cpu));
+    s->cpus[index] = cpu;
+}
+
+static void phytium_e2000_pbr_realize(DeviceState *dev, Error **errp)
+{
+    PhytiumE2000PBRState *s = PHYTIUM_E2000_PBR(dev);
+    bool loaded;
+
+    if (!s->boot_mode) {
+        error_setg(errp, "boot-mode was not configured");
+        return;
+    }
+    if (!strcmp(s->boot_mode, PHYTIUM_E2000_PBR_BOOT_MODE_QSPI)) {
+        s->boot_media = PHYTIUM_E2000_PBR_BOOT_MEDIA_QSPI;
+    } else if (!strcmp(s->boot_mode, PHYTIUM_E2000_PBR_BOOT_MODE_SD0)) {
+        s->boot_media = PHYTIUM_E2000_PBR_BOOT_MEDIA_SD0;
+    } else {
+        error_setg(errp, "invalid boot-mode '%s'; valid values are "
+                   "'qspi' and 'sd'", s->boot_mode);
+        return;
+    }
+    if (!s->num_cpus) {
+        error_setg(errp, "CPU topology was not configured");
+        return;
+    }
+    loaded = phytium_e2000_pbr_load_firmware(s, errp);
+    s->boot_blk = NULL;
+    if (!loaded) {
+        return;
+    }
+}
+
+static const Property phytium_e2000_pbr_properties[] = {
+    DEFINE_PROP_STRING("boot-mode", PhytiumE2000PBRState, boot_mode),
+};
+
+static void phytium_e2000_pbr_init(Object *obj)
+{
+    PhytiumE2000PBRState *s = PHYTIUM_E2000_PBR(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    RegisterInfoArray *reg_array;
+
+    reg_array = register_init_block32(
+        DEVICE(obj), phytium_e2000_pbr_regs_info,
+        ARRAY_SIZE(phytium_e2000_pbr_regs_info), s->regs_info, s->regs,
+        &phytium_e2000_pbr_ops, false, PHYTIUM_E2000_PBR_MMIO_SIZE);
+    sysbus_init_mmio(sbd, &reg_array->mem);
+
+    memory_region_init_ram(&s->boot_sram, obj, "phytium-e2000.boot-sram",
+                           PHYTIUM_E2000_PBR_BOOT_SRAM_SIZE, &error_abort);
+    sysbus_init_mmio(sbd, &s->boot_sram);
+    memory_region_init_ram(&s->iacc, obj, "phytium-e2000.iacc",
+                           PHYTIUM_E2000_PBR_IACC_SIZE, &error_abort);
+    sysbus_init_mmio(sbd, &s->iacc);
+    s->primary_cpu = -1;
+}
+
+static void phytium_e2000_pbr_finalize(Object *obj)
+{
+    PhytiumE2000PBRState *s = PHYTIUM_E2000_PBR(obj);
+    unsigned int i;
+
+    for (i = 0; i < s->num_cpus; i++) {
+        if (s->cpus[i]) {
+            object_unref(OBJECT(s->cpus[i]));
+        }
+    }
+    g_free(s->iacc_image);
+    g_free(s->bl1);
+}
+
+/*
+ * boot_sram and iacc are RAMBlocks and migrate independently. The parsed
+ * image buffers remain immutable reset sources reconstructed from the boot
+ * backend while the destination machine is realized.
+ */
+static const VMStateDescription phytium_e2000_pbr_vmsd = {
+    .name = TYPE_PHYTIUM_E2000_PBR,
+    .version_id = 2,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32_ARRAY(regs, PhytiumE2000PBRState,
+                             PHYTIUM_E2000_PBR_R_MAX),
+        VMSTATE_BOOL_V(firmware_loaded, PhytiumE2000PBRState, 2),
+        VMSTATE_INT32_V(primary_cpu, PhytiumE2000PBRState, 2),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void phytium_e2000_pbr_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->vmsd = &phytium_e2000_pbr_vmsd;
+    dc->realize = phytium_e2000_pbr_realize;
+    device_class_set_props(dc, phytium_e2000_pbr_properties);
+    rc->phases.enter = phytium_e2000_pbr_reset_enter;
+    rc->phases.hold = phytium_e2000_pbr_reset_hold;
+    rc->phases.exit = phytium_e2000_pbr_reset_exit;
+}
+
+static const TypeInfo phytium_e2000_pbr_info = {
+    .name = TYPE_PHYTIUM_E2000_PBR,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(PhytiumE2000PBRState),
+    .instance_init = phytium_e2000_pbr_init,
+    .instance_finalize = phytium_e2000_pbr_finalize,
+    .class_init = phytium_e2000_pbr_class_init,
+};
+
+static void phytium_e2000_pbr_register_types(void)
+{
+    type_register_static(&phytium_e2000_pbr_info);
+}
+
+type_init(phytium_e2000_pbr_register_types)
diff --git a/include/hw/misc/phytium_e2000_pbr.h b/include/hw/misc/phytium_e2000_pbr.h
new file mode 100644
index 0000000000..890d419254
--- /dev/null
+++ b/include/hw/misc/phytium_e2000_pbr.h
@@ -0,0 +1,55 @@
+/*
+ * Phytium E2000 PBR (Phytium Boot ROM) model
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_PHYTIUM_E2000_PBR_H
+#define HW_MISC_PHYTIUM_E2000_PBR_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_PHYTIUM_E2000_PBR "phytium-e2000-pbr"
+OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000PBRState, PHYTIUM_E2000_PBR)
+
+/*
+ * These are E2000 physical addresses, not locations chosen by a particular
+ * PBF build. Keep them with the PBR device so that the machine memory map and
+ * the firmware parser do not grow independent copies of the same constants.
+ *
+ * The boot SRAM contains the handoff left by the on-chip PBR. IACC is the
+ * vendor-named boot-time execution window into which PBR places fip-all.bin.
+ * The status window is consumed by PBF as a snapshot of PBR-owned state.
+ */
+#define PHYTIUM_E2000_PBR_STATUS_BASE       0x32a11804
+#define PHYTIUM_E2000_PBR_BOOT_SRAM_BASE    0x30c00000
+#define PHYTIUM_E2000_PBR_IACC_BASE         0x38000000
+
+#define PHYTIUM_E2000_PBR_MMIO_SIZE         0x64
+#define PHYTIUM_E2000_PBR_BOOT_SRAM_SIZE    0x00100000
+#define PHYTIUM_E2000_PBR_IACC_SIZE         0x08000000
+#define PHYTIUM_E2000_PBR_MAX_CPUS          4
+
+#define PHYTIUM_E2000_PBR_BOOT_MEDIA_QSPI   0x1
+#define PHYTIUM_E2000_PBR_BOOT_MEDIA_SD0    0x4
+
+#define PHYTIUM_E2000_PBR_BOOT_MODE_QSPI    "qspi"
+#define PHYTIUM_E2000_PBR_BOOT_MODE_SD0     "sd"
+
+void phytium_e2000_pbr_configure(PhytiumE2000PBRState *s,
+                                 BlockBackend *boot_blk,
+                                 hwaddr ram_base, uint64_t ram_size,
+                                 const uint64_t *cpu_mpidrs,
+                                 unsigned int num_cpus);
+bool phytium_e2000_pbr_firmware_loaded(PhytiumE2000PBRState *s);
+int phytium_e2000_pbr_primary_cpu(PhytiumE2000PBRState *s);
+void phytium_e2000_pbr_connect_cpu(PhytiumE2000PBRState *s,
+                                   unsigned int index, CPUState *cpu);
+
+#endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 16/33] hw/arm: phytium: Integrate the Phytium E2000 PBR
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (12 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 15/33] hw/misc: Add Phytium E2000 PBR model Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 17/33] hw/arm: phytium: Add Phytium E2000 control region placeholders Bin Meng
                   ` (14 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

Instantiate PBR in the Phytium Pi machine, map its status and boot
memories, provide the heterogeneous MPIDR topology, and connect all
CPU objects for reset-time handoff.

Use SD0 as the board firmware backend. Explicitly reject the unsupported
-bios and pflash interfaces.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c | 97 +++++++++++++++++++++++++++++++++++++++---
 1 file changed, 92 insertions(+), 5 deletions(-)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index c6d789bb04..e74a0328fe 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -13,6 +13,7 @@
 #include "qemu/error-report.h"
 #include "qemu/units.h"
 #include "qapi/error.h"
+#include "exec/cpu-common.h"
 #include "system/address-spaces.h"
 #include "system/kvm.h"
 #include "system/system.h"
@@ -20,6 +21,7 @@
 #include "hw/arm/boot.h"
 #include "hw/arm/bsa.h"
 #include "hw/arm/machines-qom.h"
+#include "hw/block/flash.h"
 #include "hw/char/pl011.h"
 #include "hw/core/boards.h"
 #include "hw/core/qdev-properties.h"
@@ -27,6 +29,7 @@
 #include "hw/intc/arm_gicv3_its_common.h"
 #include "hw/misc/phytium_e2000_ddr.h"
 #include "hw/misc/phytium_e2000_mhu.h"
+#include "hw/misc/phytium_e2000_pbr.h"
 #include "hw/misc/unimp.h"
 #include "hw/net/cadence_gem.h"
 #include "hw/pci/pci.h"
@@ -96,8 +99,10 @@ struct PhytiumE2000State {
     struct arm_boot_info bootinfo;
     DeviceState *gic;
     DeviceState *qspi;
+    PhytiumE2000PBRState *pbr;
     PhytiumE2000MciState *mci[PHYTIUM_E2000_NUM_MCIS];
     CadenceGEMState *gem[PHYTIUM_E2000_NUM_GEMS];
+    CPUState *cpu[PHYTIUM_E2000_NUM_CPUS];
     MemoryRegion scp_sram;
     MemoryRegion ram_low;
     MemoryRegion ram_high;
@@ -208,6 +213,17 @@ phytium_e2000_cpu_config[PHYTIUM_E2000_NUM_CPUS] = {
     },
 };
 
+static uint64_t phytium_e2000_cpu_mp_affinity(unsigned int cpu)
+{
+    /*
+     * E2000Q exposes one core in each of the first two clusters and two cores
+     * in the third cluster. Firmware stores these MPIDRs in its parameter
+     * tables, so a linear CPU index is not a valid affinity value.
+     */
+    g_assert(cpu < ARRAY_SIZE(phytium_e2000_cpu_config));
+    return phytium_e2000_cpu_config[cpu].mp_affinity;
+}
+
 static void phytium_e2000_create_its(PhytiumE2000State *s)
 {
     DeviceState *dev = qdev_new(its_class_name());
@@ -410,6 +426,15 @@ static void phytium_e2000_create_pcie(PhytiumE2000State *s)
     }
 }
 
+static void phytium_e2000_reject_legacy_firmware(MachineState *ms)
+{
+    if (ms->firmware || drive_get(IF_PFLASH, 0, 0)) {
+        error_report("phytium-pi: -bios and pflash firmware are not "
+                     "supported; use an if=sd,index=0 image");
+        exit(1);
+    }
+}
+
 static BlockBackend *phytium_e2000_sd_blk(int index)
 {
     DriveInfo *dinfo = drive_get(IF_SD, 0, index);
@@ -471,6 +496,47 @@ static void phytium_e2000_create_qspi(PhytiumE2000State *s)
         phytium_e2000_memmap[PHYTIUM_E2000_QSPI_DIRECT].base, 2);
 }
 
+static bool phytium_e2000_create_pbr(PhytiumE2000State *s)
+{
+    MachineState *ms = MACHINE(s);
+    DeviceState *dev = qdev_new(TYPE_PHYTIUM_E2000_PBR);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    BlockBackend *boot_blk = NULL;
+    uint64_t cpu_mpidrs[PHYTIUM_E2000_NUM_CPUS];
+    int i;
+
+    if (!ms->kernel_filename) {
+        boot_blk = phytium_e2000_sd_blk(0);
+    }
+
+    for (i = 0; i < ms->smp.cpus; i++) {
+        cpu_mpidrs[i] = phytium_e2000_cpu_mp_affinity(i);
+    }
+
+    qdev_prop_set_string(dev, "boot-mode",
+                         PHYTIUM_E2000_PBR_BOOT_MODE_SD0);
+    phytium_e2000_pbr_configure(PHYTIUM_E2000_PBR(dev), boot_blk,
+                                phytium_e2000_memmap[
+                                    PHYTIUM_E2000_RAM].base,
+                                ms->ram_size, cpu_mpidrs, ms->smp.cpus);
+
+    /*
+     * PBR owns the status snapshot and both boot memories. The status block
+     * overlaps the broad board-control placeholder and therefore needs the
+     * higher mapping priority used by the previous status-only device.
+     */
+    object_property_add_child(OBJECT(s), "pbr", OBJECT(dev));
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map_overlap(sbd, 0, PHYTIUM_E2000_PBR_STATUS_BASE, 2);
+    sysbus_mmio_map(sbd, 1,
+                    phytium_e2000_memmap[PHYTIUM_E2000_BOOT_SRAM].base);
+    sysbus_mmio_map(sbd, 2,
+                    phytium_e2000_memmap[PHYTIUM_E2000_BOOT_IACC].base);
+    s->pbr = PHYTIUM_E2000_PBR(dev);
+
+    return phytium_e2000_pbr_firmware_loaded(s->pbr);
+}
+
 static void phytium_e2000_create_ddr_status(PhytiumE2000State *s)
 {
     DeviceState *dev = qdev_new(TYPE_PHYTIUM_E2000_DDR);
@@ -570,7 +636,8 @@ static void phytium_e2000_create_ram(PhytiumE2000State *s)
         phytium_e2000_memmap[PHYTIUM_E2000_RAM_HIGH].base, &s->ram_high);
 }
 
-static void phytium_e2000_create_cpus(PhytiumE2000State *s)
+static void phytium_e2000_create_cpus(PhytiumE2000State *s,
+                                      bool firmware_loaded)
 {
     MachineState *ms = MACHINE(s);
     const CPUArchIdList *possible_cpus;
@@ -587,18 +654,30 @@ static void phytium_e2000_create_cpus(PhytiumE2000State *s)
                                 &error_abort);
         object_property_set_int(cpuobj, "cntfrq", PHYTIUM_E2000_GTIMER_HZ,
                                 &error_abort);
-        if (object_property_find(cpuobj, "has_el3")) {
+        if (!firmware_loaded && object_property_find(cpuobj, "has_el3")) {
             /*
              * The generic-loader U-Boot path starts after the EL3 firmware
              * stages that normally provide the Phytium SMC services.
              */
             object_property_set_bool(cpuobj, "has_el3", false, &error_abort);
         }
+        /*
+         * PBR releases only the primary MPIDR named in the firmware parameter
+         * header. Secondary CPUs remain powered off for later firmware or
+         * PSCI bring-up.
+         */
+        if (firmware_loaded &&
+            i != phytium_e2000_pbr_primary_cpu(s->pbr)) {
+            object_property_set_bool(cpuobj, "start-powered-off", true,
+                                     &error_abort);
+        }
         object_property_set_link(cpuobj, "memory", OBJECT(get_system_memory()),
                                  &error_abort);
         cs = CPU(cpuobj);
         cs->cpu_index = i;
         qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
+        s->cpu[i] = cs;
+        phytium_e2000_pbr_connect_cpu(s->pbr, i, cs);
         object_unref(cpuobj);
     }
 }
@@ -606,6 +685,7 @@ static void phytium_e2000_create_cpus(PhytiumE2000State *s)
 static void phytium_pi_init(MachineState *ms)
 {
     PhytiumE2000State *s = PHYTIUM_PI(ms);
+    bool firmware_loaded;
     int i;
 
     if (kvm_enabled()) {
@@ -626,12 +706,15 @@ static void phytium_pi_init(MachineState *ms)
         exit(1);
     }
 
+    phytium_e2000_reject_legacy_firmware(ms);
+
     phytium_e2000_create_ram(s);
     phytium_e2000_create_unimplemented();
 
     phytium_e2000_create_qspi(s);
+    firmware_loaded = phytium_e2000_create_pbr(s);
 
-    phytium_e2000_create_cpus(s);
+    phytium_e2000_create_cpus(s, firmware_loaded);
     phytium_e2000_create_gic(s);
 
     phytium_e2000_create_scp_sram(s);
@@ -654,7 +737,7 @@ static void phytium_pi_init(MachineState *ms)
     s->bootinfo.board_id = -1;
     s->bootinfo.loader_start = phytium_e2000_memmap[PHYTIUM_E2000_RAM].base;
     s->bootinfo.psci_conduit = QEMU_PSCI_CONDUIT_SMC;
-    s->bootinfo.firmware_loaded = false;
+    s->bootinfo.firmware_loaded = firmware_loaded;
     arm_load_kernel(ARM_CPU(first_cpu), ms, &s->bootinfo);
 }
 
@@ -701,7 +784,11 @@ static void phytium_pi_class_init(ObjectClass *oc, const void *data)
     mc->valid_cpu_types = valid_cpu_types;
     mc->max_cpus = PHYTIUM_E2000_NUM_CPUS;
     mc->default_cpus = PHYTIUM_E2000_NUM_CPUS;
-    mc->default_ram_size = 1 * GiB;
+    /*
+     * PBF/BL1 relocates to 0xf8c40000, which is outside a 1 GiB RAM window
+     * starting at 0x80000000. Two GiB is the minimum useful firmware default.
+     */
+    mc->default_ram_size = 2 * GiB;
     mc->default_ram_id = "phytium-e2000.ram";
     mc->minimum_page_bits = 12;
     mc->block_default_type = IF_SD;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 17/33] hw/arm: phytium: Add Phytium E2000 control region placeholders
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (13 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 16/33] hw/arm: phytium: Integrate the Phytium E2000 PBR Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 18/33] hw/misc: Support Phytium E2000 SCMI CPU power control Bin Meng
                   ` (13 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

The vendor PBF accesses clock, reset, platform, security, and chip
control windows while bringing up the SoC.

Expose these windows as low-priority unimplemented regions. This
keeps the firmware-visible address map observable and allows later
device models to overlap individual windows without inventing
register semantics.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c | 35 +++++++++++++++++++++++++++++++++++
 1 file changed, 35 insertions(+)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index e74a0328fe..1c337bf36e 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -74,6 +74,8 @@ enum {
     PHYTIUM_E2000_UART4,
     PHYTIUM_E2000_UART5,
     PHYTIUM_E2000_UART6,
+    PHYTIUM_E2000_CLK_CTRL,
+    PHYTIUM_E2000_SYSTEM_CTRL,
     PHYTIUM_E2000_GIC_DIST,
     PHYTIUM_E2000_GIC_ITS,
     PHYTIUM_E2000_GIC_REDIST,
@@ -85,6 +87,9 @@ enum {
     PHYTIUM_E2000_GEM1,
     PHYTIUM_E2000_GEM2,
     PHYTIUM_E2000_GEM3,
+    PHYTIUM_E2000_PLATFORM_CTRL,
+    PHYTIUM_E2000_SECURITY_CTRL,
+    PHYTIUM_E2000_CHIP_CTRL,
     PHYTIUM_E2000_BOOT_IACC,
     PHYTIUM_E2000_PCIE_ECAM,
     PHYTIUM_E2000_PCIE_PIO,
@@ -126,6 +131,8 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_UART4] =          { 0x28014000, 0x00001000 },
     [PHYTIUM_E2000_UART5] =          { 0x2802a000, 0x00001000 },
     [PHYTIUM_E2000_UART6] =          { 0x28032000, 0x00001000 },
+    [PHYTIUM_E2000_CLK_CTRL] =       { 0x28100000, 0x00001000 },
+    [PHYTIUM_E2000_SYSTEM_CTRL] =    { 0x30000000, 0x00001000 },
     [PHYTIUM_E2000_GIC_DIST] =       { 0x30800000, 0x00020000 },
     [PHYTIUM_E2000_GIC_ITS] =        { 0x30820000, 0x00020000 },
     [PHYTIUM_E2000_GIC_REDIST] =     { 0x30880000, 0x00080000 },
@@ -137,6 +144,9 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_GEM1] =           { 0x3200e000, 0x00002000 },
     [PHYTIUM_E2000_GEM2] =           { 0x32010000, 0x00002000 },
     [PHYTIUM_E2000_GEM3] =           { 0x32012000, 0x00002000 },
+    [PHYTIUM_E2000_PLATFORM_CTRL] =  { 0x32e40000, 0x00010000 },
+    [PHYTIUM_E2000_SECURITY_CTRL] =  { 0x32f00000, 0x00001000 },
+    [PHYTIUM_E2000_CHIP_CTRL] =      { 0x33000000, 0x00010000 },
     [PHYTIUM_E2000_BOOT_IACC] =      { 0x38000000, 0x08000000 },
     [PHYTIUM_E2000_PCIE_ECAM] =      { 0x40000000, 0x10000000 },
     [PHYTIUM_E2000_PCIE_PIO] =       { 0x50000000, 0x00f00000 },
@@ -590,6 +600,19 @@ static void phytium_e2000_create_unimplemented(void)
         "phytium-e2000.low-peripheral",
         phytium_e2000_memmap[PHYTIUM_E2000_LOW_PERIPH].base,
         phytium_e2000_memmap[PHYTIUM_E2000_LOW_PERIPH].size);
+    /*
+     * PBF writes clock and reset controls as part of physical SoC bring-up.
+     * QEMU derives virtual clocks and reset state elsewhere, so retaining
+     * placeholder visibility is sufficient for these write-only setup paths.
+     */
+    create_unimplemented_device(
+        "phytium-e2000.clock-control",
+        phytium_e2000_memmap[PHYTIUM_E2000_CLK_CTRL].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_CLK_CTRL].size);
+    create_unimplemented_device(
+        "phytium-e2000.system-control",
+        phytium_e2000_memmap[PHYTIUM_E2000_SYSTEM_CTRL].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_SYSTEM_CTRL].size);
     /*
      * PBF programs SoC-specific PCIe PHY and port controls before U-Boot
      * enumerates ECAM. Their values do not affect the generic host bridge, so
@@ -607,6 +630,18 @@ static void phytium_e2000_create_unimplemented(void)
         "phytium-e2000.board-control",
         phytium_e2000_memmap[PHYTIUM_E2000_BOARD_CTRL].base,
         phytium_e2000_memmap[PHYTIUM_E2000_BOARD_CTRL].size);
+    create_unimplemented_device(
+        "phytium-e2000.platform-control",
+        phytium_e2000_memmap[PHYTIUM_E2000_PLATFORM_CTRL].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_PLATFORM_CTRL].size);
+    create_unimplemented_device(
+        "phytium-e2000.security-control",
+        phytium_e2000_memmap[PHYTIUM_E2000_SECURITY_CTRL].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_SECURITY_CTRL].size);
+    create_unimplemented_device(
+        "phytium-e2000.chip-control",
+        phytium_e2000_memmap[PHYTIUM_E2000_CHIP_CTRL].base,
+        phytium_e2000_memmap[PHYTIUM_E2000_CHIP_CTRL].size);
 }
 
 static void phytium_e2000_create_ram(PhytiumE2000State *s)
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 18/33] hw/misc: Support Phytium E2000 SCMI CPU power control
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (14 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 17/33] hw/arm: phytium: Add Phytium E2000 control region placeholders Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:24 ` [PATCH 19/33] hw/arm: phytium: Select the Phytium E2000 PBR boot medium Bin Meng
                   ` (12 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

The E2000 MHU previously acknowledged every SCMI message without
applying power-state changes. Consequently, BL31 reported successful
PSCI CPU_ON calls while all secondary CPUs remained powered off.

Implement the power-domain requests and the Phytium PSOSTAT query used
by the firmware. Follow the firmware-published runtime object graph,
complete its SCP handoff, and reset each target CPU at the resident
secondary entry published by BL1.

The secondary handoff is not described by the public PBF specifications.
Scan BL1 for invariant control-flow, MPIDR, and PBR-root anchors while
masking compiler-dependent branch displacements, then obtain the
firmware-owned vector-slot address from the adjacent literal. Reject
missing, ambiguous, unaligned, or null handoff records.

Pass the validated slot from PBR to MHU and dereference it for every
CPU_ON request because firmware may publish the entry after boot and
reuse the temporary BL1 image before Linux starts secondary CPUs.

With this change, booting from SDK firmware images can bring up all
four cores successfully.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c              |  18 ++
 hw/misc/phytium_e2000_mhu.c         | 384 ++++++++++++++++++++++++++--
 hw/misc/phytium_e2000_pbr.c         | 153 ++++++++++-
 include/hw/misc/phytium_e2000_mhu.h |   9 +
 include/hw/misc/phytium_e2000_pbr.h |   7 +
 5 files changed, 543 insertions(+), 28 deletions(-)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 1c337bf36e..1f2642df13 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -579,12 +579,30 @@ static void phytium_e2000_create_mhu(PhytiumE2000State *s)
 {
     DeviceState *dev = qdev_new(TYPE_PHYTIUM_E2000_MHU);
     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    int i;
 
     /*
      * MHU is the notification side of the SCMI transport. The message body
      * remains in SCP SRAM, so this device only owns the doorbell aperture.
      */
     object_property_add_child(OBJECT(s), "mhu", OBJECT(dev));
+    if (phytium_e2000_pbr_firmware_loaded(s->pbr)) {
+        /*
+         * PBR validates the firmware-specific BL1 handoff and owns all FIP
+         * interpretation.  Pass only the resulting slot address to MHU; the
+         * transport must not parse firmware or assume a PBF build layout.
+         * Direct Linux boot has no firmware SCMI CPU_ON path and therefore
+         * intentionally leaves the slot unset.
+         */
+        phytium_e2000_mhu_set_secondary_vector_slot(
+            PHYTIUM_E2000_MHU(dev),
+            phytium_e2000_pbr_secondary_vector_slot(s->pbr));
+    }
+    for (i = 0; i < MACHINE(s)->smp.cpus; i++) {
+        phytium_e2000_mhu_connect_cpu(PHYTIUM_E2000_MHU(dev), i,
+                                      phytium_e2000_cpu_mp_affinity(i),
+                                      s->cpu[i]);
+    }
     sysbus_realize_and_unref(sbd, &error_fatal);
     sysbus_mmio_map_overlap(sbd, 0, PHYTIUM_E2000_MHU_BASE, 2);
 }
diff --git a/hw/misc/phytium_e2000_mhu.c b/hw/misc/phytium_e2000_mhu.c
index 4ea23af900..554448398b 100644
--- a/hw/misc/phytium_e2000_mhu.c
+++ b/hw/misc/phytium_e2000_mhu.c
@@ -18,15 +18,40 @@
 
 #include "hw/core/register.h"
 #include "migration/vmstate.h"
+#include "qapi/error.h"
+#include "qemu/bitops.h"
 #include "qemu/module.h"
 #include "system/address-spaces.h"
+#include "target/arm/arm-powerctl.h"
+#include "target/arm/cpu.h"
 
 #define PHYTIUM_E2000_PBF_SCMI_MBOX_BASE  0x32a10400
 #define PHYTIUM_E2000_SCMI_STATUS_OFFSET  0x04
 #define PHYTIUM_E2000_SCMI_LEN_OFFSET     0x14
+#define PHYTIUM_E2000_SCMI_HEADER_OFFSET  0x18
 #define PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET 0x1c
 #define PHYTIUM_E2000_SCMI_STATUS_FREE    BIT(0)
 
+#define SCMI_MESSAGE_ID(header)    extract32((header), 0, 8)
+#define SCMI_PROTOCOL_ID(header)   extract32((header), 10, 8)
+#define SCMI_PROTOCOL_POWER_DOMAIN 0x11
+#define SCMI_PROTOCOL_PHYTIUM      0x81
+#define SCMI_POWER_STATE_SET       0x4
+#define SCMI_PHYTIUM_GET_PSOSTAT   0x3
+#define SCMI_POWER_STATE_TYPE      BIT(30)
+#define SCMI_POWER_STATE_ID_MASK   (SCMI_POWER_STATE_TYPE - 1)
+
+#define SCMI_SUCCESS            0
+#define SCMI_INVALID_PARAMETERS (-2)
+#define SCMI_GENERIC_ERROR      (-8)
+
+#define PHYTIUM_E2000_PBF_ROOT_ANCHOR       \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0xf00)
+#define PHYTIUM_E2000_CPU_TARGET_OFFSET     0x08
+#define PHYTIUM_E2000_CPU_LOCK_DEPTH_OFFSET 0x28
+#define PHYTIUM_E2000_CPU_LOCK_OWNER_OFFSET 0x30
+#define PHYTIUM_E2000_CPU_ON_COMPLETE       0xabcdef98
+
 /*
  * The SDK defines AP OS status/set/clear at 0x100/0x108/0x110 within a
  * channel. PBF selects the channel at MHU offset 0x200, producing the global
@@ -44,42 +69,308 @@ struct PhytiumE2000MHUState {
 
     uint32_t regs[PHYTIUM_E2000_MHU_R_MAX];
     RegisterInfo regs_info[PHYTIUM_E2000_MHU_R_MAX];
+    uint64_t cpu_mpidrs[PHYTIUM_E2000_MHU_MAX_CPUS];
+    CPUState *cpus[PHYTIUM_E2000_MHU_MAX_CPUS];
+    /* Firmware-owned slot address supplied by the PBR before realization */
+    hwaddr secondary_vector_slot;
+    unsigned int num_cpus;
 };
 
-static void phytium_e2000_mhu_complete_scmi(void)
+static bool phytium_e2000_phys_readl(hwaddr addr, uint32_t *value)
+{
+    uint8_t buf[sizeof(*value)];
+
+    if (address_space_read(&address_space_memory, addr,
+                           MEMTXATTRS_UNSPECIFIED, buf,
+                           sizeof(buf)) != MEMTX_OK) {
+        return false;
+    }
+    *value = ldl_le_p(buf);
+    return true;
+}
+
+static bool phytium_e2000_phys_readq(hwaddr addr, uint64_t *value)
+{
+    uint8_t buf[sizeof(*value)];
+
+    if (address_space_read(&address_space_memory, addr,
+                           MEMTXATTRS_UNSPECIFIED, buf,
+                           sizeof(buf)) != MEMTX_OK) {
+        return false;
+    }
+    *value = ldq_le_p(buf);
+    return true;
+}
+
+static bool phytium_e2000_phys_writel(hwaddr addr, uint32_t value)
+{
+    uint8_t buf[sizeof(value)];
+
+    stl_le_p(buf, value);
+    return address_space_write(&address_space_memory, addr,
+                               MEMTXATTRS_UNSPECIFIED, buf,
+                               sizeof(buf)) == MEMTX_OK;
+}
+
+static bool phytium_e2000_phys_writeq(hwaddr addr, uint64_t value)
+{
+    uint8_t buf[sizeof(value)];
+
+    stq_le_p(buf, value);
+    return address_space_write(&address_space_memory, addr,
+                               MEMTXATTRS_UNSPECIFIED, buf,
+                               sizeof(buf)) == MEMTX_OK;
+}
+
+static uint32_t phytium_e2000_scmi_readl(hwaddr offset)
 {
     uint8_t buf[sizeof(uint32_t)];
-    uint32_t len;
 
-    /*
-     * Preserve the caller's message length, but reserve one status word for
-     * the minimal success response returned in the payload.
-     */
     address_space_read(&address_space_memory,
-                       PHYTIUM_E2000_PBF_SCMI_MBOX_BASE +
-                       PHYTIUM_E2000_SCMI_LEN_OFFSET,
+                       PHYTIUM_E2000_PBF_SCMI_MBOX_BASE + offset,
                        MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
-    len = MAX(ldl_le_p(buf), (uint32_t)sizeof(uint32_t));
+    return ldl_le_p(buf);
+}
 
-    stl_le_p(buf, 0);
-    address_space_write(&address_space_memory,
-                        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE +
-                        PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET,
-                        MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
-    stl_le_p(buf, len);
+static void phytium_e2000_scmi_writel(hwaddr offset, uint32_t value)
+{
+    uint8_t buf[sizeof(uint32_t)];
+
+    stl_le_p(buf, value);
     address_space_write(&address_space_memory,
-                        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE +
-                        PHYTIUM_E2000_SCMI_LEN_OFFSET,
+                        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE + offset,
                         MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
-    stl_le_p(buf, PHYTIUM_E2000_SCMI_STATUS_FREE);
+}
+
+static void phytium_e2000_scmi_publish(uint32_t len)
+{
+    phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_LEN_OFFSET, len);
     /*
      * Publish the free bit last. PBF polls this field as the ownership handoff
      * and may consume the response immediately after observing it.
      */
-    address_space_write(&address_space_memory,
-                        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE +
-                        PHYTIUM_E2000_SCMI_STATUS_OFFSET,
-                        MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
+    phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_STATUS_OFFSET,
+                              PHYTIUM_E2000_SCMI_STATUS_FREE);
+}
+
+static bool phytium_e2000_mhu_cpu_is_on(PhytiumE2000MHUState *s,
+                                        uint64_t mpidr)
+{
+    unsigned int i;
+
+    for (i = 0; i < s->num_cpus; i++) {
+        if ((s->cpu_mpidrs[i] & 0xffff) == (mpidr & 0xffff)) {
+            return ARM_CPU(s->cpus[i])->power_state == PSCI_ON;
+        }
+    }
+
+    return false;
+}
+
+static bool phytium_e2000_mhu_prepare_cpu_on(PhytiumE2000MHUState *s,
+                                             uint64_t mpidr,
+                                             uint64_t *runtime_cpu_control,
+                                             uint64_t *secondary_entry)
+{
+    uint64_t pbr_cpu_control;
+    uint64_t runtime_root;
+    uint64_t target;
+    uint64_t magic;
+    uint32_t first_instruction;
+    uint32_t lock_depth;
+    uint32_t lock_owner;
+
+    /*
+     * BL1 and EL3 deliberately use different roots after PBF relocates the
+     * runtime object graph. BL1's reset trampoline follows the PBR-owned root
+     * at 0x30c01000, while EL3 follows the relocatable anchor at 0x30c00f00.
+     * The emulated SCP therefore copies the requested MPIDR into BL1's
+     * control block before releasing the secondary CPU.
+     *
+     * The secondary-vector slot itself was recovered and validated while PBR
+     * parsed BL1.  Read the slot for every CPU_ON request rather than caching
+     * its contents: BL1 first publishes the resident EL3 entry at runtime and
+     * the temporary BL1 mapping may subsequently be overwritten.
+     */
+    if (!phytium_e2000_phys_readq(PHYTIUM_E2000_PBR_ROOT,
+                                  &pbr_cpu_control) ||
+        pbr_cpu_control != PHYTIUM_E2000_PBR_CPU_CONTROL ||
+        !phytium_e2000_phys_readq(pbr_cpu_control, &magic) ||
+        magic != PHYTIUM_E2000_PBR_CPU_CONTROL_MAGIC ||
+        !phytium_e2000_phys_readq(PHYTIUM_E2000_PBF_ROOT_ANCHOR,
+                                  &runtime_root) ||
+        runtime_root == PHYTIUM_E2000_PBR_ROOT ||
+        !QEMU_IS_ALIGNED(runtime_root, sizeof(uint64_t)) ||
+        !phytium_e2000_phys_readq(runtime_root, runtime_cpu_control) ||
+        !QEMU_IS_ALIGNED(*runtime_cpu_control, sizeof(uint64_t)) ||
+        *runtime_cpu_control < PHYTIUM_E2000_PBR_BOOT_SRAM_BASE ||
+        *runtime_cpu_control > PHYTIUM_E2000_PBR_BOOT_SRAM_BASE +
+                               PHYTIUM_E2000_PBR_BOOT_SRAM_SIZE -
+                               (PHYTIUM_E2000_CPU_LOCK_OWNER_OFFSET +
+                                sizeof(uint32_t)) ||
+        !phytium_e2000_phys_readq(*runtime_cpu_control +
+                                  PHYTIUM_E2000_CPU_TARGET_OFFSET,
+                                  &target) ||
+        (target & 0xffff) != (mpidr & 0xffff) ||
+        !phytium_e2000_phys_readl(*runtime_cpu_control +
+                                  PHYTIUM_E2000_CPU_LOCK_DEPTH_OFFSET,
+                                  &lock_depth) ||
+        !phytium_e2000_phys_readl(*runtime_cpu_control +
+                                  PHYTIUM_E2000_CPU_LOCK_OWNER_OFFSET,
+                                  &lock_owner) ||
+        !s->secondary_vector_slot ||
+        !phytium_e2000_phys_readq(s->secondary_vector_slot,
+                                  secondary_entry) ||
+        !QEMU_IS_ALIGNED(*secondary_entry, sizeof(uint32_t)) ||
+        !phytium_e2000_phys_readl(*secondary_entry, &first_instruction) ||
+        first_instruction == 0 || first_instruction == UINT32_MAX) {
+        return false;
+    }
+
+    /*
+     * EL3 records three nested power-domain lock levels for the first CPU_ON.
+     * Later requests observe the already retired zero state. The lock owner
+     * must name a CPU which is currently powered on.
+     */
+    if (!((lock_depth == 3 &&
+           phytium_e2000_mhu_cpu_is_on(s, lock_owner)) ||
+          (lock_depth == 0 && lock_owner == 0))) {
+        return false;
+    }
+
+    return phytium_e2000_phys_writeq(
+        pbr_cpu_control + PHYTIUM_E2000_CPU_TARGET_OFFSET, mpidr & 0xffff);
+}
+
+static bool phytium_e2000_mhu_complete_cpu_on(uint64_t runtime_cpu_control)
+{
+    /*
+     * EL3 polls its relocated control block for 0xabcdef98 after issuing the
+     * SCMI request. The secondary's on-finish hook begins by acquiring the
+     * same reentrant lock and writes the completion value only afterwards.
+     * The power-controller handoff must therefore retire the primary's lock
+     * state before publishing completion, or both CPUs wait on each other.
+     *
+     * This ordering and the offsets were recovered from the Phytium Pi and
+     * COMe SDK BL1/EL3 binaries; they are not described by the published PBF
+     * ABI.
+     */
+    return phytium_e2000_phys_writel(
+               runtime_cpu_control + PHYTIUM_E2000_CPU_LOCK_DEPTH_OFFSET, 0) &&
+           phytium_e2000_phys_writel(
+               runtime_cpu_control + PHYTIUM_E2000_CPU_LOCK_OWNER_OFFSET, 0) &&
+           phytium_e2000_phys_writeq(
+               runtime_cpu_control + PHYTIUM_E2000_CPU_TARGET_OFFSET,
+               PHYTIUM_E2000_CPU_ON_COMPLETE);
+}
+
+static uint32_t phytium_e2000_mhu_psostat(PhytiumE2000MHUState *s)
+{
+    uint32_t status = 0;
+    unsigned int i;
+
+    /*
+     * Phytium PBF's vendor SCMI query returns two bits per E2000 core in the
+     * SoC's physical CPU order. A value of 2 denotes powered off and 0
+     * denotes powered on. This produces the 0x8a reset value observed on
+     * hardware when MPIDR 0x200 is the only running core.
+     */
+    for (i = 0; i < s->num_cpus; i++) {
+        if (ARM_CPU(s->cpus[i])->power_state != PSCI_ON) {
+            status |= 2U << (2 * i);
+        }
+    }
+
+    return status;
+}
+
+static int32_t phytium_e2000_mhu_set_power_state(PhytiumE2000MHUState *s)
+{
+    uint32_t domain_id = phytium_e2000_scmi_readl(
+        PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET + 4);
+    uint32_t power_state = phytium_e2000_scmi_readl(
+        PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET + 8);
+    uint32_t core_mask = power_state & SCMI_POWER_STATE_ID_MASK;
+    bool power_on = power_state & SCMI_POWER_STATE_TYPE;
+    uint64_t runtime_cpu_control;
+    uint64_t secondary_entry;
+    unsigned int i;
+    int ret;
+
+    /*
+     * The E2000 firmware encodes Aff1 as the SCMI power domain and a single
+     * Aff0 bit in the vendor power-state ID. This relation is visible in the
+     * PBF request builder: MPIDR 0x201 becomes domain 2, state 0x40000002;
+     * MPIDR 0x100 becomes domain 1, state 0x40000001.
+     */
+    if (!is_power_of_2(core_mask)) {
+        return SCMI_INVALID_PARAMETERS;
+    }
+
+    for (i = 0; i < s->num_cpus; i++) {
+        uint64_t mpidr = s->cpu_mpidrs[i];
+        unsigned int aff0 = extract64(mpidr, 0, 8);
+        unsigned int aff1 = extract64(mpidr, 8, 8);
+
+        if (aff0 >= 30 || aff1 != domain_id || BIT(aff0) != core_mask) {
+            continue;
+        }
+
+        if (power_on) {
+            if (!phytium_e2000_mhu_prepare_cpu_on(
+                    s, mpidr, &runtime_cpu_control, &secondary_entry)) {
+                return SCMI_GENERIC_ERROR;
+            }
+            /*
+             * BL1 publishes the resident EL3 secondary entry in a fixed
+             * vector slot. Some firmware reuses the temporary BL1 image
+             * before Linux requests CPU_ON, so reset directly into the
+             * published resident entry rather than a BL1 flash offset.
+             */
+            object_property_set_int(OBJECT(s->cpus[i]), "rvbar",
+                                    secondary_entry, &error_abort);
+            ret = arm_set_cpu_on_and_reset(mpidr);
+            if (ret == QEMU_ARM_POWERCTL_RET_SUCCESS &&
+                !phytium_e2000_mhu_complete_cpu_on(runtime_cpu_control)) {
+                return SCMI_GENERIC_ERROR;
+            }
+        } else {
+            ret = arm_set_cpu_off(mpidr);
+        }
+        return ret == QEMU_ARM_POWERCTL_RET_SUCCESS ?
+               SCMI_SUCCESS : SCMI_GENERIC_ERROR;
+    }
+
+    return SCMI_INVALID_PARAMETERS;
+}
+
+static void phytium_e2000_mhu_complete_scmi(PhytiumE2000MHUState *s)
+{
+    uint32_t header = phytium_e2000_scmi_readl(
+        PHYTIUM_E2000_SCMI_HEADER_OFFSET);
+    uint32_t len = MAX(phytium_e2000_scmi_readl(
+        PHYTIUM_E2000_SCMI_LEN_OFFSET), (uint32_t)sizeof(uint32_t));
+    int32_t scmi_status = SCMI_SUCCESS;
+
+    /*
+     * PBF issues clock and platform setup commands whose side effects do not
+     * affect modeled devices. Preserve their payload length and acknowledge
+     * them; only messages that change modeled CPU state need special handling.
+     */
+    if (SCMI_PROTOCOL_ID(header) == SCMI_PROTOCOL_PHYTIUM &&
+        SCMI_MESSAGE_ID(header) == SCMI_PHYTIUM_GET_PSOSTAT) {
+        phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET + 4,
+                                  phytium_e2000_mhu_psostat(s));
+        len = 3 * sizeof(uint32_t);
+    } else if (SCMI_PROTOCOL_ID(header) == SCMI_PROTOCOL_POWER_DOMAIN &&
+               SCMI_MESSAGE_ID(header) == SCMI_POWER_STATE_SET) {
+        scmi_status = phytium_e2000_mhu_set_power_state(s);
+        len = 2 * sizeof(uint32_t);
+    }
+
+    phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET,
+                              scmi_status);
+    phytium_e2000_scmi_publish(len);
 }
 
 void phytium_e2000_mhu_seed_mailbox(void)
@@ -88,18 +379,22 @@ void phytium_e2000_mhu_seed_mailbox(void)
      * PBR leaves the shared channel available before releasing PBF. Seed the
      * same ownership and success state even before the first doorbell write.
      */
-    phytium_e2000_mhu_complete_scmi();
+    phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET,
+                              SCMI_SUCCESS);
+    phytium_e2000_scmi_publish(sizeof(uint32_t));
 }
 
 static void phytium_e2000_mhu_doorbell_post_write(RegisterInfo *reg,
                                                   uint64_t value)
 {
+    PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(reg->opaque);
+
     /*
      * Complete requests synchronously because no separate SCP CPU executes in
      * this model. Zero writes only update doorbell storage.
      */
     if (value) {
-        phytium_e2000_mhu_complete_scmi();
+        phytium_e2000_mhu_complete_scmi(s);
     }
 }
 
@@ -138,6 +433,36 @@ static void phytium_e2000_mhu_reset(DeviceState *dev)
     }
 }
 
+void phytium_e2000_mhu_connect_cpu(PhytiumE2000MHUState *s,
+                                   unsigned int index, uint64_t mpidr,
+                                   CPUState *cpu)
+{
+    g_assert(!DEVICE(s)->realized);
+    g_assert(index < PHYTIUM_E2000_MHU_MAX_CPUS);
+    g_assert(index == s->num_cpus);
+    g_assert(cpu);
+
+    object_ref(OBJECT(cpu));
+    s->cpus[index] = cpu;
+    s->cpu_mpidrs[index] = mpidr;
+    s->num_cpus++;
+}
+
+void phytium_e2000_mhu_set_secondary_vector_slot(PhytiumE2000MHUState *s,
+                                                 hwaddr slot)
+{
+    /*
+     * This is immutable firmware configuration, not guest-programmable MHU
+     * state.  Requiring it before realization prevents CPU_ON from observing
+     * a partially configured transport.
+     */
+    g_assert(!DEVICE(s)->realized);
+    g_assert(!s->secondary_vector_slot);
+    g_assert(slot && QEMU_IS_ALIGNED(slot, sizeof(uint64_t)));
+
+    s->secondary_vector_slot = slot;
+}
+
 static void phytium_e2000_mhu_init(Object *obj)
 {
     PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(obj);
@@ -150,6 +475,16 @@ static void phytium_e2000_mhu_init(Object *obj)
     sysbus_init_mmio(SYS_BUS_DEVICE(obj), &reg_array->mem);
 }
 
+static void phytium_e2000_mhu_finalize(Object *obj)
+{
+    PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(obj);
+    unsigned int i;
+
+    for (i = 0; i < s->num_cpus; i++) {
+        object_unref(OBJECT(s->cpus[i]));
+    }
+}
+
 static const VMStateDescription phytium_e2000_mhu_vmsd = {
     .name = TYPE_PHYTIUM_E2000_MHU,
     .version_id = 1,
@@ -174,6 +509,7 @@ static const TypeInfo phytium_e2000_mhu_info = {
     .parent = TYPE_SYS_BUS_DEVICE,
     .instance_size = sizeof(PhytiumE2000MHUState),
     .instance_init = phytium_e2000_mhu_init,
+    .instance_finalize = phytium_e2000_mhu_finalize,
     .class_init = phytium_e2000_mhu_class_init,
 };
 
diff --git a/hw/misc/phytium_e2000_pbr.c b/hw/misc/phytium_e2000_pbr.c
index 4485f073e1..d6f7b3b26e 100644
--- a/hw/misc/phytium_e2000_pbr.c
+++ b/hw/misc/phytium_e2000_pbr.c
@@ -55,9 +55,38 @@ REG32(ETH_TRAINING_STATUS, 0x60)
 #define PHYTIUM_E2000_BL1_SIZE                      0x00090000
 #define PHYTIUM_E2000_PBR_BL1_RUNTIME_BASE          0xf8c40000
 
+/*
+ * This is not a published PBF structure.  It is the smallest instruction and
+ * literal window that identifies the secondary-CPU handoff in each inspected
+ * BL1 image.  Keep the offsets named so the checks below document which parts
+ * of the recovered sequence are treated as its compatibility contract.
+ *
+ *   +0x00  BL  <select/check primary CPU>
+ *   +0x04  CBZ W0, <primary path>
+ *   +0x10  MRS X0, MPIDR_EL1
+ *   +0x48  literal: PHYTIUM_E2000_PBR_ROOT
+ *   +0x50  literal: address of the runtime secondary-vector slot
+ *
+ * Instructions between these anchors may change between compiler builds and
+ * are deliberately not matched.
+ */
+#define PHYTIUM_E2000_BL1_HANDOFF_SIZE              0x58
+#define PHYTIUM_E2000_BL1_HANDOFF_BL_OFFSET         0x00
+#define PHYTIUM_E2000_BL1_HANDOFF_CBZ_OFFSET        0x04
+#define PHYTIUM_E2000_BL1_HANDOFF_MPIDR_OFFSET      0x10
+#define PHYTIUM_E2000_BL1_HANDOFF_ROOT_OFFSET       0x48
+#define PHYTIUM_E2000_BL1_HANDOFF_SLOT_OFFSET       0x50
+
+/* AArch64 BL has a six-bit opcode and a build-dependent imm26 displacement */
+#define PHYTIUM_E2000_BL1_HANDOFF_BRANCH_MASK       0xfc000000
+#define PHYTIUM_E2000_BL1_HANDOFF_BRANCH            0x94000000
+
+/* Match CBZ W0 while ignoring its build-dependent imm19 displacement */
+#define PHYTIUM_E2000_BL1_HANDOFF_CBZ_W0_MASK       0xff00001f
+#define PHYTIUM_E2000_BL1_HANDOFF_CBZ_W0            0x34000000
+#define PHYTIUM_E2000_BL1_SECONDARY_ENTRY_MPIDR     0xd53800a0
+
 #define PHYTIUM_E2000_PBR_ROOT_OFFSET 0x00000f00
-#define PHYTIUM_E2000_PBR_ROOT        \
-    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x1000)
 #define PHYTIUM_E2000_PBR_PARAM_NODE  \
     (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x10a0)
 #define PHYTIUM_E2000_PBR_PARAM_SLOT  \
@@ -249,6 +278,8 @@ struct PhytiumE2000PBRState {
     unsigned int num_cpus;
     bool firmware_loaded;
     int32_t primary_cpu;
+    /* Physical address of the vector slot recovered from the BL1 handoff */
+    hwaddr secondary_vector_slot;
     uint32_t parameter_sizes[PHYTIUM_E2000_PBF_PARAM_COUNT];
     PhytiumE2000TfaIoHandoff tfa_io;
     uint8_t *iacc_image;
@@ -752,6 +783,89 @@ static bool phytium_e2000_pbr_tfa_io_handoff_valid(
     return true;
 }
 
+static bool phytium_e2000_pbr_secondary_handoff(const uint8_t *bl1,
+                                                hwaddr *vector_slot,
+                                                Error **errp)
+{
+    /* Zero is also the invalid-slot value, so it can represent no match */
+    hwaddr match = 0;
+    size_t offset;
+
+    /*
+     * The 2 GiB and 4 GiB Phytium Pi SDK images and the COMe SDK image all
+     * expose the same BL1 secondary reset ABI. It checks whether this CPU is
+     * the PBF-selected primary, matches the requested MPIDR through the PBR
+     * CPU-control block, and branches through a runtime entry pointer.
+     *
+     * Compiler placement and branch displacements are not part of that ABI.
+     * Scan BL1 for its invariant instruction and PBR-root anchors, ignoring
+     * the immediate fields of BL and CBZ, then obtain the vector-slot address
+     * from the adjacent literal. This permits another compatible PBF build to
+     * move the trampoline or its published entry slot without adding a QEMU
+     * constant. The interface specifications do not publish this sequence,
+     * so reject missing, ambiguous, or malformed matches.
+     */
+    /* AArch64 instructions are four-byte aligned throughout the BL1 image */
+    for (offset = 0; offset <= PHYTIUM_E2000_BL1_SIZE -
+                                   PHYTIUM_E2000_BL1_HANDOFF_SIZE;
+         offset += 4) {
+        const uint8_t *candidate = bl1 + offset;
+        hwaddr slot;
+
+        /*
+         * BL and CBZ establish the control-flow shape but their relative
+         * targets move with the code.  The exact MRS instruction establishes
+         * that the path is selecting a physical CPU.  Finally, the PBR root
+         * literal ties the otherwise generic instruction sequence to this
+         * firmware handoff rather than to an unrelated BL1 routine.
+         */
+        if ((ldl_le_p(candidate +
+                      PHYTIUM_E2000_BL1_HANDOFF_BL_OFFSET) &
+             PHYTIUM_E2000_BL1_HANDOFF_BRANCH_MASK) !=
+                PHYTIUM_E2000_BL1_HANDOFF_BRANCH ||
+            (ldl_le_p(candidate +
+                      PHYTIUM_E2000_BL1_HANDOFF_CBZ_OFFSET) &
+             PHYTIUM_E2000_BL1_HANDOFF_CBZ_W0_MASK) !=
+                PHYTIUM_E2000_BL1_HANDOFF_CBZ_W0 ||
+            ldl_le_p(candidate +
+                     PHYTIUM_E2000_BL1_HANDOFF_MPIDR_OFFSET) !=
+                PHYTIUM_E2000_BL1_SECONDARY_ENTRY_MPIDR ||
+            ldq_le_p(candidate +
+                     PHYTIUM_E2000_BL1_HANDOFF_ROOT_OFFSET) !=
+                PHYTIUM_E2000_PBR_ROOT) {
+            continue;
+        }
+
+        /*
+         * The literal contains the slot address, not the secondary entry.
+         * BL1 publishes the resident entry into that slot later at runtime.
+         */
+        slot = ldq_le_p(candidate +
+                        PHYTIUM_E2000_BL1_HANDOFF_SLOT_OFFSET);
+        if (!slot || !QEMU_IS_ALIGNED(slot, sizeof(uint64_t))) {
+            error_setg(errp, "PBR firmware BL1 secondary vector slot is "
+                       "invalid");
+            return false;
+        }
+        /* Multiple candidates would make the inferred ABI unsafe to use */
+        if (match) {
+            error_setg(errp, "PBR firmware BL1 secondary reset ABI is "
+                       "ambiguous");
+            return false;
+        }
+        match = slot;
+    }
+
+    if (!match) {
+        error_setg(errp, "PBR firmware BL1 secondary reset ABI is not "
+                   "recognized");
+        return false;
+    }
+
+    *vector_slot = match;
+    return true;
+}
+
 static bool phytium_e2000_pbr_parse_firmware(PhytiumE2000PBRState *s,
                                              const uint8_t *data,
                                              size_t size, Error **errp)
@@ -767,6 +881,18 @@ static bool phytium_e2000_pbr_parse_firmware(PhytiumE2000PBRState *s,
         return false;
     }
 
+    /*
+     * Discover the handoff while the complete FIP image is available.  Only
+     * its validated slot address is retained; the runtime entry is
+     * intentionally not cached because firmware does not publish it until
+     * after BL1 starts.
+     */
+    if (!phytium_e2000_pbr_secondary_handoff(
+            data + PHYTIUM_E2000_BL1_FLASH_OFFSET,
+            &s->secondary_vector_slot, errp)) {
+        return false;
+    }
+
     if (!phytium_e2000_pbr_parameter_valid(data, size,
                                            &phytium_e2000_pbf_summary,
                                            NULL, errp)) {
@@ -803,8 +929,7 @@ static bool phytium_e2000_pbr_parse_firmware(PhytiumE2000PBRState *s,
         return false;
     }
 
-    bl1_end = PHYTIUM_E2000_PBR_BL1_RUNTIME_BASE +
-              PHYTIUM_E2000_BL1_SIZE;
+    bl1_end = PHYTIUM_E2000_PBR_BL1_RUNTIME_BASE + PHYTIUM_E2000_BL1_SIZE;
     if (s->ram_size < bl1_end - s->ram_base) {
         error_setg(errp, "PBR firmware requires RAM to cover PBF runtime "
                    "address 0x%" HWADDR_PRIx "; use -m 2G",
@@ -927,6 +1052,19 @@ static void phytium_e2000_pbr_seed_shared(PhytiumE2000PBRState *s)
      */
     stq_le_p(sram + PHYTIUM_E2000_PBR_ROOT_OFFSET,
              PHYTIUM_E2000_PBR_ROOT);
+    /*
+     * This PBR-owned CPU-control block remains private to the BL1 reset
+     * trampoline after PBF relocates the EL3 object graph. The SCP copies a
+     * POWER_STATE_SET target to +0x08 before releasing a secondary. The
+     * leading 0xffaabbcc value is the reset-state sentinel polled by BL1.
+     * These pointer and sentinel values are present in all three inspected
+     * firmware families and independently in the earlier external Phytium Pi
+     * model.
+     */
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_ROOT - sram_base),
+             PHYTIUM_E2000_PBR_CPU_CONTROL);
+    stq_le_p(sram + (PHYTIUM_E2000_PBR_CPU_CONTROL - sram_base),
+             PHYTIUM_E2000_PBR_CPU_CONTROL_MAGIC);
     stq_le_p(sram + (PHYTIUM_E2000_PBR_ROOT - sram_base) + 0x10,
              PHYTIUM_E2000_PBR_PARAM_NODE);
     stq_le_p(sram + (PHYTIUM_E2000_PBR_PARAM_NODE - sram_base) + 0x18,
@@ -1095,6 +1233,13 @@ int phytium_e2000_pbr_primary_cpu(PhytiumE2000PBRState *s)
     return s->primary_cpu;
 }
 
+hwaddr phytium_e2000_pbr_secondary_vector_slot(PhytiumE2000PBRState *s)
+{
+    g_assert(s->firmware_loaded);
+    g_assert(s->secondary_vector_slot);
+    return s->secondary_vector_slot;
+}
+
 void phytium_e2000_pbr_connect_cpu(PhytiumE2000PBRState *s,
                                    unsigned int index, CPUState *cpu)
 {
diff --git a/include/hw/misc/phytium_e2000_mhu.h b/include/hw/misc/phytium_e2000_mhu.h
index 0527d6fe8d..013f55a820 100644
--- a/include/hw/misc/phytium_e2000_mhu.h
+++ b/include/hw/misc/phytium_e2000_mhu.h
@@ -12,14 +12,23 @@
 #ifndef HW_MISC_PHYTIUM_E2000_MHU_H
 #define HW_MISC_PHYTIUM_E2000_MHU_H
 
+#include "hw/core/cpu.h"
 #include "hw/core/sysbus.h"
+#include "hw/misc/phytium_e2000_pbr.h"
 #include "qom/object.h"
 
 #define TYPE_PHYTIUM_E2000_MHU "phytium-e2000-mhu"
 OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000MHUState, PHYTIUM_E2000_MHU)
 
 #define PHYTIUM_E2000_MHU_MMIO_SIZE 0x1000
+#define PHYTIUM_E2000_MHU_MAX_CPUS  4
 
 void phytium_e2000_mhu_seed_mailbox(void);
+void phytium_e2000_mhu_connect_cpu(PhytiumE2000MHUState *s,
+                                   unsigned int index, uint64_t mpidr,
+                                   CPUState *cpu);
+/* Configure the PBR-validated, firmware-owned secondary-vector slot */
+void phytium_e2000_mhu_set_secondary_vector_slot(PhytiumE2000MHUState *s,
+                                                 hwaddr slot);
 
 #endif
diff --git a/include/hw/misc/phytium_e2000_pbr.h b/include/hw/misc/phytium_e2000_pbr.h
index 890d419254..03f055dcd0 100644
--- a/include/hw/misc/phytium_e2000_pbr.h
+++ b/include/hw/misc/phytium_e2000_pbr.h
@@ -35,6 +35,11 @@ OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000PBRState, PHYTIUM_E2000_PBR)
 #define PHYTIUM_E2000_PBR_BOOT_SRAM_SIZE    0x00100000
 #define PHYTIUM_E2000_PBR_IACC_SIZE         0x08000000
 #define PHYTIUM_E2000_PBR_MAX_CPUS          4
+#define PHYTIUM_E2000_PBR_ROOT              \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x1000)
+#define PHYTIUM_E2000_PBR_CPU_CONTROL       \
+    (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0x2000)
+#define PHYTIUM_E2000_PBR_CPU_CONTROL_MAGIC 0xffaabbcc
 
 #define PHYTIUM_E2000_PBR_BOOT_MEDIA_QSPI   0x1
 #define PHYTIUM_E2000_PBR_BOOT_MEDIA_SD0    0x4
@@ -49,6 +54,8 @@ void phytium_e2000_pbr_configure(PhytiumE2000PBRState *s,
                                  unsigned int num_cpus);
 bool phytium_e2000_pbr_firmware_loaded(PhytiumE2000PBRState *s);
 int phytium_e2000_pbr_primary_cpu(PhytiumE2000PBRState *s);
+/* Return the firmware-owned slot address, not its runtime entry value */
+hwaddr phytium_e2000_pbr_secondary_vector_slot(PhytiumE2000PBRState *s);
 void phytium_e2000_pbr_connect_cpu(PhytiumE2000PBRState *s,
                                    unsigned int index, CPUState *cpu);
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 19/33] hw/arm: phytium: Select the Phytium E2000 PBR boot medium
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (15 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 18/33] hw/misc: Support Phytium E2000 SCMI CPU power control Bin Meng
@ 2026-09-03 11:24 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 20/33] hw/arm: phytium: Connect the Phytium E2000 I2C controller Bin Meng
                   ` (11 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:24 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

Introduce an abstract E2000 machine class and keep the Phytium Pi
board strap fixed to SD0. Pass the selected backend and boot-mode
property to the PBR device so firmware-visible boot-media state is
owned by PBR rather than a user-settable machine option.

The class-level selection also provides the board-specific hook
required by a later E2000Q COMe machine without allowing either
board to change its physical boot strap.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c | 84 +++++++++++++++++++++++++++++++-----------
 1 file changed, 62 insertions(+), 22 deletions(-)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 1f2642df13..fe362dd4bd 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -44,8 +44,12 @@
 #include "target/arm/cpu-qom.h"
 #include "target/arm/gtimer.h"
 
+#define TYPE_PHYTIUM_E2000_MACHINE \
+    MACHINE_TYPE_NAME("phytium-e2000-base")
+OBJECT_DECLARE_TYPE(PhytiumE2000State, PhytiumE2000MachineClass,
+                    PHYTIUM_E2000_MACHINE)
+
 #define TYPE_PHYTIUM_PI MACHINE_TYPE_NAME("phytium-pi")
-OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000State, PHYTIUM_PI)
 
 #define PHYTIUM_E2000_NUM_CPUS        4
 #define PHYTIUM_E2000_NUM_IRQS        256
@@ -100,7 +104,7 @@ enum {
 };
 
 struct PhytiumE2000State {
-    MachineState parent;
+    MachineState parent_obj;
     struct arm_boot_info bootinfo;
     DeviceState *gic;
     DeviceState *qspi;
@@ -113,6 +117,13 @@ struct PhytiumE2000State {
     MemoryRegion ram_high;
 };
 
+struct PhytiumE2000MachineClass {
+    MachineClass parent_class;
+
+    const char *machine_name;
+    const char *pbr_boot_mode;
+};
+
 /*
  * Keep the physical addresses used by the vendor firmware even before every
  * device behind them is modeled. In particular, boot SRAM carries PBR/PBF
@@ -436,11 +447,16 @@ static void phytium_e2000_create_pcie(PhytiumE2000State *s)
     }
 }
 
-static void phytium_e2000_reject_legacy_firmware(MachineState *ms)
+static void phytium_e2000_reject_legacy_firmware(
+    MachineState *ms, PhytiumE2000MachineClass *pemc)
 {
     if (ms->firmware || drive_get(IF_PFLASH, 0, 0)) {
-        error_report("phytium-pi: -bios and pflash firmware are not "
-                     "supported; use an if=sd,index=0 image");
+        error_report("%s: -bios and pflash firmware are not supported; "
+                     "use an if=%s,index=0 image",
+                     pemc->machine_name,
+                     !strcmp(pemc->pbr_boot_mode,
+                             PHYTIUM_E2000_PBR_BOOT_MODE_QSPI) ?
+                         "mtd" : "sd");
         exit(1);
     }
 }
@@ -509,13 +525,17 @@ static void phytium_e2000_create_qspi(PhytiumE2000State *s)
 static bool phytium_e2000_create_pbr(PhytiumE2000State *s)
 {
     MachineState *ms = MACHINE(s);
+    PhytiumE2000MachineClass *pemc =
+        PHYTIUM_E2000_MACHINE_GET_CLASS(s);
     DeviceState *dev = qdev_new(TYPE_PHYTIUM_E2000_PBR);
     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
     BlockBackend *boot_blk = NULL;
     uint64_t cpu_mpidrs[PHYTIUM_E2000_NUM_CPUS];
     int i;
 
-    if (!ms->kernel_filename) {
+    if (!ms->kernel_filename &&
+        !strcmp(pemc->pbr_boot_mode,
+                PHYTIUM_E2000_PBR_BOOT_MODE_SD0)) {
         boot_blk = phytium_e2000_sd_blk(0);
     }
 
@@ -523,8 +543,7 @@ static bool phytium_e2000_create_pbr(PhytiumE2000State *s)
         cpu_mpidrs[i] = phytium_e2000_cpu_mp_affinity(i);
     }
 
-    qdev_prop_set_string(dev, "boot-mode",
-                         PHYTIUM_E2000_PBR_BOOT_MODE_SD0);
+    qdev_prop_set_string(dev, "boot-mode", pemc->pbr_boot_mode);
     phytium_e2000_pbr_configure(PHYTIUM_E2000_PBR(dev), boot_blk,
                                 phytium_e2000_memmap[
                                     PHYTIUM_E2000_RAM].base,
@@ -735,19 +754,21 @@ static void phytium_e2000_create_cpus(PhytiumE2000State *s,
     }
 }
 
-static void phytium_pi_init(MachineState *ms)
+static void phytium_e2000_init(MachineState *ms)
 {
-    PhytiumE2000State *s = PHYTIUM_PI(ms);
+    PhytiumE2000State *s = PHYTIUM_E2000_MACHINE(ms);
+    PhytiumE2000MachineClass *pemc =
+        PHYTIUM_E2000_MACHINE_GET_CLASS(ms);
     bool firmware_loaded;
     int i;
 
     if (kvm_enabled()) {
-        error_report("phytium-pi: KVM is not supported");
+        error_report("%s: KVM is not supported", pemc->machine_name);
         exit(1);
     }
 
     if (ms->smp.cpus > PHYTIUM_E2000_NUM_CPUS) {
-        error_report("phytium-pi supports at most %d CPUs",
+        error_report("%s supports at most %d CPUs", pemc->machine_name,
                      PHYTIUM_E2000_NUM_CPUS);
         exit(1);
     }
@@ -755,11 +776,11 @@ static void phytium_pi_init(MachineState *ms)
     if (ms->ram_size >
         phytium_e2000_memmap[PHYTIUM_E2000_RAM].size +
         phytium_e2000_memmap[PHYTIUM_E2000_RAM_HIGH].size) {
-        error_report("phytium-pi supports at most 8 GiB RAM");
+        error_report("%s supports at most 8 GiB RAM", pemc->machine_name);
         exit(1);
     }
 
-    phytium_e2000_reject_legacy_firmware(ms);
+    phytium_e2000_reject_legacy_firmware(ms, pemc);
 
     phytium_e2000_create_ram(s);
     phytium_e2000_create_unimplemented();
@@ -822,7 +843,7 @@ static const CPUArchIdList *phytium_e2000_possible_cpu_arch_ids(MachineState *ms
     return ms->possible_cpus;
 }
 
-static void phytium_pi_class_init(ObjectClass *oc, const void *data)
+static void phytium_e2000_class_init(ObjectClass *oc, const void *data)
 {
     MachineClass *mc = MACHINE_CLASS(oc);
     static const char * const valid_cpu_types[] = {
@@ -831,8 +852,7 @@ static void phytium_pi_class_init(ObjectClass *oc, const void *data)
         NULL,
     };
 
-    mc->init = phytium_pi_init;
-    mc->desc = "Phytium Pi board (Phytium E2000Q)";
+    mc->init = phytium_e2000_init;
     mc->default_cpu_type = ARM_CPU_TYPE_NAME("phytium-ftc664");
     mc->valid_cpu_types = valid_cpu_types;
     mc->max_cpus = PHYTIUM_E2000_NUM_CPUS;
@@ -844,22 +864,42 @@ static void phytium_pi_class_init(ObjectClass *oc, const void *data)
     mc->default_ram_size = 2 * GiB;
     mc->default_ram_id = "phytium-e2000.ram";
     mc->minimum_page_bits = 12;
-    mc->block_default_type = IF_SD;
     mc->no_cdrom = 1;
     mc->possible_cpu_arch_ids = phytium_e2000_possible_cpu_arch_ids;
 }
 
+static void phytium_pi_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+    PhytiumE2000MachineClass *pemc =
+        PHYTIUM_E2000_MACHINE_CLASS(oc);
+
+    mc->desc = "Phytium Pi board (Phytium E2000Q)";
+    mc->block_default_type = IF_SD;
+    pemc->machine_name = "phytium-pi";
+    pemc->pbr_boot_mode = PHYTIUM_E2000_PBR_BOOT_MODE_SD0;
+}
+
+static const TypeInfo phytium_e2000_base_info = {
+    .name = TYPE_PHYTIUM_E2000_MACHINE,
+    .parent = TYPE_MACHINE,
+    .abstract = true,
+    .class_init = phytium_e2000_class_init,
+    .class_size = sizeof(PhytiumE2000MachineClass),
+    .instance_size = sizeof(PhytiumE2000State),
+};
+
 static const TypeInfo phytium_pi_info = {
     .name = TYPE_PHYTIUM_PI,
-    .parent = TYPE_MACHINE,
+    .parent = TYPE_PHYTIUM_E2000_MACHINE,
     .class_init = phytium_pi_class_init,
-    .instance_size = sizeof(PhytiumE2000State),
     .interfaces = aarch64_machine_interfaces,
 };
 
-static void phytium_pi_machine_init(void)
+static void phytium_e2000_machine_init(void)
 {
+    type_register_static(&phytium_e2000_base_info);
     type_register_static(&phytium_pi_info);
 }
 
-type_init(phytium_pi_machine_init);
+type_init(phytium_e2000_machine_init);
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 20/33] hw/arm: phytium: Connect the Phytium E2000 I2C controller
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (16 preceding siblings ...)
  2026-09-03 11:24 ` [PATCH 19/33] hw/arm: phytium: Select the Phytium E2000 PBR boot medium Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 21/33] hw/arm: phytium: Add Phytium E2000 xHCI controllers Bin Meng
                   ` (10 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Instantiate DesignWare-compatible I2C controller in the shared E2000
machine initialization and wire its documented interrupt.

Leave board-specific I2C slave devices unmodeled.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |  1 +
 hw/arm/phytium_e2000.c | 19 +++++++++++++++++++
 2 files changed, 20 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 891d09e86b..7edf1bcb21 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -133,6 +133,7 @@ config PHYTIUM_E2000
     imply PCI_DEVICES
     select ARM_GIC
     select CADENCE
+    select DESIGNWARE_I2C
     select DW_MCI
     select PCI_EXPRESS
     select PCI_EXPRESS_GENERIC_BRIDGE
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index fe362dd4bd..8194afb6c3 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -27,6 +27,7 @@
 #include "hw/core/qdev-properties.h"
 #include "hw/intc/arm_gicv3_common.h"
 #include "hw/intc/arm_gicv3_its_common.h"
+#include "hw/i2c/designware_i2c.h"
 #include "hw/misc/phytium_e2000_ddr.h"
 #include "hw/misc/phytium_e2000_mhu.h"
 #include "hw/misc/phytium_e2000_pbr.h"
@@ -78,6 +79,7 @@ enum {
     PHYTIUM_E2000_UART4,
     PHYTIUM_E2000_UART5,
     PHYTIUM_E2000_UART6,
+    PHYTIUM_E2000_I2C,
     PHYTIUM_E2000_CLK_CTRL,
     PHYTIUM_E2000_SYSTEM_CTRL,
     PHYTIUM_E2000_GIC_DIST,
@@ -141,6 +143,7 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_UART3] =          { 0x2800f000, 0x00001000 },
     [PHYTIUM_E2000_UART4] =          { 0x28014000, 0x00001000 },
     [PHYTIUM_E2000_UART5] =          { 0x2802a000, 0x00001000 },
+    [PHYTIUM_E2000_I2C] =            { 0x28030000, 0x00001000 },
     [PHYTIUM_E2000_UART6] =          { 0x28032000, 0x00001000 },
     [PHYTIUM_E2000_CLK_CTRL] =       { 0x28100000, 0x00001000 },
     [PHYTIUM_E2000_SYSTEM_CTRL] =    { 0x30000000, 0x00001000 },
@@ -182,6 +185,8 @@ static const int phytium_e2000_uart_irqmap[] = {
     [6] = 107,
 };
 
+static const int phytium_e2000_i2c_irq = 106;
+
 static const uint8_t phytium_e2000_gem_num_queues[] = { 8, 4, 4, 4 };
 
 static const int phytium_e2000_gem_irqmap[PHYTIUM_E2000_NUM_GEMS]
@@ -328,6 +333,19 @@ static void phytium_e2000_create_uart(PhytiumE2000State *s, int index)
         qdev_get_gpio_in(s->gic, phytium_e2000_uart_irqmap[index]));
 }
 
+static void phytium_e2000_create_i2c(PhytiumE2000State *s)
+{
+    DeviceState *dev = qdev_new(TYPE_DESIGNWARE_I2C);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    object_property_add_child(OBJECT(s), "i2c", OBJECT(dev));
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map_overlap(sbd, 0,
+        phytium_e2000_memmap[PHYTIUM_E2000_I2C].base, 1);
+    sysbus_connect_irq(sbd, 0,
+        qdev_get_gpio_in(s->gic, phytium_e2000_i2c_irq));
+}
+
 static void phytium_e2000_create_gem(PhytiumE2000State *s, int index)
 {
     DeviceState *dev = qdev_new(TYPE_CADENCE_GEM);
@@ -801,6 +819,7 @@ static void phytium_e2000_init(MachineState *ms)
     for (i = 0; i < PHYTIUM_E2000_NUM_UARTS; i++) {
         phytium_e2000_create_uart(s, i);
     }
+    phytium_e2000_create_i2c(s);
     for (i = 0; i < PHYTIUM_E2000_NUM_GEMS; i++) {
         phytium_e2000_create_gem(s, i);
     }
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 21/33] hw/arm: phytium: Add Phytium E2000 xHCI controllers
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (17 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 20/33] hw/arm: phytium: Connect the Phytium E2000 I2C controller Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 22/33] hw/misc: Model the Phytium E2000 random generator Bin Meng
                   ` (9 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Instantiate two sysbus xHCI controllers at the documented addresses
and wire their single interrupts to the GIC. Match the platform quirk
by disabling streams and expose one paired USB 2.0/3.0 root port per
controller.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |  1 +
 hw/arm/phytium_e2000.c | 34 ++++++++++++++++++++++++++++++++++
 2 files changed, 35 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 7edf1bcb21..61b9ca021f 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -141,6 +141,7 @@ config PHYTIUM_E2000
     select PL011
     select SD
     select UNIMP
+    select USB_XHCI_SYSBUS
 
 config REALVIEW
     bool
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 8194afb6c3..32c2f23443 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -38,6 +38,7 @@
 #include "hw/sd/phytium_e2000_mci.h"
 #include "hw/sd/sd.h"
 #include "hw/ssi/phytium_qspi.h"
+#include "hw/usb/xhci.h"
 #include "net/net.h"
 #include "qobject/qlist.h"
 #include "qom/object.h"
@@ -57,6 +58,7 @@ OBJECT_DECLARE_TYPE(PhytiumE2000State, PhytiumE2000MachineClass,
 
 #define PHYTIUM_E2000_NUM_MCIS        2
 #define PHYTIUM_E2000_NUM_UARTS       7
+#define PHYTIUM_E2000_NUM_XHCIS       2
 #define PHYTIUM_E2000_NUM_GEMS        4
 
 #define PHYTIUM_E2000_MHU_BASE        0x32a00000
@@ -89,6 +91,8 @@ enum {
     PHYTIUM_E2000_PCIE_CTRL,
     PHYTIUM_E2000_PCIE_PHY_CTRL,
     PHYTIUM_E2000_BOARD_CTRL,
+    PHYTIUM_E2000_XHCI0,
+    PHYTIUM_E2000_XHCI1,
     PHYTIUM_E2000_GEM0,
     PHYTIUM_E2000_GEM1,
     PHYTIUM_E2000_GEM2,
@@ -154,6 +158,8 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_PCIE_CTRL] =      { 0x31000000, 0x00200000 },
     [PHYTIUM_E2000_PCIE_PHY_CTRL] =  { 0x31500000, 0x00001000 },
     [PHYTIUM_E2000_BOARD_CTRL] =     { 0x31800000, 0x01400000 },
+    [PHYTIUM_E2000_XHCI0] =          { 0x31a08000, 0x00018000 },
+    [PHYTIUM_E2000_XHCI1] =          { 0x31a28000, 0x00018000 },
     [PHYTIUM_E2000_GEM0] =           { 0x3200c000, 0x00002000 },
     [PHYTIUM_E2000_GEM1] =           { 0x3200e000, 0x00002000 },
     [PHYTIUM_E2000_GEM2] =           { 0x32010000, 0x00002000 },
@@ -187,6 +193,11 @@ static const int phytium_e2000_uart_irqmap[] = {
 
 static const int phytium_e2000_i2c_irq = 106;
 
+static const int phytium_e2000_xhci_irqmap[] = {
+    [0] = 16,
+    [1] = 17,
+};
+
 static const uint8_t phytium_e2000_gem_num_queues[] = { 8, 4, 4, 4 };
 
 static const int phytium_e2000_gem_irqmap[PHYTIUM_E2000_NUM_GEMS]
@@ -383,6 +394,26 @@ static void phytium_e2000_create_gem(PhytiumE2000State *s, int index)
     }
 }
 
+static void phytium_e2000_create_xhci(PhytiumE2000State *s, int index)
+{
+    int map_idx = PHYTIUM_E2000_XHCI0 + index;
+    DeviceState *dev = qdev_new(TYPE_XHCI_SYSBUS);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    g_autofree char *name = g_strdup_printf("xhci%d", index);
+
+    object_property_add_child(OBJECT(s), name, OBJECT(dev));
+    qdev_prop_set_uint32(dev, "intrs", 1);
+    qdev_prop_set_uint32(dev, "slots", XHCI_MAXSLOTS);
+    qdev_prop_set_uint32(dev, "p2", 1);
+    qdev_prop_set_uint32(dev, "p3", 1);
+    qdev_prop_set_bit(dev, "streams", false);
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map_overlap(
+        sbd, 0, phytium_e2000_memmap[map_idx].base, 2);
+    sysbus_connect_irq(sbd, 0,
+        qdev_get_gpio_in(s->gic, phytium_e2000_xhci_irqmap[index]));
+}
+
 /* The vendor DT maps root-bus INTx solely by pin */
 static int phytium_e2000_pcie_map_irq(PCIDevice *pdev, int pin)
 {
@@ -820,6 +851,9 @@ static void phytium_e2000_init(MachineState *ms)
         phytium_e2000_create_uart(s, i);
     }
     phytium_e2000_create_i2c(s);
+    for (i = 0; i < PHYTIUM_E2000_NUM_XHCIS; i++) {
+        phytium_e2000_create_xhci(s, i);
+    }
     for (i = 0; i < PHYTIUM_E2000_NUM_GEMS; i++) {
         phytium_e2000_create_gem(s, i);
     }
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 22/33] hw/misc: Model the Phytium E2000 random generator
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (18 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 21/33] hw/arm: phytium: Add Phytium E2000 xHCI controllers Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 23/33] hw/arm: phytium: Connect " Bin Meng
                   ` (8 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: qemu-arm

Implement the polled control, mode, status, data, and reseed registers
for the E2000 hardware RNG. Use the guest random source for data and
preserve deterministic replay.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/misc/meson.build                 |   1 +
 hw/misc/phytium_e2000_rng.c         | 182 ++++++++++++++++++++++++++++
 include/hw/misc/phytium_e2000_rng.h |  23 ++++
 3 files changed, 206 insertions(+)
 create mode 100644 hw/misc/phytium_e2000_rng.c
 create mode 100644 include/hw/misc/phytium_e2000_rng.h

diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 8db55e3ef9..7fafafd87c 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -19,6 +19,7 @@ system_ss.add(when: 'CONFIG_ARM11SCU', if_true: files('arm11scu.c'))
 system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_ddr.c'))
 system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_mhu.c'))
 system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_pbr.c'))
+system_ss.add(when: 'CONFIG_PHYTIUM_E2000', if_true: files('phytium_e2000_rng.c'))
 
 system_ss.add(when: 'CONFIG_ARM_V7M', if_true: files('armv7m_ras.c'))
 
diff --git a/hw/misc/phytium_e2000_rng.c b/hw/misc/phytium_e2000_rng.c
new file mode 100644
index 0000000000..6862fb9ec6
--- /dev/null
+++ b/hw/misc/phytium_e2000_rng.c
@@ -0,0 +1,182 @@
+/*
+ * Phytium E2000 random number generator
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/misc/phytium_e2000_rng.h"
+
+#include "migration/vmstate.h"
+#include "qemu/bitops.h"
+#include "qemu/guest-random.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+
+#define TRNG_CR             0x00
+#define TRNG_CR_RNGEN       BIT(0)
+#define TRNG_CR_ROSEN_MASK  MAKE_64BIT_MASK(4, 4)
+#define TRNG_CR_DIEN        BIT(16)
+#define TRNG_CR_ERIEN       BIT(17)
+#define TRNG_CR_IRQEN       BIT(24)
+#define TRNG_CR_MASK        (TRNG_CR_RNGEN | TRNG_CR_ROSEN_MASK | \
+                             TRNG_CR_DIEN | TRNG_CR_ERIEN | TRNG_CR_IRQEN)
+
+#define TRNG_MSEL           0x04
+#define TRNG_MSEL_PRNG      BIT(0)
+
+#define TRNG_SR             0x08
+#define TRNG_SR_DRDY        BIT(1)
+
+#define TRNG_DR             0x0c
+
+#define TRNG_RESEED         0x40
+
+struct PhytiumE2000RNGState {
+    SysBusDevice parent_obj;
+
+    MemoryRegion iomem;
+    uint32_t control;
+    uint32_t mode;
+};
+
+static bool phytium_e2000_rng_enabled(PhytiumE2000RNGState *s)
+{
+    return s->control & TRNG_CR_RNGEN;
+}
+
+static uint64_t phytium_e2000_rng_read(void *opaque, hwaddr offset,
+                                       unsigned int size)
+{
+    PhytiumE2000RNGState *s = opaque;
+    uint32_t value;
+
+    switch (offset) {
+    case TRNG_CR:
+        return s->control;
+    case TRNG_MSEL:
+        return s->mode;
+    case TRNG_SR:
+        /*
+         * Generation takes zero virtual time, so DRDY is reasserted as soon
+         * as the guest acknowledges the previous batch.
+         */
+        return phytium_e2000_rng_enabled(s) ? TRNG_SR_DRDY : 0;
+    case TRNG_DR:
+        if (!phytium_e2000_rng_enabled(s)) {
+            return 0;
+        }
+        qemu_guest_getrandom_nofail(&value, sizeof(value));
+        return value;
+    case TRNG_RESEED:
+        return 0;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: read from invalid offset 0x%" HWADDR_PRIx "\n",
+                      DEVICE(s)->canonical_path, offset);
+        return 0;
+    }
+}
+
+static void phytium_e2000_rng_write(void *opaque, hwaddr offset,
+                                    uint64_t value, unsigned int size)
+{
+    PhytiumE2000RNGState *s = opaque;
+
+    switch (offset) {
+    case TRNG_CR:
+        s->control = value & TRNG_CR_MASK;
+        break;
+    case TRNG_MSEL:
+        s->mode = value & TRNG_MSEL_PRNG;
+        break;
+    case TRNG_SR:
+        break;
+    case TRNG_DR:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: write to read-only data register\n",
+                      DEVICE(s)->canonical_path);
+        break;
+    case TRNG_RESEED:
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "%s: write to invalid offset 0x%" HWADDR_PRIx "\n",
+                      DEVICE(s)->canonical_path, offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps phytium_e2000_rng_ops = {
+    .read = phytium_e2000_rng_read,
+    .write = phytium_e2000_rng_write,
+    .endianness = DEVICE_LITTLE_ENDIAN,
+    .impl = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+    },
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 4,
+        .unaligned = false,
+    },
+};
+
+static void phytium_e2000_rng_reset(Object *obj, ResetType type)
+{
+    PhytiumE2000RNGState *s = PHYTIUM_E2000_RNG(obj);
+
+    s->control = 0;
+    s->mode = 0;
+}
+
+static const VMStateDescription phytium_e2000_rng_vmsd = {
+    .name = TYPE_PHYTIUM_E2000_RNG,
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .fields = (const VMStateField[]) {
+        VMSTATE_UINT32(control, PhytiumE2000RNGState),
+        VMSTATE_UINT32(mode, PhytiumE2000RNGState),
+        VMSTATE_END_OF_LIST()
+    },
+};
+
+static void phytium_e2000_rng_init(Object *obj)
+{
+    PhytiumE2000RNGState *s = PHYTIUM_E2000_RNG(obj);
+
+    memory_region_init_io(&s->iomem, obj, &phytium_e2000_rng_ops, s,
+                          TYPE_PHYTIUM_E2000_RNG,
+                          PHYTIUM_E2000_RNG_MMIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+}
+
+static void phytium_e2000_rng_class_init(ObjectClass *klass, const void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+    dc->desc = "Phytium E2000 random number generator";
+    dc->vmsd = &phytium_e2000_rng_vmsd;
+    rc->phases.enter = phytium_e2000_rng_reset;
+}
+
+static const TypeInfo phytium_e2000_rng_info = {
+    .name = TYPE_PHYTIUM_E2000_RNG,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(PhytiumE2000RNGState),
+    .instance_init = phytium_e2000_rng_init,
+    .class_init = phytium_e2000_rng_class_init,
+};
+
+static void phytium_e2000_rng_register_types(void)
+{
+    type_register_static(&phytium_e2000_rng_info);
+}
+
+type_init(phytium_e2000_rng_register_types)
diff --git a/include/hw/misc/phytium_e2000_rng.h b/include/hw/misc/phytium_e2000_rng.h
new file mode 100644
index 0000000000..d8c73e20a9
--- /dev/null
+++ b/include/hw/misc/phytium_e2000_rng.h
@@ -0,0 +1,23 @@
+/*
+ * Phytium E2000 random number generator
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ *   Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_MISC_PHYTIUM_E2000_RNG_H
+#define HW_MISC_PHYTIUM_E2000_RNG_H
+
+#include "hw/core/sysbus.h"
+#include "qom/object.h"
+
+#define TYPE_PHYTIUM_E2000_RNG "phytium-e2000-rng"
+OBJECT_DECLARE_SIMPLE_TYPE(PhytiumE2000RNGState, PHYTIUM_E2000_RNG)
+
+#define PHYTIUM_E2000_RNG_MMIO_SIZE 0x1000
+
+#endif
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 23/33] hw/arm: phytium: Connect the Phytium E2000 random generator
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (19 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 22/33] hw/misc: Model the Phytium E2000 random generator Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 24/33] hw/arm: phytium: Support Phytium E2000 direct Linux boot Bin Meng
                   ` (7 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

Instantiate the RNG in the shared E2000 machine initialization and map
it over the placeholder.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 32c2f23443..5dd91f662b 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -31,6 +31,7 @@
 #include "hw/misc/phytium_e2000_ddr.h"
 #include "hw/misc/phytium_e2000_mhu.h"
 #include "hw/misc/phytium_e2000_pbr.h"
+#include "hw/misc/phytium_e2000_rng.h"
 #include "hw/misc/unimp.h"
 #include "hw/net/cadence_gem.h"
 #include "hw/pci/pci.h"
@@ -97,6 +98,7 @@ enum {
     PHYTIUM_E2000_GEM1,
     PHYTIUM_E2000_GEM2,
     PHYTIUM_E2000_GEM3,
+    PHYTIUM_E2000_RNG_REGS,
     PHYTIUM_E2000_PLATFORM_CTRL,
     PHYTIUM_E2000_SECURITY_CTRL,
     PHYTIUM_E2000_CHIP_CTRL,
@@ -164,6 +166,7 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_GEM1] =           { 0x3200e000, 0x00002000 },
     [PHYTIUM_E2000_GEM2] =           { 0x32010000, 0x00002000 },
     [PHYTIUM_E2000_GEM3] =           { 0x32012000, 0x00002000 },
+    [PHYTIUM_E2000_RNG_REGS] =       { 0x32a36000, 0x00001000 },
     [PHYTIUM_E2000_PLATFORM_CTRL] =  { 0x32e40000, 0x00010000 },
     [PHYTIUM_E2000_SECURITY_CTRL] =  { 0x32f00000, 0x00001000 },
     [PHYTIUM_E2000_CHIP_CTRL] =      { 0x33000000, 0x00010000 },
@@ -630,6 +633,17 @@ static void phytium_e2000_create_ddr_status(PhytiumE2000State *s)
     sysbus_mmio_map_overlap(sbd, 0, PHYTIUM_E2000_DDR_STATUS_BASE, 2);
 }
 
+static void phytium_e2000_create_rng(PhytiumE2000State *s)
+{
+    DeviceState *dev = qdev_new(TYPE_PHYTIUM_E2000_RNG);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+    object_property_add_child(OBJECT(s), "rng", OBJECT(dev));
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map_overlap(sbd, 0,
+        phytium_e2000_memmap[PHYTIUM_E2000_RNG_REGS].base, 2);
+}
+
 static void phytium_e2000_create_scp_sram(PhytiumE2000State *s)
 {
     /*
@@ -842,6 +856,7 @@ static void phytium_e2000_init(MachineState *ms)
 
     phytium_e2000_create_scp_sram(s);
     phytium_e2000_create_mhu(s);
+    phytium_e2000_create_rng(s);
     phytium_e2000_create_ddr_status(s);
 
     for (i = 0; i < PHYTIUM_E2000_NUM_MCIS; i++) {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 24/33] hw/arm: phytium: Support Phytium E2000 direct Linux boot
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (20 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 23/33] hw/arm: phytium: Connect " Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 25/33] hw/arm: phytium: Add Phytium E2000Q COMe machine Bin Meng
                   ` (6 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

Allow the Phytium Pi machine to boot a Linux Image directly through
the generic Arm loader. Require the matching SDK phytiumpi_firefly.dtb
whenever -kernel is used.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c | 65 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 65 insertions(+)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 5dd91f662b..5d1e528cc1 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -15,6 +15,7 @@
 #include "qapi/error.h"
 #include "exec/cpu-common.h"
 #include "system/address-spaces.h"
+#include "system/device_tree.h"
 #include "system/kvm.h"
 #include "system/system.h"
 #include "exec/hwaddr.h"
@@ -129,6 +130,7 @@ struct PhytiumE2000MachineClass {
     MachineClass parent_class;
 
     const char *machine_name;
+    const char *direct_boot_dtb;
     const char *pbr_boot_mode;
 };
 
@@ -771,6 +773,61 @@ static void phytium_e2000_create_ram(PhytiumE2000State *s)
         phytium_e2000_memmap[PHYTIUM_E2000_RAM_HIGH].base, &s->ram_high);
 }
 
+static void phytium_e2000_add_memory_node(void *fdt, hwaddr base,
+                                          uint64_t size)
+{
+    uint32_t acells;
+    uint32_t scells;
+    g_autofree char *name = g_strdup_printf("/memory@%" HWADDR_PRIx, base);
+
+    acells = qemu_fdt_getprop_cell(fdt, "/", "#address-cells",
+                                   NULL, &error_fatal);
+    scells = qemu_fdt_getprop_cell(fdt, "/", "#size-cells",
+                                   NULL, &error_fatal);
+
+    qemu_fdt_add_subnode(fdt, name);
+    qemu_fdt_setprop_string(fdt, name, "device_type", "memory");
+    qemu_fdt_setprop_sized_cells(fdt, name, "reg",
+                                 acells, base, scells, size);
+}
+
+static void phytium_e2000_modify_dtb(const struct arm_boot_info *info,
+                                     void *fdt)
+{
+    uint64_t low_size =
+        MIN(info->ram_size, phytium_e2000_memmap[PHYTIUM_E2000_RAM].size);
+    uint64_t high_size = info->ram_size - low_size;
+    g_auto(GStrv) memory_nodes = NULL;
+    Error *err = NULL;
+    int i;
+
+    if (!high_size) {
+        return;
+    }
+
+    /*
+     * arm_load_dtb() normally describes RAM as one range beginning at
+     * loader_start. E2000 RAM above 2 GiB is instead mapped at 0x2000000000,
+     * beyond the PCIe aperture. Replace the generic range so Linux never
+     * treats the intervening address-space hole as RAM.
+     */
+    memory_nodes = qemu_fdt_node_unit_path(fdt, "memory", &err);
+    if (err) {
+        error_report_err(err);
+        exit(1);
+    }
+    for (i = 0; memory_nodes[i]; i++) {
+        if (g_str_has_prefix(memory_nodes[i], "/memory")) {
+            qemu_fdt_nop_node(fdt, memory_nodes[i]);
+        }
+    }
+
+    phytium_e2000_add_memory_node(
+        fdt, phytium_e2000_memmap[PHYTIUM_E2000_RAM].base, low_size);
+    phytium_e2000_add_memory_node(
+        fdt, phytium_e2000_memmap[PHYTIUM_E2000_RAM_HIGH].base, high_size);
+}
+
 static void phytium_e2000_create_cpus(PhytiumE2000State *s,
                                       bool firmware_loaded)
 {
@@ -830,6 +887,12 @@ static void phytium_e2000_init(MachineState *ms)
         exit(1);
     }
 
+    if (ms->kernel_filename && !ms->dtb) {
+        error_report("%s: direct Linux boot requires the SDK %s via -dtb",
+                     pemc->machine_name, pemc->direct_boot_dtb);
+        exit(1);
+    }
+
     if (ms->smp.cpus > PHYTIUM_E2000_NUM_CPUS) {
         error_report("%s supports at most %d CPUs", pemc->machine_name,
                      PHYTIUM_E2000_NUM_CPUS);
@@ -880,6 +943,7 @@ static void phytium_e2000_init(MachineState *ms)
     s->bootinfo.loader_start = phytium_e2000_memmap[PHYTIUM_E2000_RAM].base;
     s->bootinfo.psci_conduit = QEMU_PSCI_CONDUIT_SMC;
     s->bootinfo.firmware_loaded = firmware_loaded;
+    s->bootinfo.modify_dtb = phytium_e2000_modify_dtb;
     arm_load_kernel(ARM_CPU(first_cpu), ms, &s->bootinfo);
 }
 
@@ -945,6 +1009,7 @@ static void phytium_pi_class_init(ObjectClass *oc, const void *data)
     mc->desc = "Phytium Pi board (Phytium E2000Q)";
     mc->block_default_type = IF_SD;
     pemc->machine_name = "phytium-pi";
+    pemc->direct_boot_dtb = "phytiumpi_firefly.dtb";
     pemc->pbr_boot_mode = PHYTIUM_E2000_PBR_BOOT_MODE_SD0;
 }
 
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 25/33] hw/arm: phytium: Add Phytium E2000Q COMe machine
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (21 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 24/33] hw/arm: phytium: Support Phytium E2000 direct Linux boot Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 27/33] hw/arm: phytium: Connect the Phytium E2000Q COMe QSPI flash Bin Meng
                   ` (5 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

Add the Phytium E2000Q COMe board as a second child of the shared
E2000 machine class. Keep Phytium Pi fixed to SD0, while COMe selects
QSPI0 firmware and requires its own DTB for direct Linux boot.

Instantiate the same E2000Q SoC controllers for both boards.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c | 23 ++++++++++++++++++++++-
 1 file changed, 22 insertions(+), 1 deletion(-)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 5d1e528cc1..5a2155ee32 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -53,7 +53,8 @@
 OBJECT_DECLARE_TYPE(PhytiumE2000State, PhytiumE2000MachineClass,
                     PHYTIUM_E2000_MACHINE)
 
-#define TYPE_PHYTIUM_PI MACHINE_TYPE_NAME("phytium-pi")
+#define TYPE_PHYTIUM_PI         MACHINE_TYPE_NAME("phytium-pi")
+#define TYPE_PHYTIUM_E2000_COME MACHINE_TYPE_NAME("phytium-e2000-come")
 
 #define PHYTIUM_E2000_NUM_CPUS        4
 #define PHYTIUM_E2000_NUM_IRQS        256
@@ -1013,6 +1014,18 @@ static void phytium_pi_class_init(ObjectClass *oc, const void *data)
     pemc->pbr_boot_mode = PHYTIUM_E2000_PBR_BOOT_MODE_SD0;
 }
 
+static void phytium_e2000_come_class_init(ObjectClass *oc, const void *data)
+{
+    MachineClass *mc = MACHINE_CLASS(oc);
+    PhytiumE2000MachineClass *pemc =
+        PHYTIUM_E2000_MACHINE_CLASS(oc);
+
+    mc->desc = "Phytium E2000Q COMe Development Board";
+    pemc->machine_name = "phytium-e2000-come";
+    pemc->direct_boot_dtb = "e2000q-come-board.dtb";
+    pemc->pbr_boot_mode = PHYTIUM_E2000_PBR_BOOT_MODE_QSPI;
+}
+
 static const TypeInfo phytium_e2000_base_info = {
     .name = TYPE_PHYTIUM_E2000_MACHINE,
     .parent = TYPE_MACHINE,
@@ -1029,10 +1042,18 @@ static const TypeInfo phytium_pi_info = {
     .interfaces = aarch64_machine_interfaces,
 };
 
+static const TypeInfo phytium_e2000_come_info = {
+    .name = TYPE_PHYTIUM_E2000_COME,
+    .parent = TYPE_PHYTIUM_E2000_MACHINE,
+    .class_init = phytium_e2000_come_class_init,
+    .interfaces = aarch64_machine_interfaces,
+};
+
 static void phytium_e2000_machine_init(void)
 {
     type_register_static(&phytium_e2000_base_info);
     type_register_static(&phytium_pi_info);
+    type_register_static(&phytium_e2000_come_info);
 }
 
 type_init(phytium_e2000_machine_init);
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 27/33] hw/arm: phytium: Connect the Phytium E2000Q COMe QSPI flash
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (22 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 25/33] hw/arm: phytium: Add Phytium E2000Q COMe machine Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 28/33] hw/arm: phytium: Add Phytium E2000 AHCI controllers Bin Meng
                   ` (4 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Attach the board GD25Q128 to QSPI0 chip select zero and expose an
MTD backend for COMe firmware storage.

Describe the flash model in the COMe machine class so the shared
E2000 SoC still instantiates QSPI0 while Phytium Pi does not acquire
a board-level SPI flash.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |  1 +
 hw/arm/phytium_e2000.c | 37 +++++++++++++++++++++++++++++++++----
 2 files changed, 34 insertions(+), 4 deletions(-)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 61b9ca021f..940457b58b 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -140,6 +140,7 @@ config PHYTIUM_E2000
     select PHYTIUM_E2000_QSPI
     select PL011
     select SD
+    select SSI_M25P80
     select UNIMP
     select USB_XHCI_SYSBUS
 
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 5a2155ee32..499f5e0ffb 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -40,6 +40,7 @@
 #include "hw/sd/phytium_e2000_mci.h"
 #include "hw/sd/sd.h"
 #include "hw/ssi/phytium_qspi.h"
+#include "hw/ssi/ssi.h"
 #include "hw/usb/xhci.h"
 #include "net/net.h"
 #include "qobject/qlist.h"
@@ -118,6 +119,7 @@ struct PhytiumE2000State {
     struct arm_boot_info bootinfo;
     DeviceState *gic;
     DeviceState *qspi;
+    DeviceState *qspi_flash;
     PhytiumE2000PBRState *pbr;
     PhytiumE2000MciState *mci[PHYTIUM_E2000_NUM_MCIS];
     CadenceGEMState *gem[PHYTIUM_E2000_NUM_GEMS];
@@ -133,6 +135,7 @@ struct PhytiumE2000MachineClass {
     const char *machine_name;
     const char *direct_boot_dtb;
     const char *pbr_boot_mode;
+    const char *qspi_flash_model;
 };
 
 /*
@@ -577,6 +580,25 @@ static void phytium_e2000_create_qspi(PhytiumE2000State *s)
         phytium_e2000_memmap[PHYTIUM_E2000_QSPI_DIRECT].base, 2);
 }
 
+static void phytium_e2000_attach_qspi_flash(PhytiumE2000State *s,
+                                            const char *model)
+{
+    DeviceState *flash = qdev_new(model);
+    DriveInfo *dinfo = drive_get(IF_MTD, 0, 0);
+    qemu_irq flash_cs;
+
+    if (dinfo) {
+        qdev_prop_set_drive_err(flash, "drive", blk_by_legacy_dinfo(dinfo),
+                                &error_fatal);
+    }
+
+    s->qspi_flash = flash;
+    qdev_realize_and_unref(flash, qdev_get_child_bus(s->qspi, "spi"),
+                           &error_fatal);
+    flash_cs = qdev_get_gpio_in_named(flash, SSI_GPIO_CS, 0);
+    qdev_connect_gpio_out_named(s->qspi, "cs", 0, flash_cs);
+}
+
 static bool phytium_e2000_create_pbr(PhytiumE2000State *s)
 {
     MachineState *ms = MACHINE(s);
@@ -588,10 +610,13 @@ static bool phytium_e2000_create_pbr(PhytiumE2000State *s)
     uint64_t cpu_mpidrs[PHYTIUM_E2000_NUM_CPUS];
     int i;
 
-    if (!ms->kernel_filename &&
-        !strcmp(pemc->pbr_boot_mode,
-                PHYTIUM_E2000_PBR_BOOT_MODE_SD0)) {
-        boot_blk = phytium_e2000_sd_blk(0);
+    if (!ms->kernel_filename) {
+        if (!strcmp(pemc->pbr_boot_mode,
+                    PHYTIUM_E2000_PBR_BOOT_MODE_QSPI)) {
+            boot_blk = m25p80_get_blk(s->qspi_flash);
+        } else {
+            boot_blk = phytium_e2000_sd_blk(0);
+        }
     }
 
     for (i = 0; i < ms->smp.cpus; i++) {
@@ -913,6 +938,9 @@ static void phytium_e2000_init(MachineState *ms)
     phytium_e2000_create_unimplemented();
 
     phytium_e2000_create_qspi(s);
+    if (pemc->qspi_flash_model) {
+        phytium_e2000_attach_qspi_flash(s, pemc->qspi_flash_model);
+    }
     firmware_loaded = phytium_e2000_create_pbr(s);
 
     phytium_e2000_create_cpus(s, firmware_loaded);
@@ -1024,6 +1052,7 @@ static void phytium_e2000_come_class_init(ObjectClass *oc, const void *data)
     pemc->machine_name = "phytium-e2000-come";
     pemc->direct_boot_dtb = "e2000q-come-board.dtb";
     pemc->pbr_boot_mode = PHYTIUM_E2000_PBR_BOOT_MODE_QSPI;
+    pemc->qspi_flash_model = "gd25q128";
 }
 
 static const TypeInfo phytium_e2000_base_info = {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 28/33] hw/arm: phytium: Add Phytium E2000 AHCI controllers
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (23 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 27/33] hw/arm: phytium: Connect the Phytium E2000Q COMe QSPI flash Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 29/33] hw/arm: Add Phytium E2000 Linux SCMI channel Bin Meng
                   ` (3 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

Add the two single-port sysbus AHCI controllers integrated in the E2000
SoC and route their interrupts to the GIC. Both boards expose the fixed
controller blocks, but Phytium Pi does not wire either controller to a
SATA disk and therefore rejects an IDE backend.

The physical COMe board wires its boot SSD to the second controller.
Attach its SATA backend to AHCI1 so that U-Boot and Linux observe the
same controller order as on hardware.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |  3 +++
 hw/arm/phytium_e2000.c | 54 ++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 57 insertions(+)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 940457b58b..11c16bbebc 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -131,10 +131,13 @@ config PHYTIUM_E2000
     default y
     depends on TCG && AARCH64
     imply PCI_DEVICES
+    select AHCI_SYSBUS
     select ARM_GIC
     select CADENCE
     select DESIGNWARE_I2C
     select DW_MCI
+    select IDE_BUS
+    select IDE_DEV
     select PCI_EXPRESS
     select PCI_EXPRESS_GENERIC_BRIDGE
     select PHYTIUM_E2000_QSPI
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 499f5e0ffb..3acd9b716b 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -26,6 +26,8 @@
 #include "hw/char/pl011.h"
 #include "hw/core/boards.h"
 #include "hw/core/qdev-properties.h"
+#include "hw/ide/ahci-sysbus.h"
+#include "hw/ide/ide-bus.h"
 #include "hw/intc/arm_gicv3_common.h"
 #include "hw/intc/arm_gicv3_its_common.h"
 #include "hw/i2c/designware_i2c.h"
@@ -63,6 +65,9 @@ OBJECT_DECLARE_TYPE(PhytiumE2000State, PhytiumE2000MachineClass,
 #define PHYTIUM_E2000_NUM_MCIS        2
 #define PHYTIUM_E2000_NUM_UARTS       7
 #define PHYTIUM_E2000_NUM_XHCIS       2
+#define PHYTIUM_E2000_NUM_AHCIS       2
+#define PHYTIUM_E2000_NUM_SATA_PORTS  1
+#define PHYTIUM_E2000_SATA_BOOT_AHCI  1
 #define PHYTIUM_E2000_NUM_GEMS        4
 
 #define PHYTIUM_E2000_MHU_BASE        0x32a00000
@@ -97,10 +102,12 @@ enum {
     PHYTIUM_E2000_BOARD_CTRL,
     PHYTIUM_E2000_XHCI0,
     PHYTIUM_E2000_XHCI1,
+    PHYTIUM_E2000_AHCI0,
     PHYTIUM_E2000_GEM0,
     PHYTIUM_E2000_GEM1,
     PHYTIUM_E2000_GEM2,
     PHYTIUM_E2000_GEM3,
+    PHYTIUM_E2000_AHCI1,
     PHYTIUM_E2000_RNG_REGS,
     PHYTIUM_E2000_PLATFORM_CTRL,
     PHYTIUM_E2000_SECURITY_CTRL,
@@ -136,6 +143,7 @@ struct PhytiumE2000MachineClass {
     const char *direct_boot_dtb;
     const char *pbr_boot_mode;
     const char *qspi_flash_model;
+    bool has_sata_boot;
 };
 
 /*
@@ -168,10 +176,12 @@ static const MemMapEntry phytium_e2000_memmap[] = {
     [PHYTIUM_E2000_BOARD_CTRL] =     { 0x31800000, 0x01400000 },
     [PHYTIUM_E2000_XHCI0] =          { 0x31a08000, 0x00018000 },
     [PHYTIUM_E2000_XHCI1] =          { 0x31a28000, 0x00018000 },
+    [PHYTIUM_E2000_AHCI0] =          { 0x31a40000, 0x00001000 },
     [PHYTIUM_E2000_GEM0] =           { 0x3200c000, 0x00002000 },
     [PHYTIUM_E2000_GEM1] =           { 0x3200e000, 0x00002000 },
     [PHYTIUM_E2000_GEM2] =           { 0x32010000, 0x00002000 },
     [PHYTIUM_E2000_GEM3] =           { 0x32012000, 0x00002000 },
+    [PHYTIUM_E2000_AHCI1] =          { 0x32014000, 0x00001000 },
     [PHYTIUM_E2000_RNG_REGS] =       { 0x32a36000, 0x00001000 },
     [PHYTIUM_E2000_PLATFORM_CTRL] =  { 0x32e40000, 0x00010000 },
     [PHYTIUM_E2000_SECURITY_CTRL] =  { 0x32f00000, 0x00001000 },
@@ -207,6 +217,11 @@ static const int phytium_e2000_xhci_irqmap[] = {
     [1] = 17,
 };
 
+static const int phytium_e2000_ahci_irqmap[] = {
+    [0] = 42,
+    [1] = 43,
+};
+
 static const uint8_t phytium_e2000_gem_num_queues[] = { 8, 4, 4, 4 };
 
 static const int phytium_e2000_gem_irqmap[PHYTIUM_E2000_NUM_GEMS]
@@ -403,6 +418,32 @@ static void phytium_e2000_create_gem(PhytiumE2000State *s, int index)
     }
 }
 
+static void phytium_e2000_create_ahci(PhytiumE2000State *s, int index,
+                                      bool attach_boot_disk)
+{
+    int map_idx = index ? PHYTIUM_E2000_AHCI1 : PHYTIUM_E2000_AHCI0;
+    DriveInfo *hd[PHYTIUM_E2000_NUM_SATA_PORTS] = {};
+    DeviceState *dev = qdev_new(TYPE_SYSBUS_AHCI);
+    SysbusAHCIState *sysahci;
+
+    /*
+     * The COMe boot disk is wired to the second fixed AHCI controller. U-Boot
+     * probes both controllers and exposes the only attached disk as scsi 0.
+     */
+    qdev_prop_set_uint32(dev, "num-ports", PHYTIUM_E2000_NUM_SATA_PORTS);
+    sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
+    sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0,
+        phytium_e2000_memmap[map_idx].base);
+    sysbus_connect_irq(SYS_BUS_DEVICE(dev), 0,
+        qdev_get_gpio_in(s->gic, phytium_e2000_ahci_irqmap[index]));
+
+    if (attach_boot_disk) {
+        sysahci = SYSBUS_AHCI(dev);
+        ide_drive_get(hd, ARRAY_SIZE(hd));
+        ahci_ide_create_devs(&sysahci->ahci, hd);
+    }
+}
+
 static void phytium_e2000_create_xhci(PhytiumE2000State *s, int index)
 {
     int map_idx = PHYTIUM_E2000_XHCI0 + index;
@@ -932,6 +973,12 @@ static void phytium_e2000_init(MachineState *ms)
         exit(1);
     }
 
+    if (!pemc->has_sata_boot && drive_get_by_index(IF_IDE, 0)) {
+        error_report("%s does not expose a SATA boot disk; "
+                     "use if=sd,index=0", pemc->machine_name);
+        exit(1);
+    }
+
     phytium_e2000_reject_legacy_firmware(ms, pemc);
 
     phytium_e2000_create_ram(s);
@@ -961,6 +1008,10 @@ static void phytium_e2000_init(MachineState *ms)
     for (i = 0; i < PHYTIUM_E2000_NUM_XHCIS; i++) {
         phytium_e2000_create_xhci(s, i);
     }
+    for (i = 0; i < PHYTIUM_E2000_NUM_AHCIS; i++) {
+        phytium_e2000_create_ahci(s, i,
+            pemc->has_sata_boot && i == PHYTIUM_E2000_SATA_BOOT_AHCI);
+    }
     for (i = 0; i < PHYTIUM_E2000_NUM_GEMS; i++) {
         phytium_e2000_create_gem(s, i);
     }
@@ -1040,6 +1091,7 @@ static void phytium_pi_class_init(ObjectClass *oc, const void *data)
     pemc->machine_name = "phytium-pi";
     pemc->direct_boot_dtb = "phytiumpi_firefly.dtb";
     pemc->pbr_boot_mode = PHYTIUM_E2000_PBR_BOOT_MODE_SD0;
+    pemc->has_sata_boot = false;
 }
 
 static void phytium_e2000_come_class_init(ObjectClass *oc, const void *data)
@@ -1049,10 +1101,12 @@ static void phytium_e2000_come_class_init(ObjectClass *oc, const void *data)
         PHYTIUM_E2000_MACHINE_CLASS(oc);
 
     mc->desc = "Phytium E2000Q COMe Development Board";
+    mc->block_default_type = IF_IDE;
     pemc->machine_name = "phytium-e2000-come";
     pemc->direct_boot_dtb = "e2000q-come-board.dtb";
     pemc->pbr_boot_mode = PHYTIUM_E2000_PBR_BOOT_MODE_QSPI;
     pemc->qspi_flash_model = "gd25q128";
+    pemc->has_sata_boot = true;
 }
 
 static const TypeInfo phytium_e2000_base_info = {
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 29/33] hw/arm: Add Phytium E2000 Linux SCMI channel
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (24 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 28/33] hw/arm: phytium: Add Phytium E2000 AHCI controllers Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 30/33] hw/arm: phytium: Connect the Phytium E2000 SMMUv3 Bin Meng
                   ` (2 subsequent siblings)
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

The E2000 model only serviced the private PBF mailbox channel. The
vendor Linux driver uses a separate shared-memory channel and therefore
timed out before it could discover any SCMI protocols.

Model the Linux MHU doorbells and Base protocol while leaving
Performance and Sensor unadvertised until their platform data can be
represented faithfully. Document the limitation.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/phytium_e2000.c      |   4 +
 hw/misc/phytium_e2000_mhu.c | 457 +++++++++++++++++++++++++++++-------
 2 files changed, 381 insertions(+), 80 deletions(-)

diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index 3acd9b716b..cfd1ca70c9 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -232,6 +232,8 @@ static const int phytium_e2000_gem_irqmap[PHYTIUM_E2000_NUM_GEMS]
     [3] = { 68, 69, 70, 71 },
 };
 
+static const int phytium_e2000_mhu_irq = 22;
+
 static const int phytium_e2000_pcie_irqmap[PCI_NUM_PINS] = {
     [0] = 4,
     [1] = 5,
@@ -756,6 +758,8 @@ static void phytium_e2000_create_mhu(PhytiumE2000State *s)
     }
     sysbus_realize_and_unref(sbd, &error_fatal);
     sysbus_mmio_map_overlap(sbd, 0, PHYTIUM_E2000_MHU_BASE, 2);
+    sysbus_connect_irq(sbd, 0,
+                       qdev_get_gpio_in(s->gic, phytium_e2000_mhu_irq));
 }
 
 static void phytium_e2000_create_unimplemented(void)
diff --git a/hw/misc/phytium_e2000_mhu.c b/hw/misc/phytium_e2000_mhu.c
index 554448398b..f158918af5 100644
--- a/hw/misc/phytium_e2000_mhu.c
+++ b/hw/misc/phytium_e2000_mhu.c
@@ -1,9 +1,8 @@
 /*
  * Phytium E2000 MHU/SCMI doorbell
  *
- * This is a boot-oriented SCMI transport proxy. It acknowledges requests in
- * shared SRAM so PBF can complete clock and platform setup; it is not a full
- * SCMI protocol server.
+ * This models the PBF firmware transport and the SCMI Base protocol used by
+ * Linux. Protocols whose platform data is not available remain unsupported.
  *
  * Copyright (c) 2026 Process Mission
  *
@@ -16,34 +15,65 @@
 #include "qemu/osdep.h"
 #include "hw/misc/phytium_e2000_mhu.h"
 
-#include "hw/core/register.h"
+#include "hw/core/irq.h"
+#include "hw/core/registerfields.h"
 #include "migration/vmstate.h"
 #include "qapi/error.h"
 #include "qemu/bitops.h"
+#include "qemu/log.h"
 #include "qemu/module.h"
 #include "system/address-spaces.h"
 #include "target/arm/arm-powerctl.h"
 #include "target/arm/cpu.h"
 
-#define PHYTIUM_E2000_PBF_SCMI_MBOX_BASE  0x32a10400
-#define PHYTIUM_E2000_SCMI_STATUS_OFFSET  0x04
-#define PHYTIUM_E2000_SCMI_LEN_OFFSET     0x14
-#define PHYTIUM_E2000_SCMI_HEADER_OFFSET  0x18
-#define PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET 0x1c
-#define PHYTIUM_E2000_SCMI_STATUS_FREE    BIT(0)
-
-#define SCMI_MESSAGE_ID(header)    extract32((header), 0, 8)
-#define SCMI_PROTOCOL_ID(header)   extract32((header), 10, 8)
-#define SCMI_PROTOCOL_POWER_DOMAIN 0x11
-#define SCMI_PROTOCOL_PHYTIUM      0x81
-#define SCMI_POWER_STATE_SET       0x4
-#define SCMI_PHYTIUM_GET_PSOSTAT   0x3
-#define SCMI_POWER_STATE_TYPE      BIT(30)
-#define SCMI_POWER_STATE_ID_MASK   (SCMI_POWER_STATE_TYPE - 1)
+#define PHYTIUM_E2000_PBF_SCMI_MBOX_BASE     0x32a10400
+#define PHYTIUM_E2000_OS_SCMI_MBOX_BASE      0x32a11400
+#define PHYTIUM_E2000_OS_SCMI_MBOX_SIZE      0x400
+#define PHYTIUM_E2000_SCMI_STATUS_OFFSET     0x04
+#define PHYTIUM_E2000_SCMI_FLAGS_OFFSET      0x10
+#define PHYTIUM_E2000_SCMI_LEN_OFFSET        0x14
+#define PHYTIUM_E2000_SCMI_HEADER_OFFSET     0x18
+#define PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET    0x1c
+#define PHYTIUM_E2000_SCMI_STATUS_FREE       BIT(0)
+#define PHYTIUM_E2000_SCMI_FLAG_INTR_ENABLED BIT(0)
+#define PHYTIUM_E2000_SCMI_MAX_FRAME_SIZE    \
+    (PHYTIUM_E2000_OS_SCMI_MBOX_SIZE - \
+     PHYTIUM_E2000_SCMI_HEADER_OFFSET)
+
+#define SCMI_MESSAGE_ID(header)              extract32((header), 0, 8)
+#define SCMI_MESSAGE_TYPE(header)            extract32((header), 8, 2)
+#define SCMI_PROTOCOL_ID(header)             extract32((header), 10, 8)
+#define SCMI_RESERVED(header)                extract32((header), 28, 4)
+#define SCMI_MESSAGE_TYPE_COMMAND            0
+#define SCMI_PROTOCOL_BASE                   0x10
+#define SCMI_PROTOCOL_POWER_DOMAIN           0x11
+#define SCMI_PROTOCOL_PHYTIUM                0x81
+#define SCMI_PROTOCOL_VERSION                0x0
+#define SCMI_PROTOCOL_ATTRIBUTES             0x1
+#define SCMI_PROTOCOL_MESSAGE_ATTRIBUTES     0x2
+#define SCMI_BASE_DISCOVER_VENDOR            0x3
+#define SCMI_BASE_DISCOVER_SUB_VENDOR        0x4
+#define SCMI_BASE_DISCOVER_IMPLEMENT_VERSION 0x5
+#define SCMI_BASE_DISCOVER_LIST_PROTOCOLS    0x6
+#define SCMI_BASE_DISCOVER_AGENT             0x7
+#define SCMI_BASE_NOTIFY_ERRORS              0x8
+#define SCMI_POWER_STATE_SET                 0x4
+#define SCMI_PHYTIUM_GET_PSOSTAT             0x3
+#define SCMI_POWER_STATE_TYPE                BIT(30)
+#define SCMI_POWER_STATE_ID_MASK             (SCMI_POWER_STATE_TYPE - 1)
+/* Values reported by the E2000 SCP firmware */
+#define SCMI_BASE_VERSION                    0x00020000
+#define SCMI_BASE_IMPLEMENTATION_VERSION     0x02050000
+#define SCMI_BASE_NUM_PROTOCOLS              0
+#define SCMI_BASE_NUM_AGENTS                 2
+#define SCMI_DOORBELL_COMPLETE               BIT(31)
 
 #define SCMI_SUCCESS            0
+#define SCMI_NOT_SUPPORTED      (-1)
 #define SCMI_INVALID_PARAMETERS (-2)
+#define SCMI_NOT_FOUND          (-4)
 #define SCMI_GENERIC_ERROR      (-8)
+#define SCMI_PROTOCOL_ERROR     (-10)
 
 #define PHYTIUM_E2000_PBF_ROOT_ANCHOR       \
     (PHYTIUM_E2000_PBR_BOOT_SRAM_BASE + 0xf00)
@@ -52,11 +82,14 @@
 #define PHYTIUM_E2000_CPU_LOCK_OWNER_OFFSET 0x30
 #define PHYTIUM_E2000_CPU_ON_COMPLETE       0xabcdef98
 
-/*
- * The SDK defines AP OS status/set/clear at 0x100/0x108/0x110 within a
- * channel. PBF selects the channel at MHU offset 0x200, producing the global
- * offsets below. Writes to AP_OS_SET are the request notification.
- */
+REG32(AP_RX_STAT, 0x000)
+REG32(AP_RX_SET, 0x008)
+REG32(AP_RX_CLR, 0x010)
+REG32(AP_TX_STAT, 0x100)
+REG32(AP_TX_SET, 0x108)
+REG32(AP_TX_CLR, 0x110)
+
+/* PBF selects the AP OS channel at offset 0x200 */
 REG32(AP_OS_STAT, 0x300)
 REG32(AP_OS_SET, 0x308)
 REG32(AP_OS_CLR, 0x310)
@@ -67,8 +100,9 @@ REG32(AP_OS_CLR, 0x310)
 struct PhytiumE2000MHUState {
     SysBusDevice parent_obj;
 
+    MemoryRegion iomem;
+    qemu_irq irq;
     uint32_t regs[PHYTIUM_E2000_MHU_R_MAX];
-    RegisterInfo regs_info[PHYTIUM_E2000_MHU_R_MAX];
     uint64_t cpu_mpidrs[PHYTIUM_E2000_MHU_MAX_CPUS];
     CPUState *cpus[PHYTIUM_E2000_MHU_MAX_CPUS];
     /* Firmware-owned slot address supplied by the PBR before realization */
@@ -122,34 +156,34 @@ static bool phytium_e2000_phys_writeq(hwaddr addr, uint64_t value)
                                sizeof(buf)) == MEMTX_OK;
 }
 
-static uint32_t phytium_e2000_scmi_readl(hwaddr offset)
+static bool phytium_e2000_phys_write(hwaddr addr, const void *buf, size_t size)
 {
-    uint8_t buf[sizeof(uint32_t)];
-
-    address_space_read(&address_space_memory,
-                       PHYTIUM_E2000_PBF_SCMI_MBOX_BASE + offset,
-                       MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
-    return ldl_le_p(buf);
+    return address_space_write(&address_space_memory, addr,
+                               MEMTXATTRS_UNSPECIFIED, buf, size) == MEMTX_OK;
 }
 
-static void phytium_e2000_scmi_writel(hwaddr offset, uint32_t value)
+static uint32_t phytium_e2000_scmi_readl(hwaddr base, hwaddr offset)
 {
-    uint8_t buf[sizeof(uint32_t)];
+    uint32_t value = 0;
 
-    stl_le_p(buf, value);
-    address_space_write(&address_space_memory,
-                        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE + offset,
-                        MEMTXATTRS_UNSPECIFIED, buf, sizeof(buf));
+    phytium_e2000_phys_readl(base + offset, &value);
+    return value;
+}
+
+static void phytium_e2000_scmi_writel(hwaddr base, hwaddr offset,
+                                      uint32_t value)
+{
+    phytium_e2000_phys_writel(base + offset, value);
 }
 
-static void phytium_e2000_scmi_publish(uint32_t len)
+static void phytium_e2000_scmi_publish(hwaddr base, uint32_t len)
 {
-    phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_LEN_OFFSET, len);
+    phytium_e2000_scmi_writel(base, PHYTIUM_E2000_SCMI_LEN_OFFSET, len);
     /*
      * Publish the free bit last. PBF polls this field as the ownership handoff
      * and may consume the response immediately after observing it.
      */
-    phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_STATUS_OFFSET,
+    phytium_e2000_scmi_writel(base, PHYTIUM_E2000_SCMI_STATUS_OFFSET,
                               PHYTIUM_E2000_SCMI_STATUS_FREE);
 }
 
@@ -287,8 +321,10 @@ static uint32_t phytium_e2000_mhu_psostat(PhytiumE2000MHUState *s)
 static int32_t phytium_e2000_mhu_set_power_state(PhytiumE2000MHUState *s)
 {
     uint32_t domain_id = phytium_e2000_scmi_readl(
+        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE,
         PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET + 4);
     uint32_t power_state = phytium_e2000_scmi_readl(
+        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE,
         PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET + 8);
     uint32_t core_mask = power_state & SCMI_POWER_STATE_ID_MASK;
     bool power_on = power_state & SCMI_POWER_STATE_TYPE;
@@ -347,8 +383,10 @@ static int32_t phytium_e2000_mhu_set_power_state(PhytiumE2000MHUState *s)
 static void phytium_e2000_mhu_complete_scmi(PhytiumE2000MHUState *s)
 {
     uint32_t header = phytium_e2000_scmi_readl(
+        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE,
         PHYTIUM_E2000_SCMI_HEADER_OFFSET);
     uint32_t len = MAX(phytium_e2000_scmi_readl(
+        PHYTIUM_E2000_PBF_SCMI_MBOX_BASE,
         PHYTIUM_E2000_SCMI_LEN_OFFSET), (uint32_t)sizeof(uint32_t));
     int32_t scmi_status = SCMI_SUCCESS;
 
@@ -359,7 +397,8 @@ static void phytium_e2000_mhu_complete_scmi(PhytiumE2000MHUState *s)
      */
     if (SCMI_PROTOCOL_ID(header) == SCMI_PROTOCOL_PHYTIUM &&
         SCMI_MESSAGE_ID(header) == SCMI_PHYTIUM_GET_PSOSTAT) {
-        phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET + 4,
+        phytium_e2000_scmi_writel(PHYTIUM_E2000_PBF_SCMI_MBOX_BASE,
+                                  PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET + 4,
                                   phytium_e2000_mhu_psostat(s));
         len = 3 * sizeof(uint32_t);
     } else if (SCMI_PROTOCOL_ID(header) == SCMI_PROTOCOL_POWER_DOMAIN &&
@@ -368,9 +407,202 @@ static void phytium_e2000_mhu_complete_scmi(PhytiumE2000MHUState *s)
         len = 2 * sizeof(uint32_t);
     }
 
-    phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET,
+    phytium_e2000_scmi_writel(PHYTIUM_E2000_PBF_SCMI_MBOX_BASE,
+                              PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET,
                               scmi_status);
-    phytium_e2000_scmi_publish(len);
+    phytium_e2000_scmi_publish(PHYTIUM_E2000_PBF_SCMI_MBOX_BASE, len);
+}
+
+static bool phytium_e2000_scmi_base_message_supported(uint32_t message_id)
+{
+    return message_id <= SCMI_BASE_DISCOVER_AGENT;
+}
+
+static int32_t phytium_e2000_mhu_complete_base(uint32_t message_id,
+                                               uint32_t request_len,
+                                               uint8_t response[16],
+                                               size_t *response_len)
+{
+    uint32_t parameter;
+
+    *response_len = 0;
+
+    switch (message_id) {
+    case SCMI_PROTOCOL_VERSION:
+        if (request_len != sizeof(uint32_t)) {
+            return SCMI_PROTOCOL_ERROR;
+        }
+        stl_le_p(response, SCMI_BASE_VERSION);
+        *response_len = sizeof(uint32_t);
+        return SCMI_SUCCESS;
+    case SCMI_PROTOCOL_ATTRIBUTES:
+        if (request_len != sizeof(uint32_t)) {
+            return SCMI_PROTOCOL_ERROR;
+        }
+        response[0] = SCMI_BASE_NUM_PROTOCOLS;
+        response[1] = SCMI_BASE_NUM_AGENTS;
+        *response_len = sizeof(uint32_t);
+        return SCMI_SUCCESS;
+    case SCMI_PROTOCOL_MESSAGE_ATTRIBUTES:
+        if (request_len != 2 * sizeof(uint32_t) ||
+            !phytium_e2000_phys_readl(
+                PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+                PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET, &parameter)) {
+            return SCMI_PROTOCOL_ERROR;
+        }
+        if (!phytium_e2000_scmi_base_message_supported(parameter)) {
+            return SCMI_NOT_FOUND;
+        }
+        stl_le_p(response, 0);
+        *response_len = sizeof(uint32_t);
+        return SCMI_SUCCESS;
+    case SCMI_BASE_DISCOVER_VENDOR:
+        if (request_len != sizeof(uint32_t)) {
+            return SCMI_PROTOCOL_ERROR;
+        }
+        memcpy(response, "Phytium", sizeof("Phytium"));
+        *response_len = 16;
+        return SCMI_SUCCESS;
+    case SCMI_BASE_DISCOVER_SUB_VENDOR:
+        if (request_len != sizeof(uint32_t)) {
+            return SCMI_PROTOCOL_ERROR;
+        }
+        memcpy(response, "E2000", sizeof("E2000"));
+        *response_len = 16;
+        return SCMI_SUCCESS;
+    case SCMI_BASE_DISCOVER_IMPLEMENT_VERSION:
+        if (request_len != sizeof(uint32_t)) {
+            return SCMI_PROTOCOL_ERROR;
+        }
+        stl_le_p(response, SCMI_BASE_IMPLEMENTATION_VERSION);
+        *response_len = sizeof(uint32_t);
+        return SCMI_SUCCESS;
+    case SCMI_BASE_DISCOVER_LIST_PROTOCOLS:
+        if (request_len != 2 * sizeof(uint32_t) ||
+            !phytium_e2000_phys_readl(
+                PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+                PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET, &parameter)) {
+            return SCMI_PROTOCOL_ERROR;
+        }
+        if (parameter) {
+            return SCMI_INVALID_PARAMETERS;
+        }
+        stl_le_p(response, 0);
+        *response_len = sizeof(uint32_t);
+        return SCMI_SUCCESS;
+    case SCMI_BASE_DISCOVER_AGENT:
+        if (request_len != 2 * sizeof(uint32_t) ||
+            !phytium_e2000_phys_readl(
+                PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+                PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET, &parameter)) {
+            return SCMI_PROTOCOL_ERROR;
+        }
+        if (parameter > SCMI_BASE_NUM_AGENTS - 1) {
+            return SCMI_NOT_FOUND;
+        }
+        /*
+         * The E2000 SCP advertises Base v2.0 but returns the v1 16-byte agent
+         * name. Match this quirk because the SDK 5.10 client parses that
+         * layout rather than the v2 agent-ID-plus-name response.
+         */
+        if (parameter == 0) {
+            memcpy(response, "platform", sizeof("platform"));
+        } else {
+            memcpy(response, "OSPM", sizeof("OSPM"));
+        }
+        *response_len = 16;
+        return SCMI_SUCCESS;
+    case SCMI_BASE_NOTIFY_ERRORS:
+    default:
+        return SCMI_NOT_SUPPORTED;
+    }
+}
+
+static bool phytium_e2000_mhu_publish_os_response(uint32_t header,
+                                                  int32_t status,
+                                                  const uint8_t *response,
+                                                  size_t response_len)
+{
+    hwaddr payload = PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+                     PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET;
+
+    if (!phytium_e2000_phys_writel(payload, status) ||
+        (response_len &&
+         !phytium_e2000_phys_write(payload + sizeof(uint32_t), response,
+                                   response_len)) ||
+        !phytium_e2000_phys_writel(
+            PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+            PHYTIUM_E2000_SCMI_HEADER_OFFSET, header) ||
+        !phytium_e2000_phys_writel(
+            PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+            PHYTIUM_E2000_SCMI_LEN_OFFSET,
+            2 * sizeof(uint32_t) + response_len) ||
+        !phytium_e2000_phys_writel(
+            PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+            PHYTIUM_E2000_SCMI_STATUS_OFFSET,
+            PHYTIUM_E2000_SCMI_STATUS_FREE)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "phytium-e2000-mhu: failed to publish SCMI response\n");
+        return false;
+    }
+
+    return true;
+}
+
+static bool phytium_e2000_mhu_complete_os_scmi(PhytiumE2000MHUState *s)
+{
+    uint8_t response[16] = { 0 };
+    uint32_t flags;
+    uint32_t header;
+    uint32_t len;
+    size_t response_len = 0;
+    int32_t status;
+
+    if (!phytium_e2000_phys_readl(
+            PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+            PHYTIUM_E2000_SCMI_LEN_OFFSET, &len) ||
+        !phytium_e2000_phys_readl(
+            PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+            PHYTIUM_E2000_SCMI_HEADER_OFFSET, &header) ||
+        !phytium_e2000_phys_readl(
+            PHYTIUM_E2000_OS_SCMI_MBOX_BASE +
+            PHYTIUM_E2000_SCMI_FLAGS_OFFSET, &flags)) {
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "phytium-e2000-mhu: failed to read SCMI request\n");
+        return false;
+    }
+
+    if (len < sizeof(uint32_t) || len > PHYTIUM_E2000_SCMI_MAX_FRAME_SIZE ||
+        !QEMU_IS_ALIGNED(len, sizeof(uint32_t)) ||
+        (flags & ~PHYTIUM_E2000_SCMI_FLAG_INTR_ENABLED) ||
+        SCMI_MESSAGE_TYPE(header) != SCMI_MESSAGE_TYPE_COMMAND ||
+        SCMI_RESERVED(header)) {
+        status = SCMI_PROTOCOL_ERROR;
+    } else if (SCMI_PROTOCOL_ID(header) != SCMI_PROTOCOL_BASE) {
+        status = SCMI_NOT_SUPPORTED;
+    } else {
+        status = phytium_e2000_mhu_complete_base(
+            SCMI_MESSAGE_ID(header), len, response, &response_len);
+    }
+
+    g_assert(response_len <= sizeof(response));
+    if (!phytium_e2000_mhu_publish_os_response(
+            header, status, response, response_len)) {
+        return false;
+    }
+
+    /*
+     * The vendor mailbox poll callback recognizes only BIT(31) as transmit
+     * completion. Raise RX only when the requester selected interrupts.
+     */
+    s->regs[A_AP_TX_STAT / sizeof(uint32_t)] = SCMI_DOORBELL_COMPLETE;
+    if (flags & PHYTIUM_E2000_SCMI_FLAG_INTR_ENABLED) {
+        s->regs[A_AP_RX_STAT / sizeof(uint32_t)] |=
+            SCMI_DOORBELL_COMPLETE;
+        qemu_set_irq(s->irq, 1);
+    }
+
+    return true;
 }
 
 void phytium_e2000_mhu_seed_mailbox(void)
@@ -379,41 +611,98 @@ void phytium_e2000_mhu_seed_mailbox(void)
      * PBR leaves the shared channel available before releasing PBF. Seed the
      * same ownership and success state even before the first doorbell write.
      */
-    phytium_e2000_scmi_writel(PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET,
+    phytium_e2000_scmi_writel(PHYTIUM_E2000_PBF_SCMI_MBOX_BASE,
+                              PHYTIUM_E2000_SCMI_PAYLOAD_OFFSET,
                               SCMI_SUCCESS);
-    phytium_e2000_scmi_publish(sizeof(uint32_t));
+    phytium_e2000_scmi_publish(PHYTIUM_E2000_PBF_SCMI_MBOX_BASE,
+                               sizeof(uint32_t));
 }
 
-static void phytium_e2000_mhu_doorbell_post_write(RegisterInfo *reg,
-                                                  uint64_t value)
+static void phytium_e2000_mhu_update_irq(PhytiumE2000MHUState *s)
 {
-    PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(reg->opaque);
+    qemu_set_irq(s->irq,
+                 s->regs[A_AP_RX_STAT / sizeof(uint32_t)] != 0);
+}
 
-    /*
-     * Complete requests synchronously because no separate SCP CPU executes in
-     * this model. Zero writes only update doorbell storage.
-     */
-    if (value) {
-        phytium_e2000_mhu_complete_scmi(s);
+static uint64_t phytium_e2000_mhu_read(void *opaque, hwaddr offset,
+                                       unsigned size)
+{
+    PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(opaque);
+
+    switch (offset) {
+    case A_AP_RX_STAT:
+    case A_AP_TX_STAT:
+    case A_AP_OS_STAT:
+    case A_AP_OS_SET:
+    case A_AP_OS_CLR:
+        return s->regs[offset / sizeof(uint32_t)];
+    case A_AP_RX_SET:
+    case A_AP_RX_CLR:
+    case A_AP_TX_SET:
+    case A_AP_TX_CLR:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "phytium-e2000-mhu: read from write-only register "
+                      "at 0x%" HWADDR_PRIx "\n", offset);
+        return 0;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "phytium-e2000-mhu: read from unimplemented register "
+                      "at 0x%" HWADDR_PRIx "\n", offset);
+        return 0;
     }
 }
 
-static const RegisterAccessInfo phytium_e2000_mhu_regs_info[] = {
-    /*
-     * The functional transport does not model an SCP interrupt line. STAT is
-     * therefore idle, SET completes the shared-memory transaction, and CLR
-     * remains ordinary register storage for the firmware acknowledge path.
-     */
-    { .name = "AP_OS_STAT", .addr = A_AP_OS_STAT,
-      .ro = UINT32_MAX },
-    { .name = "AP_OS_SET", .addr = A_AP_OS_SET,
-      .post_write = phytium_e2000_mhu_doorbell_post_write },
-    { .name = "AP_OS_CLR", .addr = A_AP_OS_CLR },
-};
+static void phytium_e2000_mhu_write(void *opaque, hwaddr offset,
+                                    uint64_t value, unsigned size)
+{
+    PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(opaque);
+    uint32_t val = value;
+
+    switch (offset) {
+    case A_AP_RX_SET:
+        s->regs[A_AP_RX_STAT / sizeof(uint32_t)] |= val;
+        phytium_e2000_mhu_update_irq(s);
+        break;
+    case A_AP_RX_CLR:
+        s->regs[A_AP_RX_STAT / sizeof(uint32_t)] &= ~val;
+        phytium_e2000_mhu_update_irq(s);
+        break;
+    case A_AP_TX_SET:
+        if (val) {
+            s->regs[A_AP_TX_STAT / sizeof(uint32_t)] |= val;
+            phytium_e2000_mhu_complete_os_scmi(s);
+        }
+        break;
+    case A_AP_TX_CLR:
+        s->regs[A_AP_TX_STAT / sizeof(uint32_t)] &= ~val;
+        break;
+    case A_AP_OS_SET:
+        s->regs[A_AP_OS_SET / sizeof(uint32_t)] = val;
+        if (val) {
+            phytium_e2000_mhu_complete_scmi(s);
+        }
+        break;
+    case A_AP_OS_CLR:
+        s->regs[A_AP_OS_CLR / sizeof(uint32_t)] = val;
+        break;
+    case A_AP_RX_STAT:
+    case A_AP_TX_STAT:
+    case A_AP_OS_STAT:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "phytium-e2000-mhu: write to read-only register "
+                      "at 0x%" HWADDR_PRIx "\n", offset);
+        break;
+    default:
+        qemu_log_mask(LOG_GUEST_ERROR,
+                      "phytium-e2000-mhu: write to unimplemented register "
+                      "at 0x%" HWADDR_PRIx "\n", offset);
+        break;
+    }
+}
 
 static const MemoryRegionOps phytium_e2000_mhu_ops = {
-    .read = register_read_memory,
-    .write = register_write_memory,
+    .read = phytium_e2000_mhu_read,
+    .write = phytium_e2000_mhu_write,
     .endianness = DEVICE_LITTLE_ENDIAN,
     .valid = {
         .min_access_size = 4,
@@ -425,12 +714,12 @@ static const MemoryRegionOps phytium_e2000_mhu_ops = {
 static void phytium_e2000_mhu_reset(DeviceState *dev)
 {
     PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(dev);
-    int i;
 
-    for (i = 0; i < ARRAY_SIZE(phytium_e2000_mhu_regs_info); i++) {
-        register_reset(&s->regs_info[
-            phytium_e2000_mhu_regs_info[i].addr / sizeof(uint32_t)]);
-    }
+    memset(s->regs, 0, sizeof(s->regs));
+    phytium_e2000_mhu_update_irq(s);
+    phytium_e2000_scmi_writel(PHYTIUM_E2000_OS_SCMI_MBOX_BASE,
+                              PHYTIUM_E2000_SCMI_STATUS_OFFSET,
+                              PHYTIUM_E2000_SCMI_STATUS_FREE);
 }
 
 void phytium_e2000_mhu_connect_cpu(PhytiumE2000MHUState *s,
@@ -466,13 +755,12 @@ void phytium_e2000_mhu_set_secondary_vector_slot(PhytiumE2000MHUState *s,
 static void phytium_e2000_mhu_init(Object *obj)
 {
     PhytiumE2000MHUState *s = PHYTIUM_E2000_MHU(obj);
-    RegisterInfoArray *reg_array;
 
-    reg_array = register_init_block32(
-        DEVICE(obj), phytium_e2000_mhu_regs_info,
-        ARRAY_SIZE(phytium_e2000_mhu_regs_info), s->regs_info, s->regs,
-        &phytium_e2000_mhu_ops, false, PHYTIUM_E2000_MHU_MMIO_SIZE);
-    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &reg_array->mem);
+    memory_region_init_io(&s->iomem, obj, &phytium_e2000_mhu_ops, s,
+                          TYPE_PHYTIUM_E2000_MHU,
+                          PHYTIUM_E2000_MHU_MMIO_SIZE);
+    sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
+    sysbus_init_irq(SYS_BUS_DEVICE(obj), &s->irq);
 }
 
 static void phytium_e2000_mhu_finalize(Object *obj)
@@ -485,10 +773,19 @@ static void phytium_e2000_mhu_finalize(Object *obj)
     }
 }
 
+static int phytium_e2000_mhu_post_load(void *opaque, int version_id)
+{
+    PhytiumE2000MHUState *s = opaque;
+
+    phytium_e2000_mhu_update_irq(s);
+    return 0;
+}
+
 static const VMStateDescription phytium_e2000_mhu_vmsd = {
     .name = TYPE_PHYTIUM_E2000_MHU,
     .version_id = 1,
     .minimum_version_id = 1,
+    .post_load = phytium_e2000_mhu_post_load,
     .fields = (const VMStateField[]) {
         VMSTATE_UINT32_ARRAY(regs, PhytiumE2000MHUState,
                              PHYTIUM_E2000_MHU_R_MAX),
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 30/33] hw/arm: phytium: Connect the Phytium E2000 SMMUv3
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (25 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 29/33] hw/arm: Add Phytium E2000 Linux SCMI channel Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 31/33] docs/system/arm: Document Phytium E2000 machines Bin Meng
  2026-09-03 11:25 ` [PATCH 32/33] tests/functional/aarch64: Add Phytium Pi boot tests Bin Meng
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

The vendor device tree routes PCIe DMA through an Arm SMMUv3, but
its MMIO range currently reaches an unimplemented placeholder. Linux
therefore reports that the IOMMU has no translation support.

Attach the generic SMMUv3 model to the GPEX root bus with the
documented interrupt wiring and document stage-1 DMA translation
support.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 hw/arm/Kconfig         |  1 +
 hw/arm/phytium_e2000.c | 39 +++++++++++++++++++++++++++++++++++++--
 2 files changed, 38 insertions(+), 2 deletions(-)

diff --git a/hw/arm/Kconfig b/hw/arm/Kconfig
index 11c16bbebc..ea16a8b3f4 100644
--- a/hw/arm/Kconfig
+++ b/hw/arm/Kconfig
@@ -133,6 +133,7 @@ config PHYTIUM_E2000
     imply PCI_DEVICES
     select AHCI_SYSBUS
     select ARM_GIC
+    select ARM_SMMUV3
     select CADENCE
     select DESIGNWARE_I2C
     select DW_MCI
diff --git a/hw/arm/phytium_e2000.c b/hw/arm/phytium_e2000.c
index cfd1ca70c9..8ffd566b36 100644
--- a/hw/arm/phytium_e2000.c
+++ b/hw/arm/phytium_e2000.c
@@ -22,6 +22,7 @@
 #include "hw/arm/boot.h"
 #include "hw/arm/bsa.h"
 #include "hw/arm/machines-qom.h"
+#include "hw/arm/smmuv3.h"
 #include "hw/block/flash.h"
 #include "hw/char/pl011.h"
 #include "hw/core/boards.h"
@@ -69,6 +70,7 @@ OBJECT_DECLARE_TYPE(PhytiumE2000State, PhytiumE2000MachineClass,
 #define PHYTIUM_E2000_NUM_SATA_PORTS  1
 #define PHYTIUM_E2000_SATA_BOOT_AHCI  1
 #define PHYTIUM_E2000_NUM_GEMS        4
+#define PHYTIUM_E2000_NUM_SMMU_IRQS   4
 
 #define PHYTIUM_E2000_MHU_BASE        0x32a00000
 #define PHYTIUM_E2000_SCP_SRAM_BASE   0x32a10000
@@ -212,6 +214,14 @@ static const int phytium_e2000_uart_irqmap[] = {
 
 static const int phytium_e2000_i2c_irq = 106;
 
+/* Keep the architectural SMMUv3 output order aligned with the vendor DT */
+static const int phytium_e2000_smmu_irqmap[] = {
+    [SMMU_IRQ_EVTQ] = 240,
+    [SMMU_IRQ_PRIQ] = 239,
+    [SMMU_IRQ_CMD_SYNC] = 236,
+    [SMMU_IRQ_GERROR] = 242,
+};
+
 static const int phytium_e2000_xhci_irqmap[] = {
     [0] = 16,
     [1] = 17,
@@ -472,7 +482,7 @@ static int phytium_e2000_pcie_map_irq(PCIDevice *pdev, int pin)
     return pin;
 }
 
-static void phytium_e2000_create_pcie(PhytiumE2000State *s)
+static PCIBus *phytium_e2000_create_pcie(PhytiumE2000State *s)
 {
     DeviceState *dev = qdev_new(TYPE_GPEX_HOST);
     SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
@@ -546,6 +556,29 @@ static void phytium_e2000_create_pcie(PhytiumE2000State *s)
             qdev_get_gpio_in(s->gic, phytium_e2000_pcie_irqmap[i]));
         gpex_set_irq_num(GPEX_HOST(dev), i, phytium_e2000_pcie_irqmap[i]);
     }
+
+    return PCI_HOST_BRIDGE(dev)->bus;
+}
+
+static void phytium_e2000_create_smmu(PhytiumE2000State *s, PCIBus *bus)
+{
+    DeviceState *dev = qdev_new(TYPE_ARM_SMMUV3);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    int i;
+
+    object_property_add_child(OBJECT(s), "smmu", OBJECT(dev));
+    object_property_set_link(OBJECT(dev), "primary-bus", OBJECT(bus),
+                             &error_abort);
+    object_property_set_link(OBJECT(dev), "memory",
+                             OBJECT(get_system_memory()), &error_abort);
+    sysbus_realize_and_unref(sbd, &error_fatal);
+    sysbus_mmio_map_overlap(sbd, 0,
+        phytium_e2000_memmap[PHYTIUM_E2000_SYSTEM_CTRL].base, 2);
+
+    for (i = 0; i < PHYTIUM_E2000_NUM_SMMU_IRQS; i++) {
+        sysbus_connect_irq(sbd, i,
+            qdev_get_gpio_in(s->gic, phytium_e2000_smmu_irqmap[i]));
+    }
 }
 
 static void phytium_e2000_reject_legacy_firmware(
@@ -951,6 +984,7 @@ static void phytium_e2000_init(MachineState *ms)
     PhytiumE2000MachineClass *pemc =
         PHYTIUM_E2000_MACHINE_GET_CLASS(ms);
     bool firmware_loaded;
+    PCIBus *pcie_bus;
     int i;
 
     if (kvm_enabled()) {
@@ -1020,7 +1054,8 @@ static void phytium_e2000_init(MachineState *ms)
         phytium_e2000_create_gem(s, i);
     }
 
-    phytium_e2000_create_pcie(s);
+    pcie_bus = phytium_e2000_create_pcie(s);
+    phytium_e2000_create_smmu(s, pcie_bus);
 
     s->bootinfo.ram_size = ms->ram_size;
     s->bootinfo.board_id = -1;
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 31/33] docs/system/arm: Document Phytium E2000 machines
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (26 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 30/33] hw/arm: phytium: Connect the Phytium E2000 SMMUv3 Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 11:25 ` [PATCH 32/33] tests/functional/aarch64: Add Phytium Pi boot tests Bin Meng
  28 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, Pierrick Bouvier, qemu-arm

Document the shared E2000Q SoC model and the Phytium Pi and COMe board
variants in one file. Keep common PBR, CPU, device, direct-boot, and
limitation details together while separating the SD0 and QSPI-to-SATA
workflows.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 docs/system/arm/phytium_e2000.rst | 412 ++++++++++++++++++++++++++++++
 docs/system/target-arm.rst        |   1 +
 2 files changed, 413 insertions(+)
 create mode 100644 docs/system/arm/phytium_e2000.rst

diff --git a/docs/system/arm/phytium_e2000.rst b/docs/system/arm/phytium_e2000.rst
new file mode 100644
index 0000000000..d9da1b67a8
--- /dev/null
+++ b/docs/system/arm/phytium_e2000.rst
@@ -0,0 +1,412 @@
+.. SPDX-License-Identifier: GPL-2.0-or-later
+
+Phytium E2000Q machines (``phytium-pi``, ``phytium-e2000-come``)
+================================================================
+
+Overview
+--------
+
+QEMU models two boards built around the heterogeneous Phytium E2000Q SoC:
+
+.. list-table:: E2000Q machine variants
+   :header-rows: 1
+   :widths: 20 20 20 20 20
+
+   * - Machine
+     - Firmware medium
+     - Linux storage
+     - SDK DTB
+     - Direct kernel
+   * - ``phytium-pi``
+     - SD0
+     - SD0
+     - ``phytiumpi_firefly.dtb``
+     - ``Image.gz``
+   * - ``phytium-e2000-come``
+     - QSPI0
+     - AHCI1 SATA
+     - ``e2000q-come-board.dtb``
+     - ``Image``
+
+Both machines are intended for Linux and firmware images produced by the
+Phytium Buildroot SDK. Phytium Pi has no board-attached SATA boot disk. The
+COMe board attaches a GD25Q128 SPI NOR flash to QSPI0 and a SATA disk to
+AHCI1.
+
+The Phytium firmware stack has three conceptual layers. The on-chip Phytium
+Boot ROM (PBR) establishes the root of trust and prepares the early handoff,
+Processor Base Firmware (PBF) initializes the processor, and System Firmware
+(SFW) provides later firmware services and the bootloader. QEMU does not
+execute the internal ROM. It recreates the PBR handoff state, reads a
+``fip-all.bin`` image from byte zero of the board's selected firmware medium,
+starts the PBF payload at EL3, and lets PBF hand off through the SFW stages to
+U-Boot in the same image.
+
+The SFW flow loads BL31, whose resident EL3 runtime services handle the
+Phytium private SMCs issued by U-Boot and Linux. QEMU does not intercept these
+calls on the firmware boot path.
+
+The boot strap is fixed board wiring. Phytium Pi reports SD0 through the PBR
+handoff while COMe reports QSPI0. Firmware may persist its environment or DDR
+training data to the selected backend, so use a writable image or ``-snapshot``.
+
+The machines do not generate a device tree. Direct Linux boot with ``-kernel``
+requires the corresponding SDK DTB listed above through ``-dtb``. A later
+Linux boot performed by firmware likewise requires the matching board DTB in
+the guest-visible boot environment.
+
+PBR device ownership
+--------------------
+
+Firmware boot is implemented by the ``phytium-e2000-pbr`` device. The machine
+model creates the QSPI and SD topology, maps the PBR status window, boot SRAM,
+and IACC, fixes the device's ``boot-mode`` property from the board strap, and
+connects the selected backend and CPU topology. It does not parse or stage
+the firmware itself. ``boot-mode`` belongs to the PBR device and is not
+exposed as a user-settable machine property.
+
+The PBR device reads only the selected board medium. It validates the outer
+image extent, the embedded TF-A FIP, the primary MPIDR, and the
+platform-parameter records. It then stages the complete computed image extent
+at IACC ``0x38000000``, relocates PBF/BL1 to ``0xf8c40000``, constructs the
+boot SRAM handoff, and releases the primary CPU named by the image.
+
+The public PBF interface defines a common 16-byte header containing magic,
+version, size, and a reserved word for PLL, PCIe, DDR, and COMMON service
+parameters. The E2000 firmware profile supplies their private container and
+handoff placement:
+
+* PLL: FIP offset ``0xf5000`` to SCP SRAM ``0x32a10c00``, magic
+  ``0x54460020``
+* DDR/MCU (Memory Controller Unit): FIP offset ``0xf5300`` to SCP SRAM
+  ``0x32a10d00``, magic ``0x54460024``
+* PCIe: FIP offset ``0xf5100`` to SCP SRAM ``0x32a10e00``, magic
+  ``0x54460021``
+* COMMON: FIP offset ``0xf5200`` to SCP SRAM ``0x32a10f00``, magic
+  ``0x54460013``
+
+In this PBF terminology, MCU means Memory Controller Unit. The record supplies
+DDR controller configuration, DIMM/SPD data, and training parameters.
+
+The PBR device validates each exact E2000 magic, declared size, source bounds,
+and the ``0x100``-byte destination-slot bound. It copies the declared record
+without interpreting its service-specific payload and clears the remainder
+of the destination slot. Parameter versions belong to their individual PBF
+services and do not select a PBR handoff or TF-A object layout. In particular,
+QEMU does not generate DDR/SPD data, rewrite the DDR/MCU record version, or
+force DDR training controls.
+
+PBF embeds TF-A v2.3 FIP and memmap I/O drivers plus its platform I/O policy
+table. The PBR device locates these from their TF-A data-structure
+relationships and constructs the boot-SRAM state that registration and
+``dev_open`` would have produced. This permits different PBF builds,
+including two builds carrying version 4 DDR/MCU parameter records, to relocate
+their I/O objects independently of the record version.
+
+Some remaining handoff details are private to the vendor PBR/PBF contract
+rather than a published TF-A or hardware ABI. In particular, the fixed
+boot-SRAM parameter graph and the placement of the FIP driver's runtime state
+were reconstructed from the early PBF accesses and callback disassembly in
+the 2 GiB and 4 GiB Phytium Pi SDK firmware samples and the COMe SDK firmware
+sample. The implementation documents these evidence boundaries next to the
+relevant code and rejects firmware whose surrounding TF-A structures do not
+match.
+
+The device also owns reset, migration, and cleanup for this state. Its status
+registers and firmware/primary-CPU state have explicit VMState, while the
+device-owned boot SRAM and IACC RAM regions are migrated as RAMBlocks. A reset
+restages the immutable input image and restores the handoff and primary entry.
+This is a behavioral model of the ROM contract, not an Arm instruction-level
+implementation of the on-chip PBR.
+
+CPU topology
+------------
+
+The E2000Q is heterogeneous. QEMU models the three non-uniform CPU clusters
+described by the SDK Linux device tree:
+
+* cluster 0 contains one FTC664 core at MPIDR affinity ``0x0``
+* cluster 1 contains one FTC664 core at MPIDR affinity ``0x100``
+* cluster 2 contains two FTC310 cores at MPIDR affinities ``0x200`` and
+  ``0x201``
+
+The corresponding QEMU CPU slot order is ``0x0``, ``0x100``, ``0x200``,
+``0x201``. Firmware images that select primary affinity ``0x200`` therefore
+release QEMU CPU index 2. CPU types are assigned by the board and must not be
+replaced with a homogeneous ``-cpu`` model.
+
+The FTC310 and FTC664 TCG models use a Cortex-A72 execution base. Their
+Phytium MIDR values, architectural instruction-feature fields, AArch32
+floating-point feature fields, and VIPT/PIPT I-cache policy match the values
+observed on the physical E2000Q board. This is an architectural compatibility
+model, not a performance or cache-capacity model.
+
+Supported devices
+-----------------
+
+Both machines currently support:
+
+* two FTC664 and two FTC310 AArch64 CPU slots in three clusters
+* RAM starting at ``0x80000000``, with 2 GiB by default and up to 8 GiB
+* PBR-owned boot SRAM and IACC RAM used by the vendor firmware stack
+* a GICv3 interrupt controller with ITS
+* seven PL011 UARTs
+* the DesignWare-compatible I2C controller
+* the E2000 hardware random number generator
+* two USB 3.0 xHCI host controllers
+* two one-port sysbus AHCI controllers
+* two SD/MMC controllers
+* a GPEX PCIe host bridge with MSI support through the ITS and stage-1 DMA
+  translation through an Arm SMMUv3; PCIe endpoints such as network devices
+  must be added explicitly with ``-device``
+* a QSPI0 controller with a direct-mapped read window
+* PBR, DDR, and MHU/SCMI compatibility behavior needed by the vendor
+  firmware, plus the SCMI Base protocol used by Linux
+
+Board wiring differs as follows:
+
+* Phytium Pi uses SD0 for firmware and its root filesystem and has no attached
+  QSPI flash or SATA boot disk
+* COMe uses the GD25Q128 on QSPI0 for firmware and the AHCI1 SATA disk for
+  Linux
+
+Firmware boot
+-------------
+
+Passing firmware through ``-bios`` or a pflash drive is not supported. Use
+the board's fixed SD0 or QSPI0 medium as described below.
+
+Phytium Pi SD boot
+~~~~~~~~~~~~~~~~~~
+
+Build the complete SD image from the Phytium-maintained Buildroot release. No
+separate ``make phytium_defconfig`` step is needed because
+``merge_config.sh`` consumes the base defconfig directly:
+
+.. code-block:: shell
+
+   $ git clone https://gitee.com/phytium_embedded/phytium-linux-buildroot.git
+   $ cd phytium-linux-buildroot
+   $ git checkout phytium-linux-buildroot_v2.4
+
+   $ ./support/kconfig/merge_config.sh \
+       configs/phytium_defconfig \
+       configs/phytiumpi_sdcard.config
+   $ make
+
+The resulting complete image is ``output/images/sdcard.img``. The SD-card
+fragment selects the vendor 4 GiB firmware, builds ``fitImage`` from the
+Buildroot kernel and Phytium Pi DTB, and packages those files together with
+``rootfs.ext2``. Use this image directly rather than assembling its components
+by hand.
+
+The vendor-generated SD layout reserves the first 64 MiB outside the root
+filesystem:
+
+.. list-table:: Phytium Pi SD image layout
+   :header-rows: 1
+   :widths: 25 20 55
+
+   * - Start
+     - Reserved size
+     - Content
+   * - ``0x00000000``
+     - 4 MiB
+     - ``fip-all.bin`` firmware region
+   * - ``0x00400000``
+     - 60 MiB
+     - U-Boot FIT image
+   * - ``0x04000000``
+     - remaining image
+     - first partition, containing the ext4 root filesystem
+
+The default Phytium Pi SDK U-Boot environment reads the FIT from SD block
+``0x2000`` (byte offset 4 MiB) and boots with ``root=/dev/mmcblk0p1``. The
+first partition must therefore start at byte offset 64 MiB. A DOS partition
+table may replace FIP sector zero, matching the vendor image recipe; the PBR
+data used by the model begins at later fixed offsets in the firmware region.
+
+The SDK v2.4 image recipe declares the root partition as 16 GiB even when the
+generated ``sdcard.img`` is shorter. Linux consequently reports that
+``mmcblk0p1`` extends beyond the end of the device and truncates the reported
+partition size. This warning is expected and does not prevent the contained
+ext4 filesystem from mounting.
+
+UART1 carries the U-Boot and Linux console. The following command boots a
+complete raw SD image without injecting U-Boot commands. ``-snapshot`` keeps
+the source image unchanged when firmware writes its environment or DDR
+training data:
+
+The following examples use images generated by Buildroot::
+
+  IMAGES=/path/to/buildroot/output/images
+
+.. code-block:: shell
+
+   $ qemu-system-aarch64 \
+       -machine phytium-pi \
+       -smp 4 -m 4G \
+       -display none -monitor none \
+       -serial file:pbr-uart0.log \
+       -serial stdio \
+       -nic user \
+       -snapshot \
+       -drive file="$IMAGES/sdcard.img",if=sd,index=0,format=raw
+
+The explicit 4 GiB RAM size matches the firmware selected by
+``phytiumpi_sdcard.config``. The verified flow with a version 4 DDR/MCU
+parameter record completes PBF and DDR initialization, enters OP-TEE and
+U-Boot, reads the FIT from SD, starts Linux on all four modeled CPUs, detects
+SD0 as ``mmcblk0``, mounts partition 1, and reaches the login prompt without
+serial or monitor input.
+The SDK root filesystem uses a normal ``getty`` rather than an automatic
+login, so entering a shell requires an interactive UART1 chardev.
+
+COMe QSPI-to-SATA boot
+~~~~~~~~~~~~~~~~~~~~~~
+
+This is the preferred COMe boot flow when a matching E2000Q
+``fip-all.bin`` is available. The FIP container must begin at byte zero of a
+raw QSPI image.
+
+Build the standard SATA disk image from the Phytium-maintained Buildroot
+release:
+
+.. code-block:: shell
+
+   $ git clone https://gitee.com/phytium_embedded/phytium-linux-buildroot.git
+   $ cd phytium-linux-buildroot
+   $ git checkout phytium-linux-buildroot_v2.4
+
+   $ make phytium_defconfig
+   $ make
+
+The resulting ``$SDK/disk.img`` has a GPT partition table. Its first partition
+is a 400 MiB FAT filesystem containing ``Image``, the DTBs, and GRUB. Its
+second partition contains the ext4 root filesystem. Attach this image directly
+to AHCI1; no host-side repartitioning or file copying is required.
+
+Create a disposable 16 MiB GD25Q128 image in the erased state and copy the
+FIP to byte zero:
+
+.. code-block:: shell
+
+   $ export FIP=/path/to/sdk/fip-all.bin
+   $ export QSPI=/path/to/e2000q-gd25q128.bin
+
+   $ dd if=/dev/zero bs=1M count=16 | tr '\000' '\377' > "$QSPI"
+   $ dd if="$FIP" of="$QSPI" conv=notrunc
+
+UART1 carries the interactive U-Boot console. UART0 is written to a separate
+log so that early firmware output remains available. ``-snapshot`` keeps the
+input images unchanged when firmware writes training data, its environment,
+or the root filesystem:
+
+.. code-block:: shell
+
+   $ qemu-system-aarch64 \
+       -machine phytium-e2000-come \
+       -smp 4 -m 2G \
+       -display none -monitor none \
+       -serial file:pbr-uart0.log \
+       -serial stdio \
+       -nic user \
+       -snapshot \
+       -drive file="$QSPI",if=mtd,index=0,format=raw \
+       -drive file="$IMAGES/disk.img",if=ide,index=0,format=raw
+
+With the SDK firmware, the verified flow preserves the version 5 DDR/MCU
+parameter record, identifies 2 GiB DDR4/X16, completes software training, data
+BIST, and address BIST, detects the GD25Q128, reports ``boot media is qspi!``,
+and enumerates the SATA disk as ``scsi 0`` through AHCI1.
+
+The U-Boot default environment may not match the layout of the Buildroot
+``disk.img``. Press any key during the autoboot countdown to reach the
+``E2000#`` prompt, then enter the following commands to load the raw arm64
+kernel and COMe DTB from the FAT partition and boot without an initrd:
+
+.. code-block:: shell
+
+   setenv bootargs 'console=ttyAMA1,115200 root=/dev/sda2 rootwait rw cma=256M'
+   fatload scsi 0:1 0x90100000 Image
+   fatload scsi 0:1 0x90000000 e2000q-come-board.dtb
+   booti 0x90100000 - 0x90000000
+
+The verified flow starts Linux on all four modeled CPUs, mounts ``sda2``, and
+reaches the Buildroot login prompt on UART1.
+
+Direct Linux boot fallback
+--------------------------
+
+Use QEMU's direct ``-kernel`` interface only as a fallback when the Buildroot
+SDK does not provide matching EDK2 or U-Boot source code and no usable vendor
+FIP flow is available. This path bypasses PBR, PBF, SFW, and U-Boot. It
+requires the board-specific SDK kernel and matching DTB listed in the overview;
+omitting ``-dtb`` is an error.
+
+Starting the vendor U-Boot binary directly with ``-kernel`` is not supported.
+That would bypass BL31 and leave the bootloader without the Phytium private
+SMC services normally provided by the resident EL3 firmware.
+
+The following commands boot the SDK kernel with its initramfs and do not
+require a disk image.
+
+Phytium Pi direct boot
+~~~~~~~~~~~~~~~~~~~~~~
+
+.. code-block:: shell
+
+   $ qemu-system-aarch64 \
+       -machine phytium-pi \
+       -smp 4 \
+       -display none -monitor none \
+       -serial null \
+       -serial stdio \
+       -nic user \
+       -kernel "$IMAGES/Image.gz" \
+       -dtb "$IMAGES/phytiumpi_firefly.dtb" \
+       -initrd "$IMAGES/rootfs.cpio.gz" \
+       -append 'console=ttyAMA1,115200 earlycon=pl011,mmio32,0x2800d000 rdinit=/init'
+
+COMe direct boot
+~~~~~~~~~~~~~~~~
+
+.. code-block:: shell
+
+   $ qemu-system-aarch64 \
+       -machine phytium-e2000-come \
+       -smp 4 \
+       -display none -monitor none \
+       -serial null \
+       -serial stdio \
+       -nic user \
+       -kernel "$IMAGES/Image" \
+       -dtb "$IMAGES/e2000q-come-board.dtb" \
+       -initrd "$IMAGES/rootfs.cpio.gz" \
+       -append 'console=ttyAMA1,115200 earlycon=pl011,mmio32,0x2800d000 rdinit=/init'
+
+QEMU applies the normal Arm direct-boot fixups to the supplied SDK DTB, but
+does not synthesize a replacement hardware description.
+
+Known limitations
+-----------------
+
+The machines are functional models for the tested firmware and direct Linux
+boot paths, not complete models of the physical development boards.
+
+* QEMU models the PBR handoff behavior needed by the tested ``fip-all.bin``
+  image, not ROM instruction execution, authentication, or the complete
+  on-chip root of trust.
+* The boot SRAM object graph and SFW callback addresses are limited to the
+  inspected firmware samples. Those samples carry version 4 or version 5
+  DDR/MCU parameter records.
+* FTC310 and FTC664 cache capacities and microarchitectural performance are
+  not modeled. Only the evidenced architected identities and I-cache policy
+  differ between the TCG CPU types.
+* Standalone vendor U-Boot with ``-kernel`` is not supported; use a matching
+  ``fip-all.bin`` so BL31 provides the platform SMC services.
+* Non-boot-critical peripherals remain unimplemented, so guest probe failures
+  are expected. The Phytium Pi ES8336 audio codec is not modeled.
+* The Linux SCMI transport implements the Base protocol only. Performance and
+  Sensor protocols are not modeled, so SCMI CPU-frequency and temperature
+  interfaces are unavailable.
diff --git a/docs/system/target-arm.rst b/docs/system/target-arm.rst
index 260cd7e4d6..77c18b5f2e 100644
--- a/docs/system/target-arm.rst
+++ b/docs/system/target-arm.rst
@@ -95,6 +95,7 @@ Board-specific documentation
    arm/mcimx7d-sabre
    arm/imx8m
    arm/orangepi
+   arm/phytium_e2000
    arm/raspi
    arm/collie
    arm/sx1
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* [PATCH 32/33] tests/functional/aarch64: Add Phytium Pi boot tests
  2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
                   ` (27 preceding siblings ...)
  2026-09-03 11:25 ` [PATCH 31/33] docs/system/arm: Document Phytium E2000 machines Bin Meng
@ 2026-09-03 11:25 ` Bin Meng
  2026-09-03 14:38   ` Alex Bennée
  28 siblings, 1 reply; 45+ messages in thread
From: Bin Meng @ 2026-09-03 11:25 UTC (permalink / raw)
  To: QEMU; +Cc: Peter Maydell, qemu-arm

The Phytium Pi machine supports both the board firmware flow from SD
and direct Linux boot. Exercise both paths with an SDK image bundle so
firmware handoff and kernel loading regressions are caught.

Verify U-Boot, PBF relocation, the Linux handoff, and login for the
firmware path. The Linux test loads the bundled kernel, DTB, and
initramfs and waits for the same login prompt.

Signed-off-by: Bin Meng <bin.meng@processmission.com>
---

 tests/functional/aarch64/meson.build        |  2 +
 tests/functional/aarch64/test_phytium_pi.py | 77 +++++++++++++++++++++
 2 files changed, 79 insertions(+)
 create mode 100644 tests/functional/aarch64/test_phytium_pi.py

diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build
index f0881bed16..71bf45e4ea 100644
--- a/tests/functional/aarch64/meson.build
+++ b/tests/functional/aarch64/meson.build
@@ -7,6 +7,7 @@ test_aarch64_timeouts = {
   'device_passthrough' : 720,
   'imx8mm_evk' : 240,
   'imx8mp_evk' : 240,
+  'phytium_pi' : 600,
   'raspi4' : 480,
   'reverse_debug' : 180,
   'rme_virt' : 1200,
@@ -39,6 +40,7 @@ tests_aarch64_system_thorough = [
   'imx8mp_evk',
   'kvm',
   'multiprocess',
+  'phytium_pi',
   'raspi3',
   'raspi4',
   'replay',
diff --git a/tests/functional/aarch64/test_phytium_pi.py b/tests/functional/aarch64/test_phytium_pi.py
new file mode 100644
index 0000000000..4ae0392bdd
--- /dev/null
+++ b/tests/functional/aarch64/test_phytium_pi.py
@@ -0,0 +1,77 @@
+#!/usr/bin/env python3
+#
+# Functional tests for the Phytium Pi machine
+#
+# Copyright (c) 2026 Process Mission
+#
+# Author:
+#   Bin Meng <bin.meng@processmission.com>
+#
+# SPDX-License-Identifier: GPL-2.0-or-later
+
+from qemu_test import Asset, LinuxKernelTest
+from qemu_test import skipBigDataTest, skipIfMissingCommands
+
+
+class PhytiumPiMachine(LinuxKernelTest):
+
+    ASSET_BUILDROOT = Asset(
+        ('https://github.com/processmission/qemu-machine-images/releases/'
+         'download/v1.0.0/aarch64-phytium-pi-v1.0.0.tar.zst'),
+        '5219d52b862e1245b12f79ba72e9a6144f5e0cf1a50061f8fda2db0aeeb92428')
+
+    def _prepare_images(self):
+        self.set_machine('phytium-pi')
+
+        archive_path = self.uncompress(
+            self.ASSET_BUILDROOT,
+            target='aarch64-phytium-pi-v1.0.0.tar',
+            format='zstd')
+        self.archive_extract(archive_path, format='tar')
+
+        self.vm.set_console(console_index=1)
+        self.vm.add_args('-smp', '4',
+                         '-m', '4G',
+                         '-display', 'none',
+                         '-nic', 'none',
+                         '-no-reboot')
+
+    @skipIfMissingCommands('zstd')
+    @skipBigDataTest()
+    def test_firmware_boot(self):
+        self._prepare_images()
+        sdcard = self.scratch_file('images', 'sdcard.img')
+
+        self.vm.add_args(
+            '-snapshot',
+            '-drive', f'file={sdcard},format=raw,if=sd,index=0')
+        self.vm.launch()
+
+        self.wait_for_console_pattern('U-Boot 2022.01')
+        self.wait_for_console_pattern('PBF relocate done')
+        self.wait_for_console_pattern('Starting kernel ...')
+        self.wait_for_console_pattern('Phytium login:')
+
+    @skipIfMissingCommands('zstd')
+    @skipBigDataTest()
+    def test_linux_boot(self):
+        self._prepare_images()
+        kernel = self.scratch_file('images', 'Image.gz')
+        dtb = self.scratch_file('images', 'phytiumpi_firefly.dtb')
+        initrd = self.scratch_file('images', 'rootfs.cpio.gz')
+
+        self.vm.add_args(
+            '-kernel', kernel,
+            '-dtb', dtb,
+            '-initrd', initrd,
+            '-append', 'console=ttyAMA1,115200 '
+                       'earlycon=pl011,mmio32,0x2800d000 rdinit=/init')
+        self.vm.launch()
+
+        self.wait_for_console_pattern('Booting Linux on physical CPU')
+        self.wait_for_console_pattern('Machine model: Phytium Pi Board')
+        self.wait_for_console_pattern('Phytium login:')
+
+
+if __name__ == '__main__':
+    LinuxKernelTest.main()
-- 
2.53.0



^ permalink raw reply related	[flat|nested] 45+ messages in thread

* Re: [PATCH 32/33] tests/functional/aarch64: Add Phytium Pi boot tests
  2026-09-03 11:25 ` [PATCH 32/33] tests/functional/aarch64: Add Phytium Pi boot tests Bin Meng
@ 2026-09-03 14:38   ` Alex Bennée
  2026-09-04  8:52     ` Bin Meng
  0 siblings, 1 reply; 45+ messages in thread
From: Alex Bennée @ 2026-09-03 14:38 UTC (permalink / raw)
  To: Bin Meng; +Cc: QEMU, Peter Maydell, qemu-arm

Bin Meng <bin.meng@processmission.com> writes:

> The Phytium Pi machine supports both the board firmware flow from SD
> and direct Linux boot. Exercise both paths with an SDK image bundle so
> firmware handoff and kernel loading regressions are caught.
>
> Verify U-Boot, PBF relocation, the Linux handoff, and login for the
> firmware path. The Linux test loads the bundled kernel, DTB, and
> initramfs and waits for the same login prompt.
>
> Signed-off-by: Bin Meng <bin.meng@processmission.com>
> ---
>
>  tests/functional/aarch64/meson.build        |  2 +
>  tests/functional/aarch64/test_phytium_pi.py | 77 +++++++++++++++++++++
>  2 files changed, 79 insertions(+)
>  create mode 100644 tests/functional/aarch64/test_phytium_pi.py
>
> diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build
> index f0881bed16..71bf45e4ea 100644
> --- a/tests/functional/aarch64/meson.build
> +++ b/tests/functional/aarch64/meson.build
> @@ -7,6 +7,7 @@ test_aarch64_timeouts = {
>    'device_passthrough' : 720,
>    'imx8mm_evk' : 240,
>    'imx8mp_evk' : 240,
> +  'phytium_pi' : 600,
>    'raspi4' : 480,
>    'reverse_debug' : 180,
>    'rme_virt' : 1200,
> @@ -39,6 +40,7 @@ tests_aarch64_system_thorough = [
>    'imx8mp_evk',
>    'kvm',
>    'multiprocess',
> +  'phytium_pi',
>    'raspi3',
>    'raspi4',
>    'replay',
> diff --git a/tests/functional/aarch64/test_phytium_pi.py b/tests/functional/aarch64/test_phytium_pi.py
> new file mode 100644
> index 0000000000..4ae0392bdd
> --- /dev/null
> +++ b/tests/functional/aarch64/test_phytium_pi.py
> @@ -0,0 +1,77 @@
> +#!/usr/bin/env python3
> +#
> +# Functional tests for the Phytium Pi machine
> +#
> +# Copyright (c) 2026 Process Mission
> +#
> +# Author:
> +#   Bin Meng <bin.meng@processmission.com>
> +#
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +
> +from qemu_test import Asset, LinuxKernelTest
> +from qemu_test import skipBigDataTest, skipIfMissingCommands
> +
> +
> +class PhytiumPiMachine(LinuxKernelTest):
> +
> +    ASSET_BUILDROOT = Asset(
> +        ('https://github.com/processmission/qemu-machine-images/releases/'
> +         'download/v1.0.0/aarch64-phytium-pi-v1.0.0.tar.zst'),
> +
> '5219d52b862e1245b12f79ba72e9a6144f5e0cf1a50061f8fda2db0aeeb92428')

Nice ;-)

I've been looking into something similar to make it easy to set up
github projects that can build and host test assets:

  https://github.com/stsquad/buildroot-asset

although my builds seem to take quite a bit longer that yours.

I wonder if there is a way to have a common repository set up but in a
way that the forks can just build their own images. Either that or a
core repository which everyone just forks adds their device and merges.

Anyway, test looks sane:

Reviewed-by: Alex Bennée <alex.bennee@linaro.org>

> +
> +    def _prepare_images(self):
> +        self.set_machine('phytium-pi')
> +
> +        archive_path = self.uncompress(
> +            self.ASSET_BUILDROOT,
> +            target='aarch64-phytium-pi-v1.0.0.tar',
> +            format='zstd')
> +        self.archive_extract(archive_path, format='tar')
> +
> +        self.vm.set_console(console_index=1)
> +        self.vm.add_args('-smp', '4',
> +                         '-m', '4G',
> +                         '-display', 'none',
> +                         '-nic', 'none',
> +                         '-no-reboot')
> +
> +    @skipIfMissingCommands('zstd')
> +    @skipBigDataTest()
> +    def test_firmware_boot(self):
> +        self._prepare_images()
> +        sdcard = self.scratch_file('images', 'sdcard.img')
> +
> +        self.vm.add_args(
> +            '-snapshot',
> +            '-drive', f'file={sdcard},format=raw,if=sd,index=0')
> +        self.vm.launch()
> +
> +        self.wait_for_console_pattern('U-Boot 2022.01')
> +        self.wait_for_console_pattern('PBF relocate done')
> +        self.wait_for_console_pattern('Starting kernel ...')
> +        self.wait_for_console_pattern('Phytium login:')
> +
> +    @skipIfMissingCommands('zstd')
> +    @skipBigDataTest()
> +    def test_linux_boot(self):
> +        self._prepare_images()
> +        kernel = self.scratch_file('images', 'Image.gz')
> +        dtb = self.scratch_file('images', 'phytiumpi_firefly.dtb')
> +        initrd = self.scratch_file('images', 'rootfs.cpio.gz')
> +
> +        self.vm.add_args(
> +            '-kernel', kernel,
> +            '-dtb', dtb,
> +            '-initrd', initrd,
> +            '-append', 'console=ttyAMA1,115200 '
> +                       'earlycon=pl011,mmio32,0x2800d000 rdinit=/init')
> +        self.vm.launch()
> +
> +        self.wait_for_console_pattern('Booting Linux on physical CPU')
> +        self.wait_for_console_pattern('Machine model: Phytium Pi Board')
> +        self.wait_for_console_pattern('Phytium login:')
> +
> +
> +if __name__ == '__main__':
> +    LinuxKernelTest.main()

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 01/33] target/arm: Add Phytium FTC310 and FTC664 CPU models
  2026-09-03 11:24 ` [PATCH 01/33] target/arm: Add Phytium FTC310 and FTC664 CPU models Bin Meng
@ 2026-09-03 14:45   ` Alex Bennée
  2026-09-05  3:39     ` Bin Meng
  0 siblings, 1 reply; 45+ messages in thread
From: Alex Bennée @ 2026-09-03 14:45 UTC (permalink / raw)
  To: Bin Meng; +Cc: QEMU, Peter Maydell, qemu-arm

Bin Meng <bin.meng@processmission.com> writes:

> The Phytium E2000Q combines two FTC310 cores and two FTC664 cores.
> Its EL3 firmware accesses implementation-defined system registers
> while setting up the cores. Using generic Cortex-A72 CPUs leaves
> these registers undefined and traps firmware before it can reach
> U-Boot.
>
> Add FTC310 and FTC664 CPU types using Cortex-A72 as their common
> execution base. Provide conservative RAZ/WI and NOP stubs for the
> firmware-visible E2000 controls that have no modeled behavior.
>
> Set each CPU DT compatible string, MIDR, instruction feature fields,
> AArch32 floating-point feature fields, and CTR_EL0.L1Ip field to
> values observed on an E2000Q board. The FTC310 reports a VIPT
> instruction cache, while the FTC664 reports a PIPT instruction cache.
>
> Signed-off-by: Bin Meng <bin.meng@processmission.com>
> ---
>
>  target/arm/tcg/cpu64.c | 81
>  ++++++++++++++++++++++++++++++++++++++++++

I wonder if cpu64 is the best place to model this? The alternative is to
treat it like other SoCs (e.g. the Pi's BCM SoCs). Was the reason it
ended up here because you needed access to aarch64_a72_initfn? Could you
not instantiate:

    soc_base->cpu_type = ARM_CPU_TYPE_NAME("cortex-a72");
    
and then at realize:

    object_initialize_child(OBJECT(dev), "cpu[*]", &s->cpu[n].core,
                                soc_base->cpu_type);

or does that not allow for tweaking the CP regs?


>  1 file changed, 81 insertions(+)
>
> diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c
> index affd87a3ae..0fd2f79bbc 100644
> --- a/target/arm/tcg/cpu64.c
> +++ b/target/arm/tcg/cpu64.c
> @@ -336,6 +336,85 @@ static void aarch64_a72_initfn(Object *obj)
>      define_cortex_a72_a57_a53_cp_reginfo(cpu);
>  }
>  
> +static const ARMCPRegInfo phytium_e2000_cp_reginfo[] = {
> +    /*
> +     * The E2000 EL3 firmware touches implementation-defined CPU registers
> +     * during the PBF/BL1 cache and core setup. QEMU does not model these
> +     * controls, so expose conservative RAZ/WI stubs for the boot firmware.
> +     *
> +     * PBF reads these identification and cluster controls after writing
> +     * them. Returning zero preserves the reset state without claiming that
> +     * QEMU implements the associated cache or coherency controls.
> +     */
> +    { .name = "E2000_CPUID_CTL1", .state = ARM_CP_STATE_AA64,
> +      .opc0 = 3, .opc1 = 1, .crn = 15, .crm = 1, .opc2 = 0,
> +      .access = PL1_RW, .type = ARM_CP_CONST | ARM_CP_NO_RAW,
> +      .resetvalue = 0 },
> +    { .name = "E2000_CLUSTER_CTL", .state = ARM_CP_STATE_AA64,
> +      .opc0 = 3, .opc1 = 1, .crn = 11, .crm = 8, .opc2 = 6,
> +      .access = PL1_RW, .type = ARM_CP_CONST | ARM_CP_NO_RAW,
> +      .resetvalue = 0 },
> +    /*
> +     * The remaining controls are only programmed as part of firmware setup.
> +     * Accept the writes without retaining state because no modeled CPU
> +     * behavior depends on their values.
> +     */
> +    { .name = "E2000_EL1_CTL", .state = ARM_CP_STATE_AA64,
> +      .opc0 = 3, .opc1 = 2, .crn = 15, .crm = 15, .opc2 = 0,
> +      .access = PL1_RW, .type = ARM_CP_NOP | ARM_CP_NO_RAW },
> +    { .name = "E2000_EL2_CTL", .state = ARM_CP_STATE_AA64,
> +      .opc0 = 3, .opc1 = 4, .crn = 15, .crm = 15, .opc2 = 0,
> +      .access = PL2_RW, .type = ARM_CP_NOP | ARM_CP_NO_RAW },
> +    { .name = "E2000_EL2_CTL2", .state = ARM_CP_STATE_AA64,
> +      .opc0 = 3, .opc1 = 4, .crn = 15, .crm = 2, .opc2 = 4,
> +      .access = PL2_RW, .type = ARM_CP_NOP | ARM_CP_NO_RAW },
> +    { .name = "E2000_EL3_CTL", .state = ARM_CP_STATE_AA64,
> +      .opc0 = 3, .opc1 = 6, .crn = 15, .crm = 15, .opc2 = 0,
> +      .access = PL3_RW, .type = ARM_CP_NOP | ARM_CP_NO_RAW },
> +};
> +
> +/*
> + * Use the Cortex-A72 execution model as the common E2000 TCG base, then
> + * replace the architected identity fields that differ between the two
> + * physical core types.
> + */
> +static void aarch64_phytium_e2000_base_initfn(Object *obj)
> +{
> +    ARMCPU *cpu = ARM_CPU(obj);
> +
> +    aarch64_a72_initfn(obj);
> +    define_arm_cp_regs(cpu, phytium_e2000_cp_reginfo);
> +}
> +
> +static void aarch64_phytium_ftc310_initfn(Object *obj)
> +{
> +    ARMCPU *cpu = ARM_CPU(obj);
> +    ARMISARegisters *isar = &cpu->isar;
> +
> +    aarch64_phytium_e2000_base_initfn(obj);
> +
> +    /* FTC310 cores identify with the FTC303 part number */
> +    cpu->dtb_compatible = "phytium,ftc310";
> +    cpu->midr = 0x700f3034;
> +    SET_IDREG(isar, ID_AA64ISAR0, 0x00011100012120);
> +    cpu->isar.mvfr0 = 0x10110222;
> +    cpu->ctr = FIELD_DP64(cpu->ctr, CTR_EL0, L1IP, 2); /* VIPT */
> +}
> +
> +static void aarch64_phytium_ftc664_initfn(Object *obj)
> +{
> +    ARMCPU *cpu = ARM_CPU(obj);
> +    ARMISARegisters *isar = &cpu->isar;
> +
> +    aarch64_phytium_e2000_base_initfn(obj);
> +
> +    cpu->dtb_compatible = "phytium,ftc664";
> +    cpu->midr = 0x701f6643;
> +    SET_IDREG(isar, ID_AA64ISAR0, 0x00000100012120);
> +    cpu->isar.mvfr0 = 0x10111222;
> +    cpu->ctr = FIELD_DP64(cpu->ctr, CTR_EL0, L1IP, 3); /* PIPT */
> +}
> +
>  static void aarch64_a76_initfn(Object *obj)
>  {
>      ARMCPU *cpu = ARM_CPU(obj);
> @@ -1533,6 +1612,8 @@ static const ARMCPUInfo aarch64_cpus[] = {
>      { .name = "cortex-a55",         .initfn = aarch64_a55_initfn },
>      { .name = "cortex-a72",         .initfn = aarch64_a72_initfn },
>      { .name = "cortex-a76",         .initfn = aarch64_a76_initfn },
> +    { .name = "phytium-ftc310",     .initfn = aarch64_phytium_ftc310_initfn },
> +    { .name = "phytium-ftc664",     .initfn = aarch64_phytium_ftc664_initfn },
>      /*
>       * The Cortex-A78AE differs slightly from the plain Cortex-A78. We don't
>       * currently model the latter.

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-03 11:24 ` [PATCH 02/33] hw/arm: Add basic Phytium Pi machine Bin Meng
@ 2026-09-03 16:56   ` Philippe Mathieu-Daudé
  2026-09-04  7:57     ` Bin Meng
  0 siblings, 1 reply; 45+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-09-03 16:56 UTC (permalink / raw)
  To: Bin Meng, QEMU; +Cc: Paolo Bonzini, Peter Maydell, qemu-arm

On 3/9/26 13:24, Bin Meng wrote:
> Add the base topology for the Phytium Pi board built around the
> E2000Q SoC, with two FTC310 and two FTC664 CPUs, RAM, GICv3/ITS,
> and PL011 UARTs.
> 
> Describe the CPUs as three non-uniform clusters matching the vendor
> Linux device tree. Preserve the board CPU slot order so firmware
> MPIDR 0x200 maps to QEMU CPU index 2.
> 
> Signed-off-by: Bin Meng <bin.meng@processmission.com>
> ---
> 
>   hw/arm/Kconfig         |   8 +
>   hw/arm/meson.build     |   1 +
>   hw/arm/phytium_e2000.c | 406 +++++++++++++++++++++++++++++++++++++++++
>   3 files changed, 415 insertions(+)
>   create mode 100644 hw/arm/phytium_e2000.c


> +struct PhytiumE2000State {
> +    MachineState parent;
> +    struct arm_boot_info bootinfo;
> +    DeviceState *gic;
> +    MemoryRegion ram_low;
> +    MemoryRegion ram_high;
> +};


> +static void phytium_e2000_create_gic(PhytiumE2000State *s)
> +{
> +    MachineState *ms = MACHINE(s);
> +    SysBusDevice *gicbusdev;
> +    QList *redist_region_count;
> +    int i;
> +
> +    s->gic = qdev_new(gicv3_class_name());
> +    qdev_prop_set_uint32(s->gic, "revision", 3);
> +    qdev_prop_set_uint32(s->gic, "num-cpu", ms->smp.cpus);
> +    qdev_prop_set_uint32(s->gic, "num-irq", PHYTIUM_E2000_NUM_IRQS + 32);
> +    qdev_prop_set_bit(s->gic, "has-security-extensions", true);
> +    qdev_prop_set_bit(s->gic, "has-lpi", true);
> +
> +    redist_region_count = qlist_new();
> +    qlist_append_int(redist_region_count, ms->smp.cpus);
> +    qdev_prop_set_array(s->gic, "redist-region-count", redist_region_count);
> +
> +    object_property_set_link(OBJECT(s->gic), "sysmem",
> +                             OBJECT(get_system_memory()), &error_fatal);
> +
> +    gicbusdev = SYS_BUS_DEVICE(s->gic);
> +    sysbus_realize_and_unref(gicbusdev, &error_fatal);
> +    sysbus_mmio_map(gicbusdev, 0,
> +                    phytium_e2000_memmap[PHYTIUM_E2000_GIC_DIST].base);
> +    sysbus_mmio_map(gicbusdev, 1,
> +                    phytium_e2000_memmap[PHYTIUM_E2000_GIC_REDIST].base);
> +
> +    for (i = 0; i < ms->smp.cpus; i++) {
> +        DeviceState *cpudev = DEVICE(qemu_get_cpu(i));

I'd rather you keep a reference in PhytiumE2000State when creating
in [*] and access the CPUs that way, not with qemu_get_cpu() (because
we plan to restrict it to accel/, as it is problematic on heterogeneous
emulation).

> +        int intidbase = PHYTIUM_E2000_NUM_IRQS + i * GIC_INTERNAL;
> +        static const int timer_irq[] = {
> +            [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ,
> +            [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ,
> +            [GTIMER_HYP]  = ARCH_TIMER_NS_EL2_IRQ,
> +            [GTIMER_SEC]  = ARCH_TIMER_S_EL1_IRQ,
> +        };
> +
> +        for (int irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) {
> +            qdev_connect_gpio_out(cpudev, irq,
> +                qdev_get_gpio_in(s->gic, intidbase + timer_irq[irq]));
> +        }
> +        qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", 0,
> +            qdev_get_gpio_in(s->gic, intidbase + ARCH_GIC_MAINT_IRQ));
> +        qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0,
> +            qdev_get_gpio_in(s->gic, intidbase + VIRTUAL_PMU_IRQ));
> +
> +        sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
> +        sysbus_connect_irq(gicbusdev, i + ms->smp.cpus,
> +                           qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
> +        sysbus_connect_irq(gicbusdev, i + 2 * ms->smp.cpus,
> +                           qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ));
> +        sysbus_connect_irq(gicbusdev, i + 3 * ms->smp.cpus,
> +                           qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ));
> +    }
> +
> +    phytium_e2000_create_its(s);
> +}


> +static void phytium_e2000_create_cpus(PhytiumE2000State *s)
> +{
> +    MachineState *ms = MACHINE(s);
> +    const CPUArchIdList *possible_cpus;
> +    int i;
> +
> +    possible_cpus = MACHINE_GET_CLASS(ms)->possible_cpu_arch_ids(ms);
> +
> +    for (i = 0; i < ms->smp.cpus; i++) {
> +        Object *cpuobj = object_new(possible_cpus->cpus[i].type);

[*]

> +        CPUState *cs;
> +
> +        object_property_set_int(cpuobj, "mp-affinity",
> +                                possible_cpus->cpus[i].arch_id,
> +                                &error_abort);
> +        object_property_set_int(cpuobj, "cntfrq", PHYTIUM_E2000_GTIMER_HZ,
> +                                &error_abort);
> +        if (object_property_find(cpuobj, "has_el3")) {
> +            /*
> +             * The generic-loader U-Boot path starts after the EL3 firmware
> +             * stages that normally provide the Phytium SMC services.
> +             */
> +            object_property_set_bool(cpuobj, "has_el3", false, &error_abort);
> +        }
> +        object_property_set_link(cpuobj, "memory", OBJECT(get_system_memory()),
> +                                 &error_abort);
> +        cs = CPU(cpuobj);
> +        cs->cpu_index = i;
> +        qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
> +        object_unref(cpuobj);
> +    }
> +}
> +
> +static void phytium_pi_init(MachineState *ms)
> +{
> +    PhytiumE2000State *s = PHYTIUM_PI(ms);
> +    int i;
> +
> +    if (kvm_enabled()) {
> +        error_report("phytium-pi: KVM is not supported");

Why is it relevant?
> +        exit(1);
> +    }

> +}
Conditional to not using qemu_get_cpu():
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-03 16:56   ` Philippe Mathieu-Daudé
@ 2026-09-04  7:57     ` Bin Meng
  2026-09-04 10:15       ` Philippe Mathieu-Daudé
  0 siblings, 1 reply; 45+ messages in thread
From: Bin Meng @ 2026-09-04  7:57 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé
  Cc: Bin Meng, QEMU, Paolo Bonzini, Peter Maydell, qemu-arm

Hi Philippe,

On Fri, Sep 4, 2026 at 12:56 AM Philippe Mathieu-Daudé
<philmd@oss.qualcomm.com> wrote:
>
> On 3/9/26 13:24, Bin Meng wrote:
> > Add the base topology for the Phytium Pi board built around the
> > E2000Q SoC, with two FTC310 and two FTC664 CPUs, RAM, GICv3/ITS,
> > and PL011 UARTs.
> >
> > Describe the CPUs as three non-uniform clusters matching the vendor
> > Linux device tree. Preserve the board CPU slot order so firmware
> > MPIDR 0x200 maps to QEMU CPU index 2.
> >
> > Signed-off-by: Bin Meng <bin.meng@processmission.com>
> > ---
> >
> >   hw/arm/Kconfig         |   8 +
> >   hw/arm/meson.build     |   1 +
> >   hw/arm/phytium_e2000.c | 406 +++++++++++++++++++++++++++++++++++++++++
> >   3 files changed, 415 insertions(+)
> >   create mode 100644 hw/arm/phytium_e2000.c
>
>
> > +struct PhytiumE2000State {
> > +    MachineState parent;
> > +    struct arm_boot_info bootinfo;
> > +    DeviceState *gic;
> > +    MemoryRegion ram_low;
> > +    MemoryRegion ram_high;
> > +};
>
>
> > +static void phytium_e2000_create_gic(PhytiumE2000State *s)
> > +{
> > +    MachineState *ms = MACHINE(s);
> > +    SysBusDevice *gicbusdev;
> > +    QList *redist_region_count;
> > +    int i;
> > +
> > +    s->gic = qdev_new(gicv3_class_name());
> > +    qdev_prop_set_uint32(s->gic, "revision", 3);
> > +    qdev_prop_set_uint32(s->gic, "num-cpu", ms->smp.cpus);
> > +    qdev_prop_set_uint32(s->gic, "num-irq", PHYTIUM_E2000_NUM_IRQS + 32);
> > +    qdev_prop_set_bit(s->gic, "has-security-extensions", true);
> > +    qdev_prop_set_bit(s->gic, "has-lpi", true);
> > +
> > +    redist_region_count = qlist_new();
> > +    qlist_append_int(redist_region_count, ms->smp.cpus);
> > +    qdev_prop_set_array(s->gic, "redist-region-count", redist_region_count);
> > +
> > +    object_property_set_link(OBJECT(s->gic), "sysmem",
> > +                             OBJECT(get_system_memory()), &error_fatal);
> > +
> > +    gicbusdev = SYS_BUS_DEVICE(s->gic);
> > +    sysbus_realize_and_unref(gicbusdev, &error_fatal);
> > +    sysbus_mmio_map(gicbusdev, 0,
> > +                    phytium_e2000_memmap[PHYTIUM_E2000_GIC_DIST].base);
> > +    sysbus_mmio_map(gicbusdev, 1,
> > +                    phytium_e2000_memmap[PHYTIUM_E2000_GIC_REDIST].base);
> > +
> > +    for (i = 0; i < ms->smp.cpus; i++) {
> > +        DeviceState *cpudev = DEVICE(qemu_get_cpu(i));
>
> I'd rather you keep a reference in PhytiumE2000State when creating
> in [*] and access the CPUs that way, not with qemu_get_cpu() (because
> we plan to restrict it to accel/, as it is problematic on heterogeneous
> emulation).

Will fix in v2.

>
> > +        int intidbase = PHYTIUM_E2000_NUM_IRQS + i * GIC_INTERNAL;
> > +        static const int timer_irq[] = {
> > +            [GTIMER_PHYS] = ARCH_TIMER_NS_EL1_IRQ,
> > +            [GTIMER_VIRT] = ARCH_TIMER_VIRT_IRQ,
> > +            [GTIMER_HYP]  = ARCH_TIMER_NS_EL2_IRQ,
> > +            [GTIMER_SEC]  = ARCH_TIMER_S_EL1_IRQ,
> > +        };
> > +
> > +        for (int irq = 0; irq < ARRAY_SIZE(timer_irq); irq++) {
> > +            qdev_connect_gpio_out(cpudev, irq,
> > +                qdev_get_gpio_in(s->gic, intidbase + timer_irq[irq]));
> > +        }
> > +        qdev_connect_gpio_out_named(cpudev, "gicv3-maintenance-interrupt", 0,
> > +            qdev_get_gpio_in(s->gic, intidbase + ARCH_GIC_MAINT_IRQ));
> > +        qdev_connect_gpio_out_named(cpudev, "pmu-interrupt", 0,
> > +            qdev_get_gpio_in(s->gic, intidbase + VIRTUAL_PMU_IRQ));
> > +
> > +        sysbus_connect_irq(gicbusdev, i, qdev_get_gpio_in(cpudev, ARM_CPU_IRQ));
> > +        sysbus_connect_irq(gicbusdev, i + ms->smp.cpus,
> > +                           qdev_get_gpio_in(cpudev, ARM_CPU_FIQ));
> > +        sysbus_connect_irq(gicbusdev, i + 2 * ms->smp.cpus,
> > +                           qdev_get_gpio_in(cpudev, ARM_CPU_VIRQ));
> > +        sysbus_connect_irq(gicbusdev, i + 3 * ms->smp.cpus,
> > +                           qdev_get_gpio_in(cpudev, ARM_CPU_VFIQ));
> > +    }
> > +
> > +    phytium_e2000_create_its(s);
> > +}
>
>
> > +static void phytium_e2000_create_cpus(PhytiumE2000State *s)
> > +{
> > +    MachineState *ms = MACHINE(s);
> > +    const CPUArchIdList *possible_cpus;
> > +    int i;
> > +
> > +    possible_cpus = MACHINE_GET_CLASS(ms)->possible_cpu_arch_ids(ms);
> > +
> > +    for (i = 0; i < ms->smp.cpus; i++) {
> > +        Object *cpuobj = object_new(possible_cpus->cpus[i].type);
>
> [*]
>
> > +        CPUState *cs;
> > +
> > +        object_property_set_int(cpuobj, "mp-affinity",
> > +                                possible_cpus->cpus[i].arch_id,
> > +                                &error_abort);
> > +        object_property_set_int(cpuobj, "cntfrq", PHYTIUM_E2000_GTIMER_HZ,
> > +                                &error_abort);
> > +        if (object_property_find(cpuobj, "has_el3")) {
> > +            /*
> > +             * The generic-loader U-Boot path starts after the EL3 firmware
> > +             * stages that normally provide the Phytium SMC services.
> > +             */
> > +            object_property_set_bool(cpuobj, "has_el3", false, &error_abort);
> > +        }
> > +        object_property_set_link(cpuobj, "memory", OBJECT(get_system_memory()),
> > +                                 &error_abort);
> > +        cs = CPU(cpuobj);
> > +        cs->cpu_index = i;
> > +        qdev_realize(DEVICE(cpuobj), NULL, &error_fatal);
> > +        object_unref(cpuobj);
> > +    }
> > +}
> > +
> > +static void phytium_pi_init(MachineState *ms)
> > +{
> > +    PhytiumE2000State *s = PHYTIUM_PI(ms);
> > +    int i;
> > +
> > +    if (kvm_enabled()) {
> > +        error_report("phytium-pi: KVM is not supported");
>
> Why is it relevant?

This is because the machine relies on heterogeneous named
FTC310/FTC664 TCG CPU models and Arm KVM generally requires the host
CPU model. KVM is rejected explicitly instead of failing later during
vCPU initialization.

> > +        exit(1);
> > +    }
>
> > +}
> Conditional to not using qemu_get_cpu():
> Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>

Thanks for the review!

Regards,
Bin


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 32/33] tests/functional/aarch64: Add Phytium Pi boot tests
  2026-09-03 14:38   ` Alex Bennée
@ 2026-09-04  8:52     ` Bin Meng
  2026-09-04 14:23       ` Alex Bennée
  0 siblings, 1 reply; 45+ messages in thread
From: Bin Meng @ 2026-09-04  8:52 UTC (permalink / raw)
  To: Alex Bennée; +Cc: Bin Meng, QEMU, Peter Maydell, qemu-arm

Hi Alex,

On Thu, Sep 3, 2026 at 10:38 PM Alex Bennée <alex.bennee@linaro.org> wrote:
>
> Bin Meng <bin.meng@processmission.com> writes:
>
> > The Phytium Pi machine supports both the board firmware flow from SD
> > and direct Linux boot. Exercise both paths with an SDK image bundle so
> > firmware handoff and kernel loading regressions are caught.
> >
> > Verify U-Boot, PBF relocation, the Linux handoff, and login for the
> > firmware path. The Linux test loads the bundled kernel, DTB, and
> > initramfs and waits for the same login prompt.
> >
> > Signed-off-by: Bin Meng <bin.meng@processmission.com>
> > ---
> >
> >  tests/functional/aarch64/meson.build        |  2 +
> >  tests/functional/aarch64/test_phytium_pi.py | 77 +++++++++++++++++++++
> >  2 files changed, 79 insertions(+)
> >  create mode 100644 tests/functional/aarch64/test_phytium_pi.py
> >
> > diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build
> > index f0881bed16..71bf45e4ea 100644
> > --- a/tests/functional/aarch64/meson.build
> > +++ b/tests/functional/aarch64/meson.build
> > @@ -7,6 +7,7 @@ test_aarch64_timeouts = {
> >    'device_passthrough' : 720,
> >    'imx8mm_evk' : 240,
> >    'imx8mp_evk' : 240,
> > +  'phytium_pi' : 600,
> >    'raspi4' : 480,
> >    'reverse_debug' : 180,
> >    'rme_virt' : 1200,
> > @@ -39,6 +40,7 @@ tests_aarch64_system_thorough = [
> >    'imx8mp_evk',
> >    'kvm',
> >    'multiprocess',
> > +  'phytium_pi',
> >    'raspi3',
> >    'raspi4',
> >    'replay',
> > diff --git a/tests/functional/aarch64/test_phytium_pi.py b/tests/functional/aarch64/test_phytium_pi.py
> > new file mode 100644
> > index 0000000000..4ae0392bdd
> > --- /dev/null
> > +++ b/tests/functional/aarch64/test_phytium_pi.py
> > @@ -0,0 +1,77 @@
> > +#!/usr/bin/env python3
> > +#
> > +# Functional tests for the Phytium Pi machine
> > +#
> > +# Copyright (c) 2026 Process Mission
> > +#
> > +# Author:
> > +#   Bin Meng <bin.meng@processmission.com>
> > +#
> > +# SPDX-License-Identifier: GPL-2.0-or-later
> > +
> > +from qemu_test import Asset, LinuxKernelTest
> > +from qemu_test import skipBigDataTest, skipIfMissingCommands
> > +
> > +
> > +class PhytiumPiMachine(LinuxKernelTest):
> > +
> > +    ASSET_BUILDROOT = Asset(
> > +        ('https://github.com/processmission/qemu-machine-images/releases/'
> > +         'download/v1.0.0/aarch64-phytium-pi-v1.0.0.tar.zst'),
> > +
> > '5219d52b862e1245b12f79ba72e9a6144f5e0cf1a50061f8fda2db0aeeb92428')
>
> Nice ;-)
>
> I've been looking into something similar to make it easy to set up
> github projects that can build and host test assets:
>
>   https://github.com/stsquad/buildroot-asset

This is actually very close to what I have been doing with
https://github.com/processmission/qemu-machine-images. It is intended
to be a common repository for QEMU machine test assets.

Each machine has a small description under:

  machine/<architecture>/<machine>/

It selects the independently built components, required images and
machine-specific QEMU launcher. The container builders, packaging,
checksums, release manifest and GitHub Actions workflow are shared.
The workflow also distinguishes between the main repository and forks.
A fork can build selected machines and upload run-scoped Actions
artifacts without publishing a release. It can optionally publish its
own development prerelease, while the main repository publishes
frozen, versioned assets.

>
> although my builds seem to take quite a bit longer that yours.

Yeah, as I only built the minimal busybox-based buildroot
configuration, which is enough for QEMU boot testing :)

>
> I wonder if there is a way to have a common repository set up but in a
> way that the forks can just build their own images. Either that or a
> core repository which everyone just forks adds their device and merges.

So perhaps qemu-machine-images could serve as a starting point for the
common repository you describe. I would be happy to accept machine
definitions from other contributors and make the add-a-machine
workflow simpler.

Your buildroot-asset project also has useful ideas, particularly
including enough Buildroot configuration and source metadata to
reproduce an image manually. It would be good to align the two efforts
instead of growing separate repositories for each maintainer or board.

>
> Anyway, test looks sane:
>
> Reviewed-by: Alex Bennée <alex.bennee@linaro.org>

Thanks for the review!

>
> > +
> > +    def _prepare_images(self):
> > +        self.set_machine('phytium-pi')
> > +
> > +        archive_path = self.uncompress(
> > +            self.ASSET_BUILDROOT,
> > +            target='aarch64-phytium-pi-v1.0.0.tar',
> > +            format='zstd')
> > +        self.archive_extract(archive_path, format='tar')
> > +
> > +        self.vm.set_console(console_index=1)
> > +        self.vm.add_args('-smp', '4',
> > +                         '-m', '4G',
> > +                         '-display', 'none',
> > +                         '-nic', 'none',
> > +                         '-no-reboot')
> > +
> > +    @skipIfMissingCommands('zstd')
> > +    @skipBigDataTest()
> > +    def test_firmware_boot(self):
> > +        self._prepare_images()
> > +        sdcard = self.scratch_file('images', 'sdcard.img')
> > +
> > +        self.vm.add_args(
> > +            '-snapshot',
> > +            '-drive', f'file={sdcard},format=raw,if=sd,index=0')
> > +        self.vm.launch()
> > +
> > +        self.wait_for_console_pattern('U-Boot 2022.01')
> > +        self.wait_for_console_pattern('PBF relocate done')
> > +        self.wait_for_console_pattern('Starting kernel ...')
> > +        self.wait_for_console_pattern('Phytium login:')
> > +
> > +    @skipIfMissingCommands('zstd')
> > +    @skipBigDataTest()
> > +    def test_linux_boot(self):
> > +        self._prepare_images()
> > +        kernel = self.scratch_file('images', 'Image.gz')
> > +        dtb = self.scratch_file('images', 'phytiumpi_firefly.dtb')
> > +        initrd = self.scratch_file('images', 'rootfs.cpio.gz')
> > +
> > +        self.vm.add_args(
> > +            '-kernel', kernel,
> > +            '-dtb', dtb,
> > +            '-initrd', initrd,
> > +            '-append', 'console=ttyAMA1,115200 '
> > +                       'earlycon=pl011,mmio32,0x2800d000 rdinit=/init')
> > +        self.vm.launch()
> > +
> > +        self.wait_for_console_pattern('Booting Linux on physical CPU')
> > +        self.wait_for_console_pattern('Machine model: Phytium Pi Board')
> > +        self.wait_for_console_pattern('Phytium login:')
> > +
> > +
> > +if __name__ == '__main__':
> > +    LinuxKernelTest.main()
>
> --

Regards,
Bin


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-04  7:57     ` Bin Meng
@ 2026-09-04 10:15       ` Philippe Mathieu-Daudé
  2026-09-04 10:25         ` Daniel P. Berrangé
  2026-09-04 10:25         ` Peter Maydell
  0 siblings, 2 replies; 45+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-09-04 10:15 UTC (permalink / raw)
  To: Bin Meng, Pierrick Bouvier, Daniel Henrique Barboza
  Cc: Bin Meng, QEMU, Paolo Bonzini, Peter Maydell, qemu-arm,
	Yonggang Luo, Anton Johansson

On 4/9/26 09:57, Bin Meng wrote:
> Hi Philippe,
> 
> On Fri, Sep 4, 2026 at 12:56 AM Philippe Mathieu-Daudé
> <philmd@oss.qualcomm.com> wrote:
>>
>> On 3/9/26 13:24, Bin Meng wrote:
>>> Add the base topology for the Phytium Pi board built around the
>>> E2000Q SoC, with two FTC310 and two FTC664 CPUs, RAM, GICv3/ITS,
>>> and PL011 UARTs.
>>>
>>> Describe the CPUs as three non-uniform clusters matching the vendor
>>> Linux device tree. Preserve the board CPU slot order so firmware
>>> MPIDR 0x200 maps to QEMU CPU index 2.
>>>
>>> Signed-off-by: Bin Meng <bin.meng@processmission.com>
>>> ---
>>>
>>>    hw/arm/Kconfig         |   8 +
>>>    hw/arm/meson.build     |   1 +
>>>    hw/arm/phytium_e2000.c | 406 +++++++++++++++++++++++++++++++++++++++++
>>>    3 files changed, 415 insertions(+)
>>>    create mode 100644 hw/arm/phytium_e2000.c


>>> +static void phytium_pi_init(MachineState *ms)
>>> +{
>>> +    PhytiumE2000State *s = PHYTIUM_PI(ms);
>>> +    int i;
>>> +
>>> +    if (kvm_enabled()) {
>>> +        error_report("phytium-pi: KVM is not supported");
>>
>> Why is it relevant?
> 
> This is because the machine relies on heterogeneous named
> FTC310/FTC664 TCG CPU models and Arm KVM generally requires the host
> CPU model. KVM is rejected explicitly instead of failing later during
> vCPU initialization.

Hmm OK. Cc'ing Pierrick to add to our list of things to think about for
heterogenous emulation. I'd rather have this handled in a generic way,
not per-machine. Hopefully TypeInfo::is_available will be sufficient
to not expose the machine if KVM is not available. No need to think
about that now.

> 
>>> +        exit(1);
>>> +    }
>>
>>> +}



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-04 10:15       ` Philippe Mathieu-Daudé
@ 2026-09-04 10:25         ` Daniel P. Berrangé
  2026-09-04 10:28           ` Peter Maydell
  2026-09-04 10:25         ` Peter Maydell
  1 sibling, 1 reply; 45+ messages in thread
From: Daniel P. Berrangé @ 2026-09-04 10:25 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé
  Cc: Bin Meng, Pierrick Bouvier, Daniel Henrique Barboza, Bin Meng,
	QEMU, Paolo Bonzini, Peter Maydell, qemu-arm, Yonggang Luo,
	Anton Johansson

On Fri, Sep 04, 2026 at 12:15:26PM +0200, Philippe Mathieu-Daudé wrote:
> On 4/9/26 09:57, Bin Meng wrote:
> > Hi Philippe,
> > 
> > On Fri, Sep 4, 2026 at 12:56 AM Philippe Mathieu-Daudé
> > <philmd@oss.qualcomm.com> wrote:
> > > 
> > > On 3/9/26 13:24, Bin Meng wrote:
> > > > Add the base topology for the Phytium Pi board built around the
> > > > E2000Q SoC, with two FTC310 and two FTC664 CPUs, RAM, GICv3/ITS,
> > > > and PL011 UARTs.
> > > > 
> > > > Describe the CPUs as three non-uniform clusters matching the vendor
> > > > Linux device tree. Preserve the board CPU slot order so firmware
> > > > MPIDR 0x200 maps to QEMU CPU index 2.
> > > > 
> > > > Signed-off-by: Bin Meng <bin.meng@processmission.com>
> > > > ---
> > > > 
> > > >    hw/arm/Kconfig         |   8 +
> > > >    hw/arm/meson.build     |   1 +
> > > >    hw/arm/phytium_e2000.c | 406 +++++++++++++++++++++++++++++++++++++++++
> > > >    3 files changed, 415 insertions(+)
> > > >    create mode 100644 hw/arm/phytium_e2000.c
> 
> 
> > > > +static void phytium_pi_init(MachineState *ms)
> > > > +{
> > > > +    PhytiumE2000State *s = PHYTIUM_PI(ms);
> > > > +    int i;
> > > > +
> > > > +    if (kvm_enabled()) {
> > > > +        error_report("phytium-pi: KVM is not supported");
> > > 
> > > Why is it relevant?
> > 
> > This is because the machine relies on heterogeneous named
> > FTC310/FTC664 TCG CPU models and Arm KVM generally requires the host
> > CPU model. KVM is rejected explicitly instead of failing later during
> > vCPU initialization.

We have several other hardware accelerators for other platforms.
It is right to be checking KVM specifically, or should we have
general check that only passes for TCG/QTest ?

> Hmm OK. Cc'ing Pierrick to add to our list of things to think about for
> heterogenous emulation. I'd rather have this handled in a generic way,
> not per-machine. Hopefully TypeInfo::is_available will be sufficient
> to not expose the machine if KVM is not available. No need to think
> about that now.

We need to use QAPI MachineInfo / MachineClass to report to mgmt apps
whether a machine is capable of using hardware acceleration or not in
response to "query-machines".

Currently if a target supports HW accel, then apps assume that all
machines in that target can use acceleration. This was a convenient
short cut assumption, but this new machine suggests we can no make
do with that assumption, and need to explicitly report it per-machine.

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-04 10:15       ` Philippe Mathieu-Daudé
  2026-09-04 10:25         ` Daniel P. Berrangé
@ 2026-09-04 10:25         ` Peter Maydell
  1 sibling, 0 replies; 45+ messages in thread
From: Peter Maydell @ 2026-09-04 10:25 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé
  Cc: Bin Meng, Pierrick Bouvier, Daniel Henrique Barboza, Bin Meng,
	QEMU, Paolo Bonzini, qemu-arm, Yonggang Luo, Anton Johansson

On Fri, 4 Sept 2026 at 11:15, Philippe Mathieu-Daudé
<philmd@oss.qualcomm.com> wrote:
>
> On 4/9/26 09:57, Bin Meng wrote:
> > Hi Philippe,
> >
> > On Fri, Sep 4, 2026 at 12:56 AM Philippe Mathieu-Daudé
> > <philmd@oss.qualcomm.com> wrote:
> >>
> >> On 3/9/26 13:24, Bin Meng wrote:
> >>> Add the base topology for the Phytium Pi board built around the
> >>> E2000Q SoC, with two FTC310 and two FTC664 CPUs, RAM, GICv3/ITS,
> >>> and PL011 UARTs.
> >>>
> >>> Describe the CPUs as three non-uniform clusters matching the vendor
> >>> Linux device tree. Preserve the board CPU slot order so firmware
> >>> MPIDR 0x200 maps to QEMU CPU index 2.
> >>>
> >>> Signed-off-by: Bin Meng <bin.meng@processmission.com>
> >>> ---
> >>>
> >>>    hw/arm/Kconfig         |   8 +
> >>>    hw/arm/meson.build     |   1 +
> >>>    hw/arm/phytium_e2000.c | 406 +++++++++++++++++++++++++++++++++++++++++
> >>>    3 files changed, 415 insertions(+)
> >>>    create mode 100644 hw/arm/phytium_e2000.c
>
>
> >>> +static void phytium_pi_init(MachineState *ms)
> >>> +{
> >>> +    PhytiumE2000State *s = PHYTIUM_PI(ms);
> >>> +    int i;
> >>> +
> >>> +    if (kvm_enabled()) {
> >>> +        error_report("phytium-pi: KVM is not supported");
> >>
> >> Why is it relevant?
> >
> > This is because the machine relies on heterogeneous named
> > FTC310/FTC664 TCG CPU models and Arm KVM generally requires the host
> > CPU model. KVM is rejected explicitly instead of failing later during
> > vCPU initialization.
>
> Hmm OK. Cc'ing Pierrick to add to our list of things to think about for
> heterogenous emulation. I'd rather have this handled in a generic way,
> not per-machine. Hopefully TypeInfo::is_available will be sufficient
> to not expose the machine if KVM is not available. No need to think
> about that now.

Explicitly checking for KVM is something we do already in
the sbsa-ref board. But I think you should only need
to do that if your board type accepts arbitrary CPU types
rather than hardwiring the CPU type.

-- PMM


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-04 10:25         ` Daniel P. Berrangé
@ 2026-09-04 10:28           ` Peter Maydell
  2026-09-04 10:36             ` Daniel P. Berrangé
  0 siblings, 1 reply; 45+ messages in thread
From: Peter Maydell @ 2026-09-04 10:28 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Philippe Mathieu-Daudé, Bin Meng, Pierrick Bouvier,
	Daniel Henrique Barboza, Bin Meng, QEMU, Paolo Bonzini, qemu-arm,
	Yonggang Luo, Anton Johansson

On Fri, 4 Sept 2026 at 11:25, Daniel P. Berrangé <berrange@redhat.com> wrote:
> We need to use QAPI MachineInfo / MachineClass to report to mgmt apps
> whether a machine is capable of using hardware acceleration or not in
> response to "query-machines".
>
> Currently if a target supports HW accel, then apps assume that all
> machines in that target can use acceleration. This was a convenient
> short cut assumption, but this new machine suggests we can no make
> do with that assumption, and need to explicitly report it per-machine.

That assumption has never been true for Arm; this new machine
type is no different to any of our existing boards in that regard.
Support for KVM etc is only present for the 'virt' machine type and
one or two others. Most of the rest don't work with KVM because they
create a specific CPU type (not 'host' or 'max') and that won't work
with KVM, or (as with sbsa-ref) because they want EL3 support and
KVM doesn't provide that.

I suspect it's probably not a valid assumption for non-x86
architectures more generally.

thanks
-- PMM


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-04 10:28           ` Peter Maydell
@ 2026-09-04 10:36             ` Daniel P. Berrangé
  2026-09-04 10:47               ` Peter Maydell
  0 siblings, 1 reply; 45+ messages in thread
From: Daniel P. Berrangé @ 2026-09-04 10:36 UTC (permalink / raw)
  To: Peter Maydell
  Cc: Philippe Mathieu-Daudé, Bin Meng, Pierrick Bouvier,
	Daniel Henrique Barboza, Bin Meng, QEMU, Paolo Bonzini, qemu-arm,
	Yonggang Luo, Anton Johansson

On Fri, Sep 04, 2026 at 11:28:35AM +0100, Peter Maydell wrote:
> On Fri, 4 Sept 2026 at 11:25, Daniel P. Berrangé <berrange@redhat.com> wrote:
> > We need to use QAPI MachineInfo / MachineClass to report to mgmt apps
> > whether a machine is capable of using hardware acceleration or not in
> > response to "query-machines".
> >
> > Currently if a target supports HW accel, then apps assume that all
> > machines in that target can use acceleration. This was a convenient
> > short cut assumption, but this new machine suggests we can no make
> > do with that assumption, and need to explicitly report it per-machine.
> 
> That assumption has never been true for Arm; this new machine
> type is no different to any of our existing boards in that regard.
> Support for KVM etc is only present for the 'virt' machine type and
> one or two others. Most of the rest don't work with KVM because they
> create a specific CPU type (not 'host' or 'max') and that won't work
> with KVM, or (as with sbsa-ref) because they want EL3 support and
> KVM doesn't provide that.

Oh, then the problem is already way worse than I realized which
really makes we think we should consider exposing whether 
machines can use HW acceleration or not.

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-04 10:36             ` Daniel P. Berrangé
@ 2026-09-04 10:47               ` Peter Maydell
  2026-09-04 11:24                 ` Philippe Mathieu-Daudé
  0 siblings, 1 reply; 45+ messages in thread
From: Peter Maydell @ 2026-09-04 10:47 UTC (permalink / raw)
  To: Daniel P. Berrangé
  Cc: Philippe Mathieu-Daudé, Bin Meng, Pierrick Bouvier,
	Daniel Henrique Barboza, Bin Meng, QEMU, Paolo Bonzini, qemu-arm,
	Yonggang Luo, Anton Johansson

On Fri, 4 Sept 2026 at 11:36, Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> On Fri, Sep 04, 2026 at 11:28:35AM +0100, Peter Maydell wrote:
> > On Fri, 4 Sept 2026 at 11:25, Daniel P. Berrangé <berrange@redhat.com> wrote:
> > > We need to use QAPI MachineInfo / MachineClass to report to mgmt apps
> > > whether a machine is capable of using hardware acceleration or not in
> > > response to "query-machines".
> > >
> > > Currently if a target supports HW accel, then apps assume that all
> > > machines in that target can use acceleration. This was a convenient
> > > short cut assumption, but this new machine suggests we can no make
> > > do with that assumption, and need to explicitly report it per-machine.
> >
> > That assumption has never been true for Arm; this new machine
> > type is no different to any of our existing boards in that regard.
> > Support for KVM etc is only present for the 'virt' machine type and
> > one or two others. Most of the rest don't work with KVM because they
> > create a specific CPU type (not 'host' or 'max') and that won't work
> > with KVM, or (as with sbsa-ref) because they want EL3 support and
> > KVM doesn't provide that.
>
> Oh, then the problem is already way worse than I realized which
> really makes we think we should consider exposing whether
> machines can use HW acceleration or not.

It also depends on the options to the machine, so for instance
this should always work with any of the hw accelerators:
 qemu-system-aarch64 -M virt -cpu host
but this wants nested virt, so only works with a hw accel
that supports that and a host kernel that has the KVM side support:
 qemu-system-aarch64 -M virt,virtualization=on -cpu host
and this wants EL3, which won't work in any hw accelerator
 qemu-system-aarch64 -M virt,secure=on -cpu host

Similarly the interrupt controller choice matters, so this:
 qemu-system-aarch64 -M virt,gic-version=2 -cpu host
may or may not work depending on whether the host CPU has the
GICv2 back-compat support; and this:
 qemu-system-aarch64 -M virt,gic-version=x-5 -cpu host
is currently TCG-only. (But also it's experimental so you kind
of know you're off-piste here ;-))

If you try the things that won't work with -enable-kvm then they
should wind up causing QEMU to exit with a hopefully more or less
informative error message, but I don't think we have any mechanism
for introspection of the form "if I try this particular set of
QEMU options is it going to work?" short of actually trying.

-- PMM


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-04 10:47               ` Peter Maydell
@ 2026-09-04 11:24                 ` Philippe Mathieu-Daudé
  2026-09-04 11:40                   ` Daniel P. Berrangé
  0 siblings, 1 reply; 45+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-09-04 11:24 UTC (permalink / raw)
  To: Peter Maydell, Daniel P. Berrangé, Markus Armbruster,
	Bernhard Beschow
  Cc: Bin Meng, Pierrick Bouvier, Daniel Henrique Barboza, Bin Meng,
	QEMU, Paolo Bonzini, qemu-arm, Yonggang Luo, Anton Johansson

On 4/9/26 12:47, Peter Maydell wrote:
> On Fri, 4 Sept 2026 at 11:36, Daniel P. Berrangé <berrange@redhat.com> wrote:
>>
>> On Fri, Sep 04, 2026 at 11:28:35AM +0100, Peter Maydell wrote:
>>> On Fri, 4 Sept 2026 at 11:25, Daniel P. Berrangé <berrange@redhat.com> wrote:
>>>> We need to use QAPI MachineInfo / MachineClass to report to mgmt apps
>>>> whether a machine is capable of using hardware acceleration or not in
>>>> response to "query-machines".
>>>>
>>>> Currently if a target supports HW accel, then apps assume that all
>>>> machines in that target can use acceleration. This was a convenient
>>>> short cut assumption, but this new machine suggests we can no make
>>>> do with that assumption, and need to explicitly report it per-machine.
>>>
>>> That assumption has never been true for Arm; this new machine
>>> type is no different to any of our existing boards in that regard.
>>> Support for KVM etc is only present for the 'virt' machine type and
>>> one or two others. Most of the rest don't work with KVM because they
>>> create a specific CPU type (not 'host' or 'max') and that won't work
>>> with KVM, or (as with sbsa-ref) because they want EL3 support and
>>> KVM doesn't provide that.
>>
>> Oh, then the problem is already way worse than I realized which
>> really makes we think we should consider exposing whether
>> machines can use HW acceleration or not.
> 
> It also depends on the options to the machine, so for instance
> this should always work with any of the hw accelerators:
>   qemu-system-aarch64 -M virt -cpu host
> but this wants nested virt, so only works with a hw accel
> that supports that and a host kernel that has the KVM side support:
>   qemu-system-aarch64 -M virt,virtualization=on -cpu host
> and this wants EL3, which won't work in any hw accelerator
>   qemu-system-aarch64 -M virt,secure=on -cpu host
> 
> Similarly the interrupt controller choice matters, so this:
>   qemu-system-aarch64 -M virt,gic-version=2 -cpu host
> may or may not work depending on whether the host CPU has the
> GICv2 back-compat support; and this:
>   qemu-system-aarch64 -M virt,gic-version=x-5 -cpu host
> is currently TCG-only. (But also it's experimental so you kind
> of know you're off-piste here ;-))
> 
> If you try the things that won't work with -enable-kvm then they
> should wind up causing QEMU to exit with a hopefully more or less
> informative error message, but I don't think we have any mechanism
> for introspection of the form "if I try this particular set of
> QEMU options is it going to work?" short of actually trying.

I recall some discussion with Markus / Paolo when brainstorming
declarative dynamic machines, we'd need another MachinePhase
iterating on all selected types to instantiate and check whether
they can be instantiated, and returning impossible config error;
way before a property is evaluated on an instance at Realize time.


Here IMO accelerators should report whether a CPU model class
requested is accelerable or not (then later we can double check
with features updated on the model instances).
The machine deciding is a shortcut, not scalable and hard to maintain.


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 02/33] hw/arm: Add basic Phytium Pi machine
  2026-09-04 11:24                 ` Philippe Mathieu-Daudé
@ 2026-09-04 11:40                   ` Daniel P. Berrangé
  0 siblings, 0 replies; 45+ messages in thread
From: Daniel P. Berrangé @ 2026-09-04 11:40 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé
  Cc: Peter Maydell, Markus Armbruster, Bernhard Beschow, Bin Meng,
	Pierrick Bouvier, Daniel Henrique Barboza, Bin Meng, QEMU,
	Paolo Bonzini, qemu-arm, Yonggang Luo, Anton Johansson

On Fri, Sep 04, 2026 at 01:24:35PM +0200, Philippe Mathieu-Daudé wrote:
> On 4/9/26 12:47, Peter Maydell wrote:
> > On Fri, 4 Sept 2026 at 11:36, Daniel P. Berrangé <berrange@redhat.com> wrote:
> > > 
> > > On Fri, Sep 04, 2026 at 11:28:35AM +0100, Peter Maydell wrote:
> > > > On Fri, 4 Sept 2026 at 11:25, Daniel P. Berrangé <berrange@redhat.com> wrote:
> > > > > We need to use QAPI MachineInfo / MachineClass to report to mgmt apps
> > > > > whether a machine is capable of using hardware acceleration or not in
> > > > > response to "query-machines".
> > > > > 
> > > > > Currently if a target supports HW accel, then apps assume that all
> > > > > machines in that target can use acceleration. This was a convenient
> > > > > short cut assumption, but this new machine suggests we can no make
> > > > > do with that assumption, and need to explicitly report it per-machine.
> > > > 
> > > > That assumption has never been true for Arm; this new machine
> > > > type is no different to any of our existing boards in that regard.
> > > > Support for KVM etc is only present for the 'virt' machine type and
> > > > one or two others. Most of the rest don't work with KVM because they
> > > > create a specific CPU type (not 'host' or 'max') and that won't work
> > > > with KVM, or (as with sbsa-ref) because they want EL3 support and
> > > > KVM doesn't provide that.
> > > 
> > > Oh, then the problem is already way worse than I realized which
> > > really makes we think we should consider exposing whether
> > > machines can use HW acceleration or not.
> > 
> > It also depends on the options to the machine, so for instance
> > this should always work with any of the hw accelerators:
> >   qemu-system-aarch64 -M virt -cpu host
> > but this wants nested virt, so only works with a hw accel
> > that supports that and a host kernel that has the KVM side support:
> >   qemu-system-aarch64 -M virt,virtualization=on -cpu host
> > and this wants EL3, which won't work in any hw accelerator
> >   qemu-system-aarch64 -M virt,secure=on -cpu host
> > 
> > Similarly the interrupt controller choice matters, so this:
> >   qemu-system-aarch64 -M virt,gic-version=2 -cpu host
> > may or may not work depending on whether the host CPU has the
> > GICv2 back-compat support; and this:
> >   qemu-system-aarch64 -M virt,gic-version=x-5 -cpu host
> > is currently TCG-only. (But also it's experimental so you kind
> > of know you're off-piste here ;-))
> > 
> > If you try the things that won't work with -enable-kvm then they
> > should wind up causing QEMU to exit with a hopefully more or less
> > informative error message, but I don't think we have any mechanism
> > for introspection of the form "if I try this particular set of
> > QEMU options is it going to work?" short of actually trying.
> 
> I recall some discussion with Markus / Paolo when brainstorming
> declarative dynamic machines, we'd need another MachinePhase
> iterating on all selected types to instantiate and check whether
> they can be instantiated, and returning impossible config error;
> way before a property is evaluated on an instance at Realize time.

Mmm, yes, that is getting into quite alot of work for probably not
enough benefit.

> Here IMO accelerators should report whether a CPU model class
> requested is accelerable or not (then later we can double check
> with features updated on the model instances).
> The machine deciding is a shortcut, not scalable and hard to maintain.

Yeah, I think you're right - the machine does look like a facade
around the CPU for accelerator runnability. So it probably better
fits the QMP commands for querying CPU runnability.

With regards,
Daniel
-- 
|: https://berrange.com       ~~        https://hachyderm.io/@berrange :|
|: https://libvirt.org          ~~          https://entangle-photo.org :|
|: https://pixelfed.art/berrange   ~~    https://fstop138.berrange.com :|



^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 32/33] tests/functional/aarch64: Add Phytium Pi boot tests
  2026-09-04  8:52     ` Bin Meng
@ 2026-09-04 14:23       ` Alex Bennée
  0 siblings, 0 replies; 45+ messages in thread
From: Alex Bennée @ 2026-09-04 14:23 UTC (permalink / raw)
  To: Bin Meng; +Cc: Bin Meng, QEMU, Peter Maydell, qemu-arm

Bin Meng <bmeng.cn@gmail.com> writes:

> Hi Alex,
>
> On Thu, Sep 3, 2026 at 10:38 PM Alex Bennée <alex.bennee@linaro.org> wrote:
>>
>> Bin Meng <bin.meng@processmission.com> writes:
>>
>> > The Phytium Pi machine supports both the board firmware flow from SD
>> > and direct Linux boot. Exercise both paths with an SDK image bundle so
>> > firmware handoff and kernel loading regressions are caught.
>> >
>> > Verify U-Boot, PBF relocation, the Linux handoff, and login for the
>> > firmware path. The Linux test loads the bundled kernel, DTB, and
>> > initramfs and waits for the same login prompt.
>> >
>> > Signed-off-by: Bin Meng <bin.meng@processmission.com>
>> > ---
>> >
>> >  tests/functional/aarch64/meson.build        |  2 +
>> >  tests/functional/aarch64/test_phytium_pi.py | 77 +++++++++++++++++++++
>> >  2 files changed, 79 insertions(+)
>> >  create mode 100644 tests/functional/aarch64/test_phytium_pi.py
>> >
>> > diff --git a/tests/functional/aarch64/meson.build b/tests/functional/aarch64/meson.build
>> > index f0881bed16..71bf45e4ea 100644
>> > --- a/tests/functional/aarch64/meson.build
>> > +++ b/tests/functional/aarch64/meson.build
>> > @@ -7,6 +7,7 @@ test_aarch64_timeouts = {
>> >    'device_passthrough' : 720,
>> >    'imx8mm_evk' : 240,
>> >    'imx8mp_evk' : 240,
>> > +  'phytium_pi' : 600,
>> >    'raspi4' : 480,
>> >    'reverse_debug' : 180,
>> >    'rme_virt' : 1200,
>> > @@ -39,6 +40,7 @@ tests_aarch64_system_thorough = [
>> >    'imx8mp_evk',
>> >    'kvm',
>> >    'multiprocess',
>> > +  'phytium_pi',
>> >    'raspi3',
>> >    'raspi4',
>> >    'replay',
>> > diff --git a/tests/functional/aarch64/test_phytium_pi.py b/tests/functional/aarch64/test_phytium_pi.py
>> > new file mode 100644
>> > index 0000000000..4ae0392bdd
>> > --- /dev/null
>> > +++ b/tests/functional/aarch64/test_phytium_pi.py
>> > @@ -0,0 +1,77 @@
>> > +#!/usr/bin/env python3
>> > +#
>> > +# Functional tests for the Phytium Pi machine
>> > +#
>> > +# Copyright (c) 2026 Process Mission
>> > +#
>> > +# Author:
>> > +#   Bin Meng <bin.meng@processmission.com>
>> > +#
>> > +# SPDX-License-Identifier: GPL-2.0-or-later
>> > +
>> > +from qemu_test import Asset, LinuxKernelTest
>> > +from qemu_test import skipBigDataTest, skipIfMissingCommands
>> > +
>> > +
>> > +class PhytiumPiMachine(LinuxKernelTest):
>> > +
>> > +    ASSET_BUILDROOT = Asset(
>> > +        ('https://github.com/processmission/qemu-machine-images/releases/'
>> > +         'download/v1.0.0/aarch64-phytium-pi-v1.0.0.tar.zst'),
>> > +
>> > '5219d52b862e1245b12f79ba72e9a6144f5e0cf1a50061f8fda2db0aeeb92428')
>>
>> Nice ;-)
>>
>> I've been looking into something similar to make it easy to set up
>> github projects that can build and host test assets:
>>
>>   https://github.com/stsquad/buildroot-asset
>
> This is actually very close to what I have been doing with
> https://github.com/processmission/qemu-machine-images. It is intended
> to be a common repository for QEMU machine test assets.
>
> Each machine has a small description under:
>
>   machine/<architecture>/<machine>/
>
> It selects the independently built components, required images and
> machine-specific QEMU launcher. The container builders, packaging,
> checksums, release manifest and GitHub Actions workflow are shared.
> The workflow also distinguishes between the main repository and forks.
> A fork can build selected machines and upload run-scoped Actions
> artifacts without publishing a release. It can optionally publish its
> own development prerelease, while the main repository publishes
> frozen, versioned assets.
>
>>
>> although my builds seem to take quite a bit longer that yours.
>
> Yeah, as I only built the minimal busybox-based buildroot
> configuration, which is enough for QEMU boot testing :)
>
>>
>> I wonder if there is a way to have a common repository set up but in a
>> way that the forks can just build their own images. Either that or a
>> core repository which everyone just forks adds their device and merges.
>
> So perhaps qemu-machine-images could serve as a starting point for the
> common repository you describe. I would be happy to accept machine
> definitions from other contributors and make the add-a-machine
> workflow simpler.
>
> Your buildroot-asset project also has useful ideas, particularly
> including enough Buildroot configuration and source metadata to
> reproduce an image manually. It would be good to align the two efforts
> instead of growing separate repositories for each maintainer or board.

I agree.

Let me see if I can port my vkmark test image to your repo and add the
metadata export to the image type. 

>
>>
>> Anyway, test looks sane:
>>
>> Reviewed-by: Alex Bennée <alex.bennee@linaro.org>
>
> Thanks for the review!
>
>>
>> > +
>> > +    def _prepare_images(self):
>> > +        self.set_machine('phytium-pi')
>> > +
>> > +        archive_path = self.uncompress(
>> > +            self.ASSET_BUILDROOT,
>> > +            target='aarch64-phytium-pi-v1.0.0.tar',
>> > +            format='zstd')
>> > +        self.archive_extract(archive_path, format='tar')
>> > +
>> > +        self.vm.set_console(console_index=1)
>> > +        self.vm.add_args('-smp', '4',
>> > +                         '-m', '4G',
>> > +                         '-display', 'none',
>> > +                         '-nic', 'none',
>> > +                         '-no-reboot')
>> > +
>> > +    @skipIfMissingCommands('zstd')
>> > +    @skipBigDataTest()
>> > +    def test_firmware_boot(self):
>> > +        self._prepare_images()
>> > +        sdcard = self.scratch_file('images', 'sdcard.img')
>> > +
>> > +        self.vm.add_args(
>> > +            '-snapshot',
>> > +            '-drive', f'file={sdcard},format=raw,if=sd,index=0')
>> > +        self.vm.launch()
>> > +
>> > +        self.wait_for_console_pattern('U-Boot 2022.01')
>> > +        self.wait_for_console_pattern('PBF relocate done')
>> > +        self.wait_for_console_pattern('Starting kernel ...')
>> > +        self.wait_for_console_pattern('Phytium login:')
>> > +
>> > +    @skipIfMissingCommands('zstd')
>> > +    @skipBigDataTest()
>> > +    def test_linux_boot(self):
>> > +        self._prepare_images()
>> > +        kernel = self.scratch_file('images', 'Image.gz')
>> > +        dtb = self.scratch_file('images', 'phytiumpi_firefly.dtb')
>> > +        initrd = self.scratch_file('images', 'rootfs.cpio.gz')
>> > +
>> > +        self.vm.add_args(
>> > +            '-kernel', kernel,
>> > +            '-dtb', dtb,
>> > +            '-initrd', initrd,
>> > +            '-append', 'console=ttyAMA1,115200 '
>> > +                       'earlycon=pl011,mmio32,0x2800d000 rdinit=/init')
>> > +        self.vm.launch()
>> > +
>> > +        self.wait_for_console_pattern('Booting Linux on physical CPU')
>> > +        self.wait_for_console_pattern('Machine model: Phytium Pi Board')
>> > +        self.wait_for_console_pattern('Phytium login:')
>> > +
>> > +
>> > +if __name__ == '__main__':
>> > +    LinuxKernelTest.main()
>>
>> --
>
> Regards,
> Bin

-- 
Alex Bennée
Virtualisation Tech Lead @ Linaro


^ permalink raw reply	[flat|nested] 45+ messages in thread

* Re: [PATCH 01/33] target/arm: Add Phytium FTC310 and FTC664 CPU models
  2026-09-03 14:45   ` Alex Bennée
@ 2026-09-05  3:39     ` Bin Meng
  0 siblings, 0 replies; 45+ messages in thread
From: Bin Meng @ 2026-09-05  3:39 UTC (permalink / raw)
  To: Alex Bennée; +Cc: Bin Meng, QEMU, Peter Maydell, qemu-arm

Hi Alex,

On Thu, Sep 3, 2026 at 10:48 PM Alex Bennée <alex.bennee@linaro.org> wrote:
>
> Bin Meng <bin.meng@processmission.com> writes:
>
> > The Phytium E2000Q combines two FTC310 cores and two FTC664 cores.
> > Its EL3 firmware accesses implementation-defined system registers
> > while setting up the cores. Using generic Cortex-A72 CPUs leaves
> > these registers undefined and traps firmware before it can reach
> > U-Boot.
> >
> > Add FTC310 and FTC664 CPU types using Cortex-A72 as their common
> > execution base. Provide conservative RAZ/WI and NOP stubs for the
> > firmware-visible E2000 controls that have no modeled behavior.
> >
> > Set each CPU DT compatible string, MIDR, instruction feature fields,
> > AArch32 floating-point feature fields, and CTR_EL0.L1Ip field to
> > values observed on an E2000Q board. The FTC310 reports a VIPT
> > instruction cache, while the FTC664 reports a PIPT instruction cache.
> >
> > Signed-off-by: Bin Meng <bin.meng@processmission.com>
> > ---
> >
> >  target/arm/tcg/cpu64.c | 81
> >  ++++++++++++++++++++++++++++++++++++++++++
>
> I wonder if cpu64 is the best place to model this? The alternative is to
> treat it like other SoCs (e.g. the Pi's BCM SoCs). Was the reason it
> ended up here because you needed access to aarch64_a72_initfn? Could you
> not instantiate:
>
>     soc_base->cpu_type = ARM_CPU_TYPE_NAME("cortex-a72");
>
> and then at realize:
>
>     object_initialize_child(OBJECT(dev), "cpu[*]", &s->cpu[n].core,
>                                 soc_base->cpu_type);
>
> or does that not allow for tweaking the CP regs?
>

I followed your advice and that worked like a charm. Will move the
Phytium-specific CPU type in the SoC/machine codes in v2.

>
> >  1 file changed, 81 insertions(+)
> >
> > diff --git a/target/arm/tcg/cpu64.c b/target/arm/tcg/cpu64.c
> > index affd87a3ae..0fd2f79bbc 100644
> > --- a/target/arm/tcg/cpu64.c
> > +++ b/target/arm/tcg/cpu64.c
> > @@ -336,6 +336,85 @@ static void aarch64_a72_initfn(Object *obj)
> >      define_cortex_a72_a57_a53_cp_reginfo(cpu);
> >  }
> >

Regards,
Bin


^ permalink raw reply	[flat|nested] 45+ messages in thread

end of thread, other threads:[~2026-09-05  3:40 UTC | newest]

Thread overview: 45+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-03 11:24 [PATCH 00/33] hw/arm: Add Phytium E2000Q SoC and board support Bin Meng
2026-09-03 11:24 ` [PATCH 01/33] target/arm: Add Phytium FTC310 and FTC664 CPU models Bin Meng
2026-09-03 14:45   ` Alex Bennée
2026-09-05  3:39     ` Bin Meng
2026-09-03 11:24 ` [PATCH 02/33] hw/arm: Add basic Phytium Pi machine Bin Meng
2026-09-03 16:56   ` Philippe Mathieu-Daudé
2026-09-04  7:57     ` Bin Meng
2026-09-04 10:15       ` Philippe Mathieu-Daudé
2026-09-04 10:25         ` Daniel P. Berrangé
2026-09-04 10:28           ` Peter Maydell
2026-09-04 10:36             ` Daniel P. Berrangé
2026-09-04 10:47               ` Peter Maydell
2026-09-04 11:24                 ` Philippe Mathieu-Daudé
2026-09-04 11:40                   ` Daniel P. Berrangé
2026-09-04 10:25         ` Peter Maydell
2026-09-03 11:24 ` [PATCH 03/33] hw/arm: phytium: Add Phytium E2000 PCIe host Bin Meng
2026-09-03 11:24 ` [PATCH 05/33] hw/sd: Add Phytium E2000 MCI controller Bin Meng
2026-09-03 11:24 ` [PATCH 06/33] hw/arm: phytium: Connect Phytium E2000 MCI controllers Bin Meng
2026-09-03 11:24 ` [PATCH 08/33] hw/arm: phytium: Connect Phytium E2000 GEM controllers Bin Meng
2026-09-03 11:24 ` [PATCH 09/33] hw/misc: Add Phytium E2000 DDR status Bin Meng
2026-09-03 11:24 ` [PATCH 10/33] hw/arm: phytium: Connect the " Bin Meng
2026-09-03 11:24 ` [PATCH 11/33] hw/misc: Add Phytium E2000 MHU doorbell Bin Meng
2026-09-03 11:24 ` [PATCH 12/33] hw/arm: phytium: Connect the Phytium E2000 MHU Bin Meng
2026-09-03 11:24 ` [PATCH 13/33] hw/ssi: Add Phytium E2000 QSPI controller Bin Meng
2026-09-03 11:24 ` [PATCH 14/33] hw/arm: phytium: Connect the " Bin Meng
2026-09-03 11:24 ` [PATCH 15/33] hw/misc: Add Phytium E2000 PBR model Bin Meng
2026-09-03 11:24 ` [PATCH 16/33] hw/arm: phytium: Integrate the Phytium E2000 PBR Bin Meng
2026-09-03 11:24 ` [PATCH 17/33] hw/arm: phytium: Add Phytium E2000 control region placeholders Bin Meng
2026-09-03 11:24 ` [PATCH 18/33] hw/misc: Support Phytium E2000 SCMI CPU power control Bin Meng
2026-09-03 11:24 ` [PATCH 19/33] hw/arm: phytium: Select the Phytium E2000 PBR boot medium Bin Meng
2026-09-03 11:25 ` [PATCH 20/33] hw/arm: phytium: Connect the Phytium E2000 I2C controller Bin Meng
2026-09-03 11:25 ` [PATCH 21/33] hw/arm: phytium: Add Phytium E2000 xHCI controllers Bin Meng
2026-09-03 11:25 ` [PATCH 22/33] hw/misc: Model the Phytium E2000 random generator Bin Meng
2026-09-03 11:25 ` [PATCH 23/33] hw/arm: phytium: Connect " Bin Meng
2026-09-03 11:25 ` [PATCH 24/33] hw/arm: phytium: Support Phytium E2000 direct Linux boot Bin Meng
2026-09-03 11:25 ` [PATCH 25/33] hw/arm: phytium: Add Phytium E2000Q COMe machine Bin Meng
2026-09-03 11:25 ` [PATCH 27/33] hw/arm: phytium: Connect the Phytium E2000Q COMe QSPI flash Bin Meng
2026-09-03 11:25 ` [PATCH 28/33] hw/arm: phytium: Add Phytium E2000 AHCI controllers Bin Meng
2026-09-03 11:25 ` [PATCH 29/33] hw/arm: Add Phytium E2000 Linux SCMI channel Bin Meng
2026-09-03 11:25 ` [PATCH 30/33] hw/arm: phytium: Connect the Phytium E2000 SMMUv3 Bin Meng
2026-09-03 11:25 ` [PATCH 31/33] docs/system/arm: Document Phytium E2000 machines Bin Meng
2026-09-03 11:25 ` [PATCH 32/33] tests/functional/aarch64: Add Phytium Pi boot tests Bin Meng
2026-09-03 14:38   ` Alex Bennée
2026-09-04  8:52     ` Bin Meng
2026-09-04 14:23       ` Alex Bennée

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox