* [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot
@ 2026-10-14 1:22 Bin Meng
2026-10-14 1:22 ` [PATCH v4 01/27] hw/riscv: pfsoc: Correct the L2LIM maximum mapped size Bin Meng
` (26 more replies)
0 siblings, 27 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Alistair Francis, Alistair Francis, Alistair Francis, Bin Meng,
Chao Liu, Conor Dooley, Daniel Henrique Barboza,
Edgar E. Iglesias, Fabiano Rosas, Laurent Vivier, Liu Zhiwei,
Markus Armbruster, Palmer Dabbelt, Paolo Bonzini, Peter Maydell,
Philippe Mathieu-Daudé, Pierrick Bouvier, Sebastian Huber,
Thomas Huth, Weiwei Li, qemu-arm, qemu-block, qemu-riscv
The microchip-icicle-kit machine was originally validated with an older
Hart Software Services (HSS) firmware stack. HSS v2026.04.1 cannot boot
through U-Boot to Linux because several firmware-visible parts of the
PolarFire SoC are missing or incomplete.
The first failure occurs before the UART is initialized because the L2
zero window is not mapped. HSS then needs additional L2 cache controller
and DDR training behavior. Later boot stages exercise system services,
the RTC, SDHC version 4 DMA, and high-capacity SD card block sizing,
along with the complete DDR aliases. The existing models ither reject
these accesses or expose behavior that does not match the hardware
specifications.
This series restores the complete firmware boot flow. With these
changes, QEMU boots the HSS v2026.04.1 eNVM image, loads its U-Boot
payload from an SD card, and reaches the Buildroot 2026.05 Linux
login prompt.
The changes are organized around the firmware boot sequence:
* Correct the L2-LIM size, map the L2 zero window, and provide the DMC
status transitions required by HSS DDR initialization.
* Model the documented L2CC Config, WayEnable, and WayMask registers,
and resize L2-LIM as cache ways are enabled.
* Complete SDHCI version 4 support used by the Cadence controller,
including Host Control 2, SDMA system addressing, 64-bit ADMA2
descriptors, and SDMA boundary continuation.
* Run ADMA from a bounded timer-driven engine instead of performing work
from command or unrelated MMIO accesses. This keeps DMA progress
independent of guest register traffic and avoids consuming a polling
deadline inside the command write.
* Keep SDHC and SDXC memory transfers at their fixed 512-byte block
size, and advertise the Cadence controller's 64-bit system bus
support.
* Add the PolarFire SoC serial-number system service and RTC, and honor
the system-service notification control bit.
* Correct the DDR aliases and fix the board topology at its physical
configuration of 2 GiB of RAM and five harts.
* Document direct and HSS boot, add focused qtests and a full firmware
functional test, and restore the machine's Supported maintainer
status.
Testing performed:
* Built qemu-system-riscv64 and the documentation.
* Passed various newly added qtests.
* Booted HSS v2026.04.1, U-Boot, and Buildroot 2026.05 Linux to
the login prompt using the functional-test image.
The functional test uses the processmission/qemu-machine-images release
v1.1.1 tarball:
https://github.com/processmission/qemu-machine-images/releases/download/
v1.1.1/riscv64-microchip-icicle-kit-v1.1.1.tar.zst
When restoring the HSS boot support, 3 potential bugs were identified in
the upstream HSS source codes that either blocks the DDR training from
succeed or traps in a multi-core race at the very beginning or after
a succesfully DDR training but stuck at OpenSBI/U-Boot hand-off. There
are already bug reports filed aginst various upstream projects.
Changes in v4:
- Drop the unused ADMA callback forward declaration
- Move helper definitions before the data-transfer declaration
- Merge the ADMA timer callback and batch routine
- Use HSS v2026.04.1 and the v1.1.1 test-image recipe
- Select the SoftConsole toolchain explicitly
- Use the generated complete boot-mode-1 eNVM binary
- Use the v1.1.1 test images with HSS v2026.04.1
- Add the execution bit to the python script
- Collect Conor Dooley's Tested-by for the complete series
- Collect Bernhard Beschow's Tested-by for the ADMA patch
Changes in v3:
- Name the QOM parent field parent_obj
- Move the LIM and Zero windows into L2CC
- Use the shared SiFive name and indexed WayMask registers
- Parameterize master count and the RAM-backed Zero aperture
- Resize a LIM alias while keeping its SRAM backing fixed
- Configure and instantiate the shared SiFive L2CC
- Map the register, LIM, and Zero windows exposed by the controller
- Remove the external RAM allocations and l2-lim link
- Keep the PolarFire master count and modeled Zero aperture unchanged
- hw/riscv: sifive_u: Correct the L2LIM maximum mapped size
- hw/riscv: sifive_u: Use the shared SiFive L2 cache controller
- Rename the test for the shared SiFive L2CC model
- Exercise 15 masters on PolarFire and 21 masters on FU540
- Decode the seven-bit opcode and nine-bit mailbox word offset
- Bound serial-number responses to the documented 2 KiB mailbox
- Mirror the current control flags in the status register
- Use default access sizing for the control and mailbox windows
- Split the NOTIFY fix from interrupt acknowledgement wiring
- Separate interrupt state ownership from the NOTIFY fix
- Link SYSREG to IOSCB for MESSAGE_INT reads and writes
- Support both clearing and setting the MESSAGE_INT pending bit
- Update commit message to mention the DDR aliases is modeled following
the Icicle Kit reference design
- Change only the default hart count and retain the minimum of two
- Document reduced topologies and matching firmware requirements
- Include the DDR pattern-test patch used by the functional-test image
- Document the image-only FPGA programming tool check
- Describe startup-race symptoms and link the firmware investigations
- Explain how reduced hart topologies change the CLINT DTB tuples
Changes in v2:
- Use the Resettable API instead of a legacy reset handler
- tests/qtest: add Cadence SDHCI ADMA pacing/MMIO reproducer
- Assert bounded ADMA pacing and MMIO-independent progress
- Reuse existing SDHCI definitions and command helpers
- Verify transferred data against a known image pattern
- Make 64-bit system bus support a device property
- hw/riscv: pfsoc: Enable 64-bit SDHCI system bus support
- Reset the RTC to the documented hardware values
- Expect the documented hardware state after RTC reset
- MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer
Bin Meng (26):
hw/riscv: pfsoc: Correct the L2LIM maximum mapped size
hw/riscv: pfsoc: Map the L2 zero device window
hw/misc: pfsoc: Support DDR training with newer HSS
hw/misc: Add SiFive L2 cache controller model
hw/riscv: pfsoc: Map the SiFive L2CC memory windows
hw/riscv: sifive_u: Correct the L2LIM maximum mapped size
hw/riscv: sifive_u: Use the shared SiFive L2 cache controller
tests/qtest: Add SiFive L2 cache controller coverage
hw/sd: sdhci: Run ADMA independently of MMIO
hw/sd: sd: Keep high-capacity memory blocks at 512 bytes
hw/sd: cadence: Add 64-bit system bus property
hw/riscv: pfsoc: Enable 64-bit SDHCI system bus support
hw/rtc: Add PolarFire SoC RTC model
hw/riscv: pfsoc: Add PolarFire SoC RTC to Icicle Kit
tests/qtest: Add PolarFire SoC RTC coverage
hw/misc: pfsoc: Model PolarFire SoC serial number service
hw/misc: pfsoc: Honor service notification requests
hw/misc: pfsoc: Let IOSCB own service interrupt state
hw/riscv: pfsoc: Correct PolarFire SoC DDR aliases
hw/riscv: pfsoc: Fix Icicle Kit RAM size at 2 GiB
hw/riscv: pfsoc: Default Icicle Kit to five harts
docs/system/riscv: Document Icicle Kit HSS boot
docs/system/riscv: pfsoc: Document CLINT topology for direct Linux
boot
tests/functional/riscv64: Add Icicle Kit firmware boot test
MAINTAINERS: Add PolarFire SoC Icicle Kit maintainer
MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer
Wadim Mueller (1):
tests/qtest: add Cadence SDHCI ADMA pacing/MMIO reproducer
MAINTAINERS | 11 +-
docs/system/riscv/microchip-icicle-kit.rst | 162 +++++-
docs/system/riscv/sifive_u.rst | 2 +-
include/hw/misc/mchp_pfsoc_dmc.h | 6 +
include/hw/misc/mchp_pfsoc_ioscb.h | 10 +
include/hw/misc/mchp_pfsoc_sysreg.h | 3 +-
include/hw/misc/sifive_l2cc.h | 42 ++
include/hw/riscv/microchip_pfsoc.h | 7 +
include/hw/riscv/sifive_u.h | 3 +
include/hw/rtc/mchp_pfsoc_rtc.h | 46 ++
include/hw/sd/cadence_sdhci.h | 1 +
hw/misc/mchp_pfsoc_dmc.c | 273 +++++++++-
hw/misc/mchp_pfsoc_ioscb.c | 174 ++++++-
hw/misc/mchp_pfsoc_sysreg.c | 37 +-
hw/misc/sifive_l2cc.c | 282 +++++++++++
hw/riscv/microchip_pfsoc.c | 91 ++--
hw/riscv/sifive_u.c | 32 +-
hw/rtc/mchp_pfsoc_rtc.c | 477 ++++++++++++++++++
hw/sd/cadence_sdhci.c | 11 +
hw/sd/sd.c | 43 +-
hw/sd/sdhci.c | 146 +++---
tests/qtest/cadence-sdhci-test.c | 282 +++++++++++
tests/qtest/mchp_pfsoc_rtc_test.c | 315 ++++++++++++
tests/qtest/sifive_l2cc_test.c | 306 +++++++++++
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/riscv/Kconfig | 3 +
hw/rtc/Kconfig | 3 +
hw/rtc/meson.build | 1 +
tests/functional/riscv64/meson.build | 2 +
.../riscv64/test_microchip_icicle_kit.py | 56 ++
tests/qtest/meson.build | 8 +
32 files changed, 2639 insertions(+), 200 deletions(-)
create mode 100644 include/hw/misc/sifive_l2cc.h
create mode 100644 include/hw/rtc/mchp_pfsoc_rtc.h
create mode 100644 hw/misc/sifive_l2cc.c
create mode 100644 hw/rtc/mchp_pfsoc_rtc.c
create mode 100644 tests/qtest/cadence-sdhci-test.c
create mode 100644 tests/qtest/mchp_pfsoc_rtc_test.c
create mode 100644 tests/qtest/sifive_l2cc_test.c
create mode 100755 tests/functional/riscv64/test_microchip_icicle_kit.py
---
base-commit: a1c27ca02bcfeea7deb6d4634c70d5b3b421a76d
branch: icicle-kit
--
2.53.0
^ permalink raw reply [flat|nested] 30+ messages in thread
* [PATCH v4 01/27] hw/riscv: pfsoc: Correct the L2LIM maximum mapped size
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 02/27] hw/riscv: pfsoc: Map the L2 zero device window Bin Meng
` (25 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Chao Liu, Alistair Francis, Conor Dooley, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Sebastian Huber, Weiwei Li, qemu-riscv
PolarFire SoC has a 2MiB L2 cache, so the maximum mapped size of
L2LIM should be set to 2MiB too.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v1)
hw/riscv/microchip_pfsoc.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index edd29626fa..cfba2990b6 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -95,7 +95,7 @@ static const MemMapEntry microchip_pfsoc_memmap[] = {
[MICROCHIP_PFSOC_CLINT] = { 0x2000000, 0x10000 },
[MICROCHIP_PFSOC_L2CC] = { 0x2010000, 0x1000 },
[MICROCHIP_PFSOC_DMA] = { 0x3000000, 0x100000 },
- [MICROCHIP_PFSOC_L2LIM] = { 0x8000000, 0x2000000 },
+ [MICROCHIP_PFSOC_L2LIM] = { 0x8000000, 0x200000 },
[MICROCHIP_PFSOC_PLIC] = { 0xc000000, 0x4000000 },
[MICROCHIP_PFSOC_MMUART0] = { 0x20000000, 0x1000 },
[MICROCHIP_PFSOC_WDOG0] = { 0x20001000, 0x1000 },
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 02/27] hw/riscv: pfsoc: Map the L2 zero device window
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
2026-10-14 1:22 ` [PATCH v4 01/27] hw/riscv: pfsoc: Correct the L2LIM maximum mapped size Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 03/27] hw/misc: pfsoc: Support DDR training with newer HSS Bin Meng
` (24 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Chao Liu, Alistair Francis, Conor Dooley, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Sebastian Huber, Weiwei Li, qemu-riscv
HSS uses a window at 0x0a000000 while setting up the L2 cache,
decompressing its runtime image, and executing that image.
The missing mapping causes the E51 to trap before firmware can
initialize its UART.
Represent the window with RAM to provide its persistent read,
write, and execute effects without claiming to model the L2
cache itself.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v1)
include/hw/riscv/microchip_pfsoc.h | 1 +
hw/riscv/microchip_pfsoc.c | 13 +++++++++++++
2 files changed, 14 insertions(+)
diff --git a/include/hw/riscv/microchip_pfsoc.h b/include/hw/riscv/microchip_pfsoc.h
index a30b944afa..612fd2b69c 100644
--- a/include/hw/riscv/microchip_pfsoc.h
+++ b/include/hw/riscv/microchip_pfsoc.h
@@ -90,6 +90,7 @@ enum {
MICROCHIP_PFSOC_L2CC,
MICROCHIP_PFSOC_DMA,
MICROCHIP_PFSOC_L2LIM,
+ MICROCHIP_PFSOC_L2ZERO,
MICROCHIP_PFSOC_PLIC,
MICROCHIP_PFSOC_MMUART0,
MICROCHIP_PFSOC_WDOG0,
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index cfba2990b6..8106dbb3ad 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -96,6 +96,7 @@ static const MemMapEntry microchip_pfsoc_memmap[] = {
[MICROCHIP_PFSOC_L2CC] = { 0x2010000, 0x1000 },
[MICROCHIP_PFSOC_DMA] = { 0x3000000, 0x100000 },
[MICROCHIP_PFSOC_L2LIM] = { 0x8000000, 0x200000 },
+ [MICROCHIP_PFSOC_L2ZERO] = { 0xa000000, 0x200000 },
[MICROCHIP_PFSOC_PLIC] = { 0xc000000, 0x4000000 },
[MICROCHIP_PFSOC_MMUART0] = { 0x20000000, 0x1000 },
[MICROCHIP_PFSOC_WDOG0] = { 0x20001000, 0x1000 },
@@ -200,6 +201,7 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp)
MemoryRegion *rsvd0_mem = g_new(MemoryRegion, 1);
MemoryRegion *e51_dtim_mem = g_new(MemoryRegion, 1);
MemoryRegion *l2lim_mem = g_new(MemoryRegion, 1);
+ MemoryRegion *l2zero_mem = g_new(MemoryRegion, 1);
MemoryRegion *envm_data = g_new(MemoryRegion, 1);
MemoryRegion *qspi_xip_mem = g_new(MemoryRegion, 1);
char *plic_hart_config;
@@ -277,6 +279,17 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp)
memmap[MICROCHIP_PFSOC_L2LIM].base,
l2lim_mem);
+ /*
+ * HSS decompresses into the L2 zero-device window and executes there.
+ * Model it as RAM because QEMU does not model the backing L2 cache.
+ */
+ memory_region_init_ram(l2zero_mem, NULL, "microchip.pfsoc.l2zero",
+ memmap[MICROCHIP_PFSOC_L2ZERO].size,
+ &error_fatal);
+ memory_region_add_subregion(system_memory,
+ memmap[MICROCHIP_PFSOC_L2ZERO].base,
+ l2zero_mem);
+
/* create PLIC hart topology configuration string */
plic_hart_config = riscv_plic_hart_config_string(ms->smp.cpus);
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 03/27] hw/misc: pfsoc: Support DDR training with newer HSS
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
2026-10-14 1:22 ` [PATCH v4 01/27] hw/riscv: pfsoc: Correct the L2LIM maximum mapped size Bin Meng
2026-10-14 1:22 ` [PATCH v4 02/27] hw/riscv: pfsoc: Map the L2 zero device window Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 04/27] hw/misc: Add SiFive L2 cache controller model Bin Meng
` (23 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU; +Cc: Conor Dooley, Conor Dooley, Sebastian Huber, qemu-riscv
HSS v2024.06 performs DDR and SGMII initialization through the DMC
register window before it can complete platform startup. The existing
model, which was validated against older version HSS in the early days,
exposes only a few fixed training results, so newer version HSS stalls
while waiting for PVT, PLL, DLL, ADDCMD, and verification status.
Update the DMC model by adding the minimum state and status behavior
needed by that firmware flow. Model the SGMII control registers,
main DDR PLL lock, deterministic ADDCMD transitions, and successful
gate and DQ/DQS verification. Keep training progress and writable
controls per device and clear them on reset.
These values are compatibility responses for HSS v2024.06 rather than
a cycle-accurate description of the DDR PHY. Document that limitation
in the source.
While we are here, update the license text to use the SPDX format.
Note at the time being there is an HSS bug [1] that consistenly blocks
the DDR traning from succeed when using an HSS image built from certain
version compiler like GCC 8.3.0. A patch [2] was proposed and verified
against HSS v2024.06.
[1] https://github.com/polarfire-soc/platform/issues/36
[2] https://github.com/processmission/qemu-machine-images/blob/main/
machine/riscv64/microchip-icicle-kit/
0001-mpfs_hal-preserve-DDR-pattern-test-parameters.patch
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v2)
Changes in v2:
- Use the Resettable API instead of a legacy reset handler
include/hw/misc/mchp_pfsoc_dmc.h | 6 +
hw/misc/mchp_pfsoc_dmc.c | 273 ++++++++++++++++++++++++++++---
2 files changed, 255 insertions(+), 24 deletions(-)
diff --git a/include/hw/misc/mchp_pfsoc_dmc.h b/include/hw/misc/mchp_pfsoc_dmc.h
index 2ed582fb8c..6a1986c2c2 100644
--- a/include/hw/misc/mchp_pfsoc_dmc.h
+++ b/include/hw/misc/mchp_pfsoc_dmc.h
@@ -28,10 +28,16 @@
/* DDR SGMII PHY module */
#define MCHP_PFSOC_DDR_SGMII_PHY_REG_SIZE 0x1000
+#define MCHP_PFSOC_DDR_SGMII_PHY_REG_NUM \
+ (MCHP_PFSOC_DDR_SGMII_PHY_REG_SIZE / sizeof(uint32_t))
typedef struct MchpPfSoCDdrSgmiiPhyState {
SysBusDevice parent;
MemoryRegion sgmii_phy;
+ uint32_t regs[MCHP_PFSOC_DDR_SGMII_PHY_REG_NUM];
+ uint8_t training_status_bit;
+ uint8_t addcmd_tap;
+ bool addcmd_move_active;
} MchpPfSoCDdrSgmiiPhyState;
#define TYPE_MCHP_PFSOC_DDR_SGMII_PHY "mchp.pfsoc.ddr_sgmii_phy"
diff --git a/hw/misc/mchp_pfsoc_dmc.c b/hw/misc/mchp_pfsoc_dmc.c
index 1fe4535464..96440dac62 100644
--- a/hw/misc/mchp_pfsoc_dmc.c
+++ b/hw/misc/mchp_pfsoc_dmc.c
@@ -2,22 +2,20 @@
* Microchip PolarFire SoC DDR Memory Controller module emulation
*
* Copyright (c) 2020 Wind River Systems, Inc.
+ * Copyright (c) 2026 Process Mission
*
* Author:
* Bin Meng <bin.meng@windriver.com>
*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License as
- * published by the Free Software Foundation; either version 2 or
- * (at your option) version 3 of the License.
+ * Updated by Bin Meng <bin.meng@processmission.com> to support DDR
+ * training emulation for with newer version Hart Software Services,
+ * aka HSS.
*
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
+ * The DMC register model is based on HSS v2024.06. Its read/write
+ * behavior is the minimum needed for DDR training to complete and
+ * may not reflect actual hardware register behavior.
*
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, see <http://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-2.0-or-later
*/
#include "qemu/osdep.h"
@@ -29,17 +27,56 @@
/* DDR SGMII PHY module */
-#define SGMII_PHY_IOC_REG1 0x208
-#define SGMII_PHY_TRAINING_STATUS 0x814
-#define SGMII_PHY_DQ_DQS_ERR_DONE 0x834
-#define SGMII_PHY_DQDQS_STATUS1 0x84c
-#define SGMII_PHY_PVT_STAT 0xc20
+#define PHY_ADDCMD_CK_TRANSITION_TAP 12
+#define PHY_ADDCMD_A5_TRANSITION_TAP 20
+#define PHY_ADDCMD_CK_READBACK 5
+#define PHY_ADDCMD_A5_READBACK (3 << 8)
+#define PHY_GT_TXDLY_VALUE 0x01010101
+#define PHY_DQDQS_STATUS2_VALUE 5
+
+#define SGMII_PHY_PLL_CTRL_MAIN 0x084
+#define SGMII_PHY_PLL_CTRL_MAIN_CONTROL_LO_MASK 0x0000007f
+#define SGMII_PHY_PLL_CTRL_MAIN_LP_REQUIRES_LOCK BIT(24)
+#define SGMII_PHY_PLL_CTRL_MAIN_LOCK BIT(25)
+#define SGMII_PHY_IOC_REG1 0x208
+#define SGMII_PHY_TRAINING_STATUS 0x814
+#define SGMII_PHY_GT_ERR_COMB 0x81c
+#define SGMII_PHY_GT_CLK_SEL 0x820
+#define SGMII_PHY_GT_TXDLY 0x824
+#define SGMII_PHY_DQ_DQS_ERR_DONE 0x834
+#define SGMII_PHY_DQDQS_STATUS1 0x84c
+#define SGMII_PHY_DQDQS_STATUS2 0x850
+#define SGMII_PHY_EXPERT_DLYCNT_MOVE_REG1 0x880
+#define SGMII_PHY_EXPERT_DLYCNT_MOVE_CONTROL_MASK 0x001fffff
+#define SGMII_PHY_EXPERT_DLYCNT_MOVE_ADDCMD_MASK 0x00180000
+#define SGMII_PHY_EXPERT_DLYCNT_LOAD_REG1 0x890
+#define SGMII_PHY_EXPERT_DLYCNT_LOAD_CONTROL_MASK 0x001fffff
+#define SGMII_PHY_EXPERT_DLYCNT_LOAD_ADDCMD_MASK 0x00180000
+#define SGMII_PHY_EXPERT_ADDCMD_LN_READBACK 0x8ac
+#define SGMII_PHY_SOFT_RESET_SGMII 0xc00
+#define SGMII_PHY_SGMII_MODE 0xc04
+#define SGMII_PHY_PLL_CNTL 0xc08
+#define SGMII_PHY_PLL_CNTL_LOCK BIT(7)
+#define SGMII_PHY_CH0_CNTL 0xc0c
+#define SGMII_PHY_CH1_CNTL 0xc10
+#define SGMII_PHY_RECAL_CNTL 0xc14
+#define SGMII_PHY_RECAL_CNTL_STATUS_MASK 0xffff0000
+#define SGMII_PHY_RECAL_CNTL_LOCK BIT(23)
+#define SGMII_PHY_CLK_CNTL 0xc18
+#define SGMII_PHY_DYN_CNTL 0xc1c
+#define SGMII_PHY_PVT_STAT 0xc20
+#define SGMII_PHY_PVT_STAT_IO_ENABLE BIT(6)
+#define SGMII_PHY_PVT_STAT_CALIBRATED BIT(14)
+#define SGMII_PHY_PVT_STAT_GUEST_CTRL_MASK 0xc0000000
+#define SGMII_PHY_SPARE_CNTL 0xc24
+#define SGMII_PHY_SPARE_STAT 0xc28
static uint64_t mchp_pfsoc_ddr_sgmii_phy_read(void *opaque, hwaddr offset,
unsigned size)
{
uint32_t val = 0;
- static int training_status_bit;
+ MchpPfSoCDdrSgmiiPhyState *s = opaque;
+ uint32_t index = offset / sizeof(uint32_t);
switch (offset) {
case SGMII_PHY_IOC_REG1:
@@ -53,8 +90,8 @@ static uint64_t mchp_pfsoc_ddr_sgmii_phy_read(void *opaque, hwaddr offset,
*
* See ddr_setup() in mss_ddr.c in the HSS source codes.
*/
- val = 1 << training_status_bit;
- training_status_bit = (training_status_bit + 1) % 5;
+ val = BIT(s->training_status_bit);
+ s->training_status_bit = (s->training_status_bit + 1) % 5;
break;
case SGMII_PHY_DQ_DQS_ERR_DONE:
/*
@@ -71,8 +108,106 @@ static uint64_t mchp_pfsoc_ddr_sgmii_phy_read(void *opaque, hwaddr offset,
val = 0xff;
break;
case SGMII_PHY_PVT_STAT:
- /* See sgmii_channel_setup() in HSS */
- val = BIT(14) | BIT(6);
+ /*
+ * HSS polls IO enable and calibration status, then writes the
+ * calibration lock. The HSS register definitions mark bits 31:30
+ * as writable controls.
+ *
+ * See sgmii_channel_setup() in mss_sgmii.c in HSS.
+ */
+ val = s->regs[index] | SGMII_PHY_PVT_STAT_IO_ENABLE |
+ SGMII_PHY_PVT_STAT_CALIBRATED;
+ break;
+ case SGMII_PHY_PLL_CTRL_MAIN:
+ /*
+ * HSS programs the main DDR PLL controls and polls LOCK after
+ * changing the PLL dividers during LPDDR4 manual training. The
+ * HSS register definitions mark bits 6:0 and 24 as writable.
+ *
+ * See ddr_pll_config() in mss_pll.c and
+ * lpddr4_manual_training() in mss_ddr.c in HSS.
+ */
+ val = s->regs[index] | SGMII_PHY_PLL_CTRL_MAIN_LOCK;
+ break;
+ case SGMII_PHY_PLL_CNTL:
+ /*
+ * HSS programs the SGMII PLL controls and polls aro_pll0_lock.
+ *
+ * See setup_sgmii_rpc_per_config() and sgmii_channel_setup()
+ * in mss_sgmii.c in HSS.
+ */
+ val = s->regs[index] | SGMII_PHY_PLL_CNTL_LOCK;
+ break;
+ case SGMII_PHY_RECAL_CNTL:
+ /*
+ * HSS programs the low control half, polls sro_dll_lock, and
+ * reads sro_dll_90_code from the read-only status half.
+ *
+ * See setup_sgmii_rpc_per_config() and sgmii_channel_setup()
+ * in mss_sgmii.c in HSS.
+ */
+ val = s->regs[index] | SGMII_PHY_RECAL_CNTL_LOCK;
+ break;
+ case SGMII_PHY_EXPERT_ADDCMD_LN_READBACK:
+ /*
+ * HSS pulses the ADDCMD move controls while sampling CK from
+ * bits 3:0 and A5 from bits 9:8. The transition tap positions
+ * below are deterministic QEMU model choices.
+ *
+ * See lpddr4_manual_training() in mss_ddr.c in HSS.
+ */
+ if (s->addcmd_tap >= PHY_ADDCMD_CK_TRANSITION_TAP) {
+ val |= PHY_ADDCMD_CK_READBACK;
+ }
+ if (s->addcmd_tap >= PHY_ADDCMD_A5_TRANSITION_TAP) {
+ val |= PHY_ADDCMD_A5_READBACK;
+ }
+ break;
+ case SGMII_PHY_GT_ERR_COMB:
+ case SGMII_PHY_GT_CLK_SEL:
+ /*
+ * HSS fails DDR verification on any gate error, a zero delay
+ * selected by gt_clk_sel, or more than one zero delay byte.
+ * Model no gate errors; the all-nonzero delay below makes the
+ * deterministic clock selection immaterial.
+ *
+ * See DDR_TRAINING_IP_SM_VERIFY in ddr_setup() in mss_ddr.c
+ * in HSS.
+ */
+ val = 0;
+ break;
+ case SGMII_PHY_GT_TXDLY:
+ val = PHY_GT_TXDLY_VALUE;
+ break;
+ case SGMII_PHY_DQDQS_STATUS2:
+ /*
+ * HSS requires the calculated DQ/DQS window to be at least
+ * DQ_DQS_NUM_TAPS, which is 5. Return the minimum pass value.
+ *
+ * See DDR_TRAINING_IP_SM_VERIFY in ddr_setup() and the
+ * DQ_DQS_NUM_TAPS definition in mss_ddr.h in HSS.
+ */
+ val = PHY_DQDQS_STATUS2_VALUE;
+ break;
+ case SGMII_PHY_EXPERT_DLYCNT_MOVE_REG1:
+ case SGMII_PHY_EXPERT_DLYCNT_LOAD_REG1:
+ case SGMII_PHY_SOFT_RESET_SGMII:
+ case SGMII_PHY_SGMII_MODE:
+ case SGMII_PHY_CH0_CNTL:
+ case SGMII_PHY_CH1_CNTL:
+ case SGMII_PHY_CLK_CNTL:
+ case SGMII_PHY_DYN_CNTL:
+ case SGMII_PHY_SPARE_CNTL:
+ case SGMII_PHY_SPARE_STAT:
+ /*
+ * HSS programs the ADDCMD and SGMII controls and reads
+ * SPARE_STAT to classify the silicon and select eye-width
+ * thresholds.
+ *
+ * See lpddr4_manual_training() in mss_ddr.c and the SGMII setup
+ * functions in mss_sgmii.c in HSS.
+ */
+ val = s->regs[index];
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
@@ -87,10 +222,88 @@ static uint64_t mchp_pfsoc_ddr_sgmii_phy_read(void *opaque, hwaddr offset,
static void mchp_pfsoc_ddr_sgmii_phy_write(void *opaque, hwaddr offset,
uint64_t value, unsigned size)
{
- qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
- "(size %d, value 0x%" PRIx64
- ", offset 0x%" HWADDR_PRIx ")\n",
- __func__, size, value, offset);
+ MchpPfSoCDdrSgmiiPhyState *s = opaque;
+ uint32_t index = offset / sizeof(uint32_t);
+
+ switch (offset) {
+ case SGMII_PHY_IOC_REG1:
+ case SGMII_PHY_TRAINING_STATUS:
+ case SGMII_PHY_GT_ERR_COMB:
+ case SGMII_PHY_GT_CLK_SEL:
+ case SGMII_PHY_GT_TXDLY:
+ case SGMII_PHY_DQ_DQS_ERR_DONE:
+ case SGMII_PHY_DQDQS_STATUS1:
+ case SGMII_PHY_DQDQS_STATUS2:
+ case SGMII_PHY_EXPERT_ADDCMD_LN_READBACK:
+ case SGMII_PHY_SPARE_STAT:
+ /*
+ * The HSS register definitions declare these status and
+ * readback registers read-only.
+ *
+ * See mss_ddr_sgmii_phy_defs.h in HSS.
+ */
+ return;
+ case SGMII_PHY_PLL_CTRL_MAIN:
+ value &= SGMII_PHY_PLL_CTRL_MAIN_CONTROL_LO_MASK |
+ SGMII_PHY_PLL_CTRL_MAIN_LP_REQUIRES_LOCK;
+ break;
+ case SGMII_PHY_PLL_CNTL:
+ value &= ~SGMII_PHY_PLL_CNTL_LOCK;
+ break;
+ case SGMII_PHY_RECAL_CNTL:
+ value &= ~SGMII_PHY_RECAL_CNTL_STATUS_MASK;
+ break;
+ case SGMII_PHY_PVT_STAT:
+ value &= SGMII_PHY_PVT_STAT_GUEST_CTRL_MASK;
+ break;
+ case SGMII_PHY_EXPERT_DLYCNT_MOVE_REG1: {
+ bool active;
+
+ /*
+ * HSS pulses 0 -> 0x180000 -> 0 while scanning the ADDCMD eye.
+ * Count each rising pulse as one abstract delay tap.
+ *
+ * See lpddr4_manual_training() in mss_ddr.c in HSS.
+ */
+ value &= SGMII_PHY_EXPERT_DLYCNT_MOVE_CONTROL_MASK;
+ active = value & SGMII_PHY_EXPERT_DLYCNT_MOVE_ADDCMD_MASK;
+ if (active && !s->addcmd_move_active &&
+ s->addcmd_tap != UINT8_MAX) {
+ s->addcmd_tap++;
+ }
+ s->addcmd_move_active = active;
+ break;
+ }
+ case SGMII_PHY_EXPERT_DLYCNT_LOAD_REG1:
+ /*
+ * HSS pulses 0x180000 before each ADDCMD scan. Restart the
+ * abstract QEMU tap position when that load pulse is asserted.
+ *
+ * See lpddr4_manual_training() in mss_ddr.c in HSS.
+ */
+ value &= SGMII_PHY_EXPERT_DLYCNT_LOAD_CONTROL_MASK;
+ if (value & SGMII_PHY_EXPERT_DLYCNT_LOAD_ADDCMD_MASK) {
+ s->addcmd_tap = 0;
+ s->addcmd_move_active = false;
+ }
+ break;
+ case SGMII_PHY_SOFT_RESET_SGMII:
+ case SGMII_PHY_SGMII_MODE:
+ case SGMII_PHY_CH0_CNTL:
+ case SGMII_PHY_CH1_CNTL:
+ case SGMII_PHY_CLK_CNTL:
+ case SGMII_PHY_DYN_CNTL:
+ case SGMII_PHY_SPARE_CNTL:
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+ "(size %d, value 0x%" PRIx64
+ ", offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, value, offset);
+ return;
+ }
+
+ s->regs[index] = value;
}
static const MemoryRegionOps mchp_pfsoc_ddr_sgmii_phy_ops = {
@@ -99,6 +312,16 @@ static const MemoryRegionOps mchp_pfsoc_ddr_sgmii_phy_ops = {
.endianness = DEVICE_LITTLE_ENDIAN,
};
+static void mchp_pfsoc_ddr_sgmii_phy_reset_hold(Object *obj, ResetType type)
+{
+ MchpPfSoCDdrSgmiiPhyState *s = MCHP_PFSOC_DDR_SGMII_PHY(obj);
+
+ memset(s->regs, 0, sizeof(s->regs));
+ s->training_status_bit = 0;
+ s->addcmd_tap = 0;
+ s->addcmd_move_active = false;
+}
+
static void mchp_pfsoc_ddr_sgmii_phy_realize(DeviceState *dev, Error **errp)
{
MchpPfSoCDdrSgmiiPhyState *s = MCHP_PFSOC_DDR_SGMII_PHY(dev);
@@ -114,9 +337,11 @@ static void mchp_pfsoc_ddr_sgmii_phy_class_init(ObjectClass *klass,
const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
dc->desc = "Microchip PolarFire SoC DDR SGMII PHY module";
dc->realize = mchp_pfsoc_ddr_sgmii_phy_realize;
+ rc->phases.hold = mchp_pfsoc_ddr_sgmii_phy_reset_hold;
}
static const TypeInfo mchp_pfsoc_ddr_sgmii_phy_info = {
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 04/27] hw/misc: Add SiFive L2 cache controller model
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (2 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 03/27] hw/misc: pfsoc: Support DDR training with newer HSS Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 05/27] hw/riscv: pfsoc: Map the SiFive L2CC memory windows Bin Meng
` (22 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Alistair Francis, Palmer Dabbelt, Paolo Bonzini,
qemu-riscv
FU540 and PolarFire use the same SiFive L2 configuration and way-control
interface. Implement Config, monotonic WayEnable, and indexed WayMask
registers with a configurable master count for the 21-master FU540 and
15-master PolarFire integrations.
Own the fixed 2 MiB SRAM backing inside the controller and expose the
remaining LIM ways through a resizable alias. Also expose a separately
sized L2 Zero window backed by RAM to approximate scratchpad accesses.
The Zero aperture is independent of the cache/LIM way partition; cache
allocation, replacement, coherence, and ECC remain unimplemented.
Export register, LIM, and Zero windows for the SoC to map.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v3)
Changes in v3:
- Name the QOM parent field parent_obj
- Move the LIM and Zero windows into L2CC
- Use the shared SiFive name and indexed WayMask registers
- Parameterize master count and the RAM-backed Zero aperture
- Resize a LIM alias while keeping its SRAM backing fixed
Changes in v2:
- Use the Resettable API instead of a legacy reset handler
include/hw/misc/sifive_l2cc.h | 42 +++++
hw/misc/sifive_l2cc.c | 282 ++++++++++++++++++++++++++++++++++
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
4 files changed, 328 insertions(+)
create mode 100644 include/hw/misc/sifive_l2cc.h
create mode 100644 hw/misc/sifive_l2cc.c
diff --git a/include/hw/misc/sifive_l2cc.h b/include/hw/misc/sifive_l2cc.h
new file mode 100644
index 0000000000..b1cd9c4054
--- /dev/null
+++ b/include/hw/misc/sifive_l2cc.h
@@ -0,0 +1,42 @@
+/*
+ * SiFive L2 cache controller
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ * Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef SIFIVE_L2CC_H
+#define SIFIVE_L2CC_H
+
+#include "hw/core/register.h"
+#include "hw/core/sysbus.h"
+
+#define SIFIVE_L2CC_REG_SIZE 0x1000
+#define SIFIVE_L2CC_REG_NUM (SIFIVE_L2CC_REG_SIZE / 8)
+#define SIFIVE_L2CC_MAX_MASTERS 21
+
+enum {
+ SIFIVE_L2CC_MMIO_REGS,
+ SIFIVE_L2CC_MMIO_LIM,
+ SIFIVE_L2CC_MMIO_ZERO,
+};
+
+typedef struct SiFiveL2CCState {
+ SysBusDevice parent_obj;
+ uint64_t regs[SIFIVE_L2CC_REG_NUM];
+ RegisterInfo regs_info[SIFIVE_L2CC_REG_NUM];
+ MemoryRegion lim_ram;
+ MemoryRegion lim;
+ MemoryRegion zero;
+ uint32_t num_masters;
+ uint64_t zero_size;
+} SiFiveL2CCState;
+
+#define TYPE_SIFIVE_L2CC "sifive.l2cc"
+OBJECT_DECLARE_SIMPLE_TYPE(SiFiveL2CCState, SIFIVE_L2CC)
+
+#endif
diff --git a/hw/misc/sifive_l2cc.c b/hw/misc/sifive_l2cc.c
new file mode 100644
index 0000000000..f99c44b301
--- /dev/null
+++ b/hw/misc/sifive_l2cc.c
@@ -0,0 +1,282 @@
+/*
+ * SiFive L2 cache 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 "qemu/log.h"
+#include "qemu/units.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/register.h"
+#include "hw/core/registerfields.h"
+#include "hw/misc/sifive_l2cc.h"
+#include "migration/vmstate.h"
+
+REG64(L2_CONFIG, 0x000)
+ FIELD(L2_CONFIG, BANKS, 0, 8)
+ FIELD(L2_CONFIG, WAYS, 8, 8)
+ FIELD(L2_CONFIG, LG_SETS, 16, 8)
+ FIELD(L2_CONFIG, LG_BLOCK_BYTES, 24, 8)
+REG64(L2_WAY_ENABLE, 0x008)
+ FIELD(L2_WAY_ENABLE, VALUE, 0, 8)
+REG64(L2_WAY_MASK, 0x800)
+
+#define L2_CONFIG_RESET 0x06091004
+#define L2_LIM_WAY_COUNT 15
+#define L2_WAY_SIZE (128 * KiB)
+#define L2_CACHE_SIZE (2 * MiB)
+#define L2_ZERO_MAX_SIZE (32 * MiB)
+
+static void sifive_l2cc_update_l2lim(SiFiveL2CCState *s, uint64_t way_enable)
+{
+ uint64_t l2lim_size;
+
+ if (way_enable >= L2_LIM_WAY_COUNT) {
+ l2lim_size = 0;
+ } else {
+ l2lim_size = (L2_LIM_WAY_COUNT - way_enable) * L2_WAY_SIZE;
+ }
+
+ memory_region_transaction_begin();
+ memory_region_set_size(&s->lim, l2lim_size);
+ memory_region_set_enabled(&s->lim, l2lim_size != 0);
+ memory_region_transaction_commit();
+}
+
+static uint64_t sifive_l2cc_way_enable_pre_write(RegisterInfo *reg,
+ uint64_t value)
+{
+ uint64_t current = *(uint64_t *)reg->data;
+
+ return MAX(current, value);
+}
+
+static void sifive_l2cc_way_enable_post_write(RegisterInfo *reg,
+ uint64_t value)
+{
+ SiFiveL2CCState *s = SIFIVE_L2CC(reg->opaque);
+
+ sifive_l2cc_update_l2lim(s, value);
+}
+
+#define WAY_MASK_REGISTER(_id) \
+ { \
+ .name = "WAY_MASK_" #_id, \
+ .addr = A_L2_WAY_MASK + 8 * (_id), \
+ .reset = UINT64_MAX, \
+ }
+
+static const RegisterAccessInfo sifive_l2cc_regs_info[] = {
+ {
+ .name = "CONFIG",
+ .addr = A_L2_CONFIG,
+ .reset = L2_CONFIG_RESET,
+ .ro = UINT64_MAX,
+ }, {
+ .name = "WAY_ENABLE",
+ .addr = A_L2_WAY_ENABLE,
+ .rsvd = ~R_L2_WAY_ENABLE_VALUE_MASK,
+ .pre_write = sifive_l2cc_way_enable_pre_write,
+ .post_write = sifive_l2cc_way_enable_post_write,
+ },
+ WAY_MASK_REGISTER(0),
+ WAY_MASK_REGISTER(1),
+ WAY_MASK_REGISTER(2),
+ WAY_MASK_REGISTER(3),
+ WAY_MASK_REGISTER(4),
+ WAY_MASK_REGISTER(5),
+ WAY_MASK_REGISTER(6),
+ WAY_MASK_REGISTER(7),
+ WAY_MASK_REGISTER(8),
+ WAY_MASK_REGISTER(9),
+ WAY_MASK_REGISTER(10),
+ WAY_MASK_REGISTER(11),
+ WAY_MASK_REGISTER(12),
+ WAY_MASK_REGISTER(13),
+ WAY_MASK_REGISTER(14),
+ WAY_MASK_REGISTER(15),
+ WAY_MASK_REGISTER(16),
+ WAY_MASK_REGISTER(17),
+ WAY_MASK_REGISTER(18),
+ WAY_MASK_REGISTER(19),
+ WAY_MASK_REGISTER(20),
+};
+
+static bool sifive_l2cc_register_implemented(SiFiveL2CCState *s,
+ hwaddr offset)
+{
+ if (offset == A_L2_CONFIG || offset == A_L2_WAY_ENABLE) {
+ return true;
+ }
+
+ return offset >= A_L2_WAY_MASK &&
+ offset < A_L2_WAY_MASK + 8 * s->num_masters && !(offset & 7);
+}
+
+static uint64_t sifive_l2cc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ RegisterInfoArray *reg_array = opaque;
+ SiFiveL2CCState *s = SIFIVE_L2CC(reg_array->r[0]->opaque);
+
+ if (sifive_l2cc_register_implemented(s, offset)) {
+ return register_read_memory(opaque, offset, size);
+ }
+
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %u, offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, offset);
+ return 0;
+}
+
+static void sifive_l2cc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ RegisterInfoArray *reg_array = opaque;
+ SiFiveL2CCState *s = SIFIVE_L2CC(reg_array->r[0]->opaque);
+
+ if (sifive_l2cc_register_implemented(s, offset)) {
+ register_write_memory(opaque, offset, value, size);
+ return;
+ }
+
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+ "(size %u, value 0x%" PRIx64
+ ", offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, value, offset);
+}
+
+static const MemoryRegionOps sifive_l2cc_ops = {
+ .read = sifive_l2cc_read,
+ .write = sifive_l2cc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = { .min_access_size = 1, .max_access_size = 8 },
+ .impl = { .min_access_size = 1, .max_access_size = 8 },
+};
+
+static void sifive_l2cc_reset_hold(Object *obj, ResetType type)
+{
+ SiFiveL2CCState *s = SIFIVE_L2CC(obj);
+ size_t i;
+
+ memset(s->regs, 0, sizeof(s->regs));
+ for (i = 0; i < 2 + s->num_masters; i++) {
+ hwaddr addr = sifive_l2cc_regs_info[i].addr;
+
+ register_reset(&s->regs_info[addr / sizeof(uint64_t)]);
+ }
+}
+
+static void sifive_l2cc_init(Object *obj)
+{
+ SiFiveL2CCState *s = SIFIVE_L2CC(obj);
+ RegisterInfoArray *reg_array;
+
+ reg_array = register_init_block64(
+ DEVICE(obj), sifive_l2cc_regs_info,
+ ARRAY_SIZE(sifive_l2cc_regs_info), s->regs_info, s->regs,
+ &sifive_l2cc_ops, false, SIFIVE_L2CC_REG_SIZE);
+ sysbus_init_mmio(SYS_BUS_DEVICE(obj), ®_array->mem);
+}
+
+static void sifive_l2cc_realize(DeviceState *dev, Error **errp)
+{
+ SiFiveL2CCState *s = SIFIVE_L2CC(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ if (!s->num_masters || s->num_masters > SIFIVE_L2CC_MAX_MASTERS) {
+ error_setg(errp, "num-masters must be between 1 and %u",
+ SIFIVE_L2CC_MAX_MASTERS);
+ return;
+ }
+ if (!s->zero_size || s->zero_size > L2_ZERO_MAX_SIZE) {
+ error_setg(errp, "zero-size must be between 1 byte and 32 MiB");
+ return;
+ }
+
+ /* Keep the SRAM backing fixed while WayEnable changes the LIM alias */
+ if (!memory_region_init_ram(&s->lim_ram, OBJECT(dev),
+ TYPE_SIFIVE_L2CC ".lim-ram",
+ L2_CACHE_SIZE, errp)) {
+ return;
+ }
+ memory_region_init_alias(&s->lim, OBJECT(dev), TYPE_SIFIVE_L2CC ".lim",
+ &s->lim_ram, 0, L2_LIM_WAY_COUNT * L2_WAY_SIZE);
+ sysbus_init_mmio(sbd, &s->lim);
+
+ /*
+ * L2 Zero allocates into enabled cache ways without external memory
+ * backing. Use a separate RAM stand-in because QEMU does not model
+ * cache allocation, replacement, or coherence. Its aperture size is
+ * independent of the cache/LIM way partition.
+ */
+ if (!memory_region_init_ram(&s->zero, OBJECT(dev),
+ TYPE_SIFIVE_L2CC ".zero",
+ s->zero_size, errp)) {
+ return;
+ }
+ sysbus_init_mmio(sbd, &s->zero);
+
+ sifive_l2cc_update_l2lim(s, s->regs[R_L2_WAY_ENABLE]);
+}
+
+static const Property sifive_l2cc_properties[] = {
+ DEFINE_PROP_UINT32("num-masters", SiFiveL2CCState, num_masters,
+ SIFIVE_L2CC_MAX_MASTERS),
+ DEFINE_PROP_SIZE("zero-size", SiFiveL2CCState, zero_size,
+ L2_ZERO_MAX_SIZE),
+};
+
+static int sifive_l2cc_post_load(void *opaque, int version_id)
+{
+ SiFiveL2CCState *s = opaque;
+
+ sifive_l2cc_update_l2lim(s, s->regs[R_L2_WAY_ENABLE]);
+ return 0;
+}
+
+static const VMStateDescription vmstate_sifive_l2cc = {
+ .name = TYPE_SIFIVE_L2CC,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .post_load = sifive_l2cc_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_EQUAL(num_masters, SiFiveL2CCState),
+ VMSTATE_UINT64_EQUAL(zero_size, SiFiveL2CCState),
+ VMSTATE_UINT64_ARRAY(regs, SiFiveL2CCState, SIFIVE_L2CC_REG_NUM),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void sifive_l2cc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.hold = sifive_l2cc_reset_hold;
+ device_class_set_props(dc, sifive_l2cc_properties);
+ dc->realize = sifive_l2cc_realize;
+ dc->vmsd = &vmstate_sifive_l2cc;
+}
+
+static const TypeInfo sifive_l2cc_info = {
+ .name = TYPE_SIFIVE_L2CC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(SiFiveL2CCState),
+ .instance_init = sifive_l2cc_init,
+ .class_init = sifive_l2cc_class_init,
+};
+
+static void sifive_l2cc_register_types(void)
+{
+ type_register_static(&sifive_l2cc_info);
+}
+
+type_init(sifive_l2cc_register_types)
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index 46e3c03cc8..254103e4ff 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -206,6 +206,9 @@ config MCHP_PFSOC_IOSCB
config MCHP_PFSOC_SYSREG
bool
+config SIFIVE_L2CC
+ bool
+
config SIFIVE_TEST
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 9790afa104..6c401541e9 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -33,6 +33,7 @@ system_ss.add(when: 'CONFIG_K230', if_true: files('k230_ddr.c'))
system_ss.add(when: 'CONFIG_MCHP_PFSOC_DMC', if_true: files('mchp_pfsoc_dmc.c'))
system_ss.add(when: 'CONFIG_MCHP_PFSOC_IOSCB', if_true: files('mchp_pfsoc_ioscb.c'))
system_ss.add(when: 'CONFIG_MCHP_PFSOC_SYSREG', if_true: files('mchp_pfsoc_sysreg.c'))
+system_ss.add(when: 'CONFIG_SIFIVE_L2CC', if_true: files('sifive_l2cc.c'))
system_ss.add(when: 'CONFIG_SIFIVE_TEST', if_true: files('sifive_test.c'))
system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c'))
system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c'))
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 05/27] hw/riscv: pfsoc: Map the SiFive L2CC memory windows
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (3 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 04/27] hw/misc: Add SiFive L2 cache controller model Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 06/27] hw/riscv: sifive_u: Correct the L2LIM maximum mapped size Bin Meng
` (21 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Alistair Francis, Chao Liu, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Paolo Bonzini, Sebastian Huber, Weiwei Li, qemu-riscv
L2-LIM consists of cache SRAM ways controlled by L2CC WayEnable, and
L2 Zero is the controller's scratchpad allocation interface. Creating
their RAM stand-ins in the SoC and passing a LIM link splits ownership
of the register state and the memory views it controls.
Instantiate the shared SiFive L2CC with 15 masters and the existing
2 MiB modeled Zero aperture. Let the controller own the backing and
both windows, and map its register, LIM, and Zero regions from the SoC.
Remove the external RAM allocations and the l2-lim link.
The device restores the visible LIM range after reset or migration.
L2 Zero keeps its fixed aperture as a RAM approximation of scratchpad
access rather than tracking the remaining LIM capacity.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v3)
Changes in v3:
- Configure and instantiate the shared SiFive L2CC
- Map the register, LIM, and Zero windows exposed by the controller
- Remove the external RAM allocations and l2-lim link
- Keep the PolarFire master count and modeled Zero aperture unchanged
include/hw/riscv/microchip_pfsoc.h | 2 ++
hw/riscv/microchip_pfsoc.c | 45 ++++++++++--------------------
hw/riscv/Kconfig | 1 +
3 files changed, 17 insertions(+), 31 deletions(-)
diff --git a/include/hw/riscv/microchip_pfsoc.h b/include/hw/riscv/microchip_pfsoc.h
index 612fd2b69c..18f5003850 100644
--- a/include/hw/riscv/microchip_pfsoc.h
+++ b/include/hw/riscv/microchip_pfsoc.h
@@ -27,6 +27,7 @@
#include "hw/cpu/cluster.h"
#include "hw/dma/sifive_pdma.h"
#include "hw/misc/mchp_pfsoc_dmc.h"
+#include "hw/misc/sifive_l2cc.h"
#include "hw/misc/mchp_pfsoc_ioscb.h"
#include "hw/misc/mchp_pfsoc_sysreg.h"
#include "hw/net/cadence_gem.h"
@@ -43,6 +44,7 @@ typedef struct MicrochipPFSoCState {
RISCVHartArrayState e_cpus;
RISCVHartArrayState u_cpus;
DeviceState *plic;
+ SiFiveL2CCState l2cc;
MchpPfSoCDdrSgmiiPhyState ddr_sgmii_phy;
MchpPfSoCDdrCfgState ddr_cfg;
MchpPfSoCIoscbState ioscb;
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index 8106dbb3ad..d0d8c8f84c 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -174,6 +174,12 @@ static void microchip_pfsoc_soc_instance_init(Object *obj)
object_initialize_child(obj, "dma-controller", &s->dma,
TYPE_SIFIVE_PDMA);
+ object_initialize_child(obj, "l2-cache-controller", &s->l2cc,
+ TYPE_SIFIVE_L2CC);
+ qdev_prop_set_uint32(DEVICE(&s->l2cc), "num-masters", 15);
+ qdev_prop_set_uint64(DEVICE(&s->l2cc), "zero-size",
+ microchip_pfsoc_memmap[MICROCHIP_PFSOC_L2ZERO].size);
+
object_initialize_child(obj, "sysreg", &s->sysreg,
TYPE_MCHP_PFSOC_SYSREG);
@@ -200,8 +206,6 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp)
MemoryRegion *system_memory = get_system_memory();
MemoryRegion *rsvd0_mem = g_new(MemoryRegion, 1);
MemoryRegion *e51_dtim_mem = g_new(MemoryRegion, 1);
- MemoryRegion *l2lim_mem = g_new(MemoryRegion, 1);
- MemoryRegion *l2zero_mem = g_new(MemoryRegion, 1);
MemoryRegion *envm_data = g_new(MemoryRegion, 1);
MemoryRegion *qspi_xip_mem = g_new(MemoryRegion, 1);
char *plic_hart_config;
@@ -260,35 +264,14 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp)
RISCV_ACLINT_DEFAULT_MTIMECMP, RISCV_ACLINT_DEFAULT_MTIME,
iks->clint_timebase_freq, false);
- /* L2 cache controller */
- create_unimplemented_device("microchip.pfsoc.l2cc",
- memmap[MICROCHIP_PFSOC_L2CC].base, memmap[MICROCHIP_PFSOC_L2CC].size);
-
- /*
- * Add L2-LIM at reset size.
- * This should be reduced in size as the L2 Cache Controller WayEnable
- * register is incremented. Unfortunately I don't see a nice (or any) way
- * to handle reducing or blocking out the L2 LIM while still allowing it
- * be re returned to all enabled after a reset. For the time being, just
- * leave it enabled all the time. This won't break anything, but will be
- * too generous to misbehaving guests.
- */
- memory_region_init_ram(l2lim_mem, NULL, "microchip.pfsoc.l2lim",
- memmap[MICROCHIP_PFSOC_L2LIM].size, &error_fatal);
- memory_region_add_subregion(system_memory,
- memmap[MICROCHIP_PFSOC_L2LIM].base,
- l2lim_mem);
-
- /*
- * HSS decompresses into the L2 zero-device window and executes there.
- * Model it as RAM because QEMU does not model the backing L2 cache.
- */
- memory_region_init_ram(l2zero_mem, NULL, "microchip.pfsoc.l2zero",
- memmap[MICROCHIP_PFSOC_L2ZERO].size,
- &error_fatal);
- memory_region_add_subregion(system_memory,
- memmap[MICROCHIP_PFSOC_L2ZERO].base,
- l2zero_mem);
+ /* The L2CC owns the SRAM backing and both software-visible windows */
+ sysbus_realize(SYS_BUS_DEVICE(&s->l2cc), errp);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->l2cc), SIFIVE_L2CC_MMIO_REGS,
+ memmap[MICROCHIP_PFSOC_L2CC].base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->l2cc), SIFIVE_L2CC_MMIO_LIM,
+ memmap[MICROCHIP_PFSOC_L2LIM].base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->l2cc), SIFIVE_L2CC_MMIO_ZERO,
+ memmap[MICROCHIP_PFSOC_L2ZERO].base);
/* create PLIC hart topology configuration string */
plic_hart_config = riscv_plic_hart_config_string(ms->smp.cpus);
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index d06ac26648..46a499f70f 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -21,6 +21,7 @@ config MICROCHIP_PFSOC
select MCHP_PFSOC_MMUART
select MCHP_PFSOC_SYSREG
select RISCV_ACLINT
+ select SIFIVE_L2CC
select SIFIVE_PDMA
select SIFIVE_PLIC
select UNIMP
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 06/27] hw/riscv: sifive_u: Correct the L2LIM maximum mapped size
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (4 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 05/27] hw/riscv: pfsoc: Map the SiFive L2CC memory windows Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 07/27] hw/riscv: sifive_u: Use the shared SiFive L2 cache controller Bin Meng
` (20 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Alistair Francis, Chao Liu, Daniel Henrique Barboza,
Liu Zhiwei, Palmer Dabbelt, Weiwei Li, qemu-riscv
FU540 SoC has a 2MiB L2 cache, so the maximum mapped size of
L2LIM should be set to 2MiB too.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v3)
Changes in v3:
- New
hw/riscv/sifive_u.c | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c
index b7d5e7b4da..1ac0d1bbd9 100644
--- a/hw/riscv/sifive_u.c
+++ b/hw/riscv/sifive_u.c
@@ -72,7 +72,7 @@ static const MemMapEntry sifive_u_memmap[] = {
[SIFIVE_U_DEV_CLINT] = { 0x2000000, 0x10000 },
[SIFIVE_U_DEV_L2CC] = { 0x2010000, 0x1000 },
[SIFIVE_U_DEV_PDMA] = { 0x3000000, 0x100000 },
- [SIFIVE_U_DEV_L2LIM] = { 0x8000000, 0x2000000 },
+ [SIFIVE_U_DEV_L2LIM] = { 0x8000000, 0x200000 },
[SIFIVE_U_DEV_PLIC] = { 0xc000000, 0x4000000 },
[SIFIVE_U_DEV_PRCI] = { 0x10000000, 0x1000 },
[SIFIVE_U_DEV_UART0] = { 0x10010000, 0x1000 },
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 07/27] hw/riscv: sifive_u: Use the shared SiFive L2 cache controller
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (5 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 06/27] hw/riscv: sifive_u: Correct the L2LIM maximum mapped size Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 08/27] tests/qtest: Add SiFive L2 cache controller coverage Bin Meng
` (19 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Alistair Francis, Chao Liu, Daniel Henrique Barboza, Liu Zhiwei,
Palmer Dabbelt, Paolo Bonzini, Pierrick Bouvier, Weiwei Li,
qemu-riscv
FU540 and PolarFire use the same L2 configuration and way-control
interface, with different master counts. The `sifive_u` machine
currently leaves L2CC unimplemented and exposes L2-LIM as fixed
RAM without WayEnable control.
Instantiate the shared SiFive L2CC with 21 masters and a modeled
2 MiB Zero window.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
---
(no changes since v3)
Changes in v3:
- New
docs/system/riscv/sifive_u.rst | 2 +-
include/hw/riscv/sifive_u.h | 3 +++
hw/riscv/sifive_u.c | 30 +++++++++++++-----------------
hw/riscv/Kconfig | 1 +
4 files changed, 18 insertions(+), 18 deletions(-)
diff --git a/docs/system/riscv/sifive_u.rst b/docs/system/riscv/sifive_u.rst
index 0e4dcf3e70..e91853ffe2 100644
--- a/docs/system/riscv/sifive_u.rst
+++ b/docs/system/riscv/sifive_u.rst
@@ -14,7 +14,7 @@ The ``sifive_u`` machine supports the following devices:
* Core Local Interruptor (CLINT)
* Platform-Level Interrupt Controller (PLIC)
* Power, Reset, Clock, Interrupt (PRCI)
-* L2 Loosely Integrated Memory (L2-LIM)
+* L2 cache controller, including L2-LIM and L2-Zero windows
* DDR memory controller
* 2 UARTs
* 1 GEM Ethernet controller
diff --git a/include/hw/riscv/sifive_u.h b/include/hw/riscv/sifive_u.h
index aed966a62d..be5e6232ce 100644
--- a/include/hw/riscv/sifive_u.h
+++ b/include/hw/riscv/sifive_u.h
@@ -28,6 +28,7 @@
#include "hw/gpio/sifive_gpio.h"
#include "hw/misc/sifive_u_otp.h"
#include "hw/misc/sifive_u_prci.h"
+#include "hw/misc/sifive_l2cc.h"
#include "hw/ssi/sifive_spi.h"
#include "hw/timer/sifive_pwm.h"
@@ -45,6 +46,7 @@ typedef struct SiFiveUSoCState {
RISCVHartArrayState e_cpus;
RISCVHartArrayState u_cpus;
DeviceState *plic;
+ SiFiveL2CCState l2cc;
SiFiveUPRCIState prci;
SIFIVEGPIOState gpio;
SiFiveUOTPState otp;
@@ -82,6 +84,7 @@ enum {
SIFIVE_U_DEV_L2CC,
SIFIVE_U_DEV_PDMA,
SIFIVE_U_DEV_L2LIM,
+ SIFIVE_U_DEV_L2ZERO,
SIFIVE_U_DEV_PLIC,
SIFIVE_U_DEV_PRCI,
SIFIVE_U_DEV_UART0,
diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c
index 1ac0d1bbd9..3603cc2aa2 100644
--- a/hw/riscv/sifive_u.c
+++ b/hw/riscv/sifive_u.c
@@ -73,6 +73,7 @@ static const MemMapEntry sifive_u_memmap[] = {
[SIFIVE_U_DEV_L2CC] = { 0x2010000, 0x1000 },
[SIFIVE_U_DEV_PDMA] = { 0x3000000, 0x100000 },
[SIFIVE_U_DEV_L2LIM] = { 0x8000000, 0x200000 },
+ [SIFIVE_U_DEV_L2ZERO] = { 0xa000000, 0x200000 },
[SIFIVE_U_DEV_PLIC] = { 0xc000000, 0x4000000 },
[SIFIVE_U_DEV_PRCI] = { 0x10000000, 0x1000 },
[SIFIVE_U_DEV_UART0] = { 0x10010000, 0x1000 },
@@ -715,6 +716,10 @@ static void sifive_u_soc_instance_init(Object *obj)
object_initialize_child(obj, "gem", &s->gem, TYPE_CADENCE_GEM);
object_initialize_child(obj, "gpio", &s->gpio, TYPE_SIFIVE_GPIO);
object_initialize_child(obj, "pdma", &s->dma, TYPE_SIFIVE_PDMA);
+ object_initialize_child(obj, "l2-cache-controller", &s->l2cc,
+ TYPE_SIFIVE_L2CC);
+ qdev_prop_set_uint64(DEVICE(&s->l2cc), "zero-size",
+ sifive_u_memmap[SIFIVE_U_DEV_L2ZERO].size);
object_initialize_child(obj, "spi0", &s->spi0, TYPE_SIFIVE_SPI);
object_initialize_child(obj, "spi2", &s->spi2, TYPE_SIFIVE_SPI);
object_initialize_child(obj, "pwm0", &s->pwm[0], TYPE_SIFIVE_PWM);
@@ -728,7 +733,6 @@ static void sifive_u_soc_realize(DeviceState *dev, Error **errp)
const MemMapEntry *memmap = sifive_u_memmap;
MemoryRegion *system_memory = get_system_memory();
MemoryRegion *mask_rom = g_new(MemoryRegion, 1);
- MemoryRegion *l2lim_mem = g_new(MemoryRegion, 1);
char *plic_hart_config;
int i, j;
@@ -754,19 +758,14 @@ static void sifive_u_soc_realize(DeviceState *dev, Error **errp)
memory_region_add_subregion(system_memory, memmap[SIFIVE_U_DEV_MROM].base,
mask_rom);
- /*
- * Add L2-LIM at reset size.
- * This should be reduced in size as the L2 Cache Controller WayEnable
- * register is incremented. Unfortunately I don't see a nice (or any) way
- * to handle reducing or blocking out the L2 LIM while still allowing it
- * be re returned to all enabled after a reset. For the time being, just
- * leave it enabled all the time. This won't break anything, but will be
- * too generous to misbehaving guests.
- */
- memory_region_init_ram(l2lim_mem, NULL, "riscv.sifive.u.l2lim",
- memmap[SIFIVE_U_DEV_L2LIM].size, &error_fatal);
- memory_region_add_subregion(system_memory, memmap[SIFIVE_U_DEV_L2LIM].base,
- l2lim_mem);
+ /* L2 cache controller, LIM, and Zero windows */
+ sysbus_realize(SYS_BUS_DEVICE(&s->l2cc), &error_fatal);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->l2cc), SIFIVE_L2CC_MMIO_REGS,
+ memmap[SIFIVE_U_DEV_L2CC].base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->l2cc), SIFIVE_L2CC_MMIO_LIM,
+ memmap[SIFIVE_U_DEV_L2LIM].base);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->l2cc), SIFIVE_L2CC_MMIO_ZERO,
+ memmap[SIFIVE_U_DEV_L2ZERO].base);
/* create PLIC hart topology configuration string */
plic_hart_config = riscv_plic_hart_config_string(ms->smp.cpus);
@@ -868,9 +867,6 @@ static void sifive_u_soc_realize(DeviceState *dev, Error **errp)
create_unimplemented_device("riscv.sifive.u.dmc",
memmap[SIFIVE_U_DEV_DMC].base, memmap[SIFIVE_U_DEV_DMC].size);
- create_unimplemented_device("riscv.sifive.u.l2cc",
- memmap[SIFIVE_U_DEV_L2CC].base, memmap[SIFIVE_U_DEV_L2CC].size);
-
sysbus_realize(SYS_BUS_DEVICE(&s->spi0), errp);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->spi0), 0,
memmap[SIFIVE_U_DEV_QSPI0].base);
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index 46a499f70f..b2b816de6a 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -99,6 +99,7 @@ config SIFIVE_U
select CPU_CLUSTER
select DEVICE_TREE
select RISCV_ACLINT
+ select SIFIVE_L2CC
select SIFIVE_GPIO
select SIFIVE_PDMA
select SIFIVE_PLIC
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 08/27] tests/qtest: Add SiFive L2 cache controller coverage
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (6 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 07/27] hw/riscv: sifive_u: Use the shared SiFive L2 cache controller Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 09/27] hw/sd: sdhci: Run ADMA independently of MMIO Bin Meng
` (18 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Alistair Francis, Fabiano Rosas, Laurent Vivier, Palmer Dabbelt,
Paolo Bonzini, qemu-riscv
Exercise Config, WayEnable, and indexed WayMask registers on the
PolarFire and FU540 integrations, including their different master
counts, reset, monotonic writes, and unimplemented-register logging.
Verify that enabling cache ways hides the corresponding LIM range,
while the controller's Zero window stays accessible. Check that reset
restores hidden LIM data without clearing the Zero RAM stand-in.
Migrate register state and both RAM backings, verify the destination
LIM view, and confirm that writes to hidden addresses do not alter the
data restored by a later reset.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
---
(no changes since v3)
Changes in v3:
- Rename the test for the shared SiFive L2CC model
- Exercise 15 masters on PolarFire and 21 masters on FU540
MAINTAINERS | 1 +
tests/qtest/sifive_l2cc_test.c | 306 +++++++++++++++++++++++++++++++++
tests/qtest/meson.build | 3 +
3 files changed, 310 insertions(+)
create mode 100644 tests/qtest/sifive_l2cc_test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 1312bbd1bd..acb79a7d61 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1827,6 +1827,7 @@ F: docs/system/riscv/sifive_u.rst
F: hw/*/*sifive*.c
F: include/hw/*/*sifive*.h
F: tests/functional/riscv64/test_sifive_u.py
+F: tests/qtest/sifive_l2cc_test.c
Tenstorrent Machines
M: Joel Stanley <joel@jms.id.au>
diff --git a/tests/qtest/sifive_l2cc_test.c b/tests/qtest/sifive_l2cc_test.c
new file mode 100644
index 0000000000..fef80f3438
--- /dev/null
+++ b/tests/qtest/sifive_l2cc_test.c
@@ -0,0 +1,306 @@
+/*
+ * QTest testcase for the SiFive L2 cache 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 "qemu/units.h"
+#include "libqtest.h"
+#include "migration/migration-qmp.h"
+#include "migration/migration-util.h"
+
+#define L2CC_BASE 0x02010000
+#define L2CC_CONFIG 0x000
+#define L2CC_CONFIG_VALUE 0x06091004
+#define L2CC_WAY_ENABLE 0x008
+#define L2CC_ECC_INJECT 0x040
+#define L2CC_FLUSH64 0x200
+#define L2CC_WAY_MASK_BASE 0x800
+#define L2CC_WAY_MASK_COUNT 15
+#define L2CC_MAX_MASTERS 21
+#define L2CC_WAY_MASK(n) (L2CC_WAY_MASK_BASE + 8 * (n))
+#define L2CC_MASTER15 L2CC_WAY_MASK(L2CC_WAY_MASK_COUNT)
+#define L2CC_WAY_ENABLE_RESET 0
+#define L2CC_WAY_MASK_RESET UINT64_MAX
+
+#define L2_LIM_BASE 0x08000000
+#define L2_WAY_SIZE (128 * KiB)
+#define L2_LIM_RESET_WAYS 15
+#define L2_ZERO_BASE 0x0a000000
+#define L2_ZERO_SIZE (2 * MiB)
+
+static QTestState *sifive_l2cc_start(const char *firmware)
+{
+ return qtest_initf("-machine microchip-icicle-kit -smp 5 -m 2G "
+ "-bios \"%s\"", firmware);
+}
+
+static char *sifive_l2cc_create_firmware(void)
+{
+ static const uint8_t firmware[] = {
+ 0x13, 0x00, 0x00, 0x00,
+ 0x6f, 0x00, 0x00, 0x00,
+ };
+ char *path;
+ int fd;
+ ssize_t written;
+
+ fd = g_file_open_tmp("sifive_l2cc_XXXXXX", &path, NULL);
+ g_assert_cmpint(fd, >=, 0);
+
+ written = write(fd, firmware, sizeof(firmware));
+ g_assert_cmpint(written, ==, sizeof(firmware));
+ close(fd);
+
+ return path;
+}
+
+static uint64_t l2cc_way_mask_test_value(unsigned int master)
+{
+ return UINT64_C(0xfedcba9876543201) + master;
+}
+
+static void test_l2cc_registers(void)
+{
+ g_autofree char *firmware = sifive_l2cc_create_firmware();
+ QTestState *qts = sifive_l2cc_start(firmware);
+ unsigned int i;
+
+ unlink(firmware);
+
+ g_assert_cmphex(qtest_readl(qts, L2CC_BASE + L2CC_CONFIG), ==,
+ L2CC_CONFIG_VALUE);
+ qtest_writel(qts, L2CC_BASE + L2CC_CONFIG, UINT32_MAX);
+ g_assert_cmphex(qtest_readl(qts, L2CC_BASE + L2CC_CONFIG), ==,
+ L2CC_CONFIG_VALUE);
+
+ g_assert_cmphex(qtest_readb(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==,
+ L2CC_WAY_ENABLE_RESET);
+ qtest_writeb(qts, L2CC_BASE + L2CC_WAY_ENABLE, 0xd);
+ g_assert_cmphex(qtest_readb(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==, 0xd);
+ qtest_writeb(qts, L2CC_BASE + L2CC_WAY_ENABLE, 3);
+ g_assert_cmphex(qtest_readb(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==, 0xd);
+ qtest_writeq(qts, L2CC_BASE + L2CC_WAY_ENABLE, 0xf);
+ g_assert_cmphex(qtest_readq(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==, 0xf);
+ qtest_writeb(qts, L2CC_BASE + L2CC_WAY_ENABLE, 0xe);
+ g_assert_cmphex(qtest_readb(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==, 0xf);
+
+ for (i = 0; i < L2CC_WAY_MASK_COUNT; i++) {
+ g_assert_cmphex(qtest_readq(qts, L2CC_BASE + L2CC_WAY_MASK(i)), ==,
+ L2CC_WAY_MASK_RESET);
+ qtest_writeq(qts, L2CC_BASE + L2CC_WAY_MASK(i),
+ l2cc_way_mask_test_value(i));
+ }
+ for (i = 0; i < L2CC_WAY_MASK_COUNT; i++) {
+ g_assert_cmphex(qtest_readq(qts, L2CC_BASE + L2CC_WAY_MASK(i)), ==,
+ l2cc_way_mask_test_value(i));
+ }
+
+ qtest_system_reset(qts);
+ g_assert_cmphex(qtest_readl(qts, L2CC_BASE + L2CC_CONFIG), ==,
+ L2CC_CONFIG_VALUE);
+ g_assert_cmphex(qtest_readb(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==,
+ L2CC_WAY_ENABLE_RESET);
+ for (i = 0; i < L2CC_WAY_MASK_COUNT; i++) {
+ g_assert_cmphex(qtest_readq(qts, L2CC_BASE + L2CC_WAY_MASK(i)), ==,
+ L2CC_WAY_MASK_RESET);
+ }
+
+ qtest_quit(qts);
+}
+
+static void test_l2lim_topology(void)
+{
+ g_autofree char *firmware = sifive_l2cc_create_firmware();
+ uint32_t original[L2_LIM_RESET_WAYS];
+ const uint32_t poison = 0xdeadc0de;
+ const uint64_t zero_last = L2_ZERO_BASE + L2_ZERO_SIZE - 4;
+ QTestState *qts = sifive_l2cc_start(firmware);
+ unsigned int i;
+
+ unlink(firmware);
+
+ /* Check the fixed aperture, not the RAM stand-in's eviction behavior */
+ qtest_writel(qts, zero_last, 0xa55a9669);
+ g_assert_cmphex(qtest_readl(qts, zero_last), ==, 0xa55a9669);
+
+ for (i = 1; i <= L2_LIM_RESET_WAYS; i++) {
+ uint64_t l2lim_size = (L2_LIM_RESET_WAYS - i) * L2_WAY_SIZE;
+ uint64_t hidden = L2_LIM_BASE + l2lim_size;
+ uint32_t tail = 0x96000000 | i;
+
+ original[i - 1] = 0x69000000 | i;
+ qtest_writel(qts, hidden, original[i - 1]);
+ g_assert_cmphex(qtest_readl(qts, hidden), ==, original[i - 1]);
+
+ if (l2lim_size) {
+ qtest_writel(qts, hidden - 4, tail);
+ g_assert_cmphex(qtest_readl(qts, hidden - 4), ==, tail);
+ }
+
+ qtest_writeb(qts, L2CC_BASE + L2CC_WAY_ENABLE, i);
+ g_assert_cmphex(qtest_readb(qts,
+ L2CC_BASE + L2CC_WAY_ENABLE), ==, i);
+ if (l2lim_size) {
+ g_assert_cmphex(qtest_readl(qts, hidden - 4), ==, tail);
+ }
+ g_assert_cmphex(qtest_readl(qts, hidden), ==, 0);
+ qtest_writel(qts, hidden, poison);
+ g_assert_cmphex(qtest_readl(qts, hidden), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, zero_last), ==, 0xa55a9669);
+ }
+
+ qtest_writeb(qts, L2CC_BASE + L2CC_WAY_ENABLE, UINT8_MAX);
+ g_assert_cmphex(qtest_readb(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==,
+ UINT8_MAX);
+ g_assert_cmphex(qtest_readl(qts, L2_LIM_BASE), ==, 0);
+ qtest_writeb(qts, L2CC_BASE + L2CC_WAY_ENABLE, 3);
+ g_assert_cmphex(qtest_readb(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==,
+ UINT8_MAX);
+ g_assert_cmphex(qtest_readl(qts, L2_LIM_BASE), ==, 0);
+
+ qtest_system_reset(qts);
+ g_assert_cmphex(qtest_readb(qts, L2CC_BASE + L2CC_WAY_ENABLE), ==,
+ L2CC_WAY_ENABLE_RESET);
+ for (i = 1; i <= L2_LIM_RESET_WAYS; i++) {
+ uint64_t restored = L2_LIM_BASE +
+ (L2_LIM_RESET_WAYS - i) * L2_WAY_SIZE;
+
+ g_assert_cmphex(qtest_readl(qts, restored), ==, original[i - 1]);
+ }
+ g_assert_cmphex(qtest_readl(qts, zero_last), ==, 0xa55a9669);
+
+ qtest_quit(qts);
+}
+
+static void test_l2cc_master_count(void)
+{
+ g_autofree char *firmware = sifive_l2cc_create_firmware();
+ QTestState *qts = qtest_initf(
+ "-machine sifive_u -smp 5 -bios \"%s\"", firmware);
+ const uint64_t zero_last = L2_ZERO_BASE + L2_ZERO_SIZE - 4;
+ unsigned int i;
+
+ unlink(firmware);
+
+ for (i = 0; i < L2CC_MAX_MASTERS; i++) {
+ g_assert_cmphex(qtest_readq(qts, L2CC_BASE + L2CC_WAY_MASK(i)), ==,
+ L2CC_WAY_MASK_RESET);
+ qtest_writeq(qts, L2CC_BASE + L2CC_WAY_MASK(i),
+ l2cc_way_mask_test_value(i));
+ }
+ for (i = 0; i < L2CC_MAX_MASTERS; i++) {
+ g_assert_cmphex(qtest_readq(qts, L2CC_BASE + L2CC_WAY_MASK(i)), ==,
+ l2cc_way_mask_test_value(i));
+ }
+ g_assert_cmphex(qtest_readq(qts,
+ L2CC_BASE + L2CC_WAY_MASK(L2CC_MAX_MASTERS)),
+ ==, 0);
+ qtest_writel(qts, zero_last, 0x12345678);
+ qtest_writel(qts, L2_ZERO_BASE + L2_ZERO_SIZE, 0xdeadc0de);
+ g_assert_cmphex(qtest_readl(qts, L2_ZERO_BASE + L2_ZERO_SIZE), ==, 0);
+ qtest_writeb(qts, L2CC_BASE + L2CC_WAY_ENABLE, 15);
+ g_assert_cmphex(qtest_readl(qts, zero_last), ==, 0x12345678);
+ g_assert_cmphex(qtest_readl(qts, L2_LIM_BASE), ==, 0);
+
+ qtest_system_reset(qts);
+ for (i = 0; i < L2CC_MAX_MASTERS; i++) {
+ g_assert_cmphex(qtest_readq(qts, L2CC_BASE + L2CC_WAY_MASK(i)), ==,
+ L2CC_WAY_MASK_RESET);
+ }
+ qtest_quit(qts);
+}
+
+static void test_l2cc_migration(void)
+{
+ g_autofree char *firmware = sifive_l2cc_create_firmware();
+ QTestState *from = sifive_l2cc_start(firmware);
+ QTestState *to = qtest_initf(
+ "-machine microchip-icicle-kit -smp 5 -m 2G "
+ "-bios \"%s\" -incoming defer", firmware);
+ const uint64_t hidden = L2_LIM_BASE + 8 * L2_WAY_SIZE;
+ const uint64_t zero_last = L2_ZERO_BASE + L2_ZERO_SIZE - 4;
+
+ unlink(firmware);
+
+ qtest_writel(from, L2_LIM_BASE, 0x11223344);
+ qtest_writel(from, hidden, 0x55667788);
+ qtest_writel(from, zero_last, 0x99aabbcc);
+ qtest_writeb(from, L2CC_BASE + L2CC_WAY_ENABLE, 7);
+ qtest_writeq(from, L2CC_BASE + L2CC_WAY_MASK(14), 0x1234);
+ g_assert_cmphex(qtest_readl(from, hidden), ==, 0);
+
+ migrate_incoming_qmp(to, "tcp:127.0.0.1:0", NULL, "{}");
+ migrate_qmp(from, to, NULL, NULL, "{}");
+ wait_for_migration_complete(from);
+
+ g_assert_cmphex(qtest_readb(to, L2CC_BASE + L2CC_WAY_ENABLE), ==, 7);
+ g_assert_cmphex(qtest_readq(to, L2CC_BASE + L2CC_WAY_MASK(14)), ==, 0x1234);
+ g_assert_cmphex(qtest_readl(to, L2_LIM_BASE), ==, 0x11223344);
+ g_assert_cmphex(qtest_readl(to, hidden), ==, 0);
+ qtest_writel(to, hidden, 0xdeadc0de);
+ g_assert_cmphex(qtest_readl(to, zero_last), ==, 0x99aabbcc);
+
+ qtest_system_reset(to);
+ g_assert_cmphex(qtest_readb(to, L2CC_BASE + L2CC_WAY_ENABLE), ==, 0);
+ g_assert_cmphex(qtest_readl(to, hidden), ==, 0x55667788);
+ g_assert_cmphex(qtest_readl(to, zero_last), ==, 0x99aabbcc);
+
+ qtest_quit(from);
+ qtest_quit(to);
+}
+
+static void test_l2cc_unimplemented(void)
+{
+ g_autofree char *firmware = sifive_l2cc_create_firmware();
+ g_autofree char *log_path = NULL;
+ g_autofree char *log = NULL;
+ QTestState *qts;
+ int fd;
+
+ fd = g_file_open_tmp("sifive_l2cc_unimp_XXXXXX", &log_path, NULL);
+ g_assert_cmpint(fd, >=, 0);
+ close(fd);
+
+ qts = qtest_initf(
+ "-machine microchip-icicle-kit -smp 5 -m 2G -bios \"%s\" "
+ "-d unimp -D \"%s\"", firmware, log_path);
+ unlink(firmware);
+
+ g_assert_cmphex(qtest_readl(qts, L2CC_BASE + L2CC_ECC_INJECT), ==, 0);
+ qtest_writeq(qts, L2CC_BASE + L2CC_FLUSH64, 0x1234);
+ g_assert_cmphex(qtest_readq(qts, L2CC_BASE + L2CC_MASTER15), ==, 0);
+ qtest_quit(qts);
+
+ g_assert_true(g_file_get_contents(log_path, &log, NULL, NULL));
+ g_assert_nonnull(strstr(log,
+ "unimplemented device read (size 4, offset 0x40)"));
+ g_assert_nonnull(strstr(log,
+ "unimplemented device write (size 8, value 0x1234, offset 0x200)"));
+ g_assert_nonnull(strstr(log,
+ "unimplemented device read (size 8, offset 0x878)"));
+ unlink(log_path);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ if (qtest_has_machine("microchip-icicle-kit")) {
+ qtest_add_func("/sifive_l2cc/registers", test_l2cc_registers);
+ qtest_add_func("/sifive_l2cc/l2lim_topology", test_l2lim_topology);
+ qtest_add_func("/sifive_l2cc/unimplemented", test_l2cc_unimplemented);
+ qtest_add_func("/sifive_l2cc/migration", test_l2cc_migration);
+ }
+ if (qtest_has_machine("sifive_u")) {
+ qtest_add_func("/sifive_l2cc/master_count", test_l2cc_master_count);
+ }
+
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 200abf6f36..1fe008c9ed 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -311,6 +311,7 @@ qtests_riscv32 = \
qtests_riscv64 = ['riscv-csr-test'] + \
(unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_SIFIVE_L2CC') ? ['sifive_l2cc_test'] : []) + \
(config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
['iommu-riscv-test'] : []) + \
@@ -427,6 +428,8 @@ qtests = {
'erst-test': files('erst-test.c'),
'ivshmem-test': [rt, '../../contrib/ivshmem-server/ivshmem-server.c'],
'migration-test': test_migration_files + migration_tls_files + migration_colo_files,
+ 'sifive_l2cc_test': files('migration/migration-qmp.c',
+ 'migration/migration-util.c'),
'pxe-test': files('boot-sector.c'),
'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c',
'pnv-xive2-nvpg_bar.c'),
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 09/27] hw/sd: sdhci: Run ADMA independently of MMIO
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (7 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 08/27] tests/qtest: Add SiFive L2 cache controller coverage Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 10/27] tests/qtest: add Cadence SDHCI ADMA pacing/MMIO reproducer Bin Meng
` (17 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Bernhard Beschow, Bin Meng,
Philippe Mathieu-Daudé, qemu-block
The SDHCI model processes the first ADMA descriptor batch inside the
command MMIO write, then resumes pending batches before every later
SDHCI MMIO read or write. This makes register access perform bulk DMA
work and can delay a status read even when completion bits are set.
On the Icicle Kit, U-Boot uses ADMA2-64 and CMD18 to load a roughly
19 MiB FIT image from SD. It can report:
Timeout for status update: 00000001 00000001
Timeout for status update: 00000003 00000001
The status already contains the requested Command Complete bit, and may
also contain Transfer Complete. However, reading the interrupt status
first executes a pending ADMA batch. The MMIO read can therefore return
after U-Boot has reached its one-second polling deadline.
Turn the existing transfer timer into an ADMA-only engine. Starting an
ADMA command arms the engine directly, and each timer callback runs one
bounded batch before yielding. MMIO reads and writes no longer perform
or schedule ADMA work. Keep PIO and SDMA synchronous.
Cancel the engine and clear ADMA activity and error state on DATA or ALL
reset.
Mechanically reorder and inline existing functions per review.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
Tested-by: Bernhard Beschow <shentey@gmail.com>
---
Changes in v4:
- Drop the unused ADMA callback forward declaration
- Move helper definitions before the data-transfer declaration
- Merge the ADMA timer callback and batch routine
hw/sd/sdhci.c | 146 +++++++++++++++++++++++++++++---------------------
1 file changed, 84 insertions(+), 62 deletions(-)
diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c
index bd74c60f72..0c5b0ed984 100644
--- a/hw/sd/sdhci.c
+++ b/hw/sd/sdhci.c
@@ -277,6 +277,7 @@ static void sdhci_set_inserted(DeviceState *dev, bool level)
}
} else {
s->prnsts = 0x1fa0000;
+ timer_del(s->transfer_timer);
s->pwrcon &= ~SDHC_POWER_ON;
s->clkcon &= ~SDHC_CLOCK_SDCLK_EN;
if (s->norintstsen & SDHC_NISEN_REMOVE) {
@@ -348,7 +349,33 @@ static void sdhci_poweron_reset(DeviceState *dev)
}
}
-static void sdhci_data_transfer(void *opaque);
+static void sdhci_set_transfer_active(SDHCIState *s)
+{
+ s->prnsts |= SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE;
+ if (s->trnmod & SDHC_TRNS_READ) {
+ s->prnsts |= SDHC_DOING_READ;
+ } else {
+ s->prnsts |= SDHC_DOING_WRITE;
+ }
+}
+
+static void sdhci_schedule_adma(SDHCIState *s)
+{
+ timer_mod(s->transfer_timer,
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY);
+}
+
+static bool sdhci_adma_transfer_active(SDHCIState *s)
+{
+ return TRANSFERRING_DATA(s->prnsts) &&
+ (s->trnmod & SDHC_TRNS_DMA) &&
+ (s->cmdreg & SDHC_CMD_DATA_PRESENT) &&
+ sdhci_get_dma_type(s) != SDHC_CTRL_SDMA &&
+ !(s->admaerr & SDHC_ADMAERR_STATE_MASK) &&
+ !(s->errintsts & SDHC_EIS_ADMAERR);
+}
+
+static void sdhci_data_transfer(SDHCIState *s);
#define BLOCK_SIZE_MASK (4 * KiB - 1)
@@ -405,7 +432,13 @@ static void sdhci_send_command(SDHCIState *s)
if (!timeout && (s->blksize & BLOCK_SIZE_MASK) &&
(s->cmdreg & SDHC_CMD_DATA_PRESENT)) {
s->data_count = 0;
- sdhci_data_transfer(s);
+ if ((s->trnmod & SDHC_TRNS_DMA) &&
+ sdhci_get_dma_type(s) != SDHC_CTRL_SDMA) {
+ sdhci_set_transfer_active(s);
+ sdhci_schedule_adma(s);
+ } else {
+ sdhci_data_transfer(s);
+ }
}
}
@@ -848,10 +881,10 @@ static void get_adma_description(SDHCIState *s, ADMADescr *dscr)
}
}
-/* Advanced DMA data transfer */
-
-static void sdhci_do_adma(SDHCIState *s)
+/* Run one independently scheduled, quota-bounded ADMA batch */
+static void sdhci_adma_run_batch(void *opaque)
{
+ SDHCIState *s = (SDHCIState *)opaque;
unsigned int begin, length;
const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK;
const MemTxAttrs attrs = { .memory = true };
@@ -859,6 +892,35 @@ static void sdhci_do_adma(SDHCIState *s)
MemTxResult res = MEMTX_ERROR;
int i;
+ if (!sdhci_adma_transfer_active(s)) {
+ return;
+ }
+
+ switch (sdhci_get_dma_type(s)) {
+ case SDHC_CTRL_ADMA1_32:
+ if (!(s->capareg & R_SDHC_CAPAB_ADMA1_MASK)) {
+ trace_sdhci_error("ADMA1 not supported");
+ return;
+ }
+ break;
+ case SDHC_CTRL_ADMA2_32:
+ if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK)) {
+ trace_sdhci_error("ADMA2 not supported");
+ return;
+ }
+ break;
+ case SDHC_CTRL_ADMA2_64:
+ if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK) ||
+ !(s->capareg & R_SDHC_CAPAB_BUS64BIT_MASK)) {
+ trace_sdhci_error("64 bit ADMA not supported");
+ return;
+ }
+ break;
+ default:
+ trace_sdhci_error("Unsupported ADMA type");
+ return;
+ }
+
if (s->trnmod & SDHC_TRNS_BLK_CNT_EN && !s->blkcnt) {
/* Stop Multiple Transfer */
sdhci_end_transfer(s);
@@ -956,15 +1018,17 @@ static void sdhci_do_adma(SDHCIState *s)
}
if (res != MEMTX_OK) {
s->data_count = 0;
+ s->admaerr &= ~SDHC_ADMAERR_STATE_MASK;
+ s->admaerr |= SDHC_ADMAERR_STATE_ST_TFR;
if (s->errintstsen & SDHC_EISEN_ADMAERR) {
trace_sdhci_error("Set ADMA error flag");
s->errintsts |= SDHC_EIS_ADMAERR;
s->norintsts |= SDHC_NIS_ERR;
}
sdhci_update_irq(s);
- } else {
- s->admasysaddr += dscr.incr;
+ return;
}
+ s->admasysaddr += dscr.incr;
break;
case SDHC_ADMA_ATTR_ACT_LINK: /* link to next descriptor table */
s->admasysaddr = dscr.addr;
@@ -1012,50 +1076,15 @@ static void sdhci_do_adma(SDHCIState *s)
}
/* we have unfinished business - reschedule to continue ADMA */
- timer_mod(s->transfer_timer,
- qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + SDHC_TRANSFER_DELAY);
+ sdhci_schedule_adma(s);
}
-/* Perform data transfer according to controller configuration */
-
-static void sdhci_data_transfer(void *opaque)
+/* Perform synchronous PIO or SDMA data transfer */
+static void sdhci_data_transfer(SDHCIState *s)
{
- SDHCIState *s = (SDHCIState *)opaque;
-
if (s->trnmod & SDHC_TRNS_DMA) {
- switch (sdhci_get_dma_type(s)) {
- case SDHC_CTRL_SDMA:
- sdhci_sdma_transfer(s);
- break;
- case SDHC_CTRL_ADMA1_32:
- if (!(s->capareg & R_SDHC_CAPAB_ADMA1_MASK)) {
- trace_sdhci_error("ADMA1 not supported");
- break;
- }
-
- sdhci_do_adma(s);
- break;
- case SDHC_CTRL_ADMA2_32:
- if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK)) {
- trace_sdhci_error("ADMA2 not supported");
- break;
- }
-
- sdhci_do_adma(s);
- break;
- case SDHC_CTRL_ADMA2_64:
- if (!(s->capareg & R_SDHC_CAPAB_ADMA2_MASK) ||
- !(s->capareg & R_SDHC_CAPAB_BUS64BIT_MASK)) {
- trace_sdhci_error("64 bit ADMA not supported");
- break;
- }
-
- sdhci_do_adma(s);
- break;
- default:
- trace_sdhci_error("Unsupported DMA type");
- break;
- }
+ assert(sdhci_get_dma_type(s) == SDHC_CTRL_SDMA);
+ sdhci_sdma_transfer(s);
} else {
if ((s->trnmod & SDHC_TRNS_READ) && sdbus_data_ready(&s->sdbus)) {
s->prnsts |= SDHC_DOING_READ | SDHC_DATA_INHIBIT |
@@ -1098,21 +1127,11 @@ sdhci_buff_access_is_sequential(SDHCIState *s, unsigned byte_num)
return true;
}
-static void sdhci_resume_pending_transfer(SDHCIState *s)
-{
- timer_del(s->transfer_timer);
- sdhci_data_transfer(s);
-}
-
static uint64_t sdhci_read(void *opaque, hwaddr offset, unsigned size)
{
SDHCIState *s = (SDHCIState *)opaque;
uint32_t ret = 0;
- if (timer_pending(s->transfer_timer)) {
- sdhci_resume_pending_transfer(s);
- }
-
switch (offset & ~0x3) {
case SDHC_SYSAD:
ret = s->sdmasysad;
@@ -1235,7 +1254,10 @@ static inline void sdhci_reset_write(SDHCIState *s, uint8_t value)
s->norintsts &= ~SDHC_NIS_CMDCMP;
break;
case SDHC_RESET_DATA:
+ timer_del(s->transfer_timer);
s->data_count = 0;
+ s->admaerr = 0;
+ s->errintsts &= ~SDHC_EIS_ADMAERR;
s->prnsts &= ~(SDHC_SPACE_AVAILABLE | SDHC_DATA_AVAILABLE |
SDHC_DOING_READ | SDHC_DOING_WRITE |
SDHC_DATA_INHIBIT | SDHC_DAT_LINE_ACTIVE);
@@ -1244,6 +1266,10 @@ static inline void sdhci_reset_write(SDHCIState *s, uint8_t value)
s->sdma_boundary_paused = false;
s->norintsts &= ~(SDHC_NIS_WBUFRDY | SDHC_NIS_RBUFRDY |
SDHC_NIS_DMA | SDHC_NIS_TRSCMP | SDHC_NIS_BLKGAP);
+ if (!s->errintsts) {
+ s->norintsts &= ~SDHC_NIS_ERR;
+ }
+ sdhci_update_irq(s);
break;
}
}
@@ -1257,10 +1283,6 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size)
uint32_t value = val;
value <<= shift;
- if (timer_pending(s->transfer_timer)) {
- sdhci_resume_pending_transfer(s);
- }
-
switch (offset & ~0x3) {
case SDHC_SYSAD:
if (!TRANSFERRING_DATA(s->prnsts) ||
@@ -1521,7 +1543,7 @@ void sdhci_initfn(SDHCIState *s)
s->insert_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
sdhci_raise_insertion_irq, s);
s->transfer_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
- sdhci_data_transfer, s);
+ sdhci_adma_run_batch, s);
s->io_ops = &sdhci_mmio_ops;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 10/27] tests/qtest: add Cadence SDHCI ADMA pacing/MMIO reproducer
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (8 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 09/27] hw/sd: sdhci: Run ADMA independently of MMIO Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 11/27] hw/sd: sd: Keep high-capacity memory blocks at 512 bytes Bin Meng
` (16 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Wadim Mueller, Bin Meng, Fabiano Rosas, Laurent Vivier,
Paolo Bonzini, Philippe Mathieu-Daudé, qemu-block
From: Wadim Mueller <wafgo01@gmail.com>
Add an image-free qtest for the microchip-icicle-kit machine that drives
the generic SDHCI model (hw/sd/sdhci.c) through the Cadence wrapper
(hw/sd/cadence_sdhci.c, SRS window at 0x20008200) directly over MMIO
under "-accel qtest", where QEMU_CLOCK_VIRTUAL only advances when the
test steps it.
It reproduces two aspects of the ADMA2 engine objectively, without any
guest firmware:
1. Pacing: a 12-descriptor (> SDHC_ADMA_DESCS_PER_DELAY) non-interrupt
ADMA2 CMD18 read completes after a model-dependent number of
SDHC_TRANSFER_DELAY virtual-clock steps, which the test reports.
2. MMIO coupling: a chain carrying a DMA-boundary interrupt descriptor
yields mid-chain and reschedules on the virtual clock. With the
clock frozen, an interrupt-status read must not itself resume or
complete the transfer. This asserts the behaviour intended by "Run
ADMA independently of MMIO": it passes on the fixed model and fails
on a model that resumes ADMA from an MMIO read.
The SD bring-up (CMD0/CMD8/ACMD41/CMD2/CMD3/CMD7/CMD16) and the ADMA2
descriptor-table layout mirror the pattern already used by the SDHCI
device model; SDHCI register offsets and ADMA attribute bits are
reproduced locally because sdhci-internal.h is not standalone-includable
from a qtest.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
[bmeng: strengthen checks, reuse SDHCI helpers, verify data]
Signed-off-by: Bin Meng <bin.meng@processmission.com>
---
(no changes since v2)
Changes in v2:
- Assert bounded ADMA pacing and MMIO-independent progress
- Reuse existing SDHCI definitions and command helpers
- Verify transferred data against a known image pattern
- New
MAINTAINERS | 1 +
tests/qtest/cadence-sdhci-test.c | 282 +++++++++++++++++++++++++++++++
tests/qtest/meson.build | 2 +
3 files changed, 285 insertions(+)
create mode 100644 tests/qtest/cadence-sdhci-test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index acb79a7d61..65325450e0 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -2425,6 +2425,7 @@ F: include/hw/sd/sd*
F: hw/sd/core.c
F: hw/sd/sd*
F: hw/sd/ssi-sd.c
+F: tests/qtest/cadence-sdhci-test.c
F: tests/qtest/fuzz-sdcard-test.c
F: tests/qtest/sdhci-test.c
F: tests/qtest/xilinx-zynq-sdhci-test.c
diff --git a/tests/qtest/cadence-sdhci-test.c b/tests/qtest/cadence-sdhci-test.c
new file mode 100644
index 0000000000..71c1e53333
--- /dev/null
+++ b/tests/qtest/cadence-sdhci-test.c
@@ -0,0 +1,282 @@
+/*
+ * QTest for Cadence SDHCI ADMA2 pacing / MMIO coupling on the Microchip
+ * PolarFire SoC Icicle Kit (microchip-icicle-kit).
+ *
+ * Drives the generic SDHCI model (hw/sd/sdhci.c) through the Cadence wrapper
+ * directly over MMIO under "-accel qtest", where QEMU_CLOCK_VIRTUAL only
+ * advances when the test steps it; no guest firmware runs.
+ *
+ * Copyright (c) 2026 Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "hw/sd/sdhci.h"
+#include "hw/sd/sdhci-internal.h"
+
+#include "libqtest.h"
+#include "libqos/sdhci-cmd.h"
+
+/*
+ * Use the controller definitions and command helper shared with the existing
+ * SDHCI qtests so this reproducer stays in sync with the device model.
+ */
+
+/* Cadence maps the generic SDHCI (SRS) register window at base + 0x200 */
+#define SDHCI_BASE (0x20008000ULL + 0x200)
+
+/* Scratch in PolarFire DRAM (base 0x80000000): descriptor table + target */
+#define ADMA_TABLE_ADDR 0x82000000ULL
+#define ADMA_BUF_ADDR 0x82100000ULL
+
+#define BLK_LEN 512
+#define NDESC 12 /* > SDHC_ADMA_DESCS_PER_DELAY (5) */
+#define INT_DESC 5 /* ADMA interrupt descriptor */
+#define TRANSFER_SIZE (NDESC * BLK_LEN)
+
+/* CMDREG response-type encodings (bits [1:0]) */
+#define RESP_NONE 0x0000
+#define RESP_R2 0x0001 /* 136-bit */
+#define RESP_R48 0x0002 /* 48-bit */
+#define RESP_R1B 0x0003 /* 48-bit with busy */
+
+#define ACMD41_ARG 0x40FF8000u /* HCS + 3.3-3.6V window */
+#define CMD8_ARG 0x000001AAu /* VHS 2.7-3.6V + check pattern */
+
+static void fill_pattern(uint8_t *buf, size_t len)
+{
+ for (size_t i = 0; i < len; i++) {
+ buf[i] = (i * 13 + 7) & 0xff;
+ }
+}
+
+static void write_pattern(int fd)
+{
+ g_autofree uint8_t *buf = g_malloc(TRANSFER_SIZE);
+ ssize_t written;
+
+ fill_pattern(buf, TRANSFER_SIZE);
+ written = pwrite(fd, buf, TRANSFER_SIZE, 0);
+ g_assert_cmpint(written, ==, TRANSFER_SIZE);
+}
+
+static void check_adma_data(QTestState *qts)
+{
+ g_autofree uint8_t *actual = g_malloc(TRANSFER_SIZE);
+ g_autofree uint8_t *expected = g_malloc(TRANSFER_SIZE);
+
+ fill_pattern(expected, TRANSFER_SIZE);
+ qtest_memread(qts, ADMA_BUF_ADDR, actual, TRANSFER_SIZE);
+ g_assert_cmpmem(actual, TRANSFER_SIZE, expected, TRANSFER_SIZE);
+}
+
+/* Issue an SD command and wait (bounded) for Command Complete */
+static uint16_t sd_cmd(QTestState *qts, uint8_t cmd, uint32_t arg,
+ uint16_t flags)
+{
+ uint16_t sts = 0;
+ int i;
+
+ sdhci_cmd_regs(qts, SDHCI_BASE, 0, 0, arg, 0,
+ ((uint16_t)cmd << 8) | flags);
+
+ for (i = 0; i < 1000; i++) {
+ sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS);
+ if (sts & SDHC_NIS_CMDCMP) {
+ break;
+ }
+ }
+ g_assert_cmphex(sts & SDHC_NIS_CMDCMP, ==, SDHC_NIS_CMDCMP);
+
+ qtest_writew(qts, SDHCI_BASE + SDHC_NORINTSTS, 0xffff); /* w1c */
+ return sts;
+}
+
+/* Bring the SD card from idle to transfer state */
+static void sd_bring_up_card(QTestState *qts)
+{
+ uint32_t rca;
+
+ g_assert_cmphex(qtest_readl(qts, SDHCI_BASE + SDHC_PRNSTS) &
+ SDHC_CARD_PRESENT, ==, SDHC_CARD_PRESENT);
+
+ qtest_writeb(qts, SDHCI_BASE + SDHC_SWRST, SDHC_RESET_ALL);
+ qtest_writew(qts, SDHCI_BASE + SDHC_CLKCON,
+ SDHC_CLOCK_INT_EN | SDHC_CLOCK_SDCLK_EN);
+ qtest_writew(qts, SDHCI_BASE + SDHC_NORINTSTSEN, 0xffff);
+ qtest_writew(qts, SDHCI_BASE + SDHC_ERRINTSTSEN, 0xffff);
+ qtest_writew(qts, SDHCI_BASE + SDHC_NORINTSIGEN, 0xffff);
+
+ sd_cmd(qts, 0, 0x00000000, RESP_NONE); /* GO_IDLE_STATE */
+ sd_cmd(qts, 8, CMD8_ARG, RESP_R48); /* SEND_IF_COND */
+ sd_cmd(qts, 55, 0x00000000, RESP_R48); /* APP_CMD */
+ sd_cmd(qts, 41, ACMD41_ARG, RESP_R48); /* SD_SEND_OP_COND */
+ sd_cmd(qts, 2, 0x00000000, RESP_R2); /* ALL_SEND_CID */
+ sd_cmd(qts, 3, 0x00000000, RESP_R48); /* SEND_RELATIVE_ADDR */
+
+ rca = qtest_readl(qts, SDHCI_BASE + SDHC_RSPREG0) >> 16; /* R6 */
+
+ sd_cmd(qts, 7, rca << 16, RESP_R1B); /* SELECT_CARD */
+ sd_cmd(qts, 16, BLK_LEN, RESP_R48); /* SET_BLOCKLEN */
+}
+
+/*
+ * Build a 32-bit ADMA2 table: per 8-byte entry word0 = (len << 16) | attr,
+ * word1 = target address. When int_desc >= 0 that descriptor also carries
+ * the ADMA descriptor interrupt attribute, so the model yields mid-chain
+ * (rescheduling on QEMU_CLOCK_VIRTUAL) instead of running to completion.
+ */
+static void build_adma_table(QTestState *qts, int int_desc)
+{
+ for (int i = 0; i < NDESC; i++) {
+ uint8_t attr = SDHC_ADMA_ATTR_VALID | SDHC_ADMA_ATTR_ACT_TRAN;
+ uint32_t addr = (uint32_t)(ADMA_BUF_ADDR + (uint64_t)i * BLK_LEN);
+
+ if (i == int_desc) {
+ attr |= SDHC_ADMA_ATTR_INT;
+ }
+ if (i == NDESC - 1) {
+ attr |= SDHC_ADMA_ATTR_END;
+ }
+ qtest_writel(qts, ADMA_TABLE_ADDR + (uint64_t)i * 8,
+ ((uint32_t)BLK_LEN << 16) | attr);
+ qtest_writel(qts, ADMA_TABLE_ADDR + (uint64_t)i * 8 + 4, addr);
+ }
+}
+
+/* Program the ADMA2 engine, descriptor pointer and transfer geometry */
+static void program_adma_read(QTestState *qts)
+{
+ qtest_writeb(qts, SDHCI_BASE + SDHC_HOSTCTL, SDHC_CTRL_ADMA2_32);
+ qtest_writel(qts, SDHCI_BASE + SDHC_ADMASYSADDR, (uint32_t)ADMA_TABLE_ADDR);
+ qtest_writel(qts, SDHCI_BASE + SDHC_ADMASYSADDR + 4,
+ (uint32_t)(ADMA_TABLE_ADDR >> 32));
+ qtest_writel(qts, SDHCI_BASE + SDHC_BLKSIZE, BLK_LEN | (NDESC << 16));
+ qtest_writew(qts, SDHCI_BASE + SDHC_TRNMOD,
+ SDHC_TRNS_DMA | SDHC_TRNS_BLK_CNT_EN |
+ SDHC_TRNS_READ | SDHC_TRNS_MULTI);
+ qtest_writel(qts, SDHCI_BASE + SDHC_ARGUMENT, 0); /* start block 0 */
+ qtest_writew(qts, SDHCI_BASE + SDHC_NORINTSTS, 0xffff);
+ qtest_memset(qts, ADMA_BUF_ADDR, 0xa5, TRANSFER_SIZE);
+}
+
+/* CMD18 READ_MULTIPLE_BLOCK with data present -> kicks off ADMA */
+static void kick_cmd18(QTestState *qts)
+{
+ qtest_writew(qts, SDHCI_BASE + SDHC_CMDREG,
+ (18 << 8) | SDHC_CMD_DATA_PRESENT | RESP_R48);
+}
+
+/* Start a card-carrying icicle machine and bring the card to "tran" */
+static QTestState *icicle_start(char **tmp)
+{
+ int fd;
+ GError *err = NULL;
+ QTestState *qts;
+
+ fd = g_file_open_tmp("icicle-sdhci-XXXXXX.raw", tmp, &err);
+ g_assert_no_error(err);
+ g_assert_cmpint(fd, >=, 0);
+ g_assert_cmpint(ftruncate(fd, 1 * 1024 * 1024), ==, 0);
+ write_pattern(fd);
+ close(fd);
+
+ qts = qtest_initf("-machine microchip-icicle-kit -accel qtest "
+ "-display none -drive if=sd,file=%s,format=raw", *tmp);
+ sd_bring_up_card(qts);
+ return qts;
+}
+
+/*
+ * Facet 1 -- pacing. A non-interrupt chain completes after a bounded number
+ * of SDHC_TRANSFER_DELAY steps. With 12 descriptors and a quota of five per
+ * batch, completion requires exactly three virtual-clock steps.
+ */
+static void test_adma_pacing(void)
+{
+ char *tmp = NULL;
+ QTestState *qts = icicle_start(&tmp);
+ const int expected_steps = DIV_ROUND_UP(NDESC,
+ SDHC_ADMA_DESCS_PER_DELAY);
+ uint16_t sts;
+ int steps = 0;
+
+ build_adma_table(qts, -1);
+ program_adma_read(qts);
+ kick_cmd18(qts);
+
+ while (!((sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS)) &
+ SDHC_NIS_TRSCMP)) {
+ qtest_clock_step(qts, SDHC_TRANSFER_DELAY);
+ steps++;
+ g_assert_cmpint(steps, <=, expected_steps);
+ }
+
+ g_assert_cmpint(steps, ==, expected_steps);
+ g_assert_cmphex(sts & SDHC_NIS_TRSCMP, ==, SDHC_NIS_TRSCMP);
+ g_assert_cmphex(sts & SDHC_NIS_ERR, ==, 0);
+ check_adma_data(qts);
+
+ g_test_message("ADMA %d descriptors: %d clock steps to Transfer Complete",
+ NDESC, steps);
+
+ qtest_quit(qts);
+ unlink(tmp);
+ g_free(tmp);
+}
+
+/*
+ * Facet 2 -- MMIO coupling (regression for "Run ADMA independently of MMIO").
+ * With the clock frozen, a bare interrupt-status read must not resume the
+ * transfer left pending by the mid-chain interrupt descriptor: Transfer
+ * Complete must still be clear. A model that resumes ADMA from an MMIO read
+ * completes it on that bare read and fails the assertion.
+ */
+static void test_adma_mmio_coupling(void)
+{
+ char *tmp = NULL;
+ QTestState *qts = icicle_start(&tmp);
+ uint16_t sts;
+ int steps = 0;
+
+ build_adma_table(qts, INT_DESC);
+ program_adma_read(qts);
+ kick_cmd18(qts);
+
+ qtest_clock_step(qts, SDHC_TRANSFER_DELAY);
+ qtest_clock_step(qts, SDHC_TRANSFER_DELAY);
+
+ sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS);
+ g_assert_cmphex(sts & (SDHC_NIS_DMA | SDHC_NIS_TRSCMP), ==,
+ SDHC_NIS_DMA);
+
+ sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS);
+ g_assert_cmphex(sts & (SDHC_NIS_DMA | SDHC_NIS_TRSCMP), ==,
+ SDHC_NIS_DMA);
+
+ while (!((sts = qtest_readw(qts, SDHCI_BASE + SDHC_NORINTSTS)) &
+ SDHC_NIS_TRSCMP)) {
+ qtest_clock_step(qts, SDHC_TRANSFER_DELAY);
+ steps++;
+ g_assert_cmpint(steps, <, 1000);
+ }
+ g_assert_cmphex(sts & SDHC_NIS_ERR, ==, 0);
+ check_adma_data(qts);
+
+ g_test_message("ADMA coupling: %d clock steps to complete after bare read",
+ steps);
+
+ qtest_quit(qts);
+ unlink(tmp);
+ g_free(tmp);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+ qtest_add_func("/microchip/icicle/sdhci/adma-pacing", test_adma_pacing);
+ qtest_add_func("/microchip/icicle/sdhci/adma-mmio-coupling",
+ test_adma_mmio_coupling);
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 1fe008c9ed..29457193ed 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -311,6 +311,8 @@ qtests_riscv32 = \
qtests_riscv64 = ['riscv-csr-test'] + \
(unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
+ (config_all_devices.has_key('CONFIG_MICROCHIP_PFSOC') ?
+ ['cadence-sdhci-test'] : []) + \
(config_all_devices.has_key('CONFIG_SIFIVE_L2CC') ? ['sifive_l2cc_test'] : []) + \
(config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 11/27] hw/sd: sd: Keep high-capacity memory blocks at 512 bytes
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (9 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 10/27] tests/qtest: add Cadence SDHCI ADMA pacing/MMIO reproducer Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 12/27] hw/sd: cadence: Add 64-bit system bus property Bin Meng
` (15 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: qemu-stable, Philippe Mathieu-Daudé, Conor Dooley, Bin Meng,
qemu-block
SDHC and SDXC memory commands always transfer 512-byte blocks regardless
of the length selected by CMD16. QEMU stores that selected length for
LOCK_UNLOCK but also used it directly for several memory command paths.
Route single- and multi-block memory sizing through the existing helper
that distinguishes high-capacity cards from SDSC.
This lets PolarFire SoC HSS validate the complete GPT header and locate
its boot partition instead of receiving only the eight-byte signature.
Fixes: 2d7adea4fe57 ("hw/sd: Support SDHC size cards")
Cc: qemu-stable@nongnu.org
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v1)
hw/sd/sd.c | 43 +++++++++++++++++++++++++------------------
1 file changed, 25 insertions(+), 18 deletions(-)
diff --git a/hw/sd/sd.c b/hw/sd/sd.c
index b1b3b1226a..0083b8e064 100644
--- a/hw/sd/sd.c
+++ b/hw/sd/sd.c
@@ -1975,18 +1975,20 @@ static sd_rsp_type_t sd_cmd_SET_BLOCKLEN(SDState *sd, SDRequest req)
static sd_rsp_type_t sd_cmd_READ_SINGLE_BLOCK(SDState *sd, SDRequest req)
{
uint64_t addr;
+ uint32_t blk_len;
if (sd->state != sd_transfer_state) {
return sd_invalid_state_for_cmd(sd, req);
}
+ blk_len = sd_blk_len(sd);
addr = sd_req_get_address(sd, req);
- if (!address_in_range(sd, "READ_SINGLE_BLOCK", addr, sd->blk_len)) {
+ if (!address_in_range(sd, "READ_SINGLE_BLOCK", addr, blk_len)) {
return sd_r1;
}
- sd_blk_read(sd, addr, sd->blk_len);
- return sd_cmd_to_sendingdata(sd, req, addr, NULL, sd->blk_len);
+ sd_blk_read(sd, addr, blk_len);
+ return sd_cmd_to_sendingdata(sd, req, addr, NULL, blk_len);
}
/* CMD19 */
@@ -2025,13 +2027,15 @@ static sd_rsp_type_t sd_cmd_SET_BLOCK_COUNT(SDState *sd, SDRequest req)
static sd_rsp_type_t sd_cmd_WRITE_SINGLE_BLOCK(SDState *sd, SDRequest req)
{
uint64_t addr;
+ uint32_t blk_len;
if (sd->state != sd_transfer_state) {
return sd_invalid_state_for_cmd(sd, req);
}
+ blk_len = sd_blk_len(sd);
addr = sd_req_get_address(sd, req);
- if (!address_in_range(sd, "WRITE_SINGLE_BLOCK", addr, sd->blk_len)) {
+ if (!address_in_range(sd, "WRITE_SINGLE_BLOCK", addr, blk_len)) {
return sd_r1;
}
@@ -2045,7 +2049,7 @@ static sd_rsp_type_t sd_cmd_WRITE_SINGLE_BLOCK(SDState *sd, SDRequest req)
}
sd->blk_written = 0;
- return sd_cmd_to_receivingdata(sd, req, addr, sd->blk_len);
+ return sd_cmd_to_receivingdata(sd, req, addr, blk_len);
}
/* CMD26 */
@@ -2372,7 +2376,7 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
switch (sd->state) {
case sd_transfer_state:
- if (!address_in_range(sd, "READ_BLOCK", addr, sd->blk_len)) {
+ if (!address_in_range(sd, "READ_BLOCK", addr, sd_blk_len(sd))) {
return sd_r1;
}
@@ -2392,7 +2396,8 @@ static sd_rsp_type_t sd_normal_command(SDState *sd, SDRequest req)
switch (sd->state) {
case sd_transfer_state:
- if (!address_in_range(sd, "WRITE_BLOCK", addr, sd->blk_len)) {
+ if (!address_in_range(sd, "WRITE_BLOCK", addr,
+ sd_blk_len(sd))) {
return sd_r1;
}
@@ -2662,6 +2667,7 @@ static void sdcard_write_data_dump(const char *proto, const char *cmd_desc,
static size_t sd_write_data(SDState *sd, const void *buf, size_t length)
{
unsigned int partition_access;
+ uint32_t blk_len;
int i;
const uint8_t *value = buf;
@@ -2678,6 +2684,7 @@ static size_t sd_write_data(SDState *sd, const void *buf, size_t length)
if (sd->card_status & (ADDRESS_ERROR | WP_VIOLATION))
return length;
+ blk_len = sd_blk_len(sd);
sdcard_write_data_dump(sd->proto->name,
sd->last_cmd_name,
sd->current_cmd, sd->data_offset, buf, length);
@@ -2704,7 +2711,7 @@ static size_t sd_write_data(SDState *sd, const void *buf, size_t length)
if (sd->data_offset == 0) {
/* Start of the block - let's check the address is valid */
if (!address_in_range(sd, "WRITE_MULTIPLE_BLOCK",
- sd->data_start, sd->blk_len)) {
+ sd->data_start, blk_len)) {
break;
}
if (sd->size <= SDSC_MAX_CAPACITY) {
@@ -2715,7 +2722,7 @@ static size_t sd_write_data(SDState *sd, const void *buf, size_t length)
}
}
sd->data[sd->data_offset++] = value[0];
- if (sd->data_offset >= sd->blk_len) {
+ if (sd->data_offset >= blk_len) {
/* TODO: Check CRC before committing */
sd->state = sd_programming_state;
partition_access = sd->ext_csd[EXT_CSD_PART_CONFIG]
@@ -2726,7 +2733,7 @@ static size_t sd_write_data(SDState *sd, const void *buf, size_t length)
sd_blk_write(sd, sd->data_start, sd->data_offset);
}
sd->blk_written++;
- sd->data_start += sd->blk_len;
+ sd->data_start += blk_len;
sd->data_offset = 0;
sd->csd[14] |= 0x40;
@@ -2810,7 +2817,7 @@ static size_t sd_read_data(SDState *sd, void *buf, size_t length)
/* TODO: Append CRCs */
const uint8_t dummy_byte = 0x00;
unsigned int partition_access;
- uint32_t io_len;
+ uint32_t blk_len;
uint8_t *value = buf;
if (!sd->blk || !blk_is_inserted(sd->blk)) {
@@ -2830,11 +2837,11 @@ static size_t sd_read_data(SDState *sd, void *buf, size_t length)
return length;
}
- io_len = sd_blk_len(sd);
+ blk_len = sd_blk_len(sd);
trace_sdcard_read_data(sd->proto->name,
sd->last_cmd_name, sd->current_cmd,
- sd->data_offset, sd->data_size, io_len);
+ sd->data_offset, sd->data_size, blk_len);
switch (sd->current_cmd) {
case 6: /* CMD6: SWITCH_FUNCTION */
case 8: /* CMD8: SEND_EXT_CSD */
@@ -2859,22 +2866,22 @@ static size_t sd_read_data(SDState *sd, void *buf, size_t length)
if (sd->data_offset == 0) {
if (!address_in_range(sd, "READ_MULTIPLE_BLOCK",
- sd->data_start, io_len)) {
+ sd->data_start, blk_len)) {
*value = dummy_byte;
return length;
}
partition_access = sd->ext_csd[EXT_CSD_PART_CONFIG]
& EXT_CSD_PART_CONFIG_ACC_MASK;
if (partition_access == EXT_CSD_PART_CONFIG_ACC_RPMB) {
- emmc_rpmb_blk_read(sd, sd->data_start, io_len);
+ emmc_rpmb_blk_read(sd, sd->data_start, blk_len);
} else {
- sd_blk_read(sd, sd->data_start, io_len);
+ sd_blk_read(sd, sd->data_start, blk_len);
}
}
*value = sd->data[sd->data_offset++];
- if (sd->data_offset >= io_len) {
- sd->data_start += io_len;
+ if (sd->data_offset >= blk_len) {
+ sd->data_start += blk_len;
sd->data_offset = 0;
if (sd->multi_blk_cnt != 0) {
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 12/27] hw/sd: cadence: Add 64-bit system bus property
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (10 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 11/27] hw/sd: sd: Keep high-capacity memory blocks at 512 bytes Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 13/27] hw/riscv: pfsoc: Enable 64-bit SDHCI system bus support Bin Meng
` (14 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Alistair Francis, Edgar E. Iglesias, Peter Maydell,
qemu-arm
Cadence SDHCI integrations can differ in whether they provide a 64-bit
system bus. Advertising the capability unconditionally could make a
guest select unsupported ADMA descriptors.
Add a bus64bit property to the Cadence model and keep it disabled by
default. When enabled, expose the corresponding capability through the
generic SDHCI child.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v2)
Changes in v2:
- Make 64-bit system bus support a device property
include/hw/sd/cadence_sdhci.h | 1 +
hw/sd/cadence_sdhci.c | 11 +++++++++++
2 files changed, 12 insertions(+)
diff --git a/include/hw/sd/cadence_sdhci.h b/include/hw/sd/cadence_sdhci.h
index cd8288b7d8..553fa73842 100644
--- a/include/hw/sd/cadence_sdhci.h
+++ b/include/hw/sd/cadence_sdhci.h
@@ -34,6 +34,7 @@ typedef struct CadenceSDHCIState {
MemoryRegion container;
MemoryRegion iomem;
BusState *bus;
+ bool bus64bit;
uint32_t regs[CADENCE_SDHCI_NUM_REGS];
diff --git a/hw/sd/cadence_sdhci.c b/hw/sd/cadence_sdhci.c
index 8476baf67f..875823a50d 100644
--- a/hw/sd/cadence_sdhci.c
+++ b/hw/sd/cadence_sdhci.c
@@ -25,6 +25,7 @@
#include "qemu/error-report.h"
#include "qapi/error.h"
#include "migration/vmstate.h"
+#include "hw/core/qdev-properties.h"
#include "hw/sd/cadence_sdhci.h"
#include "sdhci-internal.h"
@@ -147,6 +148,11 @@ static void cadence_sdhci_realize(DeviceState *dev, Error **errp)
s, TYPE_CADENCE_SDHCI, CADENCE_SDHCI_REG_SIZE);
memory_region_add_subregion(&s->container, 0, &s->iomem);
+ if (s->bus64bit) {
+ object_property_set_uint(OBJECT(&s->sdhci), "capareg",
+ SDHC_CAPAB_REG_DEFAULT |
+ R_SDHC_CAPAB_BUS64BIT_MASK, &error_abort);
+ }
sysbus_realize(sbd_sdhci, errp);
memory_region_add_subregion(&s->container, CADENCE_SDHCI_SRS_BASE,
sysbus_mmio_get_region(sbd_sdhci, 0));
@@ -165,6 +171,10 @@ static const VMStateDescription vmstate_cadence_sdhci = {
},
};
+static const Property cadence_sdhci_properties[] = {
+ DEFINE_PROP_BOOL("bus64bit", CadenceSDHCIState, bus64bit, false),
+};
+
static void cadence_sdhci_class_init(ObjectClass *classp, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(classp);
@@ -172,6 +182,7 @@ static void cadence_sdhci_class_init(ObjectClass *classp, const void *data)
dc->desc = "Cadence SD/SDIO/eMMC Host Controller (SD4HC)";
dc->realize = cadence_sdhci_realize;
device_class_set_legacy_reset(dc, cadence_sdhci_reset);
+ device_class_set_props(dc, cadence_sdhci_properties);
dc->vmsd = &vmstate_cadence_sdhci;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 13/27] hw/riscv: pfsoc: Enable 64-bit SDHCI system bus support
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (11 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 12/27] hw/sd: cadence: Add 64-bit system bus property Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 14/27] hw/rtc: Add PolarFire SoC RTC model Bin Meng
` (13 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Alistair Francis, Chao Liu, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Sebastian Huber, Weiwei Li, qemu-riscv
PolarFire SoC software selects 64-bit ADMA descriptors for the Cadence
SDHCI controller. The controller supports those descriptors, but the
Cadence property remains disabled by default for other integrations.
Enable the bus64bit property from the PolarFire SoC so the generic
SDHCI descriptor engine can execute 64-bit ADMA transfers.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v2)
Changes in v2:
- New
hw/riscv/microchip_pfsoc.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index d0d8c8f84c..3d1b3246db 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -193,6 +193,8 @@ static void microchip_pfsoc_soc_instance_init(Object *obj)
object_initialize_child(obj, "sd-controller", &s->sdhci,
TYPE_CADENCE_SDHCI);
+ object_property_set_bool(OBJECT(&s->sdhci), "bus64bit", true,
+ &error_abort);
object_initialize_child(obj, "ioscb", &s->ioscb, TYPE_MCHP_PFSOC_IOSCB);
}
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 14/27] hw/rtc: Add PolarFire SoC RTC model
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (12 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 13/27] hw/riscv: pfsoc: Enable 64-bit SDHCI system bus support Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 15/27] hw/riscv: pfsoc: Add PolarFire SoC RTC to Icicle Kit Bin Meng
` (12 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU; +Cc: Conor Dooley, Conor Dooley, Paolo Bonzini, Sebastian Huber,
qemu-riscv
The PolarFire SoC exposes an RTC with binary and calendar modes,
programmable prescaling, alarms, and wakeup and match interrupts.
Add a standalone device model with explicit MMIO handling for time,
commands, alarms, interrupt state, reset, and migration. Add its
Kconfig and Meson entries and list it under the PolarFire SoC
maintainer section.
Reset the device to its documented hardware values, with the binary
counter stopped at zero.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v2)
Changes in v2:
- Reset the RTC to the documented hardware values
- Use the Resettable API instead of a legacy reset handler
MAINTAINERS | 2 +
include/hw/rtc/mchp_pfsoc_rtc.h | 46 +++
hw/rtc/mchp_pfsoc_rtc.c | 477 ++++++++++++++++++++++++++++++++
hw/rtc/Kconfig | 3 +
hw/rtc/meson.build | 1 +
5 files changed, 529 insertions(+)
create mode 100644 include/hw/rtc/mchp_pfsoc_rtc.h
create mode 100644 hw/rtc/mchp_pfsoc_rtc.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 65325450e0..2ea76ad1cd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1803,11 +1803,13 @@ F: hw/char/mchp_pfsoc_mmuart.c
F: hw/misc/mchp_pfsoc_dmc.c
F: hw/misc/mchp_pfsoc_ioscb.c
F: hw/misc/mchp_pfsoc_sysreg.c
+F: hw/rtc/mchp_pfsoc_rtc.c
F: include/hw/riscv/microchip_pfsoc.h
F: include/hw/char/mchp_pfsoc_mmuart.h
F: include/hw/misc/mchp_pfsoc_dmc.h
F: include/hw/misc/mchp_pfsoc_ioscb.h
F: include/hw/misc/mchp_pfsoc_sysreg.h
+F: include/hw/rtc/mchp_pfsoc_rtc.h
Shakti C class SoC
L: qemu-riscv@nongnu.org
diff --git a/include/hw/rtc/mchp_pfsoc_rtc.h b/include/hw/rtc/mchp_pfsoc_rtc.h
new file mode 100644
index 0000000000..17bc6ddcdc
--- /dev/null
+++ b/include/hw/rtc/mchp_pfsoc_rtc.h
@@ -0,0 +1,46 @@
+/*
+ * Microchip PolarFire SoC RTC
+ *
+ * Copyright (c) 2026 Process Mission
+ *
+ * Author:
+ * Bin Meng <bin.meng@processmission.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#ifndef HW_RTC_MCHP_PFSOC_RTC_H
+#define HW_RTC_MCHP_PFSOC_RTC_H
+
+#include "hw/core/sysbus.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+
+#define TYPE_MCHP_PFSOC_RTC "mchp.pfsoc.rtc"
+OBJECT_DECLARE_SIMPLE_TYPE(MchpPfSoCRtcState, MCHP_PFSOC_RTC)
+
+#define MCHP_PFSOC_RTC_REGS 28
+
+struct MchpPfSoCRtcState {
+ SysBusDevice parent_obj;
+
+ MemoryRegion iomem;
+ qemu_irq irq_wakeup;
+ qemu_irq irq_match;
+ QEMUTimer *alarm_timer;
+
+ int64_t tick_offset;
+ int64_t tick_offset_vmstate;
+ uint64_t frozen_count;
+ uint64_t read_latch;
+ bool running;
+ bool alarm_enabled;
+ bool wakeup_pending;
+ bool match_pending;
+ bool updated;
+ bool read_latch_valid;
+
+ uint32_t regs[MCHP_PFSOC_RTC_REGS];
+};
+
+#endif /* HW_RTC_MCHP_PFSOC_RTC_H */
diff --git a/hw/rtc/mchp_pfsoc_rtc.c b/hw/rtc/mchp_pfsoc_rtc.c
new file mode 100644
index 0000000000..f5068b15ef
--- /dev/null
+++ b/hw/rtc/mchp_pfsoc_rtc.c
@@ -0,0 +1,477 @@
+/*
+ * Microchip PolarFire SoC RTC
+ *
+ * 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/rtc/mchp_pfsoc_rtc.h"
+#include "hw/core/irq.h"
+#include "migration/vmstate.h"
+#include "qemu/cutils.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "system/rtc.h"
+#include "system/system.h"
+
+#define RTC_MMIO_SIZE 0x1000
+#define RTC_CONTROL_MASK 0x000007ff
+#define RTC_MODE_MASK 0x0000000f
+#define RTC_PRESCALER_MASK 0x03ffffff
+#define RTC_ALARM_UPPER_MASK 0x000007ff
+#define RTC_UPPER_MASK 0x3fffffff
+#define RTC_CALENDAR_MASK 0x000000ff
+
+#define RTC_CONTROL 0x00
+#define RTC_CONTROL_START_RUNNING BIT(0)
+#define RTC_CONTROL_STOP BIT(1)
+#define RTC_CONTROL_ALARM_ON BIT(2)
+#define RTC_CONTROL_ALARM_OFF BIT(3)
+#define RTC_CONTROL_RESET BIT(4)
+#define RTC_CONTROL_UPLOAD BIT(5)
+#define RTC_CONTROL_DOWNLOAD BIT(6)
+#define RTC_CONTROL_MATCH BIT(7)
+#define RTC_CONTROL_WAKEUP_CLEAR BIT(8)
+#define RTC_CONTROL_WAKEUP_SET BIT(9)
+#define RTC_CONTROL_UPDATED BIT(10)
+
+#define RTC_MODE 0x04
+#define RTC_MODE_CLOCK_CALENDAR BIT(0)
+#define RTC_MODE_WAKEUP_ENABLE BIT(1)
+#define RTC_MODE_WAKEUP_RESET BIT(2)
+#define RTC_MODE_WAKEUP_CONTINUE BIT(3)
+
+#define RTC_PRESCALER 0x08
+#define RTC_ALARM_LOWER 0x0c
+#define RTC_ALARM_UPPER 0x10
+#define RTC_COMPARE_LOWER 0x14
+#define RTC_COMPARE_UPPER 0x18
+#define RTC_DATETIME_LOWER 0x20
+#define RTC_DATETIME_UPPER 0x24
+#define RTC_SECONDS 0x30
+#define RTC_MINUTES 0x34
+#define RTC_HOURS 0x38
+#define RTC_DAY 0x3c
+#define RTC_MONTH 0x40
+#define RTC_YEAR 0x44
+#define RTC_WEEKDAY 0x48
+#define RTC_WEEK 0x4c
+#define RTC_SECONDS_COUNT 0x50
+#define RTC_MINUTES_COUNT 0x54
+#define RTC_HOURS_COUNT 0x58
+#define RTC_DAY_COUNT 0x5c
+#define RTC_MONTH_COUNT 0x60
+#define RTC_YEAR_COUNT 0x64
+#define RTC_WEEKDAY_COUNT 0x68
+#define RTC_WEEK_COUNT 0x6c
+
+#define RTC_REG(offset) ((offset) / sizeof(uint32_t))
+
+static uint64_t mchp_pfsoc_rtc_get_count(MchpPfSoCRtcState *s)
+{
+ if (!s->running) {
+ return s->frozen_count;
+ }
+
+ return s->tick_offset +
+ qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND;
+}
+
+static void mchp_pfsoc_rtc_set_count(MchpPfSoCRtcState *s, uint64_t count)
+{
+ if (s->running) {
+ s->tick_offset = (int64_t)count -
+ qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND;
+ } else {
+ s->frozen_count = count;
+ }
+}
+
+static void mchp_pfsoc_rtc_update_irq(MchpPfSoCRtcState *s)
+{
+ qemu_set_irq(s->irq_wakeup, s->wakeup_pending);
+ qemu_set_irq(s->irq_match, s->match_pending);
+}
+
+static void mchp_pfsoc_rtc_schedule_alarm(MchpPfSoCRtcState *s);
+
+static void mchp_pfsoc_rtc_alarm(void *opaque)
+{
+ MchpPfSoCRtcState *s = opaque;
+ bool periodic = s->regs[RTC_REG(RTC_MODE)] & RTC_MODE_WAKEUP_RESET;
+
+ s->alarm_enabled = periodic;
+ s->match_pending = true;
+ if (s->regs[RTC_REG(RTC_MODE)] & RTC_MODE_WAKEUP_ENABLE) {
+ s->wakeup_pending = true;
+ }
+ if (s->regs[RTC_REG(RTC_MODE)] & RTC_MODE_WAKEUP_RESET) {
+ mchp_pfsoc_rtc_set_count(s, 0);
+ }
+ if (!(s->regs[RTC_REG(RTC_MODE)] & RTC_MODE_WAKEUP_CONTINUE)) {
+ s->frozen_count = mchp_pfsoc_rtc_get_count(s);
+ s->running = false;
+ }
+ mchp_pfsoc_rtc_update_irq(s);
+ if (periodic) {
+ mchp_pfsoc_rtc_schedule_alarm(s);
+ }
+}
+
+static void mchp_pfsoc_rtc_schedule_alarm(MchpPfSoCRtcState *s)
+{
+ uint64_t compare_mask;
+ uint64_t target;
+ uint64_t now;
+ uint64_t delta;
+
+ timer_del(s->alarm_timer);
+ if (!s->alarm_enabled || !s->running ||
+ (s->regs[RTC_REG(RTC_MODE)] & RTC_MODE_CLOCK_CALENDAR)) {
+ return;
+ }
+
+ compare_mask = s->regs[RTC_REG(RTC_COMPARE_LOWER)] |
+ ((uint64_t)(s->regs[RTC_REG(RTC_COMPARE_UPPER)] &
+ RTC_UPPER_MASK) << 32);
+ if (compare_mask != MAKE_64BIT_MASK(0, 62)) {
+ return;
+ }
+
+ target = s->regs[RTC_REG(RTC_ALARM_LOWER)] |
+ ((uint64_t)(s->regs[RTC_REG(RTC_ALARM_UPPER)] &
+ RTC_ALARM_UPPER_MASK) << 32);
+ now = mchp_pfsoc_rtc_get_count(s);
+ if (target <= now) {
+ mchp_pfsoc_rtc_alarm(s);
+ return;
+ }
+
+ delta = target - now;
+ if (delta > INT64_MAX / NANOSECONDS_PER_SECOND) {
+ return;
+ }
+ timer_mod(s->alarm_timer,
+ qemu_clock_get_ns(rtc_clock) + delta * NANOSECONDS_PER_SECOND);
+}
+
+static void mchp_pfsoc_rtc_control_write(MchpPfSoCRtcState *s,
+ uint32_t value)
+{
+ uint64_t count;
+
+ value &= RTC_CONTROL_MASK;
+ if (value & RTC_CONTROL_UPDATED) {
+ s->updated = false;
+ }
+ if (value & RTC_CONTROL_RESET) {
+ mchp_pfsoc_rtc_set_count(s, 0);
+ s->updated = true;
+ }
+ if (value & RTC_CONTROL_UPLOAD) {
+ count = s->regs[RTC_REG(RTC_DATETIME_LOWER)] |
+ ((uint64_t)(s->regs[RTC_REG(RTC_DATETIME_UPPER)] &
+ RTC_UPPER_MASK) << 32);
+ mchp_pfsoc_rtc_set_count(s, count);
+ s->updated = true;
+ }
+ if (value & RTC_CONTROL_DOWNLOAD) {
+ count = mchp_pfsoc_rtc_get_count(s);
+ s->regs[RTC_REG(RTC_DATETIME_LOWER)] = count;
+ s->regs[RTC_REG(RTC_DATETIME_UPPER)] = count >> 32;
+ }
+ if ((value & RTC_CONTROL_START_RUNNING) && !s->running) {
+ s->tick_offset = (int64_t)s->frozen_count -
+ qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND;
+ s->running = true;
+ }
+ if ((value & RTC_CONTROL_STOP) && s->running) {
+ s->frozen_count = mchp_pfsoc_rtc_get_count(s);
+ s->running = false;
+ }
+ if (value & RTC_CONTROL_ALARM_OFF) {
+ s->alarm_enabled = false;
+ s->wakeup_pending = false;
+ s->match_pending = false;
+ timer_del(s->alarm_timer);
+ } else if (value & RTC_CONTROL_ALARM_ON) {
+ s->alarm_enabled = true;
+ }
+ if (value & RTC_CONTROL_WAKEUP_CLEAR) {
+ s->wakeup_pending = false;
+ s->match_pending = false;
+ }
+ if (value & RTC_CONTROL_WAKEUP_SET) {
+ s->wakeup_pending = true;
+ }
+
+ mchp_pfsoc_rtc_update_irq(s);
+ mchp_pfsoc_rtc_schedule_alarm(s);
+}
+
+static uint32_t mchp_pfsoc_rtc_control_read(MchpPfSoCRtcState *s)
+{
+ uint32_t value;
+
+ value = s->running ? RTC_CONTROL_START_RUNNING : 0;
+ if (s->alarm_enabled) {
+ value |= RTC_CONTROL_ALARM_ON;
+ }
+ if (s->match_pending) {
+ value |= RTC_CONTROL_MATCH;
+ }
+ if (s->updated) {
+ value |= RTC_CONTROL_UPDATED;
+ }
+ return value;
+}
+
+static uint32_t mchp_pfsoc_rtc_datetime_lower_read(MchpPfSoCRtcState *s)
+{
+ s->read_latch = mchp_pfsoc_rtc_get_count(s);
+ s->read_latch_valid = true;
+ return (uint32_t)s->read_latch;
+}
+
+static uint32_t mchp_pfsoc_rtc_datetime_upper_read(MchpPfSoCRtcState *s)
+{
+ uint64_t count = s->read_latch_valid ? s->read_latch :
+ mchp_pfsoc_rtc_get_count(s);
+
+ s->read_latch_valid = false;
+ return (count >> 32) & RTC_UPPER_MASK;
+}
+
+static uint64_t mchp_pfsoc_rtc_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ MchpPfSoCRtcState *s = opaque;
+
+ switch (offset) {
+ case RTC_CONTROL:
+ return mchp_pfsoc_rtc_control_read(s);
+ case RTC_DATETIME_LOWER:
+ return mchp_pfsoc_rtc_datetime_lower_read(s);
+ case RTC_DATETIME_UPPER:
+ return mchp_pfsoc_rtc_datetime_upper_read(s);
+ case RTC_MODE:
+ case RTC_PRESCALER:
+ case RTC_ALARM_LOWER:
+ case RTC_ALARM_UPPER:
+ case RTC_COMPARE_LOWER:
+ case RTC_COMPARE_UPPER:
+ case RTC_SECONDS:
+ case RTC_MINUTES:
+ case RTC_HOURS:
+ case RTC_DAY:
+ case RTC_MONTH:
+ case RTC_YEAR:
+ case RTC_WEEKDAY:
+ case RTC_WEEK:
+ case RTC_SECONDS_COUNT:
+ case RTC_MINUTES_COUNT:
+ case RTC_HOURS_COUNT:
+ case RTC_DAY_COUNT:
+ case RTC_MONTH_COUNT:
+ case RTC_YEAR_COUNT:
+ case RTC_WEEKDAY_COUNT:
+ case RTC_WEEK_COUNT:
+ return s->regs[RTC_REG(offset)];
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
+ "(size %d, offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, offset);
+ return 0;
+ }
+}
+
+static void mchp_pfsoc_rtc_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ MchpPfSoCRtcState *s = opaque;
+ bool update_alarm = false;
+
+ switch (offset) {
+ case RTC_CONTROL:
+ mchp_pfsoc_rtc_control_write(s, value);
+ return;
+ case RTC_MODE:
+ s->regs[RTC_REG(offset)] = value & RTC_MODE_MASK;
+ update_alarm = true;
+ break;
+ case RTC_PRESCALER:
+ s->regs[RTC_REG(offset)] = value & RTC_PRESCALER_MASK;
+ break;
+ case RTC_ALARM_LOWER:
+ case RTC_COMPARE_LOWER:
+ s->regs[RTC_REG(offset)] = value;
+ update_alarm = true;
+ break;
+ case RTC_ALARM_UPPER:
+ s->regs[RTC_REG(offset)] = value & RTC_ALARM_UPPER_MASK;
+ update_alarm = true;
+ break;
+ case RTC_COMPARE_UPPER:
+ s->regs[RTC_REG(offset)] = value & RTC_UPPER_MASK;
+ update_alarm = true;
+ break;
+ case RTC_DATETIME_LOWER:
+ s->regs[RTC_REG(offset)] = value;
+ break;
+ case RTC_DATETIME_UPPER:
+ s->regs[RTC_REG(offset)] = value & RTC_UPPER_MASK;
+ break;
+ case RTC_SECONDS:
+ case RTC_MINUTES:
+ case RTC_HOURS:
+ case RTC_DAY:
+ case RTC_MONTH:
+ case RTC_YEAR:
+ case RTC_WEEKDAY:
+ case RTC_WEEK:
+ s->regs[RTC_REG(offset)] = value & RTC_CALENDAR_MASK;
+ break;
+ case RTC_SECONDS_COUNT:
+ case RTC_MINUTES_COUNT:
+ case RTC_HOURS_COUNT:
+ case RTC_DAY_COUNT:
+ case RTC_MONTH_COUNT:
+ case RTC_YEAR_COUNT:
+ case RTC_WEEKDAY_COUNT:
+ case RTC_WEEK_COUNT:
+ break;
+ default:
+ qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
+ "(size %d, value 0x%" PRIx64
+ ", offset 0x%" HWADDR_PRIx ")\n",
+ __func__, size, value, offset);
+ return;
+ }
+
+ if (update_alarm) {
+ mchp_pfsoc_rtc_schedule_alarm(s);
+ }
+}
+
+static const MemoryRegionOps mchp_pfsoc_rtc_ops = {
+ .read = mchp_pfsoc_rtc_read,
+ .write = mchp_pfsoc_rtc_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = { .min_access_size = 4, .max_access_size = 4 },
+};
+
+static void mchp_pfsoc_rtc_reset_hold(Object *obj, ResetType type)
+{
+ MchpPfSoCRtcState *s = MCHP_PFSOC_RTC(obj);
+
+ timer_del(s->alarm_timer);
+ s->running = false;
+ s->alarm_enabled = false;
+ s->wakeup_pending = false;
+ s->match_pending = false;
+ s->updated = false;
+ s->read_latch_valid = false;
+ memset(s->regs, 0, sizeof(s->regs));
+
+ s->tick_offset = 0;
+ s->frozen_count = 0;
+ s->read_latch = 0;
+ mchp_pfsoc_rtc_update_irq(s);
+}
+
+static int mchp_pfsoc_rtc_pre_save(void *opaque)
+{
+ MchpPfSoCRtcState *s = opaque;
+ int64_t delta = qemu_clock_get_ns(rtc_clock) /
+ NANOSECONDS_PER_SECOND -
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) /
+ NANOSECONDS_PER_SECOND;
+
+ s->tick_offset_vmstate = s->tick_offset + delta;
+ return 0;
+}
+
+static int mchp_pfsoc_rtc_post_load(void *opaque, int version_id)
+{
+ MchpPfSoCRtcState *s = opaque;
+ int64_t delta = qemu_clock_get_ns(rtc_clock) /
+ NANOSECONDS_PER_SECOND -
+ qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) /
+ NANOSECONDS_PER_SECOND;
+
+ s->tick_offset = s->tick_offset_vmstate - delta;
+ s->read_latch_valid = false;
+ mchp_pfsoc_rtc_update_irq(s);
+ mchp_pfsoc_rtc_schedule_alarm(s);
+ return 0;
+}
+
+static const VMStateDescription vmstate_mchp_pfsoc_rtc = {
+ .name = TYPE_MCHP_PFSOC_RTC,
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .pre_save = mchp_pfsoc_rtc_pre_save,
+ .post_load = mchp_pfsoc_rtc_post_load,
+ .fields = (const VMStateField[]) {
+ VMSTATE_UINT32_ARRAY(regs, MchpPfSoCRtcState,
+ MCHP_PFSOC_RTC_REGS),
+ VMSTATE_INT64(tick_offset_vmstate, MchpPfSoCRtcState),
+ VMSTATE_UINT64(frozen_count, MchpPfSoCRtcState),
+ VMSTATE_BOOL(running, MchpPfSoCRtcState),
+ VMSTATE_BOOL(alarm_enabled, MchpPfSoCRtcState),
+ VMSTATE_BOOL(wakeup_pending, MchpPfSoCRtcState),
+ VMSTATE_BOOL(match_pending, MchpPfSoCRtcState),
+ VMSTATE_BOOL(updated, MchpPfSoCRtcState),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
+static void mchp_pfsoc_rtc_init(Object *obj)
+{
+ MchpPfSoCRtcState *s = MCHP_PFSOC_RTC(obj);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+
+ memory_region_init_io(&s->iomem, obj, &mchp_pfsoc_rtc_ops, s,
+ TYPE_MCHP_PFSOC_RTC, RTC_MMIO_SIZE);
+ sysbus_init_mmio(sbd, &s->iomem);
+ sysbus_init_irq(sbd, &s->irq_wakeup);
+ sysbus_init_irq(sbd, &s->irq_match);
+ s->alarm_timer = timer_new_ns(rtc_clock, mchp_pfsoc_rtc_alarm, s);
+}
+
+static void mchp_pfsoc_rtc_finalize(Object *obj)
+{
+ MchpPfSoCRtcState *s = MCHP_PFSOC_RTC(obj);
+
+ timer_free(s->alarm_timer);
+}
+
+static void mchp_pfsoc_rtc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ rc->phases.hold = mchp_pfsoc_rtc_reset_hold;
+ dc->vmsd = &vmstate_mchp_pfsoc_rtc;
+}
+
+static const TypeInfo mchp_pfsoc_rtc_info = {
+ .name = TYPE_MCHP_PFSOC_RTC,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(MchpPfSoCRtcState),
+ .instance_init = mchp_pfsoc_rtc_init,
+ .instance_finalize = mchp_pfsoc_rtc_finalize,
+ .class_init = mchp_pfsoc_rtc_class_init,
+};
+
+static void mchp_pfsoc_rtc_register_types(void)
+{
+ type_register_static(&mchp_pfsoc_rtc_info);
+}
+
+type_init(mchp_pfsoc_rtc_register_types)
diff --git a/hw/rtc/Kconfig b/hw/rtc/Kconfig
index 315b0e4ecc..0d53546adc 100644
--- a/hw/rtc/Kconfig
+++ b/hw/rtc/Kconfig
@@ -14,6 +14,9 @@ config M48T59
config PL031
bool
+config MCHP_PFSOC_RTC
+ bool
+
config MC146818RTC
depends on ISA_BUS
bool
diff --git a/hw/rtc/meson.build b/hw/rtc/meson.build
index 6c87864dc0..4a666bfc94 100644
--- a/hw/rtc/meson.build
+++ b/hw/rtc/meson.build
@@ -3,6 +3,7 @@ system_ss.add(when: 'CONFIG_DS1338', if_true: files('ds1338.c'))
system_ss.add(when: 'CONFIG_M41T80', if_true: files('m41t80.c'))
system_ss.add(when: 'CONFIG_M48T59', if_true: files('m48t59.c'))
system_ss.add(when: 'CONFIG_PL031', if_true: files('pl031.c'))
+system_ss.add(when: 'CONFIG_MCHP_PFSOC_RTC', if_true: files('mchp_pfsoc_rtc.c'))
system_ss.add(when: ['CONFIG_ISA_BUS', 'CONFIG_M48T59'], if_true: files('m48t59-isa.c'))
system_ss.add(when: 'CONFIG_XLNX_ZYNQMP', if_true: files('xlnx-zynqmp-rtc.c'))
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 15/27] hw/riscv: pfsoc: Add PolarFire SoC RTC to Icicle Kit
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (13 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 14/27] hw/rtc: Add PolarFire SoC RTC model Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 16/27] tests/qtest: Add PolarFire SoC RTC coverage Bin Meng
` (11 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Chao Liu, Conor Dooley, Alistair Francis, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Paolo Bonzini, Sebastian Huber, Weiwei Li, qemu-riscv
The Icicle Kit memory map advertises the PolarFire RTC, but the
machine leaves its MMIO range unmapped. Linux faults when it
programs the RTC prescaler during boot.
Select and instantiate the RTC, map it at the documented address,
and wire its wakeup and match outputs to PLIC interrupts 80 and 81.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v1)
include/hw/riscv/microchip_pfsoc.h | 4 ++++
hw/riscv/microchip_pfsoc.c | 11 +++++++++++
hw/riscv/Kconfig | 1 +
3 files changed, 16 insertions(+)
diff --git a/include/hw/riscv/microchip_pfsoc.h b/include/hw/riscv/microchip_pfsoc.h
index 18f5003850..5827d6be2f 100644
--- a/include/hw/riscv/microchip_pfsoc.h
+++ b/include/hw/riscv/microchip_pfsoc.h
@@ -31,6 +31,7 @@
#include "hw/misc/mchp_pfsoc_ioscb.h"
#include "hw/misc/mchp_pfsoc_sysreg.h"
#include "hw/net/cadence_gem.h"
+#include "hw/rtc/mchp_pfsoc_rtc.h"
#include "hw/sd/cadence_sdhci.h"
#include "hw/riscv/riscv_hart.h"
@@ -53,6 +54,7 @@ typedef struct MicrochipPFSoCState {
MchpPfSoCMMUartState *serial2;
MchpPfSoCMMUartState *serial3;
MchpPfSoCMMUartState *serial4;
+ MchpPfSoCRtcState rtc;
MchpPfSoCSysregState sysreg;
SiFivePDMAState dma;
CadenceGEMState gem0;
@@ -148,6 +150,8 @@ enum {
MICROCHIP_PFSOC_DMA_IRQ7 = 12,
MICROCHIP_PFSOC_GEM0_IRQ = 64,
MICROCHIP_PFSOC_GEM1_IRQ = 70,
+ MICROCHIP_PFSOC_RTC_WAKEUP_IRQ = 80,
+ MICROCHIP_PFSOC_RTC_MATCH_IRQ = 81,
MICROCHIP_PFSOC_EMMC_SD_IRQ = 88,
MICROCHIP_PFSOC_MMUART0_IRQ = 90,
MICROCHIP_PFSOC_MMUART1_IRQ = 91,
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index 3d1b3246db..36c894b677 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -183,6 +183,8 @@ static void microchip_pfsoc_soc_instance_init(Object *obj)
object_initialize_child(obj, "sysreg", &s->sysreg,
TYPE_MCHP_PFSOC_SYSREG);
+ object_initialize_child(obj, "rtc", &s->rtc, TYPE_MCHP_PFSOC_RTC);
+
object_initialize_child(obj, "ddr-sgmii-phy", &s->ddr_sgmii_phy,
TYPE_MCHP_PFSOC_DDR_SGMII_PHY);
object_initialize_child(obj, "ddr-cfg", &s->ddr_cfg,
@@ -293,6 +295,15 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp)
memmap[MICROCHIP_PFSOC_PLIC].size);
g_free(plic_hart_config);
+ /* RTC */
+ sysbus_realize(SYS_BUS_DEVICE(&s->rtc), errp);
+ sysbus_mmio_map(SYS_BUS_DEVICE(&s->rtc), 0,
+ memmap[MICROCHIP_PFSOC_RTC].base);
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->rtc), 0,
+ qdev_get_gpio_in(DEVICE(s->plic), MICROCHIP_PFSOC_RTC_WAKEUP_IRQ));
+ sysbus_connect_irq(SYS_BUS_DEVICE(&s->rtc), 1,
+ qdev_get_gpio_in(DEVICE(s->plic), MICROCHIP_PFSOC_RTC_MATCH_IRQ));
+
/* DMA */
sysbus_realize(SYS_BUS_DEVICE(&s->dma), errp);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->dma), 0,
diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index b2b816de6a..877c020052 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -19,6 +19,7 @@ config MICROCHIP_PFSOC
select MCHP_PFSOC_DMC
select MCHP_PFSOC_IOSCB
select MCHP_PFSOC_MMUART
+ select MCHP_PFSOC_RTC
select MCHP_PFSOC_SYSREG
select RISCV_ACLINT
select SIFIVE_L2CC
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 16/27] tests/qtest: Add PolarFire SoC RTC coverage
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (14 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 15/27] hw/riscv: pfsoc: Add PolarFire SoC RTC to Icicle Kit Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 17/27] hw/misc: pfsoc: Model PolarFire SoC serial number service Bin Meng
` (10 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Fabiano Rosas, Laurent Vivier, Paolo Bonzini,
Sebastian Huber, qemu-riscv
Exercise RTC register masks, binary counter, upload and stop controls,
alarm and wakeup interrupt behavior, reset state, and PLIC wiring on
the Icicle Kit.
Also verify RTC state and pending interrupt migration, including streams
from QEMU versions that predate the RTC device.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
---
(no changes since v2)
Changes in v2:
- Expect the documented hardware state after RTC reset
MAINTAINERS | 1 +
tests/qtest/mchp_pfsoc_rtc_test.c | 315 ++++++++++++++++++++++++++++++
tests/qtest/meson.build | 5 +-
3 files changed, 320 insertions(+), 1 deletion(-)
create mode 100644 tests/qtest/mchp_pfsoc_rtc_test.c
diff --git a/MAINTAINERS b/MAINTAINERS
index 2ea76ad1cd..12ffbd560e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1810,6 +1810,7 @@ F: include/hw/misc/mchp_pfsoc_dmc.h
F: include/hw/misc/mchp_pfsoc_ioscb.h
F: include/hw/misc/mchp_pfsoc_sysreg.h
F: include/hw/rtc/mchp_pfsoc_rtc.h
+F: tests/qtest/mchp_pfsoc_rtc_test.c
Shakti C class SoC
L: qemu-riscv@nongnu.org
diff --git a/tests/qtest/mchp_pfsoc_rtc_test.c b/tests/qtest/mchp_pfsoc_rtc_test.c
new file mode 100644
index 0000000000..ed96c15faa
--- /dev/null
+++ b/tests/qtest/mchp_pfsoc_rtc_test.c
@@ -0,0 +1,315 @@
+/*
+ * QTest testcase for the Microchip PolarFire SoC RTC
+ *
+ * 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/bitops.h"
+#include "qemu/bswap.h"
+#include "qemu/timer.h"
+#include "libqtest.h"
+#include "migration/migration-qmp.h"
+#include "migration/migration-util.h"
+
+#define RTC_BASE 0x20124000
+#define RTC_CONTROL 0x00
+#define RTC_MODE 0x04
+#define RTC_PRESCALER 0x08
+#define RTC_ALARM_LOWER 0x0c
+#define RTC_ALARM_UPPER 0x10
+#define RTC_COMPARE_LOWER 0x14
+#define RTC_COMPARE_UPPER 0x18
+#define RTC_DATETIME_LOWER 0x20
+#define RTC_DATETIME_UPPER 0x24
+#define RTC_RUNNING BIT(0)
+#define RTC_STOP BIT(1)
+#define RTC_ALARM_ON BIT(2)
+#define RTC_ALARM_OFF BIT(3)
+#define RTC_UPLOAD BIT(5)
+#define RTC_MATCH BIT(7)
+#define RTC_WAKEUP_CLEAR BIT(8)
+#define RTC_WAKEUP_SET BIT(9)
+#define RTC_UPDATED BIT(10)
+#define RTC_WAKEUP_ENABLE BIT(1)
+#define RTC_WAKEUP_RESET BIT(2)
+#define RTC_WAKEUP_CONTINUE BIT(3)
+#define RTC_PRESCALER_MASK 0x03ffffff
+#define RTC_UPPER_MASK 0x3fffffff
+#define PLIC_BASE 0x0c000000
+#define PLIC_PENDING 0x1000
+#define PLIC_RTC_PENDING (BIT(16) | BIT(17))
+#define ENVM_BASE 0x20220000
+#define HSS_HEADER_SIZE 0x100
+#define HSS_HEADER_JUMP 0x1000006f
+#define HSS_PAYLOAD_BASE (ENVM_BASE + HSS_HEADER_SIZE)
+#define HSS_PAYLOAD_SIZE 8
+
+static QTestState *microchip_icicle_kit_start_rtc(const char *firmware)
+{
+ return qtest_initf(
+ "-machine microchip-icicle-kit -smp 5 -m 2G -bios \"%s\" "
+ "-rtc clock=vm", firmware);
+}
+
+static QTestState *microchip_icicle_kit_start_ext(const char *qemu_var,
+ const char *firmware)
+{
+ g_autofree char *args = g_strdup_printf(
+ "-machine microchip-icicle-kit -smp 5 -m 2G -bios \"%s\"",
+ firmware);
+
+ return qtest_init_ext(qemu_var, args, NULL, true);
+}
+
+static char *create_firmware(void)
+{
+ uint8_t firmware[HSS_HEADER_SIZE + HSS_PAYLOAD_SIZE] = { 0 };
+ static const uint8_t payload[] = {
+ 0x13, 0x00, 0x00, 0x00,
+ 0x6f, 0x00, 0x00, 0x00,
+ };
+ char *path;
+ int fd;
+ ssize_t written;
+ unsigned int i;
+
+ stl_le_p(&firmware[0], HSS_HEADER_JUMP);
+ stl_le_p(&firmware[4], HSS_PAYLOAD_SIZE);
+ for (i = 0; i < 5; i++) {
+ stl_le_p(&firmware[8 + i * sizeof(uint32_t)], HSS_PAYLOAD_BASE);
+ }
+ memcpy(&firmware[HSS_HEADER_SIZE], payload, sizeof(payload));
+
+ fd = g_file_open_tmp("microchip-icicle-kit-XXXXXX", &path, NULL);
+ g_assert_cmpint(fd, >=, 0);
+
+ written = write(fd, firmware, sizeof(firmware));
+ g_assert_cmpint(written, ==, sizeof(firmware));
+ close(fd);
+
+ return path;
+}
+
+static uint64_t rtc_read_binary_count(QTestState *qts)
+{
+ uint64_t count = qtest_readl(qts, RTC_BASE + RTC_DATETIME_LOWER);
+
+ count |= (uint64_t)qtest_readl(qts,
+ RTC_BASE + RTC_DATETIME_UPPER) << 32;
+ return count;
+}
+
+static void test_rtc(void)
+{
+ g_autofree char *firmware = create_firmware();
+ QTestState *qts = microchip_icicle_kit_start_rtc(firmware);
+ uint32_t control;
+
+ unlink(firmware);
+ qtest_irq_intercept_out_named(qts, "/machine/soc/rtc", "sysbus-irq");
+
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_CONTROL), ==, 0);
+ g_assert_cmphex(rtc_read_binary_count(qts), ==, 0);
+ qtest_clock_step(qts, 2 * NANOSECONDS_PER_SECOND);
+ g_assert_cmphex(rtc_read_binary_count(qts), ==, 0);
+
+ qtest_writel(qts, RTC_BASE + RTC_MODE, UINT32_MAX);
+ qtest_writel(qts, RTC_BASE + RTC_PRESCALER, UINT32_MAX);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_MODE), ==, 0xf);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_PRESCALER), ==,
+ RTC_PRESCALER_MASK);
+ qtest_writel(qts, RTC_BASE + RTC_MODE, 0);
+
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_STOP);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_CONTROL) &
+ RTC_RUNNING, ==, 0);
+ qtest_clock_step(qts, 2 * NANOSECONDS_PER_SECOND);
+ g_assert_cmphex(rtc_read_binary_count(qts), ==, 0);
+
+ qtest_writel(qts, RTC_BASE + RTC_DATETIME_LOWER, 42);
+ qtest_writel(qts, RTC_BASE + RTC_DATETIME_UPPER, 0);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_UPLOAD);
+ g_assert_cmphex(rtc_read_binary_count(qts), ==, 42);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_CONTROL) &
+ (RTC_UPLOAD | RTC_UPDATED), ==, RTC_UPDATED);
+
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_RUNNING);
+ qtest_clock_step(qts, NANOSECONDS_PER_SECOND);
+ g_assert_cmphex(rtc_read_binary_count(qts), ==, 43);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_WAKEUP_SET);
+ g_assert_true(qtest_get_irq(qts, 0));
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_WAKEUP_CLEAR);
+ g_assert_false(qtest_get_irq(qts, 0));
+
+ qtest_writel(qts, RTC_BASE + RTC_ALARM_LOWER, 45);
+ qtest_writel(qts, RTC_BASE + RTC_ALARM_UPPER, 0);
+ qtest_writel(qts, RTC_BASE + RTC_COMPARE_LOWER, UINT32_MAX);
+ qtest_writel(qts, RTC_BASE + RTC_COMPARE_UPPER, RTC_UPPER_MASK);
+ qtest_writel(qts, RTC_BASE + RTC_MODE,
+ RTC_WAKEUP_ENABLE | RTC_WAKEUP_CONTINUE);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_ALARM_ON);
+ g_assert_false(qtest_get_irq(qts, 0));
+ g_assert_false(qtest_get_irq(qts, 1));
+ control = qtest_readl(qts, RTC_BASE + RTC_CONTROL);
+ g_assert_cmphex(control & RTC_ALARM_ON, ==, RTC_ALARM_ON);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, control | RTC_ALARM_OFF);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_CONTROL) &
+ RTC_ALARM_ON, ==, 0);
+ qtest_clock_step(qts, 2 * NANOSECONDS_PER_SECOND);
+ g_assert_false(qtest_get_irq(qts, 0));
+ g_assert_false(qtest_get_irq(qts, 1));
+
+ qtest_writel(qts, RTC_BASE + RTC_DATETIME_LOWER, 43);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_UPLOAD);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_ALARM_ON);
+ qtest_clock_step(qts, 2 * NANOSECONDS_PER_SECOND);
+ g_assert_true(qtest_get_irq(qts, 0));
+ g_assert_true(qtest_get_irq(qts, 1));
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_CONTROL) & RTC_MATCH,
+ ==, RTC_MATCH);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_ALARM_OFF);
+ g_assert_false(qtest_get_irq(qts, 0));
+ g_assert_false(qtest_get_irq(qts, 1));
+
+ qtest_writel(qts, RTC_BASE + RTC_DATETIME_LOWER, 0);
+ qtest_writel(qts, RTC_BASE + RTC_DATETIME_UPPER, 0);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_UPLOAD);
+ qtest_writel(qts, RTC_BASE + RTC_ALARM_LOWER, 2);
+ qtest_writel(qts, RTC_BASE + RTC_MODE,
+ RTC_WAKEUP_ENABLE | RTC_WAKEUP_RESET |
+ RTC_WAKEUP_CONTINUE);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_ALARM_ON);
+ qtest_clock_step(qts, 2 * NANOSECONDS_PER_SECOND);
+ g_assert_true(qtest_get_irq(qts, 0));
+ g_assert_cmphex(rtc_read_binary_count(qts), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_CONTROL) &
+ RTC_ALARM_ON, ==, RTC_ALARM_ON);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_WAKEUP_CLEAR);
+ qtest_clock_step(qts, 2 * NANOSECONDS_PER_SECOND);
+ g_assert_true(qtest_get_irq(qts, 0));
+ g_assert_cmphex(rtc_read_binary_count(qts), ==, 0);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_ALARM_OFF);
+ g_assert_false(qtest_get_irq(qts, 0));
+ g_assert_false(qtest_get_irq(qts, 1));
+
+ qtest_system_reset(qts);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_CONTROL), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_MODE), ==, 0);
+ g_assert_cmphex(qtest_readl(qts, RTC_BASE + RTC_PRESCALER), ==, 0);
+ g_assert_false(qtest_get_irq(qts, 0));
+ g_assert_false(qtest_get_irq(qts, 1));
+
+ qtest_quit(qts);
+}
+
+static void test_rtc_plic(void)
+{
+ g_autofree char *firmware = create_firmware();
+ QTestState *qts = microchip_icicle_kit_start_rtc(firmware);
+
+ unlink(firmware);
+
+ qtest_writel(qts, RTC_BASE + RTC_DATETIME_LOWER, 42);
+ qtest_writel(qts, RTC_BASE + RTC_DATETIME_UPPER, 0);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_UPLOAD);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_RUNNING);
+ qtest_writel(qts, RTC_BASE + RTC_ALARM_LOWER, 43);
+ qtest_writel(qts, RTC_BASE + RTC_ALARM_UPPER, 0);
+ qtest_writel(qts, RTC_BASE + RTC_COMPARE_LOWER, UINT32_MAX);
+ qtest_writel(qts, RTC_BASE + RTC_COMPARE_UPPER, RTC_UPPER_MASK);
+ qtest_writel(qts, RTC_BASE + RTC_MODE,
+ RTC_WAKEUP_ENABLE | RTC_WAKEUP_CONTINUE);
+ qtest_writel(qts, RTC_BASE + RTC_CONTROL, RTC_ALARM_ON);
+ qtest_clock_step(qts, NANOSECONDS_PER_SECOND);
+
+ g_assert_cmphex(qtest_readl(qts, PLIC_BASE + PLIC_PENDING + 8) &
+ PLIC_RTC_PENDING, ==, PLIC_RTC_PENDING);
+
+ qtest_quit(qts);
+}
+
+static void test_machine_migration(void)
+{
+ g_autofree char *firmware = create_firmware();
+ const char *old_qemu = g_getenv("QTEST_QEMU_BINARY_OLD");
+ QTestState *from;
+ QTestState *to = qtest_initf(
+ "-machine microchip-icicle-kit -smp 5 -m 2G "
+ "-bios \"%s\" -incoming defer "
+ "-rtc clock=vm", firmware);
+
+ qtest_irq_intercept_out_named(to, "/machine/soc/rtc", "sysbus-irq");
+
+ /* Allow an older QEMU source to exercise migration compatibility */
+ if (old_qemu) {
+ from = microchip_icicle_kit_start_ext("QTEST_QEMU_BINARY_OLD",
+ firmware);
+ } else {
+ from = microchip_icicle_kit_start_rtc(firmware);
+ qtest_writel(from, RTC_BASE + RTC_DATETIME_LOWER, 0x23456789);
+ qtest_writel(from, RTC_BASE + RTC_DATETIME_UPPER, 1);
+ qtest_writel(from, RTC_BASE + RTC_CONTROL, RTC_UPLOAD);
+ qtest_writel(from, RTC_BASE + RTC_CONTROL, RTC_RUNNING);
+ qtest_writel(from, RTC_BASE + RTC_ALARM_LOWER, 0x2345678b);
+ qtest_writel(from, RTC_BASE + RTC_ALARM_UPPER, 1);
+ qtest_writel(from, RTC_BASE + RTC_COMPARE_LOWER, UINT32_MAX);
+ qtest_writel(from, RTC_BASE + RTC_COMPARE_UPPER, RTC_UPPER_MASK);
+ qtest_writel(from, RTC_BASE + RTC_MODE,
+ RTC_WAKEUP_ENABLE | RTC_WAKEUP_CONTINUE);
+ qtest_writel(from, RTC_BASE + RTC_CONTROL, RTC_ALARM_ON);
+ qtest_writel(from, RTC_BASE + RTC_CONTROL, RTC_WAKEUP_SET);
+ qtest_writel(from, RTC_BASE + RTC_PRESCALER, 999999);
+ }
+
+ unlink(firmware);
+
+ migrate_incoming_qmp(to, "tcp:127.0.0.1:0", NULL, "{}");
+ migrate_qmp(from, to, NULL, NULL, "{}");
+ wait_for_migration_complete(from);
+
+ if (old_qemu) {
+ g_assert_cmphex(qtest_readl(to, RTC_BASE + RTC_CONTROL), ==, 0);
+ g_assert_cmphex(qtest_readl(to, RTC_BASE + RTC_PRESCALER), ==, 0);
+ g_assert_false(qtest_get_irq(to, 0));
+ g_assert_false(qtest_get_irq(to, 1));
+ } else {
+ g_assert_cmphex(rtc_read_binary_count(to), ==, 0x123456789);
+ g_assert_cmphex(qtest_readl(to, RTC_BASE + RTC_CONTROL) &
+ (RTC_RUNNING | RTC_ALARM_ON), ==,
+ RTC_RUNNING | RTC_ALARM_ON);
+ g_assert_cmphex(qtest_readl(to, RTC_BASE + RTC_PRESCALER), ==,
+ 999999);
+ g_assert_true(qtest_get_irq(to, 0));
+ g_assert_false(qtest_get_irq(to, 1));
+ qtest_writel(to, RTC_BASE + RTC_CONTROL, RTC_WAKEUP_CLEAR);
+ g_assert_false(qtest_get_irq(to, 0));
+ qtest_clock_step(to, 2 * NANOSECONDS_PER_SECOND);
+ g_assert_cmphex(rtc_read_binary_count(to), ==, 0x12345678b);
+ g_assert_true(qtest_get_irq(to, 0));
+ g_assert_true(qtest_get_irq(to, 1));
+ qtest_writel(to, RTC_BASE + RTC_CONTROL, RTC_ALARM_OFF);
+ g_assert_false(qtest_get_irq(to, 0));
+ g_assert_false(qtest_get_irq(to, 1));
+ }
+
+ qtest_quit(from);
+ qtest_quit(to);
+}
+
+int main(int argc, char **argv)
+{
+ g_test_init(&argc, &argv, NULL);
+
+ qtest_add_func("/mchp_pfsoc_rtc/registers", test_rtc);
+ qtest_add_func("/mchp_pfsoc_rtc/plic", test_rtc_plic);
+ qtest_add_func("/mchp_pfsoc_rtc/migration",
+ test_machine_migration);
+
+ return g_test_run();
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 29457193ed..fb3dcb200b 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -312,7 +312,8 @@ qtests_riscv32 = \
qtests_riscv64 = ['riscv-csr-test'] + \
(unpack_edk2_blobs ? ['bios-tables-test'] : []) + \
(config_all_devices.has_key('CONFIG_MICROCHIP_PFSOC') ?
- ['cadence-sdhci-test'] : []) + \
+ ['cadence-sdhci-test',
+ 'mchp_pfsoc_rtc_test'] : []) + \
(config_all_devices.has_key('CONFIG_SIFIVE_L2CC') ? ['sifive_l2cc_test'] : []) + \
(config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and
config_all_devices.has_key('CONFIG_RISCV_IOMMU') ?
@@ -432,6 +433,8 @@ qtests = {
'migration-test': test_migration_files + migration_tls_files + migration_colo_files,
'sifive_l2cc_test': files('migration/migration-qmp.c',
'migration/migration-util.c'),
+ 'mchp_pfsoc_rtc_test': files('migration/migration-qmp.c',
+ 'migration/migration-util.c'),
'pxe-test': files('boot-sector.c'),
'pnv-xive2-test': files('pnv-xive2-common.c', 'pnv-xive2-flush-sync.c',
'pnv-xive2-nvpg_bar.c'),
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 17/27] hw/misc: pfsoc: Model PolarFire SoC serial number service
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (15 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 16/27] tests/qtest: Add PolarFire SoC RTC coverage Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 18/27] hw/misc: pfsoc: Honor service notification requests Bin Meng
` (9 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU; +Cc: Conor Dooley, Conor Dooley, Sebastian Huber, qemu-riscv
U-Boot requests the PolarFire SoC device serial number during board
late initialization. The IOSCB previously rejected every request, so
board setup could not complete.
Model the System Services control registers and byte-addressable
mailbox. Obtain the 128-bit serial number from an optional device
property, using a deterministic value when it is not configured.
Return an explicit failure for unsupported services and reset the
new runtime state with the rest of the device.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v3)
Changes in v3:
- Decode the seven-bit opcode and nine-bit mailbox word offset
- Bound serial-number responses to the documented 2 KiB mailbox
- Mirror the current control flags in the status register
- Use default access sizing for the control and mailbox windows
include/hw/misc/mchp_pfsoc_ioscb.h | 6 ++
hw/misc/mchp_pfsoc_ioscb.c | 156 +++++++++++++++++++++++++----
2 files changed, 144 insertions(+), 18 deletions(-)
diff --git a/include/hw/misc/mchp_pfsoc_ioscb.h b/include/hw/misc/mchp_pfsoc_ioscb.h
index 9687ea25b1..aa108d2d88 100644
--- a/include/hw/misc/mchp_pfsoc_ioscb.h
+++ b/include/hw/misc/mchp_pfsoc_ioscb.h
@@ -25,6 +25,8 @@
#include "hw/core/sysbus.h"
+#define MCHP_PFSOC_IOSCB_MAILBOX_SIZE 0x800
+
typedef struct MchpPfSoCIoscbState {
SysBusDevice parent;
MemoryRegion container;
@@ -47,6 +49,10 @@ typedef struct MchpPfSoCIoscbState {
MemoryRegion cfm_sgmii;
MemoryRegion bc_sgmii;
MemoryRegion io_calib_sgmii;
+ uint32_t services_cr;
+ uint32_t services_sr;
+ uint8_t mailbox_data[MCHP_PFSOC_IOSCB_MAILBOX_SIZE];
+ char *serial_number;
qemu_irq irq;
} MchpPfSoCIoscbState;
diff --git a/hw/misc/mchp_pfsoc_ioscb.c b/hw/misc/mchp_pfsoc_ioscb.c
index 09b702e520..850125c8b4 100644
--- a/hw/misc/mchp_pfsoc_ioscb.c
+++ b/hw/misc/mchp_pfsoc_ioscb.c
@@ -25,6 +25,7 @@
#include "qemu/log.h"
#include "qapi/error.h"
#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
#include "hw/core/sysbus.h"
#include "hw/misc/mchp_pfsoc_ioscb.h"
@@ -37,6 +38,9 @@
#define IOSCB_CCC_REG_SIZE 0x2000000
#define IOSCB_CTRL_REG_SIZE 0x800
#define IOSCB_QSPIXIP_REG_SIZE 0x200
+#define IOSCB_SERIAL_NUMBER_SIZE 16U
+#define IOSCB_DEFAULT_SERIAL_NUMBER "0123456789abcdef"
+#define IOSCB_PROP_SERIAL_NUMBER "serial-number"
/*
@@ -186,33 +190,88 @@ static const MemoryRegionOps mchp_pfsoc_io_calib_ddr_ops = {
.endianness = DEVICE_LITTLE_ENDIAN,
};
-#define SERVICES_CR 0x50
-#define SERVICES_SR 0x54
-#define SERVICES_STATUS_SHIFT 16
+#define SERVICES_CR 0x50
+#define SERVICES_CR_REQUEST BIT(0)
+#define SERVICES_CR_NOTIFY BIT(3)
+#define SERVICES_CR_COMMAND_SHIFT 16
+#define SERVICES_CR_COMMAND_WIDTH 16
+#define SERVICES_CR_COMMAND_MASK \
+ MAKE_64BIT_MASK(SERVICES_CR_COMMAND_SHIFT, SERVICES_CR_COMMAND_WIDTH)
+#define SERVICES_CR_MASK \
+ (SERVICES_CR_REQUEST | SERVICES_CR_NOTIFY | SERVICES_CR_COMMAND_MASK)
+#define SERVICES_SR 0x54
+#define SERVICES_SR_CONTROL_MASK 0xf
+#define SERVICES_SR_STATUS_SHIFT 16
+#define SERVICES_COMMAND_SERIAL_NUMBER 0
+#define SERVICES_COMMAND_OPCODE_MASK 0x7f
+#define SERVICES_COMMAND_MAILBOX_SHIFT 7
+#define SERVICES_COMMAND_MAILBOX_MASK 0x1ff
+#define SERVICES_STATUS_SUCCESS 0
+#define SERVICES_STATUS_FAILED 1
+
+static void services_cr_write(MchpPfSoCIoscbState *s, uint32_t value)
+{
+ uint32_t command;
+ uint32_t opcode;
+ uint32_t response_offset;
+ uint32_t status = SERVICES_STATUS_FAILED;
+
+ if (device_is_in_reset(DEVICE(s)) ||
+ !(value & SERVICES_CR_REQUEST)) {
+ return;
+ }
+
+ /*
+ * System services complete synchronously in this model, so clear the
+ * request bit before exposing the response to the guest.
+ */
+ s->services_cr &= ~SERVICES_CR_REQUEST;
+
+ command = (value & SERVICES_CR_COMMAND_MASK) >>
+ SERVICES_CR_COMMAND_SHIFT;
+ opcode = command & SERVICES_COMMAND_OPCODE_MASK;
+ response_offset = ((command >> SERVICES_COMMAND_MAILBOX_SHIFT) &
+ SERVICES_COMMAND_MAILBOX_MASK) * sizeof(uint32_t);
+ if (opcode == SERVICES_COMMAND_SERIAL_NUMBER &&
+ response_offset <= sizeof(s->mailbox_data) - IOSCB_SERIAL_NUMBER_SIZE) {
+ /*
+ * The descriptor contains a seven-bit opcode and a nine-bit mailbox
+ * word offset. Return the 128-bit serial number at that address.
+ * See section 3 of the PolarFire System Services User Guide.
+ */
+ memset(&s->mailbox_data[response_offset], 0,
+ IOSCB_SERIAL_NUMBER_SIZE);
+ memcpy(&s->mailbox_data[response_offset],
+ s->serial_number, strlen(s->serial_number));
+ status = SERVICES_STATUS_SUCCESS;
+ }
+
+ s->services_sr = status << SERVICES_SR_STATUS_SHIFT;
+ qemu_irq_raise(s->irq);
+}
static uint64_t mchp_pfsoc_ctrl_read(void *opaque, hwaddr offset,
unsigned size)
{
- uint32_t val = 0;
+ MchpPfSoCIoscbState *s = opaque;
+ uint32_t value;
- switch (offset) {
+ switch (offset & ~3ULL) {
+ case SERVICES_CR:
+ value = s->services_cr;
+ break;
case SERVICES_SR:
- /*
- * Although some services have no error codes, most do. All services
- * that do implement errors, begin their error codes at 1. Treat all
- * service requests as failures & return 1.
- * See the "PolarFire® FPGA and PolarFire SoC FPGA System Services"
- * user guide for more information on service error codes.
- */
- val = 1u << SERVICES_STATUS_SHIFT;
+ value = s->services_sr |
+ (s->services_cr & SERVICES_SR_CONTROL_MASK);
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
"(size %d, offset 0x%" HWADDR_PRIx ")\n",
__func__, size, offset);
+ return 0;
}
- return val;
+ return extract32(value, (offset & 3) * 8, size * 8);
}
static void mchp_pfsoc_ctrl_write(void *opaque, hwaddr offset,
@@ -220,15 +279,22 @@ static void mchp_pfsoc_ctrl_write(void *opaque, hwaddr offset,
{
MchpPfSoCIoscbState *s = opaque;
- switch (offset) {
+ switch (offset & ~3ULL) {
case SERVICES_CR:
- qemu_irq_raise(s->irq);
+ s->services_cr = deposit32(s->services_cr, (offset & 3) * 8,
+ size * 8, value) & SERVICES_CR_MASK;
+ if (offset == SERVICES_CR) {
+ services_cr_write(s, s->services_cr);
+ }
+ break;
+ case SERVICES_SR:
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
"(size %d, value 0x%" PRIx64
", offset 0x%" HWADDR_PRIx ")\n",
__func__, size, value, offset);
+ break;
}
}
@@ -238,11 +304,62 @@ static const MemoryRegionOps mchp_pfsoc_ctrl_ops = {
.endianness = DEVICE_LITTLE_ENDIAN,
};
+/*
+ * The System Controller uses the mailbox as a byte-addressable shared buffer
+ * for service command payloads and responses.
+ */
+static uint64_t mchp_pfsoc_mailbox_read(void *opaque, hwaddr offset,
+ unsigned size)
+{
+ MchpPfSoCIoscbState *s = opaque;
+
+ return ldn_le_p(&s->mailbox_data[offset], size);
+}
+
+static void mchp_pfsoc_mailbox_write(void *opaque, hwaddr offset,
+ uint64_t value, unsigned size)
+{
+ MchpPfSoCIoscbState *s = opaque;
+
+ stn_le_p(&s->mailbox_data[offset], size, value);
+}
+
+static const MemoryRegionOps mchp_pfsoc_mailbox_ops = {
+ .read = mchp_pfsoc_mailbox_read,
+ .write = mchp_pfsoc_mailbox_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+};
+
+static void mchp_pfsoc_ioscb_reset(DeviceState *dev)
+{
+ MchpPfSoCIoscbState *s = MCHP_PFSOC_IOSCB(dev);
+
+ s->services_cr = 0;
+ s->services_sr = 0;
+ memset(s->mailbox_data, 0, sizeof(s->mailbox_data));
+ qemu_irq_lower(s->irq);
+}
+
+static const Property mchp_pfsoc_ioscb_properties[] = {
+ DEFINE_PROP_STRING(IOSCB_PROP_SERIAL_NUMBER,
+ MchpPfSoCIoscbState, serial_number),
+};
+
static void mchp_pfsoc_ioscb_realize(DeviceState *dev, Error **errp)
{
MchpPfSoCIoscbState *s = MCHP_PFSOC_IOSCB(dev);
SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+ /* Use a deterministic identity when no serial number is configured */
+ if (!s->serial_number) {
+ s->serial_number = g_strdup(IOSCB_DEFAULT_SERIAL_NUMBER);
+ }
+ if (strlen(s->serial_number) > IOSCB_SERIAL_NUMBER_SIZE) {
+ error_setg(errp, "The serial number can't be longer than %u bytes",
+ IOSCB_SERIAL_NUMBER_SIZE);
+ return;
+ }
+
memory_region_init(&s->container, OBJECT(s),
"mchp.pfsoc.ioscb", IOSCB_WHOLE_REG_SIZE);
sysbus_init_mmio(sbd, &s->container);
@@ -265,8 +382,9 @@ static void mchp_pfsoc_ioscb_realize(DeviceState *dev, Error **errp)
"mchp.pfsoc.ioscb.qspixip", IOSCB_QSPIXIP_REG_SIZE);
memory_region_add_subregion(&s->container, IOSCB_QSPIXIP_BASE, &s->qspixip);
- memory_region_init_io(&s->mailbox, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
- "mchp.pfsoc.ioscb.mailbox", IOSCB_SUBMOD_REG_SIZE);
+ memory_region_init_io(&s->mailbox, OBJECT(s), &mchp_pfsoc_mailbox_ops, s,
+ "mchp.pfsoc.ioscb.mailbox",
+ MCHP_PFSOC_IOSCB_MAILBOX_SIZE);
memory_region_add_subregion(&s->container, IOSCB_MAILBOX_BASE, &s->mailbox);
memory_region_init_io(&s->cfg, OBJECT(s), &mchp_pfsoc_dummy_ops, s,
@@ -343,6 +461,8 @@ static void mchp_pfsoc_ioscb_class_init(ObjectClass *klass, const void *data)
dc->desc = "Microchip PolarFire SoC IOSCB modules";
dc->realize = mchp_pfsoc_ioscb_realize;
+ device_class_set_legacy_reset(dc, mchp_pfsoc_ioscb_reset);
+ device_class_set_props(dc, mchp_pfsoc_ioscb_properties);
}
static const TypeInfo mchp_pfsoc_ioscb_info = {
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 18/27] hw/misc: pfsoc: Honor service notification requests
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (16 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 17/27] hw/misc: pfsoc: Model PolarFire SoC serial number service Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:22 ` [PATCH v4 19/27] hw/misc: pfsoc: Let IOSCB own service interrupt state Bin Meng
` (8 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU; +Cc: Conor Dooley, Conor Dooley, Sebastian Huber, qemu-riscv
Polling firmware requests system services with REQUEST set and NOTIFY
clear. Raising PLIC source 96 for those requests leaves an interrupt
pending that Linux can receive before submitting its first request.
The mailbox interrupt handler then has no response buffer to fill.
Raise the completion interrupt only for requests with NOTIFY set.
The serial-number service already checks REQUEST before processing a
command.
Fixes: 592f0a9429b9 ("hw/{misc, riscv}: pfsoc: add system controller as unimplemented")
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v3)
Changes in v3:
- Split the NOTIFY fix from interrupt acknowledgement wiring
hw/misc/mchp_pfsoc_ioscb.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/hw/misc/mchp_pfsoc_ioscb.c b/hw/misc/mchp_pfsoc_ioscb.c
index 850125c8b4..bde1917c10 100644
--- a/hw/misc/mchp_pfsoc_ioscb.c
+++ b/hw/misc/mchp_pfsoc_ioscb.c
@@ -247,7 +247,9 @@ static void services_cr_write(MchpPfSoCIoscbState *s, uint32_t value)
}
s->services_sr = status << SERVICES_SR_STATUS_SHIFT;
- qemu_irq_raise(s->irq);
+ if (value & SERVICES_CR_NOTIFY) {
+ qemu_irq_raise(s->irq);
+ }
}
static uint64_t mchp_pfsoc_ctrl_read(void *opaque, hwaddr offset,
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 19/27] hw/misc: pfsoc: Let IOSCB own service interrupt state
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (17 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 18/27] hw/misc: pfsoc: Honor service notification requests Bin Meng
@ 2026-10-14 1:22 ` Bin Meng
2026-10-14 1:23 ` [PATCH v4 20/27] hw/riscv: pfsoc: Correct PolarFire SoC DDR aliases Bin Meng
` (7 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:22 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Alistair Francis, Chao Liu, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Sebastian Huber, Weiwei Li, qemu-riscv
System service completion raises PLIC source 96 from IOSCB, while
software accesses MESSAGE_INT through SYSREG. The MESSAGE_INT pending
bit is not modelled explicitly, so reads return zero even when a
service interrupt is active. Model the bit so service completion and
register accesses use the same state.
Keep the pending state and PLIC output in IOSCB. Link SYSREG to IOSCB
so MESSAGE_INT reads the current pending bit and writes update the
same state.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v3)
Changes in v3:
- Separate interrupt state ownership from the NOTIFY fix
- Link SYSREG to IOSCB for MESSAGE_INT reads and writes
- Support both clearing and setting the MESSAGE_INT pending bit
include/hw/misc/mchp_pfsoc_ioscb.h | 4 ++++
include/hw/misc/mchp_pfsoc_sysreg.h | 3 ++-
hw/misc/mchp_pfsoc_ioscb.c | 20 ++++++++++++++--
hw/misc/mchp_pfsoc_sysreg.c | 37 ++++++++++++++++++++++-------
hw/riscv/microchip_pfsoc.c | 5 ++--
5 files changed, 55 insertions(+), 14 deletions(-)
diff --git a/include/hw/misc/mchp_pfsoc_ioscb.h b/include/hw/misc/mchp_pfsoc_ioscb.h
index aa108d2d88..e8386f9794 100644
--- a/include/hw/misc/mchp_pfsoc_ioscb.h
+++ b/include/hw/misc/mchp_pfsoc_ioscb.h
@@ -53,6 +53,7 @@ typedef struct MchpPfSoCIoscbState {
uint32_t services_sr;
uint8_t mailbox_data[MCHP_PFSOC_IOSCB_MAILBOX_SIZE];
char *serial_number;
+ bool irq_pending;
qemu_irq irq;
} MchpPfSoCIoscbState;
@@ -61,4 +62,7 @@ typedef struct MchpPfSoCIoscbState {
#define MCHP_PFSOC_IOSCB(obj) \
OBJECT_CHECK(MchpPfSoCIoscbState, (obj), TYPE_MCHP_PFSOC_IOSCB)
+bool mchp_pfsoc_ioscb_get_irq_pending(MchpPfSoCIoscbState *s);
+void mchp_pfsoc_ioscb_set_irq_pending(MchpPfSoCIoscbState *s, bool pending);
+
#endif /* MCHP_PFSOC_IOSCB_H */
diff --git a/include/hw/misc/mchp_pfsoc_sysreg.h b/include/hw/misc/mchp_pfsoc_sysreg.h
index c2b3688a94..d6c9027f2d 100644
--- a/include/hw/misc/mchp_pfsoc_sysreg.h
+++ b/include/hw/misc/mchp_pfsoc_sysreg.h
@@ -24,13 +24,14 @@
#define MCHP_PFSOC_SYSREG_H
#include "hw/core/sysbus.h"
+#include "hw/misc/mchp_pfsoc_ioscb.h"
#define MCHP_PFSOC_SYSREG_REG_SIZE 0x2000
typedef struct MchpPfSoCSysregState {
SysBusDevice parent;
MemoryRegion sysreg;
- qemu_irq irq;
+ MchpPfSoCIoscbState *ioscb;
} MchpPfSoCSysregState;
#define TYPE_MCHP_PFSOC_SYSREG "mchp.pfsoc.sysreg"
diff --git a/hw/misc/mchp_pfsoc_ioscb.c b/hw/misc/mchp_pfsoc_ioscb.c
index bde1917c10..1f77da6852 100644
--- a/hw/misc/mchp_pfsoc_ioscb.c
+++ b/hw/misc/mchp_pfsoc_ioscb.c
@@ -209,6 +209,22 @@ static const MemoryRegionOps mchp_pfsoc_io_calib_ddr_ops = {
#define SERVICES_STATUS_SUCCESS 0
#define SERVICES_STATUS_FAILED 1
+static void mchp_pfsoc_ioscb_update_irq(MchpPfSoCIoscbState *s)
+{
+ qemu_set_irq(s->irq, s->irq_pending);
+}
+
+bool mchp_pfsoc_ioscb_get_irq_pending(MchpPfSoCIoscbState *s)
+{
+ return s->irq_pending;
+}
+
+void mchp_pfsoc_ioscb_set_irq_pending(MchpPfSoCIoscbState *s, bool pending)
+{
+ s->irq_pending = pending;
+ mchp_pfsoc_ioscb_update_irq(s);
+}
+
static void services_cr_write(MchpPfSoCIoscbState *s, uint32_t value)
{
uint32_t command;
@@ -248,7 +264,7 @@ static void services_cr_write(MchpPfSoCIoscbState *s, uint32_t value)
s->services_sr = status << SERVICES_SR_STATUS_SHIFT;
if (value & SERVICES_CR_NOTIFY) {
- qemu_irq_raise(s->irq);
+ mchp_pfsoc_ioscb_set_irq_pending(s, true);
}
}
@@ -339,7 +355,7 @@ static void mchp_pfsoc_ioscb_reset(DeviceState *dev)
s->services_cr = 0;
s->services_sr = 0;
memset(s->mailbox_data, 0, sizeof(s->mailbox_data));
- qemu_irq_lower(s->irq);
+ mchp_pfsoc_ioscb_set_irq_pending(s, false);
}
static const Property mchp_pfsoc_ioscb_properties[] = {
diff --git a/hw/misc/mchp_pfsoc_sysreg.c b/hw/misc/mchp_pfsoc_sysreg.c
index 1d9154280a..4c52338c8a 100644
--- a/hw/misc/mchp_pfsoc_sysreg.c
+++ b/hw/misc/mchp_pfsoc_sysreg.c
@@ -24,20 +24,22 @@
#include "qemu/bitops.h"
#include "qemu/log.h"
#include "qapi/error.h"
-#include "hw/core/irq.h"
+#include "hw/core/qdev-properties.h"
#include "hw/core/sysbus.h"
#include "hw/misc/mchp_pfsoc_sysreg.h"
#include "system/runstate.h"
-#define CLOCK_CONFIG_CR 0x8
-#define RTC_CLOCK_CR 0xc
-#define MSS_RESET_CR 0x18
-#define ENVM_CR 0xb8
-#define MESSAGE_INT 0x118c
+#define CLOCK_CONFIG_CR 0x8
+#define RTC_CLOCK_CR 0xc
+#define MSS_RESET_CR 0x18
+#define ENVM_CR 0xb8
+#define MESSAGE_INT 0x118c
+#define MESSAGE_INT_PENDING BIT(0)
static uint64_t mchp_pfsoc_sysreg_read(void *opaque, hwaddr offset,
unsigned size)
{
+ MchpPfSoCSysregState *s = opaque;
uint32_t val = 0;
switch (offset) {
@@ -56,6 +58,10 @@ static uint64_t mchp_pfsoc_sysreg_read(void *opaque, hwaddr offset,
/* Indicate the eNVM is running at the configured divider rate */
val = BIT(6);
break;
+ case MESSAGE_INT:
+ val = mchp_pfsoc_ioscb_get_irq_pending(s->ioscb) ?
+ MESSAGE_INT_PENDING : 0;
+ break;
default:
qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read "
"(size %d, offset 0x%" HWADDR_PRIx ")\n",
@@ -77,7 +83,12 @@ static void mchp_pfsoc_sysreg_write(void *opaque, hwaddr offset,
}
break;
case MESSAGE_INT:
- qemu_irq_lower(s->irq);
+ /*
+ * MESSAGE_INT bit 0 is read/write: writing zero clears the interrupt
+ * and writing one sets it. IOSCB owns the state and PLIC output.
+ */
+ mchp_pfsoc_ioscb_set_irq_pending(s->ioscb,
+ value & MESSAGE_INT_PENDING);
break;
default:
qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write "
@@ -97,20 +108,30 @@ static void mchp_pfsoc_sysreg_realize(DeviceState *dev, Error **errp)
{
MchpPfSoCSysregState *s = MCHP_PFSOC_SYSREG(dev);
+ if (!s->ioscb) {
+ error_setg(errp, "The 'ioscb' link must be set");
+ return;
+ }
+
memory_region_init_io(&s->sysreg, OBJECT(dev),
&mchp_pfsoc_sysreg_ops, s,
"mchp.pfsoc.sysreg",
MCHP_PFSOC_SYSREG_REG_SIZE);
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->sysreg);
- sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
}
+static const Property mchp_pfsoc_sysreg_properties[] = {
+ DEFINE_PROP_LINK("ioscb", MchpPfSoCSysregState, ioscb,
+ TYPE_MCHP_PFSOC_IOSCB, MchpPfSoCIoscbState *),
+};
+
static void mchp_pfsoc_sysreg_class_init(ObjectClass *klass, const void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->desc = "Microchip PolarFire SoC SYSREG module";
dc->realize = mchp_pfsoc_sysreg_realize;
+ device_class_set_props(dc, mchp_pfsoc_sysreg_properties);
}
static const TypeInfo mchp_pfsoc_sysreg_info = {
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index 36c894b677..38f4e7d799 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -315,12 +315,11 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp)
}
/* SYSREG */
+ object_property_set_link(OBJECT(&s->sysreg), "ioscb", OBJECT(&s->ioscb),
+ &error_abort);
sysbus_realize(SYS_BUS_DEVICE(&s->sysreg), errp);
sysbus_mmio_map(SYS_BUS_DEVICE(&s->sysreg), 0,
memmap[MICROCHIP_PFSOC_SYSREG].base);
- sysbus_connect_irq(SYS_BUS_DEVICE(&s->sysreg), 0,
- qdev_get_gpio_in(DEVICE(s->plic),
- MICROCHIP_PFSOC_MAILBOX_IRQ));
/* AXISW */
create_unimplemented_device("microchip.pfsoc.axisw",
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 20/27] hw/riscv: pfsoc: Correct PolarFire SoC DDR aliases
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (18 preceding siblings ...)
2026-10-14 1:22 ` [PATCH v4 19/27] hw/misc: pfsoc: Let IOSCB own service interrupt state Bin Meng
@ 2026-10-14 1:23 ` Bin Meng
2026-10-14 1:23 ` [PATCH v4 21/27] hw/riscv: pfsoc: Fix Icicle Kit RAM size at 2 GiB Bin Meng
` (6 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:23 UTC (permalink / raw)
To: QEMU
Cc: Chao Liu, Conor Dooley, Alistair Francis, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Sebastian Huber, Weiwei Li, qemu-riscv
The Icicle Kit reference design maps the 32-bit cached and non-cached
windows and both 64-bit windows to the same physical DDR from offset
zero. The MSS Configurator User Guide describes configurable physical
DDR offsets, so other FPGA designs can instead arrange these regions
sequentially when their device trees describe that layout.
QEMU models the reference design used by the tested HSS and Linux
images. Treating the high windows as only the portion above 1 GiB
leaves their valid memory nodes unbacked with the board's 2 GiB of DDR.
Map both high windows over the full machine RAM and qualify the source
comment to describe this reference-design configuration.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v3)
Changes in v3:
- Update commit message to mention the DDR aliases is modeled following
the Icicle Kit reference design
hw/riscv/microchip_pfsoc.c | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index 38f4e7d799..eea2895a0a 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -554,15 +554,29 @@ static void microchip_icicle_kit_machine_init(MachineState *machine)
TYPE_MICROCHIP_PFSOC);
qdev_realize(DEVICE(&s->soc), NULL, &error_fatal);
- /* Split RAM into low and high regions using aliases to machine->ram */
+ /*
+ * Model the Icicle Kit reference design, whose four CPU-visible windows
+ * alias the same physical DDR from offset zero. With 2 GiB of DDR, they
+ * map as follows:
+ *
+ * CPU address Attribute Visible size DDR range
+ * 0x0080000000 32-bit cached 1 GiB [0, 1 GiB)
+ * 0x00c0000000 32-bit non-cached 1 GiB [0, 1 GiB)
+ * 0x1000000000 64-bit cached 2 GiB [0, 2 GiB)
+ * 0x1400000000 64-bit non-cached 2 GiB [0, 2 GiB)
+ *
+ * "Low" and "high" describe the CPU address windows, not the lower and
+ * upper portions of physical DDR. Other FPGA designs can configure
+ * different physical offsets through the MSS segmentation registers.
+ */
mem_low_size = memmap[MICROCHIP_PFSOC_DRAM_LO].size;
- mem_high_size = machine->ram_size - mem_low_size;
+ mem_high_size = machine->ram_size;
memory_region_init_alias(mem_low, NULL,
"microchip.icicle.kit.ram_low", machine->ram,
0, mem_low_size);
memory_region_init_alias(mem_high, NULL,
"microchip.icicle.kit.ram_high", machine->ram,
- mem_low_size, mem_high_size);
+ 0, mem_high_size);
/* Register RAM */
memory_region_add_subregion(system_memory,
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 21/27] hw/riscv: pfsoc: Fix Icicle Kit RAM size at 2 GiB
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (19 preceding siblings ...)
2026-10-14 1:23 ` [PATCH v4 20/27] hw/riscv: pfsoc: Correct PolarFire SoC DDR aliases Bin Meng
@ 2026-10-14 1:23 ` Bin Meng
2026-10-14 1:23 ` [PATCH v4 22/27] hw/riscv: pfsoc: Default Icicle Kit to five harts Bin Meng
` (5 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:23 UTC (permalink / raw)
To: QEMU
Cc: Chao Liu, Conor Dooley, Alistair Francis, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Pierrick Bouvier, Sebastian Huber, Weiwei Li, qemu-riscv
The Icicle Kit has 2 GiB of RAM, and its firmware device tree
describes that fixed capacity. The model currently defaults to
1537 MiB and accepts any larger size, allowing configurations
that do not match the board.
Set the default to 2 GiB and reject every other size. Update the
documentation and L2CC qtest command line accordingly.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
Reviewed-by: Conor Dooley <conor.dooley@microchip.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v1)
docs/system/riscv/microchip-icicle-kit.rst | 4 +---
hw/riscv/microchip_pfsoc.c | 15 ++++-----------
2 files changed, 5 insertions(+), 14 deletions(-)
diff --git a/docs/system/riscv/microchip-icicle-kit.rst b/docs/system/riscv/microchip-icicle-kit.rst
index 7fdb96601a..b0ad24f174 100644
--- a/docs/system/riscv/microchip-icicle-kit.rst
+++ b/docs/system/riscv/microchip-icicle-kit.rst
@@ -26,9 +26,7 @@ The ``microchip-icicle-kit`` machine supports the following devices:
* 2 GEM Ethernet controllers
* 1 SDHC storage controller
-The memory is set to 1537 MiB by default. A sanity check on RAM size is
-performed in the machine init routine to prompt user to increase the RAM size
-to > 1537 MiB when less than 1537 MiB RAM is detected.
+The machine has a fixed 2 GiB of RAM. Other memory sizes are rejected.
Boot options
------------
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index eea2895a0a..855c719f76 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -541,10 +541,10 @@ static void microchip_icicle_kit_machine_init(MachineState *machine)
DriveInfo *dinfo = drive_get(IF_SD, 0, 0);
RISCVBootInfo boot_info;
- /* Sanity check on RAM size */
- if (machine->ram_size < mc->default_ram_size) {
+ /* The board has a fixed RAM size */
+ if (machine->ram_size != mc->default_ram_size) {
char *sz = size_to_str(mc->default_ram_size);
- error_report("Invalid RAM size, should be bigger than %s", sz);
+ error_report("Invalid RAM size, should be %s", sz);
g_free(sz);
exit(EXIT_FAILURE);
}
@@ -756,14 +756,7 @@ static void microchip_icicle_kit_machine_class_init(ObjectClass *oc,
mc->default_ram_id = "microchip.icicle.kit.ram";
mc->auto_create_sdcard = true;
- /*
- * Map 513 MiB high memory, the minimum required high memory size, because
- * HSS will do memory test against the high memory address range regardless
- * of physical memory installed.
- *
- * See memory_tests() in mss_ddr.c in the HSS source code.
- */
- mc->default_ram_size = 1537 * MiB;
+ mc->default_ram_size = 2 * GiB;
object_class_property_add(oc, "clint-timebase-frequency", "uint32_t",
microchip_icicle_kit_get_clint_timebase_freq,
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 22/27] hw/riscv: pfsoc: Default Icicle Kit to five harts
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (20 preceding siblings ...)
2026-10-14 1:23 ` [PATCH v4 21/27] hw/riscv: pfsoc: Fix Icicle Kit RAM size at 2 GiB Bin Meng
@ 2026-10-14 1:23 ` Bin Meng
2026-10-14 1:23 ` [PATCH v4 23/27] docs/system/riscv: Document Icicle Kit HSS boot Bin Meng
` (4 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:23 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Alistair Francis, Chao Liu, Conor Dooley,
Daniel Henrique Barboza, Liu Zhiwei, Palmer Dabbelt,
Pierrick Bouvier, Sebastian Huber, Weiwei Li, qemu-riscv
The Icicle Kit contains one E51 management hart and four U54 application
harts. Defaulting to two harts can accidentally boot firmware and a
device tree that expect the full five-hart topology.
Default to all five harts, while retaining the existing two-to-five
range for users intentionally configuring fewer application harts.
Document that firmware and device trees must match the selected count.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Tested-by: Conor Dooley <conor.dooley@microchip.com>
---
(no changes since v3)
Changes in v3:
- Change only the default hart count and retain the minimum of two
- Document reduced topologies and matching firmware requirements
docs/system/riscv/microchip-icicle-kit.rst | 3 +++
hw/riscv/microchip_pfsoc.c | 2 +-
2 files changed, 4 insertions(+), 1 deletion(-)
diff --git a/docs/system/riscv/microchip-icicle-kit.rst b/docs/system/riscv/microchip-icicle-kit.rst
index b0ad24f174..138289a913 100644
--- a/docs/system/riscv/microchip-icicle-kit.rst
+++ b/docs/system/riscv/microchip-icicle-kit.rst
@@ -27,6 +27,9 @@ The ``microchip-icicle-kit`` machine supports the following devices:
* 1 SDHC storage controller
The machine has a fixed 2 GiB of RAM. Other memory sizes are rejected.
+The machine defaults to all five harts. ``-smp`` may select two to five harts,
+with one E51 and one to four U54 cores. The firmware and device tree must match
+the selected topology; the HSS boot flow below uses all five harts.
Boot options
------------
diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c
index 855c719f76..9d8e5916e7 100644
--- a/hw/riscv/microchip_pfsoc.c
+++ b/hw/riscv/microchip_pfsoc.c
@@ -752,7 +752,7 @@ static void microchip_icicle_kit_machine_class_init(ObjectClass *oc,
mc->max_cpus = MICROCHIP_PFSOC_MANAGEMENT_CPU_COUNT +
MICROCHIP_PFSOC_COMPUTE_CPU_COUNT;
mc->min_cpus = MICROCHIP_PFSOC_MANAGEMENT_CPU_COUNT + 1;
- mc->default_cpus = mc->min_cpus;
+ mc->default_cpus = mc->max_cpus;
mc->default_ram_id = "microchip.icicle.kit.ram";
mc->auto_create_sdcard = true;
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 23/27] docs/system/riscv: Document Icicle Kit HSS boot
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (21 preceding siblings ...)
2026-10-14 1:23 ` [PATCH v4 22/27] hw/riscv: pfsoc: Default Icicle Kit to five harts Bin Meng
@ 2026-10-14 1:23 ` Bin Meng
2026-10-14 1:23 ` [PATCH v4 24/27] docs/system/riscv: pfsoc: Document CLINT topology for direct Linux boot Bin Meng
` (3 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:23 UTC (permalink / raw)
To: QEMU; +Cc: Conor Dooley, Pierrick Bouvier, Sebastian Huber, qemu-riscv
With all previous changes, the Icicle Kit model is now able to boot
HSS v2026.04.1 through a Buildroot 2026.05 SD image to U-Boot and Linux.
Document the tested source versions and exact commands to build HSS
and Buildroot. Include the sparse resize and GPT relocation steps
needed to make the generated SD image usable by QEMU.
Explain the pinned firmware baseline and include the DDR pattern-test
patch used by the versioned test image. Document the tool check needed
for image-only builds and distinguish the DDR training failure from
the two investigated multi-hart startup races. Describe their visible
symptoms, affected source versions, and all three DTB supply paths.
Also describe the RAM and serial setup, boot stages, and remaining
model limitations.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
---
Changes in v4:
- Use HSS v2026.04.1 and the v1.1.1 test-image recipe
- Select the SoftConsole toolchain explicitly
- Use the generated complete boot-mode-1 eNVM binary
Changes in v3:
- Include the DDR pattern-test patch used by the functional-test image
- Document the image-only FPGA programming tool check
- Describe startup-race symptoms and link the firmware investigations
docs/system/riscv/microchip-icicle-kit.rst | 129 ++++++++++++++++++++-
1 file changed, 123 insertions(+), 6 deletions(-)
diff --git a/docs/system/riscv/microchip-icicle-kit.rst b/docs/system/riscv/microchip-icicle-kit.rst
index 138289a913..2159e8e461 100644
--- a/docs/system/riscv/microchip-icicle-kit.rst
+++ b/docs/system/riscv/microchip-icicle-kit.rst
@@ -19,12 +19,14 @@ The ``microchip-icicle-kit`` machine supports the following devices:
* 4 U54 cores
* Core Level Interruptor (CLINT)
* Platform-Level Interrupt Controller (PLIC)
-* L2 Loosely Integrated Memory (L2-LIM)
+* L2 cache controller, including L2-LIM and L2-Zero windows
* DDR memory controller
+* System controller and system services mailbox
* 5 MMUARTs
* 1 DMA controller
* 2 GEM Ethernet controllers
* 1 SDHC storage controller
+* 1 Real-Time Clock
The machine has a fixed 2 GiB of RAM. Other memory sizes are rejected.
The machine defaults to all five harts. ``-smp`` may select two to five harts,
@@ -103,10 +105,125 @@ CAVEATS:
Running HSS
-----------
-The machine ``microchip-icicle-kit`` used to run the Hart Software Services
-(HSS_), however, the HSS development progressed and the QEMU machine
-implementation lacks behind. Currently, running the HSS no longer works.
-There is missing support in the clock and memory controller devices. In
-particular, reading from the SD card does not work.
+The ``microchip-icicle-kit`` machine can boot the Hart Software Services
+(HSS_), which then loads an HSS payload containing U-Boot from an SD card.
+The following flow was tested with HSS v2026.04.1 and Buildroot 2026.05.
+These versions reproduce the firmware and SD card images used by the
+functional test.
+
+Configure HSS for the ``mpfs-icicle-kit-es`` board using its default
+configuration. QEMU provides the software-visible registers and deterministic
+status consumed by the HSS v2026.04.1 DDR initialization and training flow;
+it does not model the electrical properties of DDR training. HSS requires
+the RISC-V bare-metal toolchain supplied by Microchip SoftConsole to be
+available in ``PATH``.
+
+The versioned test image applies the `DDR pattern-test fix`_ before building
+HSS. Without this fix, the DDR state machine can reuse uninitialized
+pattern-test parameters across calls, particularly with the SoftConsole
+GCC 8.3.0 toolchain. Apply the same patch when reproducing the test image:
+
+.. code-block:: bash
+
+ $ git clone https://github.com/polarfire-soc/hart-software-services.git
+ $ cd hart-software-services
+ $ git checkout v2026.04.1
+ $ curl -L -o ddr-pattern-test.patch \
+ https://raw.githubusercontent.com/processmission/qemu-machine-images/v1.1.1/machine/riscv64/microchip-icicle-kit/0001-mpfs_hal-preserve-DDR-pattern-test-parameters.patch
+ $ git apply ddr-pattern-test.patch
+ $ cp boards/mpfs-icicle-kit-es/def_config .config
+ $ echo CONFIG_CC_USE_SOFTCONSOLE=y >> .config
+ $ FPGENPROG=/bin/true make -j$(nproc) BOARD=mpfs-icicle-kit-es
+
+``FPGENPROG=/bin/true`` satisfies the boot-mode programmer's FPGA programming
+tool check when generating an image for QEMU; no physical device is programmed.
+
+The HSS build creates the complete eNVM image, including the 256-byte
+boot header with the image size and per-hart reset vectors. Copy it to
+``build/hss.bin`` for the QEMU command below:
+
+.. code-block:: bash
+
+ $ cp build/bootmode1/hss-envm-wrapper-bm1-p0.bin build/hss.bin
+
+Do not pass ``build/hss-envm-wrapper.bin`` directly to QEMU. That file starts
+at eNVM offset 0x100 and does not contain the boot header with the image size
+and per-hart reset vectors.
+
+Build the SD card image with the tested Buildroot version:
+
+.. code-block:: bash
+
+ $ git clone https://gitlab.com/buildroot.org/buildroot.git
+ $ cd buildroot
+ $ git checkout 2026.05
+ $ make microchip_mpfs_icicle_defconfig
+ $ make
+
+This produces ``output/images/sdcard.img`` with three GPT partitions:
+
+* An HSS ``payload.bin`` containing U-Boot.
+* A FAT partition containing ``boot.scr`` and the kernel FIT image.
+* An ext4 Linux root filesystem.
+
+The QEMU SD card model requires a power-of-two image size. Make a sparse
+4 GiB working copy and relocate its backup GPT to the new end of the image:
+
+.. code-block:: bash
+
+ $ cp --reflink=auto output/images/sdcard.img sdcard.img
+ $ truncate -s 4G sdcard.img
+ $ sgdisk -e sdcard.img
+ $ sgdisk -v sdcard.img
+
+The Icicle Kit firmware device tree in the FIT image describes 2 GiB of RAM,
+matching the machine's fixed RAM size. The command below keeps ``-m 2G``
+explicit. Attach the image as an SD card and route both board serial ports:
+
+.. code-block:: bash
+
+ $ qemu-system-riscv64 \
+ -M microchip-icicle-kit -smp 5 -m 2G \
+ -bios path/to/hss/build/hss.bin \
+ -drive if=sd,file=path/to/sdcard.img,format=raw \
+ -display none \
+ -serial file:hss.log \
+ -serial stdio \
+ -no-reboot
+
+HSS writes to MMUART0, which the command records in ``hss.log``. U-Boot and
+Linux use MMUART1, which remains connected to the terminal. A successful
+boot proceeds through HSS payload loading, U-Boot, the Linux kernel, and the
+login prompt from the root filesystem on the third partition.
+
+Known limitations
+-----------------
+
+* Two potential multi-hart startup races were reproduced with HSS v2024.06
+ under QEMU. The affected code paths are also present in the inspected
+ HSS v2026.04.1 sources; they have not been reproduced on physical hardware.
+ See `HSS startup races`_ for the investigation and affected source versions.
+
+ * In the eNVM wrapper, a late U54 can clear the decompression completion
+ flag after E51 has set it. A boot can stop before all harts enter HSS,
+ with the affected U54s waiting in ``wait_for_decompress_done``.
+ * After DDR training and payload loading, a U54 other than the configured
+ payload owner can win the OpenSBI coldboot lottery. With the minimal DTB
+ describing only ``cpu@1``, U-Boot on another hart can repeatedly print
+ ``Could not initialize timer (err -19)`` and ``resetting ...``.
+
+* Without the DDR pattern-test fix above, some GCC 8.3.0 builds remain at
+ 60% DDR training progress while QEMU keeps running. This is a separate
+ firmware parameter-lifetime issue; see `DDR pattern-test issue`_.
+* The SD card model requires the raw image size to be a power of two. Keep
+ the backup GPT header at the end when resizing the image.
+* The machine does not generate an Icicle Kit device tree. Firmware boot can
+ obtain a DTB built into HSS, included in an HSS payload, or supplied by
+ subsequent software, such as U-Boot or a kernel FIT image. The DTB must
+ describe the selected hart topology and the modeled reference-design DDR
+ windows. Direct kernel boot must use ``-dtb`` as described above.
.. _HSS: https://github.com/polarfire-soc/hart-software-services
+.. _DDR pattern-test fix: https://github.com/processmission/qemu-machine-images/blob/v1.1.1/machine/riscv64/microchip-icicle-kit/0001-mpfs_hal-preserve-DDR-pattern-test-parameters.patch
+.. _DDR pattern-test issue: https://github.com/polarfire-soc/platform/issues/36
+.. _HSS startup races: https://github.com/polarfire-soc/hart-software-services/issues/106
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 24/27] docs/system/riscv: pfsoc: Document CLINT topology for direct Linux boot
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (22 preceding siblings ...)
2026-10-14 1:23 ` [PATCH v4 23/27] docs/system/riscv: Document Icicle Kit HSS boot Bin Meng
@ 2026-10-14 1:23 ` Bin Meng
2026-10-14 1:23 ` [PATCH v4 25/27] tests/functional/riscv64: Add Icicle Kit firmware boot test Bin Meng
` (2 subsequent siblings)
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:23 UTC (permalink / raw)
To: QEMU; +Cc: Conor Dooley, Pierrick Bouvier, Sebastian Huber, qemu-riscv
The generic OpenSBI platform derives the CLINT first hart and register
slot count from interrupts-extended. Some S-mode-only MPFS device trees
mask the E51 M-mode interrupt entries with 0xffffffff, which shifts the
MSIP and MTIMECMP slots when the same DTB is used for direct boot.
Document the complete five-hart CLINT node. Require interrupts 3 and 7
for hart 0 as well as the U54 harts.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
---
(no changes since v3)
Changes in v3:
- Explain how reduced hart topologies change the CLINT DTB tuples
docs/system/riscv/microchip-icicle-kit.rst | 26 +++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
diff --git a/docs/system/riscv/microchip-icicle-kit.rst b/docs/system/riscv/microchip-icicle-kit.rst
index 2159e8e461..038ea7221f 100644
--- a/docs/system/riscv/microchip-icicle-kit.rst
+++ b/docs/system/riscv/microchip-icicle-kit.rst
@@ -66,7 +66,31 @@ should meet the following requirements:
option.
* It should contain a node for the CLINT device with a compatible string
- "riscv,clint0".
+ "riscv,clint0" or "sifive,clint0". Its ``interrupts-extended`` property
+ must describe the M-mode software interrupt (3) and timer interrupt (7) for
+ every hart, including the E51 hart 0.
+
+For example, the default five-hart Icicle Kit CLINT node can be described as:
+
+.. code-block:: none
+
+ clint: clint@2000000 {
+ compatible = "sifive,fu540-c000-clint", "sifive,clint0";
+ reg = <0x0 0x2000000 0x0 0xc000>;
+ interrupts-extended = <&cpu0_intc 3>, <&cpu0_intc 7>,
+ <&cpu1_intc 3>, <&cpu1_intc 7>,
+ <&cpu2_intc 3>, <&cpu2_intc 7>,
+ <&cpu3_intc 3>, <&cpu3_intc 7>,
+ <&cpu4_intc 3>, <&cpu4_intc 7>;
+ };
+
+When selecting fewer harts with ``-smp``, remove the absent hart nodes and their
+CLINT interrupt tuples from the DTB while retaining the E51 hart 0 entries.
+
+Do not replace the hart 0 interrupt numbers with ``0xffffffff`` when the same
+DTB is used to initialize the generic OpenSBI platform. OpenSBI derives the
+first hart and the number of CLINT register slots from these interrupt tuples;
+masking hart 0 would shift the per-hart MSIP and MTIMECMP register mappings.
When ``-bios`` is not specified or set to ``default``, the OpenSBI
``fw_dynamic`` BIOS image for the ``generic`` platform is used to boot an
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 25/27] tests/functional/riscv64: Add Icicle Kit firmware boot test
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (23 preceding siblings ...)
2026-10-14 1:23 ` [PATCH v4 24/27] docs/system/riscv: pfsoc: Document CLINT topology for direct Linux boot Bin Meng
@ 2026-10-14 1:23 ` Bin Meng
2026-10-14 1:23 ` [PATCH v4 26/27] MAINTAINERS: Add PolarFire SoC Icicle Kit maintainer Bin Meng
2026-10-14 1:23 ` [PATCH v4 27/27] MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer Bin Meng
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:23 UTC (permalink / raw)
To: QEMU
Cc: Chao Liu, Alistair Francis, Conor Dooley, Daniel Henrique Barboza,
Liu Zhiwei, Palmer Dabbelt, Sebastian Huber, Weiwei Li,
qemu-riscv
Add functional coverage using the v1.1.1 HSS and SD card images.
Boot HSS v2026.04.1 and U-Boot with five harts and 2 GiB of RAM. Connect
the test console to MMUART1 and let U-Boot start Linux.
The complete boot exercises the SDHCI controller in the DMA modes
used at each stage. HSS uses Version 4 64-bit SDMA to load U-Boot.
U-Boot uses ADMA2-64 to read the boot script and the large FIT
image. Linux then selects 64-bit ADMA for SD card and rootfs I/O.
Check the machine identity, kernel ADMA initialization, root
filesystem mount, and login prompt.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Reviewed-by: Chao Liu <chao.liu@processmission.com>
---
Changes in v4:
- Use the v1.1.1 test images with HSS v2026.04.1
- Add the execution bit to the python script
MAINTAINERS | 1 +
tests/functional/riscv64/meson.build | 2 +
.../riscv64/test_microchip_icicle_kit.py | 56 +++++++++++++++++++
3 files changed, 59 insertions(+)
create mode 100755 tests/functional/riscv64/test_microchip_icicle_kit.py
diff --git a/MAINTAINERS b/MAINTAINERS
index 12ffbd560e..84e6423d89 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1810,6 +1810,7 @@ F: include/hw/misc/mchp_pfsoc_dmc.h
F: include/hw/misc/mchp_pfsoc_ioscb.h
F: include/hw/misc/mchp_pfsoc_sysreg.h
F: include/hw/rtc/mchp_pfsoc_rtc.h
+F: tests/functional/riscv64/test_microchip_icicle_kit.py
F: tests/qtest/mchp_pfsoc_rtc_test.c
Shakti C class SoC
diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build
index d1a3e6c2bf..cbf61d9535 100644
--- a/tests/functional/riscv64/meson.build
+++ b/tests/functional/riscv64/meson.build
@@ -3,6 +3,7 @@
test_riscv64_timeouts = {
'boston' : 120,
'k230' : 120,
+ 'microchip_icicle_kit' : 180,
'tuxrun' : 120,
}
@@ -15,6 +16,7 @@ tests_riscv64_system_thorough = [
'endianness',
'boston',
'k230',
+ 'microchip_icicle_kit',
'sifive_u',
'tt_atlantis',
'tuxrun',
diff --git a/tests/functional/riscv64/test_microchip_icicle_kit.py b/tests/functional/riscv64/test_microchip_icicle_kit.py
new file mode 100755
index 0000000000..ee627a87a8
--- /dev/null
+++ b/tests/functional/riscv64/test_microchip_icicle_kit.py
@@ -0,0 +1,56 @@
+#!/usr/bin/env python3
+#
+# Functional test that boots HSS, U-Boot, and Linux on an Icicle Kit.
+#
+# 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, skipIfMissingCommands
+
+
+class MicrochipIcicleKitMachine(LinuxKernelTest):
+
+ ASSET_IMAGES = Asset(
+ ('https://github.com/processmission/qemu-machine-images/releases/'
+ 'download/v1.1.1/'
+ 'riscv64-microchip-icicle-kit-v1.1.1.tar.zst'),
+ '226e4322e5ec53513eccdf16e4e967cdef7d57a0ba19431470ca68b883457538')
+
+ @skipIfMissingCommands('zstd')
+ def test_microchip_icicle_kit_boot(self):
+ self.set_machine('microchip-icicle-kit')
+
+ archive_path = self.uncompress(
+ self.ASSET_IMAGES,
+ target='microchip_icicle_kit_images.tar',
+ format='zstd')
+ self.archive_extract(archive_path, format='tar')
+ firmware_path = self.scratch_file('images', 'hss.bin')
+ sdcard_path = self.scratch_file('images', 'sdcard.img')
+
+ self.vm.set_console(console_index=1)
+ self.vm.add_args(
+ '-smp', '5',
+ '-m', '2G',
+ '-display', 'none',
+ '-bios', firmware_path,
+ '-snapshot',
+ '-drive', f'file={sdcard_path},format=raw,if=sd,index=0',
+ '-no-reboot')
+ self.vm.launch()
+
+ self.wait_for_console_pattern('U-Boot ')
+ self.wait_for_console_pattern('Starting kernel ...')
+ self.wait_for_console_pattern(
+ 'Machine model: Microchip PolarFire-SoC Icicle Kit')
+ self.wait_for_console_pattern('using ADMA 64-bit')
+ self.wait_for_console_pattern('VFS: Mounted root')
+ self.wait_for_console_pattern('mpfs_icicle login:')
+
+
+if __name__ == '__main__':
+ LinuxKernelTest.main()
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 26/27] MAINTAINERS: Add PolarFire SoC Icicle Kit maintainer
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (24 preceding siblings ...)
2026-10-14 1:23 ` [PATCH v4 25/27] tests/functional/riscv64: Add Icicle Kit firmware boot test Bin Meng
@ 2026-10-14 1:23 ` Bin Meng
2026-10-14 1:23 ` [PATCH v4 27/27] MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer Bin Meng
26 siblings, 0 replies; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:23 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Markus Armbruster, Peter Maydell,
Philippe Mathieu-Daudé, Pierrick Bouvier, Thomas Huth
The preceding Icicle Kit updates restore HSS, U-Boot, and Linux
boot. Functional test coverage is also added.
Restore myself as a maintainer and mark the machine as 'Supported'
to reflect its active maintenance.
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Acked-by: Conor Dooley <conor@kernel.org>
Reviewed-by: Markus Armbruster <armbru@redhat.com>
---
(no changes since v1)
MAINTAINERS | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 84e6423d89..0edc7385fd 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1793,10 +1793,11 @@ F: target/riscv/xlrbr.decode
F: tests/tcg/riscv64/test-crc32.S
Microchip PolarFire SoC Icicle Kit
+M: Bin Meng <bin.meng@processmission.com>
M: Conor Dooley <conor@kernel.org>
M: Sebastian Huber <sebastian.huber@embedded-brains.de>
L: qemu-riscv@nongnu.org
-S: Odd Fixes
+S: Supported
F: docs/system/riscv/microchip-icicle-kit.rst
F: hw/riscv/microchip_pfsoc.c
F: hw/char/mchp_pfsoc_mmuart.c
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* [PATCH v4 27/27] MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
` (25 preceding siblings ...)
2026-10-14 1:23 ` [PATCH v4 26/27] MAINTAINERS: Add PolarFire SoC Icicle Kit maintainer Bin Meng
@ 2026-10-14 1:23 ` Bin Meng
2026-10-14 4:16 ` Philippe Mathieu-Daudé
26 siblings, 1 reply; 30+ messages in thread
From: Bin Meng @ 2026-10-14 1:23 UTC (permalink / raw)
To: QEMU
Cc: Conor Dooley, Markus Armbruster, Peter Maydell,
Philippe Mathieu-Daudé, Pierrick Bouvier, Thomas Huth
Conor asked not to be listed as a maintainer, since that could imply
that Microchip sponsors his work on the platform.
Keep him listed as a reviewer so he continues to receive relevant
patches without suggesting an active maintenance commitment.
Suggested-by: Conor Dooley <conor@kernel.org>
Signed-off-by: Bin Meng <bin.meng@processmission.com>
Acked-by: Conor Dooley <conor@kernel.org>
---
(no changes since v2)
Changes in v2:
- New
MAINTAINERS | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/MAINTAINERS b/MAINTAINERS
index 0edc7385fd..40232cb65e 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1794,8 +1794,8 @@ F: tests/tcg/riscv64/test-crc32.S
Microchip PolarFire SoC Icicle Kit
M: Bin Meng <bin.meng@processmission.com>
-M: Conor Dooley <conor@kernel.org>
M: Sebastian Huber <sebastian.huber@embedded-brains.de>
+R: Conor Dooley <conor@kernel.org>
L: qemu-riscv@nongnu.org
S: Supported
F: docs/system/riscv/microchip-icicle-kit.rst
--
2.53.0
^ permalink raw reply related [flat|nested] 30+ messages in thread
* Re: [PATCH v4 27/27] MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer
2026-10-14 1:23 ` [PATCH v4 27/27] MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer Bin Meng
@ 2026-10-14 4:16 ` Philippe Mathieu-Daudé
2026-10-14 8:06 ` Daniel P. Berrangé
0 siblings, 1 reply; 30+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-10-14 4:16 UTC (permalink / raw)
To: Bin Meng, QEMU
Cc: Conor Dooley, Markus Armbruster, Peter Maydell, Pierrick Bouvier,
Thomas Huth, Paolo Bonzini, Daniel P. Berrangé,
BALATON Zoltan
On 14/10/26 03:23, Bin Meng wrote:
> Conor asked not to be listed as a maintainer, since that could imply
> that Microchip sponsors his work on the platform.
Hmm we hit a limitation of MAINTAINERS format.
If so, the Sebastian is in the same situation.
(we had a similar case with PIIX3:hobbyist / PIIX4:cloud in the past)
>
> Keep him listed as a reviewer so he continues to receive relevant
> patches without suggesting an active maintenance commitment.
>
> Suggested-by: Conor Dooley <conor@kernel.org>
> Signed-off-by: Bin Meng <bin.meng@processmission.com>
> Acked-by: Conor Dooley <conor@kernel.org>
>
>
> ---
>
> (no changes since v2)
>
> Changes in v2:
> - New
>
> MAINTAINERS | 2 +-
> 1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 0edc7385fd..40232cb65e 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1794,8 +1794,8 @@ F: tests/tcg/riscv64/test-crc32.S
>
> Microchip PolarFire SoC Icicle Kit
> M: Bin Meng <bin.meng@processmission.com>
> -M: Conor Dooley <conor@kernel.org>
> M: Sebastian Huber <sebastian.huber@embedded-brains.de>
> +R: Conor Dooley <conor@kernel.org>
> L: qemu-riscv@nongnu.org
> S: Supported
> F: docs/system/riscv/microchip-icicle-kit.rst
Is tooling confused when we define the same section twice?
Microchip PolarFire SoC Icicle Kit
M: Bin Meng <bin.meng@processmission.com>
S: Supported
F: docs/system/riscv/microchip-icicle-kit.rst
F: hw/riscv/microchip_pfsoc.c
F: hw/char/mchp_pfsoc_mmuart.c
F: hw/misc/mchp_pfsoc_dmc.c
F: hw/misc/mchp_pfsoc_ioscb.c
F: hw/misc/mchp_pfsoc_sysreg.c
F: include/hw/riscv/microchip_pfsoc.h
F: include/hw/char/mchp_pfsoc_mmuart.h
F: include/hw/misc/mchp_pfsoc_dmc.h
F: include/hw/misc/mchp_pfsoc_ioscb.h
F: include/hw/misc/mchp_pfsoc_sysreg.h
Microchip PolarFire SoC Icicle Kit
M: Conor Dooley <conor@kernel.org>
M: Sebastian Huber <sebastian.huber@embedded-brains.de>
S: Odd Fixes
F: docs/system/riscv/microchip-icicle-kit.rst
F: hw/riscv/microchip_pfsoc.c
F: hw/char/mchp_pfsoc_mmuart.c
F: hw/misc/mchp_pfsoc_dmc.c
F: hw/misc/mchp_pfsoc_ioscb.c
F: hw/misc/mchp_pfsoc_sysreg.c
F: include/hw/riscv/microchip_pfsoc.h
F: include/hw/char/mchp_pfsoc_mmuart.h
F: include/hw/misc/mchp_pfsoc_dmc.h
F: include/hw/misc/mchp_pfsoc_ioscb.h
F: include/hw/misc/mchp_pfsoc_sysreg.h
^ permalink raw reply [flat|nested] 30+ messages in thread
* Re: [PATCH v4 27/27] MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer
2026-10-14 4:16 ` Philippe Mathieu-Daudé
@ 2026-10-14 8:06 ` Daniel P. Berrangé
0 siblings, 0 replies; 30+ messages in thread
From: Daniel P. Berrangé @ 2026-10-14 8:06 UTC (permalink / raw)
To: Philippe Mathieu-Daudé
Cc: Bin Meng, QEMU, Conor Dooley, Markus Armbruster, Peter Maydell,
Pierrick Bouvier, Thomas Huth, Paolo Bonzini, BALATON Zoltan
On Wed, Oct 14, 2026 at 06:16:27AM +0200, Philippe Mathieu-Daudé wrote:
> On 14/10/26 03:23, Bin Meng wrote:
> > Conor asked not to be listed as a maintainer, since that could imply
> > that Microchip sponsors his work on the platform.
>
> Hmm we hit a limitation of MAINTAINERS format.
I guess they're concerned about "Supported" vs "Maintained" as
the subsystem status
Supported: Someone is actually paid to look after this.
Maintained: Someone actually looks after it.
Technically that language means you should not assume that
all listed M: are paid - only that at least 1 M: is a paid
maintainer. IMHO since Conor uses a non-corporate email
address there's even less reason to assume they're doing it
in a paid maintainer role, as opposed to volunteer.
> If so, the Sebastian is in the same situation.
>
> (we had a similar case with PIIX3:hobbyist / PIIX4:cloud in the past)
>
> >
> > Keep him listed as a reviewer so he continues to receive relevant
> > patches without suggesting an active maintenance commitment.
> >
> > Suggested-by: Conor Dooley <conor@kernel.org>
> > Signed-off-by: Bin Meng <bin.meng@processmission.com>
> > Acked-by: Conor Dooley <conor@kernel.org>
> >
> >
> > ---
> >
> > (no changes since v2)
> >
> > Changes in v2:
> > - New
> >
> > MAINTAINERS | 2 +-
> > 1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/MAINTAINERS b/MAINTAINERS
> > index 0edc7385fd..40232cb65e 100644
> > --- a/MAINTAINERS
> > +++ b/MAINTAINERS
> > @@ -1794,8 +1794,8 @@ F: tests/tcg/riscv64/test-crc32.S
> > Microchip PolarFire SoC Icicle Kit
> > M: Bin Meng <bin.meng@processmission.com>
> > -M: Conor Dooley <conor@kernel.org>
> > M: Sebastian Huber <sebastian.huber@embedded-brains.de>
> > +R: Conor Dooley <conor@kernel.org>
> > L: qemu-riscv@nongnu.org
> > S: Supported
> > F: docs/system/riscv/microchip-icicle-kit.rst
>
> Is tooling confused when we define the same section twice?
>
> Microchip PolarFire SoC Icicle Kit
> M: Bin Meng <bin.meng@processmission.com>
> S: Supported
> F: docs/system/riscv/microchip-icicle-kit.rst
> F: hw/riscv/microchip_pfsoc.c
> F: hw/char/mchp_pfsoc_mmuart.c
> F: hw/misc/mchp_pfsoc_dmc.c
> F: hw/misc/mchp_pfsoc_ioscb.c
> F: hw/misc/mchp_pfsoc_sysreg.c
> F: include/hw/riscv/microchip_pfsoc.h
> F: include/hw/char/mchp_pfsoc_mmuart.h
> F: include/hw/misc/mchp_pfsoc_dmc.h
> F: include/hw/misc/mchp_pfsoc_ioscb.h
> F: include/hw/misc/mchp_pfsoc_sysreg.h
>
> Microchip PolarFire SoC Icicle Kit
> M: Conor Dooley <conor@kernel.org>
> M: Sebastian Huber <sebastian.huber@embedded-brains.de>
> S: Odd Fixes
> F: docs/system/riscv/microchip-icicle-kit.rst
> F: hw/riscv/microchip_pfsoc.c
> F: hw/char/mchp_pfsoc_mmuart.c
> F: hw/misc/mchp_pfsoc_dmc.c
> F: hw/misc/mchp_pfsoc_ioscb.c
> F: hw/misc/mchp_pfsoc_sysreg.c
> F: include/hw/riscv/microchip_pfsoc.h
> F: include/hw/char/mchp_pfsoc_mmuart.h
> F: include/hw/misc/mchp_pfsoc_dmc.h
> F: include/hw/misc/mchp_pfsoc_ioscb.h
> F: include/hw/misc/mchp_pfsoc_sysreg.h
>
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] 30+ messages in thread
end of thread, other threads:[~2026-10-14 8:06 UTC | newest]
Thread overview: 30+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 1:22 [PATCH v4 00/27] hw/riscv: Restore Microchip PolarFire SoC Icicle Kit firmware boot Bin Meng
2026-10-14 1:22 ` [PATCH v4 01/27] hw/riscv: pfsoc: Correct the L2LIM maximum mapped size Bin Meng
2026-10-14 1:22 ` [PATCH v4 02/27] hw/riscv: pfsoc: Map the L2 zero device window Bin Meng
2026-10-14 1:22 ` [PATCH v4 03/27] hw/misc: pfsoc: Support DDR training with newer HSS Bin Meng
2026-10-14 1:22 ` [PATCH v4 04/27] hw/misc: Add SiFive L2 cache controller model Bin Meng
2026-10-14 1:22 ` [PATCH v4 05/27] hw/riscv: pfsoc: Map the SiFive L2CC memory windows Bin Meng
2026-10-14 1:22 ` [PATCH v4 06/27] hw/riscv: sifive_u: Correct the L2LIM maximum mapped size Bin Meng
2026-10-14 1:22 ` [PATCH v4 07/27] hw/riscv: sifive_u: Use the shared SiFive L2 cache controller Bin Meng
2026-10-14 1:22 ` [PATCH v4 08/27] tests/qtest: Add SiFive L2 cache controller coverage Bin Meng
2026-10-14 1:22 ` [PATCH v4 09/27] hw/sd: sdhci: Run ADMA independently of MMIO Bin Meng
2026-10-14 1:22 ` [PATCH v4 10/27] tests/qtest: add Cadence SDHCI ADMA pacing/MMIO reproducer Bin Meng
2026-10-14 1:22 ` [PATCH v4 11/27] hw/sd: sd: Keep high-capacity memory blocks at 512 bytes Bin Meng
2026-10-14 1:22 ` [PATCH v4 12/27] hw/sd: cadence: Add 64-bit system bus property Bin Meng
2026-10-14 1:22 ` [PATCH v4 13/27] hw/riscv: pfsoc: Enable 64-bit SDHCI system bus support Bin Meng
2026-10-14 1:22 ` [PATCH v4 14/27] hw/rtc: Add PolarFire SoC RTC model Bin Meng
2026-10-14 1:22 ` [PATCH v4 15/27] hw/riscv: pfsoc: Add PolarFire SoC RTC to Icicle Kit Bin Meng
2026-10-14 1:22 ` [PATCH v4 16/27] tests/qtest: Add PolarFire SoC RTC coverage Bin Meng
2026-10-14 1:22 ` [PATCH v4 17/27] hw/misc: pfsoc: Model PolarFire SoC serial number service Bin Meng
2026-10-14 1:22 ` [PATCH v4 18/27] hw/misc: pfsoc: Honor service notification requests Bin Meng
2026-10-14 1:22 ` [PATCH v4 19/27] hw/misc: pfsoc: Let IOSCB own service interrupt state Bin Meng
2026-10-14 1:23 ` [PATCH v4 20/27] hw/riscv: pfsoc: Correct PolarFire SoC DDR aliases Bin Meng
2026-10-14 1:23 ` [PATCH v4 21/27] hw/riscv: pfsoc: Fix Icicle Kit RAM size at 2 GiB Bin Meng
2026-10-14 1:23 ` [PATCH v4 22/27] hw/riscv: pfsoc: Default Icicle Kit to five harts Bin Meng
2026-10-14 1:23 ` [PATCH v4 23/27] docs/system/riscv: Document Icicle Kit HSS boot Bin Meng
2026-10-14 1:23 ` [PATCH v4 24/27] docs/system/riscv: pfsoc: Document CLINT topology for direct Linux boot Bin Meng
2026-10-14 1:23 ` [PATCH v4 25/27] tests/functional/riscv64: Add Icicle Kit firmware boot test Bin Meng
2026-10-14 1:23 ` [PATCH v4 26/27] MAINTAINERS: Add PolarFire SoC Icicle Kit maintainer Bin Meng
2026-10-14 1:23 ` [PATCH v4 27/27] MAINTAINERS: Move Conor Dooley to PolarFire SoC reviewer Bin Meng
2026-10-14 4:16 ` Philippe Mathieu-Daudé
2026-10-14 8:06 ` Daniel P. Berrangé
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.