[PATCH 10/11] hw/ssi: Add K230 SSI XIP read window
Kangjie Huang <[email protected]> Sun, 26 Jul 2026 20:28:28 +0800
| Newsgroups | org.nongnu.qemu-riscv,org.nongnu.qemu-devel |
|---|---|
| Message-ID | <3556bbff1b11bcebe0a4ea40c81ce0b859fcbe15.1785064313.git.flamboyant.h.01@gmail.com> |
Expose logical spi0's 128 MiB flash window as a second SSI MMIO region. Gate accesses through HI_SYS XIP_EN and build Standard, Dual, or Quad SDR read commands from the XIP instruction, address, mode, and dummy-cycle registers. PIO and IDMA cover explicit SPI transactions, while K230 firmware also uses memory-mapped accesses to read spi0 flash. Model the XIP aperture so this interface is visible to the guest. Keep the window read-only, end each access with chip select inactive, and discard stale PIO state before issuing an XIP command. Cover the XIP gate, write rejection, 24-bit and 32-bit addressing, Quad mode and dummy cycles, and PIO/XIP sharing in qtest. Also verify that U-Boot can boot Linux through the XIP read path. Signed-off-by: Kangjie Huang <[email protected]> --- docs/system/riscv/k230.rst | 1 + hw/misc/k230_hi_sys.c | 5 + hw/riscv/k230.c | 9 +- hw/ssi/k230_dw_ssi.c | 176 +++++++++++++++++++++++++++++++++ include/hw/misc/k230_hi_sys.h | 1 + include/hw/ssi/k230_dw_ssi.h | 6 ++ tests/qtest/k230-dw-ssi-test.c | 73 ++++++++++++++ 7 files changed, 268 insertions(+), 3 deletions(-) diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst index 3067c1b5d7..e92387ad98 100644 --- a/docs/system/riscv/k230.rst +++ b/docs/system/riscv/k230.rst @@ -23,6 +23,7 @@ The ``k230`` machine supports the following devices: * 3 K230 SSI controllers for SPI and QSPI * Optional SPI NOR flash on spi0 CS0 * QSPI IDMA transfers for 8-bit SDR Dual and Quad modes +* Read-only 128 MiB XIP window for spi0 flash Boot options ------------ diff --git a/hw/misc/k230_hi_sys.c b/hw/misc/k230_hi_sys.c index 44f5ac61de..1514f28fac 100644 --- a/hw/misc/k230_hi_sys.c +++ b/hw/misc/k230_hi_sys.c @@ -115,6 +115,11 @@ void k230_hi_sys_set_ssi(K230HiSysState *s, unsigned int index, s->ssi[index] = ssi; } +bool k230_hi_sys_xip_enabled(const K230HiSysState *s) +{ + return !!(s->ssi_ctrl & K230_SSI_CTRL_XIP_EN); +} + static void k230_hi_sys_reset(Object *obj, ResetType type) { K230HiSysState *s = K230_HI_SYS(obj); diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index a666a41c59..e8f3ea499e 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -262,6 +262,10 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) k230_hi_sys_set_ssi(&s->hi_sys, logical_index, &s->dw_ssi[route->ssi_index]); + if (logical_index == 0) { + k230_dw_ssi_set_hi_sys(&s->dw_ssi[route->ssi_index], + &s->hi_sys); + } } if (!sysbus_realize(SYS_BUS_DEVICE(&s->hi_sys), errp)) { @@ -284,6 +288,8 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) memmap[K230_DEV_QSPI1].base); sysbus_mmio_map(SYS_BUS_DEVICE(&s->dw_ssi[2]), 0, memmap[K230_DEV_SPI].base); + sysbus_mmio_map(SYS_BUS_DEVICE(&s->dw_ssi[2]), 1, + memmap[K230_DEV_FLASH].base); sysbus_mmio_map(SYS_BUS_DEVICE(&s->hi_sys), 0, memmap[K230_DEV_HI_SYS_CFG].base); @@ -432,9 +438,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) create_unimplemented_device("ddrc_cfg", memmap[K230_DEV_DDRC_CFG].base, memmap[K230_DEV_DDRC_CFG].size); - - create_unimplemented_device("flash", memmap[K230_DEV_FLASH].base, - memmap[K230_DEV_FLASH].size); } static void k230_soc_class_init(ObjectClass *oc, const void *data) diff --git a/hw/ssi/k230_dw_ssi.c b/hw/ssi/k230_dw_ssi.c index c1c4f40e07..d187e3345a 100644 --- a/hw/ssi/k230_dw_ssi.c +++ b/hw/ssi/k230_dw_ssi.c @@ -19,6 +19,7 @@ #include "hw/core/registerfields.h" #include "hw/core/qdev-properties.h" #include "hw/core/irq.h" +#include "hw/misc/k230_hi_sys.h" #include "hw/ssi/k230_dw_ssi.h" #include "migration/vmstate.h" #include "qapi/error.h" @@ -347,6 +348,11 @@ bool k230_dw_ssi_is_sleeping(const K230DwSsiState *s) return s->sleep_status; } +void k230_dw_ssi_set_hi_sys(K230DwSsiState *s, K230HiSysState *hi_sys) +{ + s->hi_sys = hi_sys; +} + static void k230_dw_ssi_deselect(K230DwSsiState *s) { if (s->active_cs < 0) { @@ -635,6 +641,171 @@ static uint32_t k230_dw_ssi_dummy_bytes(uint32_t spi_frf, return DIV_ROUND_UP(wait_cycles * lines, 8); } +static bool k230_dw_ssi_xip_config_supported(K230DwSsiState *s) +{ + uint32_t spi_frf = FIELD_EX32(s->regs[R_CTRLR0], CTRLR0, SPI_FRF); + uint32_t spi_ctrlr0 = s->regs[R_SPI_CTRLR0]; + uint32_t required_lines; + + switch (spi_frf) { + case 0: + required_lines = 1; + break; + case 1: + required_lines = 2; + break; + case 2: + required_lines = 4; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: XIP does not support SPI_FRF=%u\n", + DEVICE(s)->canonical_path, spi_frf); + return false; + } + + if (required_lines > s->max_lines || + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, TRANS_TYPE) > 2) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unsupported XIP line or transfer type\n", + DEVICE(s)->canonical_path); + return false; + } + + if (FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, SPI_DDR_EN) || + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, INST_DDR_EN) || + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, SPI_RXDS_EN) || + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, SPI_RXDS_SIG_EN)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: XIP DDR/RXDS mode is unsupported\n", + DEVICE(s)->canonical_path); + return false; + } + + return true; +} + +static bool k230_dw_ssi_prepare_xip_command( + K230DwSsiState *s, hwaddr address, K230DwSsiEnhancedCommand *command) +{ + uint32_t spi_ctrlr0 = s->regs[R_SPI_CTRLR0]; + uint32_t inst_l = FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, INST_L); + uint32_t addr_l = FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, ADDR_L); + + command->instruction_bits = + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, XIP_INST_EN) && inst_l ? + 1U << (inst_l + 1) : 0; + command->address_bits = addr_l << 2; + if (command->address_bits > 32) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: unsupported XIP address length %u bits\n", + DEVICE(s)->canonical_path, command->address_bits); + return false; + } + + command->mode_bits_enabled = + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, XIP_MD_BIT_EN); + if (command->mode_bits_enabled) { + uint32_t mode_length_encoding = + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, XIP_MBL); + + command->mode_bits = 1U << (mode_length_encoding + 1); + } + + command->instruction = s->regs[R_XIP_INCR_INST] & + (uint32_t)MAKE_64BIT_MASK(0, command->instruction_bits); + command->address = (uint32_t)address & + (uint32_t)MAKE_64BIT_MASK(0, command->address_bits); + command->mode = s->regs[R_XIP_MODE_BITS] & + (uint32_t)MAKE_64BIT_MASK(0, command->mode_bits); + command->wait_cycles = + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, WAIT_CYCLES); + command->spi_frf = FIELD_EX32(s->regs[R_CTRLR0], CTRLR0, SPI_FRF); + command->trans_type = + FIELD_EX32(spi_ctrlr0, SPI_CTRLR0, TRANS_TYPE); + + return true; +} + +static uint64_t k230_dw_ssi_xip_read(void *opaque, hwaddr address, + unsigned int size) +{ + K230DwSsiState *s = K230_DW_SSI(opaque); + K230DwSsiEnhancedCommand command = { 0 }; + uint64_t value = 0; + uint32_t dummy_bytes; + + if (!s->hi_sys || !k230_hi_sys_xip_enabled(s->hi_sys)) { + return 0; + } + + if (!k230_dw_ssi_xip_config_supported(s) || + !k230_dw_ssi_prepare_xip_command(s, address, &command)) { + return 0; + } + + if (s->active_cs >= 0 || s->phase != K230_DW_SSI_PHASE_IDLE) { + k230_dw_ssi_abort_transfer(s); + } + + k230_dw_ssi_select(s, 0); + if (s->active_cs != 0) { + return 0; + } + + if (command.instruction_bits != 0) { + k230_dw_ssi_send_enhanced_field(s, command.instruction, + command.instruction_bits); + } + if (command.address_bits != 0) { + k230_dw_ssi_send_enhanced_field(s, command.address, + command.address_bits); + } + if (command.mode_bits_enabled) { + k230_dw_ssi_send_enhanced_field(s, command.mode, + command.mode_bits); + } + + dummy_bytes = k230_dw_ssi_dummy_bytes( + command.spi_frf, command.trans_type, command.wait_cycles); + for (uint32_t i = 0; i < dummy_bytes; i++) { + ssi_transfer(s->spi, 0); + } + + for (unsigned int i = 0; i < size; i++) { + value |= (uint64_t)(ssi_transfer(s->spi, 0) & 0xff) << (8 * i); + } + + k230_dw_ssi_deselect(s); + return value; +} + +static void k230_dw_ssi_xip_write(void *opaque, hwaddr address, + uint64_t value, unsigned int size) +{ + K230DwSsiState *s = K230_DW_SSI(opaque); + + qemu_log_mask(LOG_GUEST_ERROR, + "%s: XIP write at 0x%" HWADDR_PRIx " is unsupported\n", + DEVICE(s)->canonical_path, address); +} + +static const MemoryRegionOps k230_dw_ssi_xip_ops = { + .read = k230_dw_ssi_xip_read, + .write = k230_dw_ssi_xip_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, + .impl = { + .min_access_size = 1, + .max_access_size = 8, + .unaligned = true, + }, +}; + static bool k230_dw_ssi_idma_enabled(const K230DwSsiState *s) { return FIELD_EX32(s->regs[R_DMACR], DMACR, IDMAE); @@ -1479,6 +1650,11 @@ static void k230_dw_ssi_init(Object *obj) TYPE_K230_DW_SSI, K230_DW_SSI_MMIO_SIZE); sysbus_init_mmio(sbd, &s->mmio); + memory_region_init_io(&s->xip, obj, &k230_dw_ssi_xip_ops, s, + TYPE_K230_DW_SSI ".xip", + K230_DW_SSI_XIP_WINDOW_SIZE); + sysbus_init_mmio(sbd, &s->xip); + for (int i = 0; i < K230_DW_SSI_IRQ_COUNT; i++) { sysbus_init_irq(sbd, &s->irqs[i]); } diff --git a/include/hw/misc/k230_hi_sys.h b/include/hw/misc/k230_hi_sys.h index 4c85da531e..5ef5d64543 100644 --- a/include/hw/misc/k230_hi_sys.h +++ b/include/hw/misc/k230_hi_sys.h @@ -50,5 +50,6 @@ struct K230HiSysState { void k230_hi_sys_set_ssi(K230HiSysState *s, unsigned int index, K230DwSsiState *ssi); +bool k230_hi_sys_xip_enabled(const K230HiSysState *s); #endif /* HW_MISC_K230_HI_SYS_H */ diff --git a/include/hw/ssi/k230_dw_ssi.h b/include/hw/ssi/k230_dw_ssi.h index 49c137d73f..892384f8fb 100644 --- a/include/hw/ssi/k230_dw_ssi.h +++ b/include/hw/ssi/k230_dw_ssi.h @@ -30,6 +30,9 @@ OBJECT_DECLARE_SIMPLE_TYPE(K230DwSsiState, K230_DW_SSI) #define K230_DW_SSI_REGS_SIZE 0x14c #define K230_DW_SSI_NUM_REGS \ (K230_DW_SSI_REGS_SIZE / sizeof(uint32_t)) +#define K230_DW_SSI_XIP_WINDOW_SIZE 0x08000000 + +typedef struct K230HiSysState K230HiSysState; /* SSI GPIO output ordering differs from RISR/ISR bit ordering. */ typedef enum K230DwSsiIrq { @@ -78,7 +81,9 @@ struct K230DwSsiState { SysBusDevice parent_obj; MemoryRegion mmio; + MemoryRegion xip; SSIBus *spi; + K230HiSysState *hi_sys; qemu_irq *cs_lines; qemu_irq irqs[K230_DW_SSI_IRQ_COUNT]; @@ -101,5 +106,6 @@ struct K230DwSsiState { uint32_t k230_dw_ssi_get_spi_mode(const K230DwSsiState *s); bool k230_dw_ssi_is_sleeping(const K230DwSsiState *s); +void k230_dw_ssi_set_hi_sys(K230DwSsiState *s, K230HiSysState *hi_sys); #endif /* HW_SSI_K230_DW_SSI_H */ diff --git a/tests/qtest/k230-dw-ssi-test.c b/tests/qtest/k230-dw-ssi-test.c index 6bae56b1a8..e69215ff6c 100644 --- a/tests/qtest/k230-dw-ssi-test.c +++ b/tests/qtest/k230-dw-ssi-test.c @@ -16,6 +16,7 @@ #define K230_SPI2_BASE 0x91583000ULL #define K230_HI_SYS_BASE 0x91585000ULL #define K230_SSI_CTRL_ADDR (K230_HI_SYS_BASE + 0x68) +#define K230_FLASH_BASE 0xc0000000ULL #define K230_PLIC_BASE 0xf00000000ULL #define K230_PLIC_PENDING_BASE 0x1000 #define K230_SSI_CTRLR0 0x000 @@ -83,6 +84,7 @@ #define K230_SSI_SPI_CTRLR0_XIP_MD_EN BIT(7) #define K230_SSI_SPI_CTRLR0_INST_L_8 (2U << 8) #define K230_SSI_SPI_CTRLR0_WAIT(v) (((v) & 0x1fU) << 11) +#define K230_SSI_SPI_CTRLR0_XIP_INST_EN BIT(20) #define K230_SSI_SPI_CTRLR0_SPI_DDR_EN BIT(16) #define K230_SSI_SPI_CTRLR0_XIP_MBL_8 (2U << 26) @@ -469,6 +471,24 @@ static void assert_idma_stopped(QTestState *qts, size_t completed) K230_SSI_SR_CMPLTD_DF_SHIFT, ==, completed); } +static void enable_xip(QTestState *qts) +{ + qtest_writel(qts, K230_SSI_CTRL_ADDR, + K230_SSI_CTRL_RESET | K230_SSI_CTRL_XIP_EN); +} + +static void configure_xip_read(QTestState *qts, uint8_t opcode, + unsigned int address_bits) +{ + uint32_t spi_ctrlr0 = K230_SSI_SPI_CTRLR0_TRANS_TYPE(0) | + K230_SSI_SPI_CTRLR0_ADDR_L(address_bits) | + K230_SSI_SPI_CTRLR0_INST_L_8 | + K230_SSI_SPI_CTRLR0_XIP_INST_EN; + + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_XIP_INCR_INST, opcode); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_SPI_CTRLR0, spi_ctrlr0); +} + static void test_register_contract(void) { QTestState *qts = k230_ssi_start(); @@ -826,6 +846,58 @@ static void test_hi_sys(void) qtest_quit(qts); } +static void test_xip_read_window(void) +{ + static const uint8_t id_expected[] = { 0xef, 0x40, 0x19 }; + K230SsiFlashImage image; + QTestState *qts = k230_ssi_start_with_flash(&image); + uint64_t low_addr = K230_FLASH_BASE + K230_SSI_FLASH_PATTERN_ADDR; + uint32_t ctrlr0; + uint32_t spi_ctrlr0; + uint8_t command = FLASH_CMD_JEDEC; + uint8_t id[ARRAY_SIZE(id_expected)]; + + g_assert_cmphex(qtest_readl(qts, low_addr), ==, 0); + configure_xip_read(qts, FLASH_CMD_READ, 24); + enable_xip(qts); + g_assert_cmphex(qtest_readl(qts, low_addr), ==, 0xc33c5aa5); + qtest_writeb(qts, low_addr, 0); + g_assert_cmphex(qtest_readb(qts, low_addr), ==, 0xa5); + + configure_xip_read(qts, FLASH_CMD_READ4, 32); + g_assert_cmphex(qtest_readl(qts, K230_FLASH_BASE + + K230_SSI_FLASH_HIGH_ADDR), + ==, 0x74737271); + + ctrlr0 = k230_ssi_readl(qts, K230_SPI0_BASE, K230_SSI_CTRLR0); + ctrlr0 &= ~K230_SSI_CTRLR0_SPI_FRF_MASK; + ctrlr0 |= K230_SSI_FRF_QUAD << K230_SSI_CTRLR0_SPI_FRF_SHIFT; + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_CTRLR0, ctrlr0); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_XIP_INCR_INST, + FLASH_CMD_QUAD_IO); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_XIP_MODE_BITS, 0xff); + spi_ctrlr0 = K230_SSI_SPI_CTRLR0_TRANS_TYPE(1) | + K230_SSI_SPI_CTRLR0_ADDR_L(24) | + K230_SSI_SPI_CTRLR0_INST_L_8 | + K230_SSI_SPI_CTRLR0_XIP_INST_EN | + K230_SSI_SPI_CTRLR0_XIP_MD_EN | + K230_SSI_SPI_CTRLR0_XIP_MBL_8 | + K230_SSI_SPI_CTRLR0_WAIT(4); + k230_ssi_writel(qts, K230_SPI0_BASE, K230_SSI_SPI_CTRLR0, spi_ctrlr0); + g_assert_cmphex(qtest_readl(qts, low_addr), ==, 0xc33c5aa5); + + k230_ssi_configure(qts, K230_SPI0_BASE, K230_SSI_TMOD_TR, 8, 0); + configure_xip_read(qts, FLASH_CMD_READ, 24); + k230_ssi_enable_cs(qts, K230_SPI0_BASE, BIT(0)); + g_assert_cmphex(qtest_readb(qts, low_addr), ==, 0xa5); + flash_read_transaction(qts, &command, 1, id, sizeof(id)); + g_assert_cmpmem(id, sizeof(id), id_expected, sizeof(id_expected)); + g_assert_cmphex(qtest_readb(qts, low_addr), ==, 0xa5); + + qtest_quit(qts); + k230_ssi_flash_image_clear(&image); +} + int main(int argc, char **argv) { g_test_init(&argc, &argv, NULL); @@ -840,5 +912,6 @@ int main(int argc, char **argv) qtest_add_func("/k230-dw-ssi/qspi-sdr", test_qspi_sdr); qtest_add_func("/k230-dw-ssi/idma", test_idma); qtest_add_func("/k230-dw-ssi/hi-sys", test_hi_sys); + qtest_add_func("/k230-dw-ssi/xip-read-window", test_xip_read_window); return g_test_run(); } -- 2.43.0