[PATCH 5/5] hw/riscv: wire K230 peripherals into the machine.
Leo Cheng <[email protected]>
| Newsgroups | gmane.comp.emulators.qemu |
|---|---|
| Message-ID | <[email protected]> |
Instantiate the GPIO, RTC, IOMUX, mailbox and SD/eMMC models on the k230 machine, add two SiFive PWM instances, and register the qtests. Also add a satellite-safety fault-injection qtest (watchdog timeout, protected-register tamper, GPIO interrupt storm/loss) exercising the modelled devices, update the machine documentation and MAINTAINERS. Signed-off-by: Leo Cheng <[email protected]> --- MAINTAINERS | 12 +++- docs/system/riscv/k230.rst | 6 ++ hw/riscv/Kconfig | 7 ++ hw/riscv/k230.c | 89 +++++++++++++++++++---- include/hw/riscv/k230.h | 18 +++++ tests/qtest/k230-pwm-test.c | 43 +++++++++++ tests/qtest/k230-safety-test.c | 127 +++++++++++++++++++++++++++++++++ tests/qtest/meson.build | 2 +- 8 files changed, 290 insertions(+), 14 deletions(-) create mode 100644 tests/qtest/k230-pwm-test.c create mode 100644 tests/qtest/k230-safety-test.c diff --git a/MAINTAINERS b/MAINTAINERS index 6171cc7..987a58f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1825,10 +1825,20 @@ L: [email protected] S: Maintained F: docs/system/riscv/k230.rst F: hw/riscv/k230.c +F: hw/gpio/k230_gpio.c +F: hw/rtc/k230_rtc.c +F: hw/misc/k230_iomux.c +F: hw/misc/k230_mailbox.c +F: hw/sd/k230_dwmmc.c F: hw/watchdog/k230_wdt.c F: include/hw/riscv/k230.h +F: include/hw/gpio/k230_gpio.h +F: include/hw/rtc/k230_rtc.h +F: include/hw/misc/k230_iomux.h +F: include/hw/misc/k230_mailbox.h +F: include/hw/sd/k230_dwmmc.h F: include/hw/watchdog/k230_wdt.h -F: tests/qtest/k230-wdt-test.c +F: tests/qtest/k230-*-test.c RX Machines ----------- diff --git a/docs/system/riscv/k230.rst b/docs/system/riscv/k230.rst index cea8202..7adafaf 100644 --- a/docs/system/riscv/k230.rst +++ b/docs/system/riscv/k230.rst @@ -20,6 +20,12 @@ The ``k230`` machine supports the following devices: * Platform-Level Interrupt Controller (PLIC) * 2 K230 Watchdog Timer * 5 UART +* 2 GPIO controllers (DesignWare APB GPIO, one PLIC line per pin) +* 2 PWM controllers (SiFive PWM) +* RTC (date/time, alarm and periodic tick interrupts) +* IOMUX pin-configuration controller +* Mailbox / IPCM block (inter-core doorbell interrupts + hardware spinlock) +* SD/eMMC host (DesignWare dw_mmc, register model) Boot options ------------ diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig index de37c08..82c9d7a 100644 --- a/hw/riscv/Kconfig +++ b/hw/riscv/Kconfig @@ -106,6 +106,7 @@ config SIFIVE_U select SIFIVE_U_OTP select SIFIVE_U_PRCI select SIFIVE_PWM + select K230_RTC select SSI_M25P80 select SSI_SD select UNIMP @@ -162,3 +163,9 @@ config K230 select SERIAL_MM select UNIMP select K230_WDT + select K230_GPIO + select SIFIVE_PWM + select K230_RTC + select K230_IOMUX + select K230_MAILBOX + select K230_DWMMC diff --git a/hw/riscv/k230.c b/hw/riscv/k230.c index 656f281..e81b434 100644 --- a/hw/riscv/k230.c +++ b/hw/riscv/k230.c @@ -110,6 +110,16 @@ static void k230_soc_init(Object *obj) object_initialize_child(obj, "c908-cpu", cpu0, TYPE_RISCV_HART_ARRAY); object_initialize_child(obj, "k230-wdt0", &s->wdt[0], TYPE_K230_WDT); object_initialize_child(obj, "k230-wdt1", &s->wdt[1], TYPE_K230_WDT); + object_initialize_child(obj, "k230-gpio0", &s->gpio[0], TYPE_K230_GPIO); + object_initialize_child(obj, "k230-gpio1", &s->gpio[1], TYPE_K230_GPIO); + object_initialize_child(obj, "k230-pwm0", &s->pwm[0], TYPE_SIFIVE_PWM); + object_initialize_child(obj, "k230-pwm1", &s->pwm[1], TYPE_SIFIVE_PWM); + object_initialize_child(obj, "k230-rtc", &s->rtc, TYPE_K230_RTC); + object_initialize_child(obj, "k230-iomux", &s->iomux, TYPE_K230_IOMUX); + object_initialize_child(obj, "k230-mailbox", &s->mailbox, + TYPE_K230_MAILBOX); + object_initialize_child(obj, "k230-sd0", &s->sd[0], TYPE_K230_DWMMC); + object_initialize_child(obj, "k230-sd1", &s->sd[1], TYPE_K230_DWMMC); qdev_prop_set_uint32(DEVICE(cpu0), "hartid-base", 0); qdev_prop_set_string(DEVICE(cpu0), "cpu-type", TYPE_RISCV_CPU_THEAD_C908); @@ -206,6 +216,73 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) sysbus_connect_irq(SYS_BUS_DEVICE(&s->wdt[1]), 0, qdev_get_gpio_in(DEVICE(s->c908_plic), K230_WDT1_IRQ)); + /* GPIO0/GPIO1: DW_apb_gpio, one PLIC line per pin */ + for (int i = 0; i < 2; i++) { + SysBusDevice *sbd = SYS_BUS_DEVICE(&s->gpio[i]); + hwaddr base = i == 0 ? memmap[K230_DEV_GPIO0].base + : memmap[K230_DEV_GPIO1].base; + int irq_base = i == 0 ? K230_GPIO0_IRQ : K230_GPIO1_IRQ; + + if (!sysbus_realize(sbd, errp)) { + return; + } + sysbus_mmio_map(sbd, 0, base); + for (int pin = 0; pin < K230_GPIO_PINS; pin++) { + sysbus_connect_irq(sbd, pin, + qdev_get_gpio_in(DEVICE(s->c908_plic), irq_base + pin)); + } + } + + /* PWM: two SiFive PWM instances at +0x00/+0x40; K230 leaves IRQs unwired */ + for (int i = 0; i < 2; i++) { + if (!sysbus_realize(SYS_BUS_DEVICE(&s->pwm[i]), errp)) { + return; + } + } + memory_region_add_subregion(sys_mem, memmap[K230_DEV_PWM].base, + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->pwm[0]), 0)); + memory_region_add_subregion_overlap(sys_mem, + memmap[K230_DEV_PWM].base + 0x40, + sysbus_mmio_get_region(SYS_BUS_DEVICE(&s->pwm[1]), 0), 1); + + /* RTC */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->rtc), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->rtc), 0, memmap[K230_DEV_RTC].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->rtc), 0, + qdev_get_gpio_in(DEVICE(s->c908_plic), K230_RTC_IRQ)); + + /* IOMUX (pinctrl) */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->iomux), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->iomux), 0, memmap[K230_DEV_IOMUX].base); + + /* Mailbox/IPCM block: doorbell interrupts + hardware spinlock at 0xA0 */ + if (!sysbus_realize(SYS_BUS_DEVICE(&s->mailbox), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->mailbox), 0, + memmap[K230_DEV_MAILBOX].base); + for (int i = 0; i < K230_MB_CHANNELS; i++) { + sysbus_connect_irq(SYS_BUS_DEVICE(&s->mailbox), i, + qdev_get_gpio_in(DEVICE(s->c908_plic), K230_MAILBOX_IRQ + i)); + } + + /* SD/eMMC (DesignWare dw_mmc) */ + for (int i = 0; i < 2; i++) { + int devid = i == 0 ? K230_DEV_SD0 : K230_DEV_SD1; + int irq = i == 0 ? K230_SD0_IRQ : K230_SD1_IRQ; + + if (!sysbus_realize(SYS_BUS_DEVICE(&s->sd[i]), errp)) { + return; + } + sysbus_mmio_map(SYS_BUS_DEVICE(&s->sd[i]), 0, memmap[devid].base); + sysbus_connect_irq(SYS_BUS_DEVICE(&s->sd[i]), 0, + qdev_get_gpio_in(DEVICE(s->c908_plic), irq)); + } + /* unimplemented devices */ create_unimplemented_device("kpu.l2-cache", memmap[K230_DEV_KPU_L2_CACHE].base, @@ -262,9 +339,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) create_unimplemented_device("pmu", memmap[K230_DEV_PMU].base, memmap[K230_DEV_PMU].size); - create_unimplemented_device("rtc", memmap[K230_DEV_RTC].base, - memmap[K230_DEV_RTC].size); - create_unimplemented_device("cmu", memmap[K230_DEV_CMU].base, memmap[K230_DEV_CMU].size); @@ -319,15 +393,6 @@ static void k230_soc_realize(DeviceState *dev, Error **errp) create_unimplemented_device("i2c4", memmap[K230_DEV_I2C4].base, memmap[K230_DEV_I2C4].size); - create_unimplemented_device("pwm", memmap[K230_DEV_PWM].base, - memmap[K230_DEV_PWM].size); - - create_unimplemented_device("gpio0", memmap[K230_DEV_GPIO0].base, - memmap[K230_DEV_GPIO0].size); - - create_unimplemented_device("gpio1", memmap[K230_DEV_GPIO1].base, - memmap[K230_DEV_GPIO1].size); - create_unimplemented_device("adc", memmap[K230_DEV_ADC].base, memmap[K230_DEV_ADC].size); diff --git a/include/hw/riscv/k230.h b/include/hw/riscv/k230.h index 592e1c2..e045f96 100644 --- a/include/hw/riscv/k230.h +++ b/include/hw/riscv/k230.h @@ -18,6 +18,12 @@ #include "hw/core/boards.h" #include "hw/riscv/riscv_hart.h" #include "hw/watchdog/k230_wdt.h" +#include "hw/gpio/k230_gpio.h" +#include "hw/timer/sifive_pwm.h" +#include "hw/rtc/k230_rtc.h" +#include "hw/misc/k230_iomux.h" +#include "hw/misc/k230_mailbox.h" +#include "hw/sd/k230_dwmmc.h" #define C908_CPU_HARTID (0) @@ -33,6 +39,12 @@ typedef struct K230SoCState { RISCVHartArrayState c908_cpu; /* Small core */ K230WdtState wdt[2]; + K230GpioState gpio[2]; + SiFivePwmState pwm[2]; + K230RtcState rtc; + K230IomuxState iomux; + K230MailboxState mailbox; + K230DwmmcState sd[2]; MemoryRegion sram; MemoryRegion bootrom; @@ -127,8 +139,14 @@ enum { K230_UART2_IRQ = 18, K230_UART3_IRQ = 19, K230_UART4_IRQ = 20, + K230_GPIO0_IRQ = 32, /* per-pin: 32..63 */ + K230_GPIO1_IRQ = 64, /* per-pin: 64..95 */ + K230_RTC_IRQ = 175, /* shared PMU/RTC */ K230_WDT0_IRQ = 107, K230_WDT1_IRQ = 108, + K230_MAILBOX_IRQ = 109, /* IPCM doorbell 109..112; ch0/ch1 use 109/110 */ + K230_SD0_IRQ = 142, + K230_SD1_IRQ = 144, }; #define K230_UART_COUNT 5 diff --git a/tests/qtest/k230-pwm-test.c b/tests/qtest/k230-pwm-test.c new file mode 100644 index 0000000..a8b3dc3 --- /dev/null +++ b/tests/qtest/k230-pwm-test.c @@ -0,0 +1,43 @@ +/* + * QTest testcase for K230 PWM (two SiFive PWM instances) + * + * Copyright (c) 2026 Leo Cheng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ +#include "qemu/osdep.h" +#include "libqtest.h" + +#define PWM0_BASE 0x9140A000 +#define PWM1_BASE 0x9140A040 + +#define PWM_PWMCMP0 0x20 +#define PWM_PWMCMP1 0x24 + +/* Both instances are mapped and independently addressable */ +static void test_two_instances(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + qtest_writel(qts, PWM0_BASE + PWM_PWMCMP0, 0x1234); + qtest_writel(qts, PWM1_BASE + PWM_PWMCMP0, 0x5678); + + g_assert_cmphex(qtest_readl(qts, PWM0_BASE + PWM_PWMCMP0), ==, 0x1234); + g_assert_cmphex(qtest_readl(qts, PWM1_BASE + PWM_PWMCMP0), ==, 0x5678); + + /* Independent compare registers within an instance */ + qtest_writel(qts, PWM0_BASE + PWM_PWMCMP1, 0x0abc); + g_assert_cmphex(qtest_readl(qts, PWM0_BASE + PWM_PWMCMP1), ==, 0x0abc); + g_assert_cmphex(qtest_readl(qts, PWM0_BASE + PWM_PWMCMP0), ==, 0x1234); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-pwm/two_instances", test_two_instances); + + return g_test_run(); +} diff --git a/tests/qtest/k230-safety-test.c b/tests/qtest/k230-safety-test.c new file mode 100644 index 0000000..9d6cddf --- /dev/null +++ b/tests/qtest/k230-safety-test.c @@ -0,0 +1,127 @@ +/* + * QTest testcase for K230 satellite-safety fault-injection scenarios + * + * Copyright (c) 2026 Leo Cheng <[email protected]> + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Each scenario injects a fault through the modelled devices and checks a + * detect -> respond -> recover flow observable from the guest side (register + * status, PLIC pending, masked-vs-raw divergence). + */ +#include "qemu/osdep.h" +#include "qemu/timer.h" +#include "qemu/bitops.h" +#include "libqtest.h" +#include "hw/watchdog/k230_wdt.h" +#include "hw/rtc/k230_rtc.h" +#include "hw/gpio/k230_gpio.h" + +#define WDT0_BASE 0x91106000 +#define RTC_BASE 0x91000C00 +#define GPIO0_BASE 0x9140B000 +#define PLIC_BASE 0xF00000000ULL +#define PLIC_PENDING (PLIC_BASE + 0x1000) +#define WDT0_IRQ 107 +#define GPIO0_PIN0_IRQ 32 + +static bool plic_pending(QTestState *qts, int irq) +{ + uint32_t word = qtest_readl(qts, PLIC_PENDING + (irq / 32) * 4); + return (word >> (irq % 32)) & 1; +} + +/* Fault: watchdog timeout. Detect via STAT/PLIC, recover via EOI + restart. */ +static void test_wdt_timeout(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + qtest_writel(qts, WDT0_BASE + K230_WDT_CR, + K230_WDT_CR_RMOD | K230_WDT_CR_WDT_EN); + qtest_writel(qts, WDT0_BASE + K230_WDT_TORR, 0x1); + + qtest_clock_step(qts, NANOSECONDS_PER_SECOND * 10); /* let it expire */ + + /* detect */ + g_assert_cmphex(qtest_readl(qts, WDT0_BASE + K230_WDT_STAT) & + K230_WDT_STAT_INT, ==, K230_WDT_STAT_INT); + g_assert_true(plic_pending(qts, WDT0_IRQ)); + + /* recover: ack + kick */ + qtest_writel(qts, WDT0_BASE + K230_WDT_EOI, 0x1); + g_assert_cmphex(qtest_readl(qts, WDT0_BASE + K230_WDT_STAT) & + K230_WDT_STAT_INT, ==, 0); + qtest_writel(qts, WDT0_BASE + K230_WDT_CRR, K230_WDT_CRR_RESTART); + + qtest_quit(qts); +} + +/* Fault: tamper a protected register with the write lock closed. */ +static void test_rtc_tamper_rejected(void) +{ + QTestState *qts = qtest_init("-machine k230"); + uint32_t golden = (12 << 16) | (30 << 8) | 45; + + /* establish golden state under the write-enable gate */ + qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, K230_RTC_TIMER_W_EN); + qtest_writel(qts, RTC_BASE + K230_RTC_TIME, golden); + g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_TIME), ==, golden); + + /* inject: close the gate, then attempt to tamper */ + qtest_writel(qts, RTC_BASE + K230_RTC_INT_CTRL, 0); + qtest_writel(qts, RTC_BASE + K230_RTC_TIME, 0xdeadbeef); + + /* detect: read-back still equals golden -> tamper rejected */ + g_assert_cmphex(qtest_readl(qts, RTC_BASE + K230_RTC_TIME), ==, golden); + + qtest_quit(qts); +} + +/* Fault: interrupt storm, then masked loss. Detect via raw-vs-masked. */ +static void test_gpio_storm_and_loss(void) +{ + QTestState *qts = qtest_init("-machine k230"); + + /* pin0: output, rising-edge interrupt, unmasked */ + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DDR, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTTYPE_LEVEL, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INT_POLARITY, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTEN, 0x1); + + /* storm: repeated rising edges */ + for (int i = 0; i < 16; i++) { + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x0); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x1); + } + /* detect the storm: status latched + PLIC pending */ + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 1, + ==, 1); + g_assert_true(plic_pending(qts, GPIO0_PIN0_IRQ)); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_PORTA_EOI, 0x1); /* recover */ + + /* inject loss: mask pin0, generate an edge */ + qtest_writel(qts, GPIO0_BASE + K230_GPIO_INTMASK, 0x1); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x0); + qtest_writel(qts, GPIO0_BASE + K230_GPIO_SWPORTA_DR, 0x1); + + /* detect loss: raw sees the edge, masked status does not */ + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_RAW_INTSTATUS) & 1, + ==, 1); + g_assert_cmphex(qtest_readl(qts, GPIO0_BASE + K230_GPIO_INTSTATUS) & 1, + ==, 0); + + qtest_quit(qts); +} + +int main(int argc, char **argv) +{ + g_test_init(&argc, &argv, NULL); + + qtest_add_func("/k230-safety/wdt_timeout", test_wdt_timeout); + qtest_add_func("/k230-safety/rtc_tamper_rejected", + test_rtc_tamper_rejected); + qtest_add_func("/k230-safety/gpio_storm_and_loss", + test_gpio_storm_and_loss); + + return g_test_run(); +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index 56ff860..be9e9ce 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -297,7 +297,7 @@ qtests_riscv64 = ['riscv-csr-test'] + \ (config_all_devices.has_key('CONFIG_IOMMU_TESTDEV') and config_all_devices.has_key('CONFIG_RISCV_IOMMU') ? ['iommu-riscv-test'] : []) + \ - (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test'] : []) + (config_all_devices.has_key('CONFIG_K230') ? ['k230-wdt-test', 'k230-gpio-test', 'k230-pwm-test', 'k230-rtc-test', 'k230-iomux-test', 'k230-mailbox-test', 'k230-safety-test', 'k230-dwmmc-test'] : []) qtests_hexagon = ['boot-serial-test'] -- 2.43.0