diff --git a/MAINTAINERS b/MAINTAINERS index ae66a6f0b7b41..26acf280697cf 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1883,21 +1883,29 @@ F: target/riscv/xlrbr.decode F: tests/tcg/riscv64/test-crc32.S Microchip PolarFire SoC Icicle Kit +M: Bin Meng M: Conor Dooley M: Sebastian Huber 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 F: hw/misc/mchp_pfsoc_dmc.c F: hw/misc/mchp_pfsoc_ioscb.c +F: hw/misc/mchp_pfsoc_l2cc.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_l2cc.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_l2cc_test.c +F: tests/qtest/mchp_pfsoc_rtc_test.c Shakti C class SoC L: qemu-riscv@nongnu.org diff --git a/docs/system/riscv/microchip-icicle-kit.rst b/docs/system/riscv/microchip-icicle-kit.rst index 9809e94b84b90..af8e984df7ef2 100644 --- a/docs/system/riscv/microchip-icicle-kit.rst +++ b/docs/system/riscv/microchip-icicle-kit.rst @@ -19,16 +19,18 @@ 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) +* Loosely Integrated Memory (L2-LIM) +* L2 cache controller (L2CC) * 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 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. +The machine always exposes all five harts. Other CPU counts are rejected. Boot options ------------ @@ -63,7 +65,28 @@ 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 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>; + }; + +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 @@ -102,10 +125,96 @@ 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 v2024.06 and Buildroot 2026.05. + +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 v2024.06 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``. Build the tested HSS version with: + +.. code-block:: bash + + $ git clone https://github.com/polarfire-soc/hart-software-services.git + $ cd hart-software-services + $ git checkout v2024.06 + $ make BOARD=mpfs-icicle-kit-es defconfig + $ make -j$(nproc) BOARD=mpfs-icicle-kit-es + +The HSS build creates both the raw wrapper and an eNVM programming image. QEMU +needs the complete eNVM image, including the 256-byte boot header added by the +Microchip boot mode programmer. Convert the generated Intel HEX file to a raw +binary image, for example: + +.. code-block:: bash + + $ riscv64-unknown-elf-objcopy -I ihex -O binary \ + build/hss-envm-wrapper.mpfs-icicle-kit-es.hex 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 +----------------- + +* The tested HSS v2024.06 flow contains 2 separate multi-hart startup races + issues which is still not fixed as of the latest v2026.04 release. A boot + may therefore stall at the very beginning or after successful DDR training + during the OpenSBI/U-Boot handoff. +* 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 must + provide one in its payload or FIT image; direct kernel boot must use + ``-dtb`` as described above. .. _HSS: https://github.com/polarfire-soc/hart-software-services diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig index 2d7a60621e314..599b414e4474a 100644 --- a/hw/misc/Kconfig +++ b/hw/misc/Kconfig @@ -292,6 +292,9 @@ config MCHP_PFSOC_DMC config MCHP_PFSOC_IOSCB bool +config MCHP_PFSOC_L2CC + bool + config MCHP_PFSOC_SYSREG bool diff --git a/hw/misc/mchp_pfsoc_dmc.c b/hw/misc/mchp_pfsoc_dmc.c index 1fe4535464f96..443de5ddd1f1e 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 * - * 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 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 . + * 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(DeviceState *dev) +{ + MchpPfSoCDdrSgmiiPhyState *s = MCHP_PFSOC_DDR_SGMII_PHY(dev); + + 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); @@ -117,6 +340,7 @@ static void mchp_pfsoc_ddr_sgmii_phy_class_init(ObjectClass *klass, dc->desc = "Microchip PolarFire SoC DDR SGMII PHY module"; dc->realize = mchp_pfsoc_ddr_sgmii_phy_realize; + device_class_set_legacy_reset(dc, mchp_pfsoc_ddr_sgmii_phy_reset); } static const TypeInfo mchp_pfsoc_ddr_sgmii_phy_info = { diff --git a/hw/misc/mchp_pfsoc_ioscb.c b/hw/misc/mchp_pfsoc_ioscb.c index 09b702e520d34..ccc2201b7a44c 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,95 @@ 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 8 +#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_STATUS_SHIFT 16 +#define SERVICES_COMMAND_SERIAL_NUMBER 0 +#define SERVICES_STATUS_SUCCESS 0 +#define SERVICES_STATUS_FAILED 1 +#define SERVICES_MAILBOX_RESPONSE_OFFSET 0 + +static void mchp_pfsoc_ioscb_update_irq(MchpPfSoCIoscbState *s) +{ + qemu_set_irq(s->irq, s->irq_pending); +} + +static void mchp_pfsoc_ioscb_clear_irq(void *opaque, int n, int level) +{ + MchpPfSoCIoscbState *s = opaque; + + if (level) { + s->irq_pending = false; + mchp_pfsoc_ioscb_update_irq(s); + } +} + +static void services_cr_write(MchpPfSoCIoscbState *s, uint32_t value) +{ + uint32_t command; + 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; + if (command == SERVICES_COMMAND_SERIAL_NUMBER) { + /* + * The serial-number service returns a 128-bit response starting at + * the beginning of the mailbox. + */ + memset(&s->mailbox_data[SERVICES_MAILBOX_RESPONSE_OFFSET], 0, + IOSCB_SERIAL_NUMBER_SIZE); + memcpy(&s->mailbox_data[SERVICES_MAILBOX_RESPONSE_OFFSET], + s->serial_number, strlen(s->serial_number)); + status = SERVICES_STATUS_SUCCESS; + } + + s->services_sr = status << SERVICES_SR_STATUS_SHIFT; + /* + * HSS and U-Boot submit polling requests with REQUEST set and NOTIFY + * clear, then poll REQUEST/BUSY for completion. Linux sets both bits + * and expects completion through PLIC source 96. + */ + if (value & SERVICES_CR_NOTIFY) { + s->irq_pending = true; + mchp_pfsoc_ioscb_update_irq(s); + } +} static uint64_t mchp_pfsoc_ctrl_read(void *opaque, hwaddr offset, unsigned size) { - uint32_t val = 0; + MchpPfSoCIoscbState *s = opaque; switch (offset) { + case SERVICES_CR: + return s->services_cr; 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; - break; + return s->services_sr; default: qemu_log_mask(LOG_UNIMP, "%s: unimplemented device read " "(size %d, offset 0x%" HWADDR_PRIx ")\n", __func__, size, offset); + return 0; } - - return val; } static void mchp_pfsoc_ctrl_write(void *opaque, hwaddr offset, @@ -222,13 +288,17 @@ static void mchp_pfsoc_ctrl_write(void *opaque, hwaddr offset, switch (offset) { case SERVICES_CR: - qemu_irq_raise(s->irq); + s->services_cr = value & SERVICES_CR_MASK; + services_cr_write(s, value); + 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; } } @@ -236,13 +306,80 @@ static const MemoryRegionOps mchp_pfsoc_ctrl_ops = { .read = mchp_pfsoc_ctrl_read, .write = mchp_pfsoc_ctrl_write, .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = sizeof(uint32_t), + .max_access_size = sizeof(uint32_t), + }, }; +/* + * 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, + .valid = { + .min_access_size = sizeof(uint8_t), + .max_access_size = sizeof(uint32_t), + }, +}; + +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)); + s->irq_pending = false; + mchp_pfsoc_ioscb_update_irq(s); +} + +static const Property mchp_pfsoc_ioscb_properties[] = { + DEFINE_PROP_STRING(IOSCB_PROP_SERIAL_NUMBER, + MchpPfSoCIoscbState, serial_number), +}; + +static void mchp_pfsoc_ioscb_init(Object *obj) +{ + /* Accept service interrupt acknowledgements from SYSREG MESSAGE_INT */ + qdev_init_gpio_in_named(DEVICE(obj), mchp_pfsoc_ioscb_clear_irq, + MCHP_PFSOC_IOSCB_IRQ_CLEAR, 1); +} + 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,7 +402,7 @@ 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, + memory_region_init_io(&s->mailbox, OBJECT(s), &mchp_pfsoc_mailbox_ops, s, "mchp.pfsoc.ioscb.mailbox", IOSCB_SUBMOD_REG_SIZE); memory_region_add_subregion(&s->container, IOSCB_MAILBOX_BASE, &s->mailbox); @@ -343,12 +480,15 @@ 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 = { .name = TYPE_MCHP_PFSOC_IOSCB, .parent = TYPE_SYS_BUS_DEVICE, .instance_size = sizeof(MchpPfSoCIoscbState), + .instance_init = mchp_pfsoc_ioscb_init, .class_init = mchp_pfsoc_ioscb_class_init, }; diff --git a/hw/misc/mchp_pfsoc_l2cc.c b/hw/misc/mchp_pfsoc_l2cc.c new file mode 100644 index 0000000000000..7f3c2672c315d --- /dev/null +++ b/hw/misc/mchp_pfsoc_l2cc.c @@ -0,0 +1,230 @@ +/* + * Microchip PolarFire SoC L2 cache controller + * + * Copyright (c) 2026 Process Mission + * + * Author: + * Bin Meng + * + * 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/mchp_pfsoc_l2cc.h" + +REG64(L2_CONFIG, 0x000) + FIELD(L2_CONFIG, BANKS, 0, 8) + FIELD(L2_CONFIG, WAYS, 8, 8) + FIELD(L2_CONFIG, SETS, 16, 8) + FIELD(L2_CONFIG, BYTES, 24, 8) +REG64(L2_WAY_ENABLE, 0x008) + FIELD(L2_WAY_ENABLE, VALUE, 0, 8) +REG64(L2_WAY_MASK_DMA, 0x800) +REG64(L2_WAY_MASK_AXI4_PORT_0, 0x808) +REG64(L2_WAY_MASK_AXI4_PORT_1, 0x810) +REG64(L2_WAY_MASK_AXI4_PORT_2, 0x818) +REG64(L2_WAY_MASK_AXI4_PORT_3, 0x820) +REG64(L2_WAY_MASK_HART0_DCACHE, 0x828) +REG64(L2_WAY_MASK_HART0_ICACHE, 0x830) +REG64(L2_WAY_MASK_HART1_DCACHE, 0x838) +REG64(L2_WAY_MASK_HART1_ICACHE, 0x840) +REG64(L2_WAY_MASK_HART2_DCACHE, 0x848) +REG64(L2_WAY_MASK_HART2_ICACHE, 0x850) +REG64(L2_WAY_MASK_HART3_DCACHE, 0x858) +REG64(L2_WAY_MASK_HART3_ICACHE, 0x860) +REG64(L2_WAY_MASK_HART4_DCACHE, 0x868) +REG64(L2_WAY_MASK_HART4_ICACHE, 0x870) + +#define L2_CONFIG_RESET 0x06091004 +#define L2_LIM_WAY_COUNT 15 +#define L2_WAY_SIZE (128 * KiB) + +static void mchp_pfsoc_l2cc_update_l2lim(MchpPfSoCL2ccState *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->l2lim, l2lim_size); + memory_region_set_enabled(s->l2lim, l2lim_size != 0); + memory_region_transaction_commit(); +} + +static uint64_t mchp_pfsoc_l2cc_way_enable_pre_write(RegisterInfo *reg, + uint64_t value) +{ + uint64_t current = *(uint64_t *)reg->data; + + return MAX(current, value); +} + +static void mchp_pfsoc_l2cc_way_enable_post_write(RegisterInfo *reg, + uint64_t value) +{ + MchpPfSoCL2ccState *s = MCHP_PFSOC_L2CC(reg->opaque); + + mchp_pfsoc_l2cc_update_l2lim(s, value); +} + +#define WAY_MASK_REGISTER(_name) \ + { \ + .name = "WAY_MASK_" #_name, \ + .addr = A_L2_WAY_MASK_ ## _name, \ + .reset = UINT64_MAX, \ + } + +static const RegisterAccessInfo mchp_pfsoc_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 = mchp_pfsoc_l2cc_way_enable_pre_write, + .post_write = mchp_pfsoc_l2cc_way_enable_post_write, + }, + WAY_MASK_REGISTER(DMA), + WAY_MASK_REGISTER(AXI4_PORT_0), + WAY_MASK_REGISTER(AXI4_PORT_1), + WAY_MASK_REGISTER(AXI4_PORT_2), + WAY_MASK_REGISTER(AXI4_PORT_3), + WAY_MASK_REGISTER(HART0_DCACHE), + WAY_MASK_REGISTER(HART0_ICACHE), + WAY_MASK_REGISTER(HART1_DCACHE), + WAY_MASK_REGISTER(HART1_ICACHE), + WAY_MASK_REGISTER(HART2_DCACHE), + WAY_MASK_REGISTER(HART2_ICACHE), + WAY_MASK_REGISTER(HART3_DCACHE), + WAY_MASK_REGISTER(HART3_ICACHE), + WAY_MASK_REGISTER(HART4_DCACHE), + WAY_MASK_REGISTER(HART4_ICACHE), +}; + +static bool mchp_pfsoc_l2cc_register_implemented(hwaddr offset) +{ + size_t i; + + for (i = 0; i < ARRAY_SIZE(mchp_pfsoc_l2cc_regs_info); i++) { + if (mchp_pfsoc_l2cc_regs_info[i].addr == offset) { + return true; + } + } + + return false; +} + +static uint64_t mchp_pfsoc_l2cc_read(void *opaque, hwaddr offset, + unsigned size) +{ + if (mchp_pfsoc_l2cc_register_implemented(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 mchp_pfsoc_l2cc_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + if (mchp_pfsoc_l2cc_register_implemented(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 mchp_pfsoc_l2cc_ops = { + .read = mchp_pfsoc_l2cc_read, + .write = mchp_pfsoc_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 mchp_pfsoc_l2cc_reset(DeviceState *dev) +{ + MchpPfSoCL2ccState *s = MCHP_PFSOC_L2CC(dev); + size_t i; + + memset(s->regs, 0, sizeof(s->regs)); + for (i = 0; i < ARRAY_SIZE(mchp_pfsoc_l2cc_regs_info); i++) { + hwaddr addr = mchp_pfsoc_l2cc_regs_info[i].addr; + + register_reset(&s->regs_info[addr / sizeof(uint64_t)]); + } +} + +static void mchp_pfsoc_l2cc_init(Object *obj) +{ + MchpPfSoCL2ccState *s = MCHP_PFSOC_L2CC(obj); + RegisterInfoArray *reg_array; + + reg_array = register_init_block64( + DEVICE(obj), mchp_pfsoc_l2cc_regs_info, + ARRAY_SIZE(mchp_pfsoc_l2cc_regs_info), s->regs_info, s->regs, + &mchp_pfsoc_l2cc_ops, false, MCHP_PFSOC_L2CC_REG_SIZE); + sysbus_init_mmio(SYS_BUS_DEVICE(obj), ®_array->mem); +} + +static void mchp_pfsoc_l2cc_realize(DeviceState *dev, Error **errp) +{ + MchpPfSoCL2ccState *s = MCHP_PFSOC_L2CC(dev); + + if (!s->l2lim) { + error_setg(errp, TYPE_MCHP_PFSOC_L2CC ": 'l2-lim' link not set"); + return; + } + + mchp_pfsoc_l2cc_update_l2lim(s, s->regs[R_L2_WAY_ENABLE]); +} + +static const Property mchp_pfsoc_l2cc_properties[] = { + DEFINE_PROP_LINK("l2-lim", MchpPfSoCL2ccState, l2lim, + TYPE_MEMORY_REGION, MemoryRegion *), +}; + +static void mchp_pfsoc_l2cc_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_legacy_reset(dc, mchp_pfsoc_l2cc_reset); + device_class_set_props(dc, mchp_pfsoc_l2cc_properties); + dc->realize = mchp_pfsoc_l2cc_realize; +} + +static const TypeInfo mchp_pfsoc_l2cc_info = { + .name = TYPE_MCHP_PFSOC_L2CC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(MchpPfSoCL2ccState), + .instance_init = mchp_pfsoc_l2cc_init, + .class_init = mchp_pfsoc_l2cc_class_init, +}; + +static void mchp_pfsoc_l2cc_register_types(void) +{ + type_register_static(&mchp_pfsoc_l2cc_info); +} + +type_init(mchp_pfsoc_l2cc_register_types) diff --git a/hw/misc/mchp_pfsoc_sysreg.c b/hw/misc/mchp_pfsoc_sysreg.c index 1d9154280a2a8..899b485da6a95 100644 --- a/hw/misc/mchp_pfsoc_sysreg.c +++ b/hw/misc/mchp_pfsoc_sysreg.c @@ -77,7 +77,14 @@ static void mchp_pfsoc_sysreg_write(void *opaque, hwaddr offset, } break; case MESSAGE_INT: - qemu_irq_lower(s->irq); + /* + * A MESSAGE_INT write is an acknowledgement event, not a level that + * remains asserted. Model it as an active-high pulse. The rising edge + * invokes IOSCB's irq-clear input with level 1, which clears + * irq_pending and lowers PLIC source 96. The falling edge invokes the + * input with level 0 and is ignored. + */ + qemu_irq_pulse(s->irq); break; default: qemu_log_mask(LOG_UNIMP, "%s: unimplemented device write " diff --git a/hw/misc/meson.build b/hw/misc/meson.build index a1688636eb37c..66bd67f3c6534 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -36,6 +36,7 @@ system_ss.add(when: 'CONFIG_VIRT_CTRL', if_true: files('virt_ctrl.c')) # RISC-V devices 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_L2CC', if_true: files('mchp_pfsoc_l2cc.c')) system_ss.add(when: 'CONFIG_MCHP_PFSOC_SYSREG', if_true: files('mchp_pfsoc_sysreg.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')) diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index 8769b27b3d4bb..6e6a4fe4f4fa7 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -18,7 +18,9 @@ config MICROCHIP_PFSOC select DEVICE_TREE select MCHP_PFSOC_DMC select MCHP_PFSOC_IOSCB + select MCHP_PFSOC_L2CC select MCHP_PFSOC_MMUART + select MCHP_PFSOC_RTC select MCHP_PFSOC_SYSREG select RISCV_ACLINT select SIFIVE_PDMA diff --git a/hw/riscv/microchip_pfsoc.c b/hw/riscv/microchip_pfsoc.c index 4017129c83043..1562423930b85 100644 --- a/hw/riscv/microchip_pfsoc.c +++ b/hw/riscv/microchip_pfsoc.c @@ -96,7 +96,8 @@ 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_L2ZERO] = { 0xa000000, 0x200000 }, [MICROCHIP_PFSOC_PLIC] = { 0xc000000, 0x4000000 }, [MICROCHIP_PFSOC_MMUART0] = { 0x20000000, 0x1000 }, [MICROCHIP_PFSOC_WDOG0] = { 0x20001000, 0x1000 }, @@ -174,9 +175,14 @@ 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_MCHP_PFSOC_L2CC); + 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, @@ -201,6 +207,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; @@ -258,18 +265,9 @@ 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. + * Back the complete L2-LIM aperture. The L2 cache controller resizes + * this MemoryRegion to 128 KiB for every way assigned to L2-LIM. */ memory_region_init_ram(l2lim_mem, NULL, "microchip.pfsoc.l2lim", memmap[MICROCHIP_PFSOC_L2LIM].size, &error_fatal); @@ -277,6 +275,24 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp) memmap[MICROCHIP_PFSOC_L2LIM].base, l2lim_mem); + /* L2 cache controller */ + object_property_set_link(OBJECT(&s->l2cc), "l2-lim", + OBJECT(l2lim_mem), &error_abort); + sysbus_realize(SYS_BUS_DEVICE(&s->l2cc), errp); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->l2cc), 0, + memmap[MICROCHIP_PFSOC_L2CC].base); + + /* + * 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); @@ -294,6 +310,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, @@ -308,9 +333,6 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp) 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", @@ -464,6 +486,9 @@ static void microchip_pfsoc_soc_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->ioscb), 0, qdev_get_gpio_in(DEVICE(s->plic), MICROCHIP_PFSOC_MAILBOX_IRQ)); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->sysreg), 0, + qdev_get_gpio_in_named(DEVICE(&s->ioscb), + MCHP_PFSOC_IOSCB_IRQ_CLEAR, 0)); /* FPGA Fabric */ create_unimplemented_device("microchip.pfsoc.fabricfic3", @@ -532,10 +557,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); } @@ -545,15 +570,27 @@ 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 */ + /* + * The four CPU-visible windows alias the same physical DDR from offset + * zero. For the Icicle Kit's 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. + */ 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, @@ -728,19 +765,12 @@ static void microchip_icicle_kit_machine_class_init(ObjectClass *oc, mc->init = microchip_icicle_kit_machine_init; 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->min_cpus = mc->max_cpus; + mc->default_cpus = mc->max_cpus; 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, diff --git a/hw/rtc/Kconfig b/hw/rtc/Kconfig index 200f056be5e46..c4862cf3a91bd 100644 --- a/hw/rtc/Kconfig +++ b/hw/rtc/Kconfig @@ -18,6 +18,9 @@ config M48T59 config PL031 bool +config MCHP_PFSOC_RTC + bool + config K230_RTC bool diff --git a/hw/rtc/mchp_pfsoc_rtc.c b/hw/rtc/mchp_pfsoc_rtc.c new file mode 100644 index 0000000000000..087fd1f5ce756 --- /dev/null +++ b/hw/rtc/mchp_pfsoc_rtc.c @@ -0,0 +1,479 @@ +/* + * Microchip PolarFire SoC RTC + * + * Copyright (c) 2026 Process Mission + * + * Author: + * Bin Meng + * + * 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(DeviceState *dev) +{ + MchpPfSoCRtcState *s = MCHP_PFSOC_RTC(dev); + struct tm tm; + + timer_del(s->alarm_timer); + s->running = true; + 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)); + + qemu_get_timedate(&tm, 0); + s->tick_offset = mktimegm(&tm) - + qemu_clock_get_ns(rtc_clock) / NANOSECONDS_PER_SECOND; + s->frozen_count = 0; + s->read_latch = mchp_pfsoc_rtc_get_count(s); + 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); + + device_class_set_legacy_reset(dc, mchp_pfsoc_rtc_reset); + 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/meson.build b/hw/rtc/meson.build index 8e72d6334310d..c9f158857776d 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_HYM8563', if_true: files('hym8563.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')) diff --git a/hw/sd/cadence_sdhci.c b/hw/sd/cadence_sdhci.c index 8476baf67fbc8..39c6e5d101c18 100644 --- a/hw/sd/cadence_sdhci.c +++ b/hw/sd/cadence_sdhci.c @@ -54,6 +54,9 @@ static void cadence_sdhci_instance_init(Object *obj) object_initialize_child(OBJECT(s), "generic-sdhci", &s->sdhci, TYPE_SYSBUS_SDHCI); + object_property_set_uint(OBJECT(&s->sdhci), "capareg", + SDHC_CAPAB_REG_DEFAULT | + R_SDHC_CAPAB_BUS64BIT_MASK, &error_abort); } static void cadence_sdhci_reset(DeviceState *dev) diff --git a/hw/sd/sd.c b/hw/sd/sd.c index 5332561e0b919..74451d3bd9030 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 */ @@ -2379,7 +2383,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; } @@ -2399,7 +2403,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; } @@ -2669,6 +2674,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; @@ -2685,6 +2691,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); @@ -2711,7 +2718,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) { @@ -2722,7 +2729,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] @@ -2733,7 +2740,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; @@ -2817,7 +2824,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)) { @@ -2837,11 +2844,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 */ @@ -2866,22 +2873,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) { diff --git a/hw/sd/sdhci-internal.h b/hw/sd/sdhci-internal.h index 6bc6b48c4e659..fc38dd8d04d6e 100644 --- a/hw/sd/sdhci-internal.h +++ b/hw/sd/sdhci-internal.h @@ -201,6 +201,7 @@ FIELD(SDHC_HOSTCTL2, UHS_II_ENA, 8, 1); /* since v4 */ FIELD(SDHC_HOSTCTL2, ADMA2_LENGTH, 10, 1); /* since v4 */ FIELD(SDHC_HOSTCTL2, CMD23_ENA, 11, 1); /* since v4 */ FIELD(SDHC_HOSTCTL2, VERSION4, 12, 1); /* since v4 */ +FIELD(SDHC_HOSTCTL2, ADDRESSING_64, 13, 1); /* since v4 */ FIELD(SDHC_HOSTCTL2, ASYNC_INT, 14, 1); FIELD(SDHC_HOSTCTL2, PRESET_ENA, 15, 1); diff --git a/hw/sd/sdhci.c b/hw/sd/sdhci.c index 9787aa58ba4b5..efd1b12448532 100644 --- a/hw/sd/sdhci.c +++ b/hw/sd/sdhci.c @@ -229,6 +229,23 @@ static bool sdhci_update_irq(SDHCIState *s) return pending; } +static uint8_t sdhci_get_dma_type(SDHCIState *s) +{ + uint8_t type = SDHC_DMA_TYPE(s->hostctl1); + + /* + * In version 4 mode, the legacy ADMA2_32 encoding selects ADMA2, + * while ADDRESSING_64 determines its address width. + */ + if (type == SDHC_CTRL_ADMA2_32 && + FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, VERSION4) && + FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, ADDRESSING_64)) { + return SDHC_CTRL_ADMA2_64; + } + + return type; +} + static void sdhci_raise_insertion_irq(void *opaque) { SDHCIState *s = (SDHCIState *)opaque; @@ -262,6 +279,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) { @@ -309,6 +327,7 @@ static void sdhci_reset(SDHCIState *s) s->data_count = 0; s->stopped_state = sdhc_not_stopped; s->pending_insert_state = false; + s->sdma_boundary_paused = false; if (object_dynamic_cast(OBJECT(s), TYPE_FSL_ESDHC_BE) || object_dynamic_cast(OBJECT(s), TYPE_FSL_ESDHC_LE)) { s->norintstsen = 0x013f; @@ -332,7 +351,34 @@ static void sdhci_poweron_reset(DeviceState *dev) } } -static void sdhci_data_transfer(void *opaque); +static void sdhci_data_transfer(SDHCIState *s); +static void sdhci_adma_engine(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); +} #define BLOCK_SIZE_MASK (4 * KiB - 1) @@ -420,7 +466,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); + } } } @@ -447,6 +499,7 @@ static void sdhci_end_transfer(SDHCIState *s) s->norintsts |= SDHC_NIS_TRSCMP; } + s->sdma_boundary_paused = false; sdhci_update_irq(s); } @@ -629,6 +682,41 @@ static void sdhci_write_dataport(SDHCIState *s, uint32_t value, unsigned size) * Single DMA data transfer */ +static bool sdhci_version4_enabled(SDHCIState *s) +{ + return FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, VERSION4); +} + +static bool sdhci_64bit_addressing_enabled(SDHCIState *s) +{ + return FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, ADDRESSING_64); +} + +static uint64_t sdhci_sdma_address(SDHCIState *s) +{ + if (!sdhci_version4_enabled(s)) { + return s->sdmasysad; + } + + return sdhci_64bit_addressing_enabled(s) ? + s->admasysaddr : (uint32_t)s->admasysaddr; +} + +static void sdhci_advance_sdma_address(SDHCIState *s, uint32_t bytes) +{ + if (!sdhci_version4_enabled(s)) { + s->sdmasysad += bytes; + } else if (sdhci_64bit_addressing_enabled(s)) { + s->admasysaddr += bytes; + } else { + uint32_t address = s->admasysaddr; + + address += bytes; + s->admasysaddr = (s->admasysaddr & 0xffffffff00000000ULL) | + address; + } +} + /* Multi block SDMA transfer */ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) { @@ -636,7 +724,8 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) unsigned int begin; const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK; uint32_t boundary_chk = 1 << (((s->blksize & ~BLOCK_SIZE_MASK) >> 12) + 12); - uint32_t boundary_count = boundary_chk - (s->sdmasysad % boundary_chk); + uint64_t sdma_address = sdhci_sdma_address(s); + uint32_t boundary_count = boundary_chk - (sdma_address % boundary_chk); if (!(s->trnmod & SDHC_TRNS_BLK_CNT_EN) || !s->blkcnt) { qemu_log_mask(LOG_UNIMP, "infinite transfer is not supported\n"); @@ -648,7 +737,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) * possible stop at page boundary if initial address is not page aligned, * allow them to work properly */ - if ((s->sdmasysad % boundary_chk) == 0) { + if ((sdma_address % boundary_chk) == 0) { page_aligned = true; } @@ -670,9 +759,10 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) s->blkcnt--; } } - dma_memory_write(s->dma_as, s->sdmasysad, &s->fifo_buffer[begin], + dma_memory_write(s->dma_as, sdhci_sdma_address(s), + &s->fifo_buffer[begin], s->data_count - begin, MEMTXATTRS_UNSPECIFIED); - s->sdmasysad += s->data_count - begin; + sdhci_advance_sdma_address(s, s->data_count - begin); if (s->data_count == block_size) { s->data_count = 0; } @@ -691,9 +781,10 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) s->data_count = block_size; boundary_count -= block_size - begin; } - dma_memory_read(s->dma_as, s->sdmasysad, &s->fifo_buffer[begin], + dma_memory_read(s->dma_as, sdhci_sdma_address(s), + &s->fifo_buffer[begin], s->data_count - begin, MEMTXATTRS_UNSPECIFIED); - s->sdmasysad += s->data_count - begin; + sdhci_advance_sdma_address(s, s->data_count - begin); if (s->data_count == block_size) { sdbus_write_data(&s->sdbus, s->fifo_buffer, block_size); s->data_count = 0; @@ -714,6 +805,7 @@ static void sdhci_sdma_transfer_multi_blocks(SDHCIState *s) if (s->blkcnt == 0) { sdhci_end_transfer(s); } else { + s->sdma_boundary_paused = true; sdhci_update_irq(s); } } @@ -725,10 +817,12 @@ static void sdhci_sdma_transfer_single_block(SDHCIState *s) if (s->trnmod & SDHC_TRNS_READ) { sdbus_read_data(&s->sdbus, s->fifo_buffer, datacnt); - dma_memory_write(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt, + dma_memory_write(s->dma_as, sdhci_sdma_address(s), + s->fifo_buffer, datacnt, MEMTXATTRS_UNSPECIFIED); } else { - dma_memory_read(s->dma_as, s->sdmasysad, s->fifo_buffer, datacnt, + dma_memory_read(s->dma_as, sdhci_sdma_address(s), + s->fifo_buffer, datacnt, MEMTXATTRS_UNSPECIFIED); sdbus_write_data(&s->sdbus, s->fifo_buffer, datacnt); } @@ -750,6 +844,21 @@ static void sdhci_sdma_transfer(SDHCIState *s) } } +static bool sdhci_sdma_transfer_active(SDHCIState *s) +{ + return TRANSFERRING_DATA(s->prnsts) && + (s->trnmod & SDHC_TRNS_DMA) && + s->blkcnt && + (s->blksize & BLOCK_SIZE_MASK) && + SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA; +} + +static void sdhci_resume_sdma_transfer(SDHCIState *s) +{ + s->sdma_boundary_paused = false; + sdhci_sdma_transfer(s); +} + typedef struct ADMADescr { hwaddr addr; uint16_t length; @@ -762,7 +871,7 @@ static void get_adma_description(SDHCIState *s, ADMADescr *dscr) uint32_t adma1 = 0; uint64_t adma2 = 0; hwaddr entry_addr = (hwaddr)s->admasysaddr; - switch (SDHC_DMA_TYPE(s->hostctl1)) { + switch (sdhci_get_dma_type(s)) { case SDHC_CTRL_ADMA2_32: dma_memory_read(s->dma_as, entry_addr, &adma2, sizeof(adma2), MEMTXATTRS_UNSPECIFIED); @@ -799,14 +908,16 @@ static void get_adma_description(SDHCIState *s, ADMADescr *dscr) MEMTXATTRS_UNSPECIFIED); dscr->addr = le64_to_cpu(dscr->addr); dscr->attr &= (uint8_t) ~0xC0; - dscr->incr = 12; + /* Version 4 pads 64-bit ADMA2 descriptors from 96 to 128 bits */ + dscr->incr = FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, VERSION4) ? + 16 : 12; break; } } /* Advanced DMA data transfer */ -static void sdhci_do_adma(SDHCIState *s) +static void sdhci_adma_run_batch(SDHCIState *s) { unsigned int begin, length; const uint16_t block_size = s->blksize & BLOCK_SIZE_MASK; @@ -912,12 +1023,15 @@ 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); + return; } else { s->admasysaddr += dscr.incr; } @@ -968,55 +1082,52 @@ 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) +/* Run one independently scheduled, quota-bounded ADMA batch */ +static void sdhci_adma_engine(void *opaque) { SDHCIState *s = (SDHCIState *)opaque; - if (s->trnmod & SDHC_TRNS_DMA) { - switch (SDHC_DMA_TYPE(s->hostctl1)) { - case SDHC_CTRL_SDMA: - if (!(s->capareg & R_SDHC_CAPAB_SDMA_MASK)) { - trace_sdhci_error("SDMA not supported"); - break; - } - - 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; - } + if (!sdhci_adma_transfer_active(s)) { + return; + } - 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; - } + 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; + } - 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_adma_run_batch(s); +} - sdhci_do_adma(s); - break; - default: - trace_sdhci_error("Unsupported DMA type"); - break; - } +/* Perform synchronous PIO or SDMA data transfer */ +static void sdhci_data_transfer(SDHCIState *s) +{ + if (s->trnmod & SDHC_TRNS_DMA) { + 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 | @@ -1059,21 +1170,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; @@ -1199,14 +1300,22 @@ 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); s->blkgap &= ~(SDHC_STOP_AT_GAP_REQ | SDHC_CONTINUE_REQ); s->stopped_state = sdhc_not_stopped; + 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; } } @@ -1220,10 +1329,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: /* @@ -1232,15 +1337,14 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) * while the data line is still active. */ if (!TRANSFERRING_DATA(s->prnsts) || - ((s->trnmod & SDHC_TRNS_DMA) && - SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA)) { + (!sdhci_version4_enabled(s) && s->sdma_boundary_paused)) { s->sdmasysad = (s->sdmasysad & mask) | value; MASKED_WRITE(s->sdmasysad, mask, value); - /* Writing to last byte of sdmasysad might trigger transfer */ - if (!(mask & 0xFF000000) && s->blkcnt && - (s->blksize & BLOCK_SIZE_MASK) && - SDHC_DMA_TYPE(s->hostctl1) == SDHC_CTRL_SDMA) { - sdhci_sdma_transfer(s); + /* A completed selected-address write resumes stopped SDMA */ + if (!sdhci_version4_enabled(s) && !(mask & 0xff000000) && + s->sdma_boundary_paused && + sdhci_sdma_transfer_active(s)) { + sdhci_resume_sdma_transfer(s); } } break; @@ -1379,10 +1483,23 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) case SDHC_ADMASYSADDR: s->admasysaddr = (s->admasysaddr & (0xFFFFFFFF00000000ULL | (uint64_t)mask)) | (uint64_t)value; + if (sdhci_version4_enabled(s) && + !sdhci_64bit_addressing_enabled(s) && + !(mask & 0xff000000) && s->sdma_boundary_paused && + sdhci_sdma_transfer_active(s)) { + sdhci_resume_sdma_transfer(s); + } break; case SDHC_ADMASYSADDR + 4: s->admasysaddr = (s->admasysaddr & (0x00000000FFFFFFFFULL | ((uint64_t)mask << 32))) | ((uint64_t)value << 32); + /* A completed selected-address write resumes boundary-stopped SDMA */ + if (sdhci_version4_enabled(s) && + sdhci_64bit_addressing_enabled(s) && + !(mask & 0xff000000) && s->sdma_boundary_paused && + sdhci_sdma_transfer_active(s)) { + sdhci_resume_sdma_transfer(s); + } break; case SDHC_FEAER: s->acmd12errsts |= value; @@ -1395,11 +1512,25 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) } sdhci_update_irq(s); break; - case SDHC_ACMD12ERRSTS: + case SDHC_ACMD12ERRSTS: { + uint16_t hostctl2_mask = mask >> 16; + uint16_t hostctl2_value = value >> 16; + MASKED_WRITE(s->acmd12errsts, mask, value & UINT16_MAX); - if (s->uhs_mode >= UHS_I) { - MASKED_WRITE(s->hostctl2, mask >> 16, value >> 16); + if (s->uhs_mode < UHS_I) { + /* + * Version 4 fields are writable even without UHS-I. Preserve all + * other Host Control 2 bits when UHS-I is not supported. + */ + uint16_t independent = R_SDHC_HOSTCTL2_VERSION4_MASK | + R_SDHC_HOSTCTL2_ADDRESSING_64_MASK; + + hostctl2_mask |= ~independent; + hostctl2_value &= independent; + } + MASKED_WRITE(s->hostctl2, hostctl2_mask, hostctl2_value); + if (s->uhs_mode >= UHS_I) { if (FIELD_EX32(s->hostctl2, SDHC_HOSTCTL2, V18_ENA)) { sdbus_set_voltage(&s->sdbus, SD_VOLTAGE_1_8V); } else { @@ -1407,6 +1538,7 @@ sdhci_write(void *opaque, hwaddr offset, uint64_t val, unsigned size) } } break; + } case SDHC_CAPAB: case SDHC_CAPAB + 4: @@ -1464,7 +1596,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_engine, s); s->io_ops = &sdhci_mmio_ops; } @@ -1507,6 +1639,24 @@ void sdhci_common_unrealize(SDHCIState *s) s->fifo_buffer = NULL; } +static bool sdhci_hostctl2_vmstate_needed(void *opaque) +{ + SDHCIState *s = opaque; + + return s->hostctl2; +} + +static const VMStateDescription sdhci_hostctl2_vmstate = { + .name = "sdhci/hostctl2", + .version_id = 1, + .minimum_version_id = 1, + .needed = sdhci_hostctl2_vmstate_needed, + .fields = (const VMStateField[]) { + VMSTATE_UINT16(hostctl2, SDHCIState), + VMSTATE_END_OF_LIST() + }, +}; + static bool sdhci_pending_insert_vmstate_needed(void *opaque) { SDHCIState *s = opaque; @@ -1514,6 +1664,32 @@ static bool sdhci_pending_insert_vmstate_needed(void *opaque) return s->pending_insert_state; } +static bool sdhci_sdma_boundary_paused_vmstate_needed(void *opaque) +{ + SDHCIState *s = opaque; + + return s->sdma_boundary_paused; +} + +static int sdhci_pre_load(void *opaque) +{ + SDHCIState *s = opaque; + + s->hostctl2 = 0; + s->sdma_boundary_paused = false; + return 0; +} + +static int sdhci_post_load(void *opaque, int version_id) +{ + SDHCIState *s = opaque; + + if (!s->sdma_boundary_paused) { + s->sdma_boundary_paused = sdhci_sdma_transfer_active(s); + } + return 0; +} + static const VMStateDescription sdhci_pending_insert_vmstate = { .name = "sdhci/pending-insert", .version_id = 1, @@ -1525,10 +1701,23 @@ static const VMStateDescription sdhci_pending_insert_vmstate = { }, }; +static const VMStateDescription sdhci_sdma_boundary_paused_vmstate = { + .name = "sdhci/sdma_boundary_paused", + .version_id = 1, + .minimum_version_id = 1, + .needed = sdhci_sdma_boundary_paused_vmstate_needed, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(sdma_boundary_paused, SDHCIState), + VMSTATE_END_OF_LIST() + }, +}; + const VMStateDescription sdhci_vmstate = { .name = "sdhci", .version_id = 1, .minimum_version_id = 1, + .pre_load = sdhci_pre_load, + .post_load = sdhci_post_load, .fields = (const VMStateField[]) { VMSTATE_UINT32(sdmasysad, SDHCIState), VMSTATE_UINT16(blksize, SDHCIState), @@ -1561,7 +1750,9 @@ const VMStateDescription sdhci_vmstate = { VMSTATE_END_OF_LIST() }, .subsections = (const VMStateDescription * const []) { + &sdhci_hostctl2_vmstate, &sdhci_pending_insert_vmstate, + &sdhci_sdma_boundary_paused_vmstate, NULL }, }; diff --git a/include/hw/misc/mchp_pfsoc_dmc.h b/include/hw/misc/mchp_pfsoc_dmc.h index 2ed582fb8ce4e..6a1986c2c2954 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/include/hw/misc/mchp_pfsoc_ioscb.h b/include/hw/misc/mchp_pfsoc_ioscb.h index 9687ea25b112e..e39d995b64bca 100644 --- a/include/hw/misc/mchp_pfsoc_ioscb.h +++ b/include/hw/misc/mchp_pfsoc_ioscb.h @@ -25,6 +25,9 @@ #include "hw/core/sysbus.h" +#define MCHP_PFSOC_IOSCB_MAILBOX_SIZE 0x1000 +#define MCHP_PFSOC_IOSCB_IRQ_CLEAR "irq-clear" + typedef struct MchpPfSoCIoscbState { SysBusDevice parent; MemoryRegion container; @@ -47,6 +50,11 @@ 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; + bool irq_pending; qemu_irq irq; } MchpPfSoCIoscbState; diff --git a/include/hw/misc/mchp_pfsoc_l2cc.h b/include/hw/misc/mchp_pfsoc_l2cc.h new file mode 100644 index 0000000000000..3a175a6340a91 --- /dev/null +++ b/include/hw/misc/mchp_pfsoc_l2cc.h @@ -0,0 +1,31 @@ +/* + * Microchip PolarFire SoC L2 cache controller + * + * Copyright (c) 2026 Process Mission + * + * Author: + * Bin Meng + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef MCHP_PFSOC_L2CC_H +#define MCHP_PFSOC_L2CC_H + +#include "hw/core/register.h" +#include "hw/core/sysbus.h" + +#define MCHP_PFSOC_L2CC_REG_SIZE 0x1000 +#define MCHP_PFSOC_L2CC_REG_NUM (MCHP_PFSOC_L2CC_REG_SIZE / 8) + +typedef struct MchpPfSoCL2ccState { + SysBusDevice parent; + uint64_t regs[MCHP_PFSOC_L2CC_REG_NUM]; + RegisterInfo regs_info[MCHP_PFSOC_L2CC_REG_NUM]; + MemoryRegion *l2lim; +} MchpPfSoCL2ccState; + +#define TYPE_MCHP_PFSOC_L2CC "mchp.pfsoc.l2cc" +OBJECT_DECLARE_SIMPLE_TYPE(MchpPfSoCL2ccState, MCHP_PFSOC_L2CC) + +#endif diff --git a/include/hw/riscv/microchip_pfsoc.h b/include/hw/riscv/microchip_pfsoc.h index a30b944afa9ce..26d28252ea734 100644 --- a/include/hw/riscv/microchip_pfsoc.h +++ b/include/hw/riscv/microchip_pfsoc.h @@ -27,9 +27,11 @@ #include "hw/cpu/cluster.h" #include "hw/dma/sifive_pdma.h" #include "hw/misc/mchp_pfsoc_dmc.h" +#include "hw/misc/mchp_pfsoc_l2cc.h" #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" @@ -43,6 +45,7 @@ typedef struct MicrochipPFSoCState { RISCVHartArrayState e_cpus; RISCVHartArrayState u_cpus; DeviceState *plic; + MchpPfSoCL2ccState l2cc; MchpPfSoCDdrSgmiiPhyState ddr_sgmii_phy; MchpPfSoCDdrCfgState ddr_cfg; MchpPfSoCIoscbState ioscb; @@ -51,6 +54,7 @@ typedef struct MicrochipPFSoCState { MchpPfSoCMMUartState *serial2; MchpPfSoCMMUartState *serial3; MchpPfSoCMMUartState *serial4; + MchpPfSoCRtcState rtc; MchpPfSoCSysregState sysreg; SiFivePDMAState dma; CadenceGEMState gem0; @@ -90,6 +94,7 @@ enum { MICROCHIP_PFSOC_L2CC, MICROCHIP_PFSOC_DMA, MICROCHIP_PFSOC_L2LIM, + MICROCHIP_PFSOC_L2ZERO, MICROCHIP_PFSOC_PLIC, MICROCHIP_PFSOC_MMUART0, MICROCHIP_PFSOC_WDOG0, @@ -145,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/include/hw/rtc/mchp_pfsoc_rtc.h b/include/hw/rtc/mchp_pfsoc_rtc.h new file mode 100644 index 0000000000000..17bc6ddcdc2d5 --- /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 + * + * 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/include/hw/sd/sdhci.h b/include/hw/sd/sdhci.h index c2afaf01cff3b..ff58aa704dd75 100644 --- a/include/hw/sd/sdhci.h +++ b/include/hw/sd/sdhci.h @@ -105,6 +105,8 @@ struct SDHCIState { * to be protected. Set wp_inverted to invert the signal. */ bool wp_inverted; + /* Indicate that SDMA transfer is paused due to hitting the boundary */ + bool sdma_boundary_paused; }; typedef struct SDHCIState SDHCIState; diff --git a/tests/functional/riscv64/meson.build b/tests/functional/riscv64/meson.build index 2b101dc7bfeea..b2ad13e09e60b 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, 'server_ref' : 120, 'spacemit_k3' : 180, 'tuxrun' : 120, @@ -17,6 +18,7 @@ tests_riscv64_system_thorough = [ 'endianness', 'boston', 'k230', + 'microchip_icicle_kit', 'server_ref', 'sifive_u', 'spacemit_k3', diff --git a/tests/functional/riscv64/test_microchip_icicle_kit.py b/tests/functional/riscv64/test_microchip_icicle_kit.py new file mode 100644 index 0000000000000..a75803fb84ecc --- /dev/null +++ b/tests/functional/riscv64/test_microchip_icicle_kit.py @@ -0,0 +1,52 @@ +#!/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 +# +# SPDX-License-Identifier: GPL-2.0-or-later + +from qemu_test import Asset, LinuxKernelTest + + +class MicrochipIcicleKitMachine(LinuxKernelTest): + + ASSET_IMAGES = Asset( + ('https://github.com/processmission/qemu-machine-images/releases/' + 'download/v1.0.0/' + 'riscv64-microchip-icicle-kit-v1.0.0.tar.zst'), + '8a8a831f5a1715503abf88386fca81c8e5edb303bff0265118f6ac35a725d27f') + + def test_microchip_icicle_kit_boot(self): + self.require_accelerator('tcg') + self.set_machine('microchip-icicle-kit') + + archive_path = self.uncompress( + self.ASSET_IMAGES, target='microchip_icicle_kit_images.tar') + 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', + '-bios', firmware_path, + '-drive', f'if=sd,file={sdcard_path},format=raw,snapshot=on', + '-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() diff --git a/tests/qtest/mchp_pfsoc_l2cc_test.c b/tests/qtest/mchp_pfsoc_l2cc_test.c new file mode 100644 index 0000000000000..3401ce4300e10 --- /dev/null +++ b/tests/qtest/mchp_pfsoc_l2cc_test.c @@ -0,0 +1,221 @@ +/* + * QTest testcase for the Microchip PolarFire SoC L2 cache controller + * + * Copyright (c) 2026 Process Mission + * + * Author: + * Bin Meng + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "qemu/units.h" +#include "libqtest.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_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 *mchp_pfsoc_l2cc_start(const char *firmware) +{ + return qtest_initf("-machine microchip-icicle-kit -smp 5 -m 2G " + "-bios \"%s\"", firmware); +} + +static char *mchp_pfsoc_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("mchp_pfsoc_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 = mchp_pfsoc_l2cc_create_firmware(); + QTestState *qts = mchp_pfsoc_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 = mchp_pfsoc_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 = mchp_pfsoc_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]); + } + + qtest_quit(qts); +} + +static void test_l2cc_unimplemented(void) +{ + g_autofree char *firmware = mchp_pfsoc_l2cc_create_firmware(); + g_autofree char *log_path = NULL; + g_autofree char *log = NULL; + QTestState *qts; + int fd; + + fd = g_file_open_tmp("mchp_pfsoc_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); + + qtest_add_func("/mchp_pfsoc_l2cc/registers", test_l2cc_registers); + qtest_add_func("/mchp_pfsoc_l2cc/l2lim_topology", + test_l2lim_topology); + qtest_add_func("/mchp_pfsoc_l2cc/unimplemented", + test_l2cc_unimplemented); + + return g_test_run(); +} diff --git a/tests/qtest/mchp_pfsoc_rtc_test.c b/tests/qtest/mchp_pfsoc_rtc_test.c new file mode 100644 index 0000000000000..18efb04a7a666 --- /dev/null +++ b/tests/qtest/mchp_pfsoc_rtc_test.c @@ -0,0 +1,318 @@ +/* + * QTest testcase for the Microchip PolarFire SoC RTC + * + * Copyright (c) 2026 Process Mission + * + * Author: + * Bin Meng + * + * 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 base=2020-01-01T00:00:00,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) +{ + const uint64_t initial_count = 1577836800; + 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), ==, + RTC_RUNNING); + g_assert_cmphex(rtc_read_binary_count(qts), ==, initial_count); + qtest_clock_step(qts, 2 * NANOSECONDS_PER_SECOND); + g_assert_cmphex(rtc_read_binary_count(qts), ==, initial_count + 2); + + 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), ==, initial_count + 2); + + 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), ==, + RTC_RUNNING); + 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_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 base=2020-01-01T00:00:00,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_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); + wait_for_migration_complete(to); + + if (old_qemu) { + g_assert_cmphex(qtest_readl(to, RTC_BASE + RTC_CONTROL), ==, + RTC_RUNNING); + 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 52d542cab82f3..053c224692928 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -252,6 +252,7 @@ qtests_arm = \ config_all_devices.has_key('CONFIG_MUSICPAL') ? ['pflash-cfi02-test'] : []) + \ (config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed : []) + \ (config_all_devices.has_key('CONFIG_NPCM7XX') ? qtests_npcm7xx : []) + \ + (config_all_devices.has_key('CONFIG_ZYNQ') ? ['xilinx-zynq-sdhci-test'] : []) + \ (config_all_devices.has_key('CONFIG_GENERIC_LOADER') ? ['hexloader-test'] : []) + \ (config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test', 'tpm-tis-i2c-swtpm-test'] : []) + \ (config_all_devices.has_key('CONFIG_VEXPRESS') ? ['test-arm-mptimer'] : []) + \ @@ -317,6 +318,9 @@ qtests_riscv32 = \ qtests_riscv64 = ['riscv-csr-test'] + \ (unpack_edk2_blobs ? ['bios-tables-test'] : []) + \ + (config_all_devices.has_key('CONFIG_MICROCHIP_PFSOC') ? + ['mchp_pfsoc_l2cc_test', + 'mchp_pfsoc_rtc_test'] : []) + \ (config_all_devices.has_key('CONFIG_SPACEMIT_K3') ? ['spacemit-k3-test'] : []) + \ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and @@ -448,6 +452,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, + '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'), diff --git a/tests/qtest/spacemit-k3-test.c b/tests/qtest/spacemit-k3-test.c index d0e827894a575..88b1c8879e0d5 100644 --- a/tests/qtest/spacemit-k3-test.c +++ b/tests/qtest/spacemit-k3-test.c @@ -784,6 +784,8 @@ static void test_sd_adma_aplic_imsic(void) 17 << 8 | SDHCI_CMD_RESP_SHORT | SDHCI_CMD_CRC | SDHCI_CMD_INDEX | SDHCI_CMD_DATA); + /* ADMA now runs independently of MMIO accesses. */ + qtest_clock_step_next(qts); status = qtest_readl(qts, K3_SDHCI0_BASE + SDHCI_NORINTSTS); g_assert_cmphex(status & (SDHCI_NIS_CMDCMP | SDHCI_NIS_TRSCMP), ==, SDHCI_NIS_CMDCMP | SDHCI_NIS_TRSCMP); diff --git a/tests/qtest/xilinx-zynq-sdhci-test.c b/tests/qtest/xilinx-zynq-sdhci-test.c new file mode 100644 index 0000000000000..b23d8430c8974 --- /dev/null +++ b/tests/qtest/xilinx-zynq-sdhci-test.c @@ -0,0 +1,179 @@ +/* +* QTests for the Xilinx Zynq SDHCI controller. +* +* Copyright (c) 2026 Tao Ding +* +* 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 "libqtest-single.h" +#include "libqos/sdhci-cmd.h" + +#define XILINX_ZYNQ_MMC_BA 0xE0100000 +#define XILINX_ZYNQ_BLK_SIZE 512 +#define XILINX_ZYNQ_TEST_IMAGE_SIZE (1 << 20) +#define XILINX_ZYNQ_SDMA_BOUNDARY_ARG 7 +#define XILINX_ZYNQ_SDMA_BOUNDARY_SIZE (512 * 1024) +#define XILINX_ZYNQ_SDMA_BLOCK_COUNT \ + ((XILINX_ZYNQ_SDMA_BOUNDARY_SIZE / XILINX_ZYNQ_BLK_SIZE) + 1) +#define XILINX_ZYNQ_SDMA_TRANSFER_SIZE \ + (XILINX_ZYNQ_SDMA_BLOCK_COUNT * XILINX_ZYNQ_BLK_SIZE) +#define XILINX_ZYNQ_DMA_ADDR 0x00100000 + +#define SDHC_MAKE_BLKSZ(boundary, size) (((boundary) << 12) | (size)) + +static char *sd_path; + +static QTestState *setup_sd_card(void) +{ + QTestState *qts; + uint16_t rca; + + qts = qtest_initf( + "-machine xilinx-zynq-a9 " + "-drive if=sd,index=0,file=%s,format=raw,auto-read-only=off", + sd_path); + + qtest_writew(qts, XILINX_ZYNQ_MMC_BA + SDHC_SWRST, SDHC_RESET_ALL); + qtest_writew(qts, XILINX_ZYNQ_MMC_BA + SDHC_CLKCON, + SDHC_CLOCK_SDCLK_EN | SDHC_CLOCK_INT_STABLE | + SDHC_CLOCK_INT_EN); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, 0, 0, SDHC_APP_CMD); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, 0x41200000, 0, + (41 << 8)); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, 0, 0, SDHC_ALL_SEND_CID); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, 0, 0, + SDHC_SEND_RELATIVE_ADDR | SDHC_CMD_RESPONSE); + rca = qtest_readl(qts, XILINX_ZYNQ_MMC_BA + SDHC_RSPREG0) >> 16; + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, 0, 0, rca << 16, 0, + SDHC_SELECT_DESELECT_CARD); + + return qts; +} + +static void write_pattern_to_image(size_t offset, size_t len) +{ + int fd; + ssize_t ret; + uint8_t *buf = g_malloc(len); + + for (size_t i = 0; i < len; i++) { + buf[i] = (i * 13 + 7) & 0xff; + } + + fd = open(sd_path, O_WRONLY | O_BINARY); + g_assert_cmpint(fd, >=, 0); + g_assert_cmpint(lseek(fd, offset, SEEK_SET), ==, offset); + ret = qemu_write_full(fd, buf, len); + close(fd); + g_assert_cmpint(ret, ==, len); + + g_free(buf); +} + +static void wait_for_block_count(QTestState *qts, uint16_t expected) +{ + int i; + + for (i = 0; i < 1000; i++) { + if (qtest_readw(qts, XILINX_ZYNQ_MMC_BA + SDHC_BLKCNT) == expected) { + return; + } + g_usleep(100); + } + + g_assert_cmpuint(qtest_readw(qts, XILINX_ZYNQ_MMC_BA + SDHC_BLKCNT), + ==, expected); +} + +/* + * SDMA stops at each boundary and requires software to update SYSAD + * to continue. The bug this test covers leaves DOING_READ/WRITE asserted, + * so the continuation SYSAD write is ignored and the transfer stalls. + */ +static void test_read_sdma_boundary_continue(void) +{ + QTestState *qts = setup_sd_card(); + uint8_t *actual = g_malloc0(XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + uint8_t *expected = g_malloc(XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + + write_pattern_to_image(0, XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + + for (size_t i = 0; i < XILINX_ZYNQ_SDMA_TRANSFER_SIZE; i++) { + expected[i] = (i * 13 + 7) & 0xff; + } + + qtest_writel(qts, XILINX_ZYNQ_DMA_ADDR, 0xdeadbeef); + qtest_writel(qts, XILINX_ZYNQ_DMA_ADDR + XILINX_ZYNQ_SDMA_BOUNDARY_SIZE, + 0xcafef00d); + + qtest_writel(qts, XILINX_ZYNQ_MMC_BA + SDHC_SYSAD, XILINX_ZYNQ_DMA_ADDR); + sdhci_cmd_regs(qts, XILINX_ZYNQ_MMC_BA, + SDHC_MAKE_BLKSZ(XILINX_ZYNQ_SDMA_BOUNDARY_ARG, + XILINX_ZYNQ_BLK_SIZE), + XILINX_ZYNQ_SDMA_BLOCK_COUNT, 0, + SDHC_TRNS_DMA | SDHC_TRNS_MULTI | SDHC_TRNS_READ | + SDHC_TRNS_BLK_CNT_EN, + SDHC_READ_MULTIPLE_BLOCK | SDHC_CMD_DATA_PRESENT); + + wait_for_block_count(qts, 1); + + qtest_writew(qts, XILINX_ZYNQ_MMC_BA + SDHC_NORINTSTS, 0xffff); + qtest_writel(qts, XILINX_ZYNQ_MMC_BA + SDHC_SYSAD, + XILINX_ZYNQ_DMA_ADDR + XILINX_ZYNQ_SDMA_BOUNDARY_SIZE); + + wait_for_block_count(qts, 0); + + qtest_memread(qts, XILINX_ZYNQ_DMA_ADDR, actual, + XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + g_assert_cmpmem(actual, XILINX_ZYNQ_SDMA_TRANSFER_SIZE, + expected, XILINX_ZYNQ_SDMA_TRANSFER_SIZE); + + g_free(expected); + g_free(actual); + qtest_quit(qts); +} + +static void drive_destroy(void) +{ + unlink(sd_path); + g_free(sd_path); +} + +static void drive_create(void) +{ + int fd; + int ret; + GError *error = NULL; + + fd = g_file_open_tmp("xilinx-zynq-sdhci.XXXXXX", &sd_path, &error); + if (fd == -1) { + fprintf(stderr, "unable to create sdhci file: %s\n", error->message); + g_error_free(error); + } + g_assert_nonnull(sd_path); + + ret = ftruncate(fd, XILINX_ZYNQ_TEST_IMAGE_SIZE); + g_assert_cmpint(ret, ==, 0); + close(fd); +} + +int main(int argc, char **argv) +{ + int ret; + + drive_create(); + + g_test_init(&argc, &argv, NULL); + qtest_add_func("xilinx-zynq-sdhci/sdma/read-boundary-continue", + test_read_sdma_boundary_continue); + + ret = g_test_run(); + drive_destroy(); + return ret; +}