:p
atchew
Login
This series adds AST1040 Caliptra MCI mailbox support for the ast1040-evb machine. AST1040 firmware accesses Caliptra through an MCI page aperture. This series adds an AST1040 SCU model with the CPTRA page-select registers, a mailbox frontend that exposes the Caliptra SRAM and CSR windows, and an optional external chardev peer so a Caliptra simulator can service EXECUTE requests. The SoC wiring maps the mailbox SRAM, CSR window and 4 KiB remap aperture. The EVB machine also gets a cptra-peer option for linking the external peer after the machine is initialized. The external peer backend is intended for co-simulation with an external Caliptra model. The current bring-up backend used for manual testing is caliptra-server: https://github.com/stevenlee7189/caliptra-server/blob/main-2.x/README.md That README includes the server command line, the matching QEMU command line, and the guest-side MFWV smoke test sequence used to validate the MCI aperture, LOCK/CMD/DLEN/EXECUTE flow, and FirmwareVersion response. The qtest added by this series does not depend on that repository; it uses a socket-backed test peer to validate the QEMU-side protocol and mailbox flow. Patch layout: - patches 1-2 add the AST1040 SCU model and mailbox frontend - patch 3 adds the external chardev peer backend - patch 4 wires the mailbox into the AST1040 SoC and EVB machine - patches 5-6 add qtest coverage and documentation The qtest covers mailbox lock, execute and completion flow through a socket-backed peer, and verifies that SCU CPTRA_PAGE_REG0 remaps the MCI aperture between CSR and SRAM pages. Validation: - scripts/checkpatch.pl --branch origin/master..HEAD - qtest coverage for the AST1040 mailbox execute path with a socket-backed external peer - manual co-simulation bring-up with caliptra-server through cptra-mbox-peer-extern Steven Lee (6): hw/misc/aspeed_scu: Add AST1040 SCU model hw/misc: Add ASPEED Caliptra mailbox frontend hw/misc: Add external Caliptra mailbox peer hw/arm/aspeed_ast1040: Wire Caliptra mailbox tests/qtest: Add ASPEED Caliptra mailbox test docs/system/arm: Document AST1040 Caliptra mailbox MAINTAINERS | 1 + docs/system/arm/aspeed.rst | 32 +++ include/hw/arm/aspeed_soc.h | 11 + include/hw/misc/aspeed_cptra_mbox.h | 133 ++++++++++ include/hw/misc/aspeed_scu.h | 10 + hw/arm/aspeed_ast1040.c | 60 ++++- hw/arm/aspeed_ast1040_evb.c | 74 +++++- hw/misc/aspeed_cptra_mbox.c | 380 +++++++++++++++++++++++++++ hw/misc/aspeed_scu.c | 251 ++++++++++++++++++ hw/misc/cptra_mbox_peer_extern.c | 318 ++++++++++++++++++++++ tests/qtest/aspeed_cptra_mbox-test.c | 223 ++++++++++++++++ hw/misc/meson.build | 2 + hw/misc/trace-events | 6 + tests/qtest/meson.build | 3 +- 14 files changed, 1498 insertions(+), 6 deletions(-) create mode 100644 include/hw/misc/aspeed_cptra_mbox.h create mode 100644 hw/misc/aspeed_cptra_mbox.c create mode 100644 hw/misc/cptra_mbox_peer_extern.c create mode 100644 tests/qtest/aspeed_cptra_mbox-test.c -- 2.43.0
Add an AST1040 variant of the ASPEED SCU with reset values, APB clock calculation, clock-stop handling, and CPTRA page-select registers. Expose the Caliptra page aperture as a QOM link so the SoC model can wire the remap window without callbacks. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- include/hw/misc/aspeed_scu.h | 10 ++ hw/misc/aspeed_scu.c | 251 +++++++++++++++++++++++++++++++++++ hw/misc/trace-events | 2 + 3 files changed, 263 insertions(+) diff --git a/include/hw/misc/aspeed_scu.h b/include/hw/misc/aspeed_scu.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/aspeed_scu.h +++ b/include/hw/misc/aspeed_scu.h @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_TYPE(AspeedSCUState, AspeedSCUClass, ASPEED_SCU) #define TYPE_ASPEED_2700_SCU TYPE_ASPEED_SCU "-ast2700" #define TYPE_ASPEED_2700_SCUIO TYPE_ASPEED_SCU "io" "-ast2700" #define TYPE_ASPEED_1030_SCU TYPE_ASPEED_SCU "-ast1030" +#define TYPE_ASPEED_1040_SCU TYPE_ASPEED_SCU "-ast1040" #define ASPEED_SCU_NR_REGS (0x1A8 >> 2) #define ASPEED_AST2600_SCU_NR_REGS (0xE20 >> 2) @@ -XXX,XX +XXX,XX @@ struct AspeedSCUState { uint32_t hw_strap1; uint32_t hw_strap2; uint32_t hw_prot_key; + + /* + * AST1040 only: alias region (the Caliptra MCI page aperture) whose + * target offset is selected by SCU_CPTRA_PAGE_REG0, linked in by the SoC. + * cptra_page_window_base is its base in the address map, used to reject a + * self-referential remap. + */ + MemoryRegion *cptra_page_window; + uint64_t cptra_page_window_base; }; #define AST2400_A1_SILICON_REV 0x02010303U diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c @@ -XXX,XX +XXX,XX @@ #include "qapi/visitor.h" #include "qemu/bitops.h" #include "qemu/log.h" +#include "qemu/range.h" #include "qemu/guest-random.h" #include "qemu/module.h" #include "trace.h" @@ -XXX,XX +XXX,XX @@ #define AST2700_SCUIO_CLK_DUTY_MEAS_RST TO_REG(0x388) #define AST2700_SCUIO_FREQ_CNT_CTL TO_REG(0x3A0) +/* AST1040 SCU */ +#define AST1040_SILICON_REV TO_REG(0x00) +#define AST1040_HW_STRAP1 TO_REG(0x10) +#define AST1040_HW_STRAP1_CLR TO_REG(0x14) +#define AST1040_HW_STRAP1_LOCK TO_REG(0x20) +#define AST1040_HW_STRAP1_SEC1 TO_REG(0x24) +#define AST1040_SCU_CPTRA_PAGE_REG0 TO_REG(0x120) +#define AST1040_SCU_CPTRA_PAGE_REG1 TO_REG(0x124) +#define AST1040_SCU_CPTRA_PAGE_REG2 TO_REG(0x128) +#define AST1040_SCU_CPTRA_PAGE_REG3 TO_REG(0x12C) +#define AST1040_SCU_CPTRA_PAGE_REG4 TO_REG(0x130) +#define AST1040_SCU_CPTRA_PAGE_REG5 TO_REG(0x134) +#define AST1040_SCU_CLK_STOP_CTL_1 TO_REG(0x240) +#define AST1040_SCU_CLK_STOP_CLR_1 TO_REG(0x244) +#define AST1040_SCU_CLK_STOP_CTL_2 TO_REG(0x260) +#define AST1040_SCU_CLK_STOP_CLR_2 TO_REG(0x264) +#define AST1040_SCU_CLK_SEL_1 TO_REG(0x280) +#define AST1040_SCU_CLK_SEL_2 TO_REG(0x284) +#define AST1040_SCU_HPLL_PARAM TO_REG(0x300) +#define AST1040_SCU_HPLL_EXT_PARAM TO_REG(0x304) +#define AST1040_SCU_APLL_PARAM TO_REG(0x310) +#define AST1040_SCU_APLL_EXT_PARAM TO_REG(0x314) +#define AST1040_SCU_DPLL_PARAM TO_REG(0x320) +#define AST1040_SCU_DPLL_EXT_PARAM TO_REG(0x324) +#define AST1040_SCU_DPLL_PARAM_READ TO_REG(0x328) +#define AST1040_SCU_DPLL_EXT_PARAM_READ TO_REG(0x32c) +#define AST1040_SCU_UARTCLK_GEN TO_REG(0x330) +#define AST1040_SCU_HUARTCLK_GEN TO_REG(0x334) +#define AST1040_SCU_CLK_DUTY_MEAS_RST TO_REG(0x388) +#define AST1040_SCU_FREQ_CNT_CTL TO_REG(0x3A0) +#define AST1040_SCU_CLK_GET_PCLK_DIV(x) (((x) >> 18) & 0x7) + +#define ASPEED_AST1040_SCU_NR_REGS (0xE20 >> 2) +#define AST1040_SCU_CPTRA_PAGE_SIZE 0x1000 + #define SCU_IO_REGION_SIZE 0x1000 static const uint32_t ast2400_a0_resets[ASPEED_SCU_NR_REGS] = { @@ -XXX,XX +XXX,XX @@ static const TypeInfo aspeed_1030_scu_info = { .class_init = aspeed_1030_scu_class_init, }; +/* AST1040 SCU */ + +static void aspeed_ast1040_scu_update_cptra_page(AspeedSCUState *s) +{ + hwaddr page_base = s->regs[AST1040_SCU_CPTRA_PAGE_REG0] & + ~(hwaddr)(AST1040_SCU_CPTRA_PAGE_SIZE - 1); + + if (!s->cptra_page_window) { + return; + } + + if (ranges_overlap(page_base, AST1040_SCU_CPTRA_PAGE_SIZE, + s->cptra_page_window_base, + AST1040_SCU_CPTRA_PAGE_SIZE)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: refusing self-referential remap to 0x%" HWADDR_PRIx + "\n", __func__, page_base); + memory_region_set_enabled(s->cptra_page_window, false); + return; + } + + memory_region_set_alias_offset(s->cptra_page_window, page_base); + memory_region_set_enabled(s->cptra_page_window, true); +} + +static uint64_t aspeed_ast1040_scu_read(void *opaque, hwaddr offset, + unsigned size) +{ + AspeedSCUState *s = ASPEED_SCU(opaque); + int reg = TO_REG(offset); + + if (reg >= ASPEED_AST1040_SCU_NR_REGS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + return 0; + } + + trace_aspeed_ast1040_scu_read(offset, size, s->regs[reg]); + return s->regs[reg]; +} + +static void aspeed_ast1040_scu_write(void *opaque, hwaddr offset, + uint64_t data64, unsigned size) +{ + AspeedSCUState *s = ASPEED_SCU(opaque); + int reg = TO_REG(offset); + uint32_t data = data64; + bool updated = false; + + if (reg >= ASPEED_AST1040_SCU_NR_REGS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + return; + } + + trace_aspeed_ast1040_scu_write(offset, size, data); + + switch (reg) { + case AST1040_SCU_CLK_STOP_CTL_1: + case AST1040_SCU_CLK_STOP_CTL_2: + s->regs[reg] |= data; + updated = true; + break; + case AST1040_SCU_CLK_STOP_CLR_1: + case AST1040_SCU_CLK_STOP_CLR_2: + s->regs[reg - 1] &= ~data; + updated = true; + break; + case AST1040_SCU_FREQ_CNT_CTL: + s->regs[reg] = deposit32(s->regs[reg], 6, 1, !!(data & BIT(1))); + updated = true; + break; + case AST1040_SCU_CPTRA_PAGE_REG0: + s->regs[reg] = data; + aspeed_ast1040_scu_update_cptra_page(s); + updated = true; + break; + case AST1040_SCU_CPTRA_PAGE_REG1: + case AST1040_SCU_CPTRA_PAGE_REG2: + case AST1040_SCU_CPTRA_PAGE_REG3: + case AST1040_SCU_CPTRA_PAGE_REG4: + case AST1040_SCU_CPTRA_PAGE_REG5: + s->regs[reg] = data; + updated = true; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Unhandled write at offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + break; + } + + if (!updated) { + s->regs[reg] = data; + } +} + +static const MemoryRegionOps aspeed_ast1040_scu_ops = { + .read = aspeed_ast1040_scu_read, + .write = aspeed_ast1040_scu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, + .valid.min_access_size = 1, + .valid.max_access_size = 8, + .valid.unaligned = false, +}; + +static uint32_t aspeed_1040_scu_calc_hpll(AspeedSCUState *s, uint32_t hpll_reg) +{ + uint32_t multiplier = 1; + uint32_t clkin = aspeed_scu_get_clkin(s); + + if (hpll_reg & SCU_AST2600_H_PLL_OFF) { + return 0; + } + + if (!(hpll_reg & SCU_AST2600_H_PLL_BYPASS_EN)) { + uint32_t p = (hpll_reg >> 19) & 0xf; + uint32_t n = (hpll_reg >> 13) & 0x3f; + uint32_t m = hpll_reg & 0x1fff; + + multiplier = ((m + 1) / (n + 1)) / (p + 1); + } + + return clkin * multiplier; +} + +static uint32_t aspeed_1040_scu_get_apb_freq(AspeedSCUState *s) +{ + AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(s); + uint32_t hpll = asc->calc_hpll(s, s->regs[AST1040_SCU_HPLL_PARAM]); + + return hpll / + (AST1040_SCU_CLK_GET_PCLK_DIV(s->regs[AST1040_SCU_CLK_SEL_1]) + 1) + / asc->apb_divider; +} + +static void aspeed_ast1040_scu_reset_hold(Object *obj, ResetType type) +{ + AspeedSCUState *s = ASPEED_SCU(obj); + AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(obj); + + memcpy(s->regs, asc->resets, asc->nr_regs * 4); + s->regs[AST1040_SILICON_REV] = s->silicon_rev; + s->regs[AST1040_HW_STRAP1] = s->hw_strap1; + aspeed_ast1040_scu_update_cptra_page(s); +} + +static const uint32_t ast1040_a0_scu_resets[ASPEED_AST1040_SCU_NR_REGS] = { + [AST1040_HW_STRAP1_CLR] = 0xFFF0FFF0, + [AST1040_HW_STRAP1_LOCK] = 0x00000FFF, + [AST1040_HW_STRAP1_SEC1] = 0x000000FF, + [AST1040_SCU_CLK_STOP_CTL_1] = 0xffff8400, + [AST1040_SCU_CLK_STOP_CTL_2] = 0x00005f30, + [AST1040_SCU_CLK_SEL_1] = 0x86900000, + [AST1040_SCU_CLK_SEL_2] = 0x00400000, + [AST1040_SCU_HPLL_PARAM] = 0x10000027, + [AST1040_SCU_HPLL_EXT_PARAM] = 0x80000014, + [AST1040_SCU_APLL_PARAM] = 0x1000001f, + [AST1040_SCU_APLL_EXT_PARAM] = 0x8000000f, + [AST1040_SCU_DPLL_PARAM] = 0x106e42ce, + [AST1040_SCU_DPLL_EXT_PARAM] = 0x80000167, + [AST1040_SCU_DPLL_PARAM_READ] = 0x106e42ce, + [AST1040_SCU_DPLL_EXT_PARAM_READ] = 0x80000167, + [AST1040_SCU_UARTCLK_GEN] = 0x00014506, + [AST1040_SCU_HUARTCLK_GEN] = 0x000145c0, + [AST1040_SCU_CLK_DUTY_MEAS_RST] = 0x0c9100d2, + [AST1040_SCU_FREQ_CNT_CTL] = 0x00000080, +}; + +static const Property aspeed_1040_scu_props[] = { + DEFINE_PROP_UINT64("cptra-page-window-base", AspeedSCUState, + cptra_page_window_base, 0), +}; + +static void aspeed_1040_scu_init(Object *obj) +{ + AspeedSCUState *s = ASPEED_SCU(obj); + + object_property_add_link(obj, "cptra-page-window", TYPE_MEMORY_REGION, + (Object **)&s->cptra_page_window, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); +} + +static void aspeed_1040_scu_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass); + + dc->desc = "ASPEED 1040 System Control Unit"; + rc->phases.hold = aspeed_ast1040_scu_reset_hold; + device_class_set_props(dc, aspeed_1040_scu_props); + asc->resets = ast1040_a0_scu_resets; + asc->calc_hpll = aspeed_1040_scu_calc_hpll; + asc->get_apb = aspeed_1040_scu_get_apb_freq; + asc->apb_divider = 2; + asc->nr_regs = ASPEED_AST1040_SCU_NR_REGS; + asc->clkin_25Mhz = true; + asc->ops = &aspeed_ast1040_scu_ops; +} + +static const TypeInfo aspeed_1040_scu_info = { + .name = TYPE_ASPEED_1040_SCU, + .parent = TYPE_ASPEED_SCU, + .instance_size = sizeof(AspeedSCUState), + .instance_init = aspeed_1040_scu_init, + .class_init = aspeed_1040_scu_class_init, +}; + static void aspeed_scu_register_types(void) { type_register_static(&aspeed_scu_info); @@ -XXX,XX +XXX,XX @@ static void aspeed_scu_register_types(void) type_register_static(&aspeed_1030_scu_info); type_register_static(&aspeed_2700_scu_info); type_register_static(&aspeed_2700_scuio_info); + type_register_static(&aspeed_1040_scu_info); } type_init(aspeed_scu_register_types); diff --git a/hw/misc/trace-events b/hw/misc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -XXX,XX +XXX,XX @@ aspeed_ast2700_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" aspeed_ast2700_scu_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_ast2700_scuio_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_ast2700_scuio_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 +aspeed_ast1040_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 +aspeed_ast1040_scu_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 # mps2-scc.c mps2_scc_read(uint64_t offset, uint64_t data, unsigned size) "MPS2 SCC read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" -- 2.43.0
Add a Caliptra mailbox frontend device with SRAM and CSR regions for the AST1040 MCI mailbox window. Model the mailbox lock, command registers, execute flow and completion path through a pluggable peer interface so backends can service commands synchronously or asynchronously. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- include/hw/misc/aspeed_cptra_mbox.h | 133 ++++++++++ hw/misc/aspeed_cptra_mbox.c | 380 ++++++++++++++++++++++++++++ hw/misc/meson.build | 1 + hw/misc/trace-events | 4 + 4 files changed, 518 insertions(+) create mode 100644 include/hw/misc/aspeed_cptra_mbox.h create mode 100644 hw/misc/aspeed_cptra_mbox.c diff --git a/include/hw/misc/aspeed_cptra_mbox.h b/include/hw/misc/aspeed_cptra_mbox.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/misc/aspeed_cptra_mbox.h @@ -XXX,XX +XXX,XX @@ +/* + * ASPEED Caliptra mailbox model (Caliptra 2.x subsystem mode) + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_ASPEED_CPTRA_MBOX_H +#define HW_MISC_ASPEED_CPTRA_MBOX_H + +#include "qom/object.h" +#include "hw/core/sysbus.h" + +typedef struct Error Error; + +/* SRAM size: 2 MiB (matches MCU_MAILBOX0_SRAM_SIZE in caliptra-mcu-sw) */ +#define CPTRA_MBOX0_SRAM_SIZE (2u * 1024u * 1024u) +#define CPTRA_MBOX0_SRAM_WORDS (CPTRA_MBOX0_SRAM_SIZE / 4) + +/* CSR window: 4 KiB (covers all defined registers) */ +#define CPTRA_MBOX0_CSR_SIZE 0x1000u + +/* CSR register offsets (relative to CSR BAR) */ +#define CPTRA_MBOX0_LOCK_OFF 0x000 +#define CPTRA_MBOX0_USER_OFF 0x004 +#define CPTRA_MBOX0_TARGET_USER_OFF 0x008 +#define CPTRA_MBOX0_TARGET_USER_VAL_OFF 0x00C +#define CPTRA_MBOX0_CMD_OFF 0x010 +#define CPTRA_MBOX0_DLEN_OFF 0x014 +#define CPTRA_MBOX0_EXECUTE_OFF 0x018 +#define CPTRA_MBOX0_TARGET_STATUS_OFF 0x01C +#define CPTRA_MBOX0_CMD_STATUS_OFF 0x020 +#define CPTRA_MBOX0_HW_STATUS_OFF 0x024 + +/* CMD_STATUS values */ +#define CPTRA_MBOX0_STATUS_BUSY 0 +#define CPTRA_MBOX0_STATUS_DATA_READY 1 +#define CPTRA_MBOX0_STATUS_COMPLETE 2 +#define CPTRA_MBOX0_STATUS_CMD_FAILURE 3 + +/* SoC agent ID reported in the USER register when the lock is acquired */ +#define CPTRA_MBOX0_SOC_USER_ID 1u + +/* + * Caliptra mailbox interface (frontend), implemented as a QOM interface so + * that backends can deliver an asynchronous response without depending on the + * concrete frontend device type. + */ +#define TYPE_CPTRA_MBOX_IF "cptra-mbox-if" +typedef struct CptraMboxIfClass CptraMboxIfClass; +DECLARE_CLASS_CHECKERS(CptraMboxIfClass, CPTRA_MBOX_IF, TYPE_CPTRA_MBOX_IF) +typedef struct CptraMboxIf CptraMboxIf; +#define CPTRA_MBOX_IF(obj) \ + INTERFACE_CHECK(CptraMboxIf, (obj), TYPE_CPTRA_MBOX_IF) + +struct CptraMboxIfClass { + InterfaceClass parent; + + /* + * Called by the peer when a command submitted via handle_execute() has + * completed. @status is a CPTRA_MBOX0_STATUS_* value; @data/@len carry the + * response payload to be written back into the mailbox SRAM (@len bytes, + * @dlen is the reported DLEN). + */ + void (*complete)(CptraMboxIf *s, uint32_t status, uint32_t dlen, + const uint8_t *data, uint32_t len); +}; + +/* + * Caliptra mailbox peer (backend) base class. + */ +#define TYPE_CPTRA_MBOX_PEER "cptra-mbox-peer" +OBJECT_DECLARE_TYPE(CptraMboxPeer, CptraMboxPeerClass, CPTRA_MBOX_PEER) + +struct CptraMboxPeer { + DeviceState parent; + + /* Set by the frontend when this peer is linked to it. */ + CptraMboxIf *intf; +}; + +struct CptraMboxPeerClass { + DeviceClass parent; + + /* + * Process a command. @data/@len is a copy of the request payload from the + * mailbox SRAM. The peer must eventually report completion by calling the + * interface's complete() method (synchronously or asynchronously). + */ + void (*handle_execute)(CptraMboxPeer *p, uint32_t cmd, uint32_t dlen, + const uint8_t *data, uint32_t len); + + /* Optional: notify the peer of a mailbox reset. */ + void (*handle_reset)(CptraMboxPeer *p); +}; + +/* Concrete frontend device type. */ +#define TYPE_ASPEED_CPTRA_MBOX "aspeed-cptra-mbox" +OBJECT_DECLARE_SIMPLE_TYPE(AspeedCptraMboxState, ASPEED_CPTRA_MBOX) + +struct AspeedCptraMboxState { + SysBusDevice parent_obj; + + /* Linked backend; NULL means no Caliptra peer is present. */ + CptraMboxPeer *peer; + + bool locked; + bool command_pending; + bool release_pending; + uint32_t user; + uint32_t target_user; + uint32_t target_user_valid; + uint32_t cmd; + uint32_t dlen; + uint32_t execute; + uint32_t target_status; + uint32_t cmd_status; + uint32_t hw_status; + + uint32_t sram[CPTRA_MBOX0_SRAM_WORDS]; + + MemoryRegion sram_mr; + MemoryRegion csr_mr; +}; + +bool aspeed_cptra_mbox_set_peer(AspeedCptraMboxState *s, CptraMboxPeer *peer, + Error **errp); + +/* Concrete external (chardev) peer type. */ +#define TYPE_CPTRA_MBOX_PEER_EXTERN "cptra-mbox-peer-extern" + +#endif /* HW_MISC_ASPEED_CPTRA_MBOX_H */ diff --git a/hw/misc/aspeed_cptra_mbox.c b/hw/misc/aspeed_cptra_mbox.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/misc/aspeed_cptra_mbox.c @@ -XXX,XX +XXX,XX @@ +/* + * ASPEED Caliptra mailbox host interface (frontend) and peer base class. + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/aspeed_cptra_mbox.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "system/memory.h" +#include "trace.h" + +static size_t cptra_mbox_padded_len(uint32_t dlen) +{ + return (size_t)((dlen + 3) / 4) * 4; +} + +static void cptra_mbox_clear(AspeedCptraMboxState *s) +{ + s->locked = false; + s->release_pending = false; + s->user = 0; + s->target_user = 0; + s->target_user_valid = 0; + s->cmd = 0; + s->dlen = 0; + s->execute = 0; + s->target_status = 0; + s->cmd_status = 0; + s->hw_status = 0; + memset(s->sram, 0, sizeof(s->sram)); +} + +static void cptra_mbox_submit(AspeedCptraMboxState *s) +{ + CptraMboxPeerClass *pc; + g_autofree uint8_t *data = NULL; + size_t len; + + if (s->dlen > CPTRA_MBOX0_SRAM_SIZE) { + s->cmd_status = CPTRA_MBOX0_STATUS_CMD_FAILURE; + qemu_log_mask(LOG_GUEST_ERROR, + "%s: DLEN 0x%x exceeds SRAM size\n", __func__, s->dlen); + return; + } + + if (!s->peer) { + /* No Caliptra peer present: the command cannot be serviced. */ + s->cmd_status = CPTRA_MBOX0_STATUS_CMD_FAILURE; + return; + } + + len = cptra_mbox_padded_len(s->dlen); + if (len) { + data = g_malloc(len); + for (size_t i = 0; i < len / 4; i++) { + stl_le_p(data + i * 4, s->sram[i]); + } + } + + s->command_pending = true; + s->release_pending = false; + trace_cptra_mbox_execute(s->cmd, s->dlen); + + pc = CPTRA_MBOX_PEER_GET_CLASS(s->peer); + pc->handle_execute(s->peer, s->cmd, s->dlen, data, len); +} + +/* CptraMboxIf::complete - called by the peer when a command finishes. */ +static void cptra_mbox_complete(CptraMboxIf *iface, uint32_t status, + uint32_t dlen, const uint8_t *data, + uint32_t len) +{ + AspeedCptraMboxState *s = ASPEED_CPTRA_MBOX(iface); + + s->command_pending = false; + trace_cptra_mbox_complete(status, dlen); + + if (s->release_pending) { + cptra_mbox_clear(s); + return; + } + + /* + * Report the peer's status verbatim and write back whatever response + * payload it returned. The peer signals a transport/command failure with + * CPTRA_MBOX0_STATUS_CMD_FAILURE and no data. + */ + s->cmd_status = status; + s->dlen = dlen; + if (data && len) { + size_t words = MIN(len, CPTRA_MBOX0_SRAM_SIZE) / 4; + + for (size_t i = 0; i < words; i++) { + s->sram[i] = ldl_le_p(data + i * 4); + } + } +} + +static uint64_t cptra_mbox_sram_read(void *opaque, hwaddr offset, unsigned size) +{ + AspeedCptraMboxState *s = opaque; + uint32_t idx = offset / 4; + + if (idx >= CPTRA_MBOX0_SRAM_WORDS) { + return 0; + } + return s->sram[idx]; +} + +static void cptra_mbox_sram_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + AspeedCptraMboxState *s = opaque; + uint32_t idx = offset / 4; + + if (idx < CPTRA_MBOX0_SRAM_WORDS) { + s->sram[idx] = (uint32_t)value; + } +} + +static const MemoryRegionOps cptra_mbox_sram_ops = { + .read = cptra_mbox_sram_read, + .write = cptra_mbox_sram_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static uint64_t cptra_mbox_csr_read(void *opaque, hwaddr offset, unsigned size) +{ + AspeedCptraMboxState *s = opaque; + + switch (offset) { + case CPTRA_MBOX0_LOCK_OFF: + if (!s->locked) { + s->locked = true; + s->user = CPTRA_MBOX0_SOC_USER_ID; + return 0; + } + return 1; + case CPTRA_MBOX0_USER_OFF: + return s->user; + case CPTRA_MBOX0_TARGET_USER_OFF: + return s->target_user; + case CPTRA_MBOX0_TARGET_USER_VAL_OFF: + return s->target_user_valid; + case CPTRA_MBOX0_CMD_OFF: + return s->cmd; + case CPTRA_MBOX0_DLEN_OFF: + return s->dlen; + case CPTRA_MBOX0_EXECUTE_OFF: + return s->execute; + case CPTRA_MBOX0_TARGET_STATUS_OFF: + return s->target_status; + case CPTRA_MBOX0_CMD_STATUS_OFF: + return s->cmd_status; + case CPTRA_MBOX0_HW_STATUS_OFF: + return s->hw_status; + default: + return 0; + } +} + +static void cptra_mbox_csr_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + AspeedCptraMboxState *s = opaque; + uint32_t val = (uint32_t)value; + + switch (offset) { + case CPTRA_MBOX0_LOCK_OFF: + case CPTRA_MBOX0_USER_OFF: + case CPTRA_MBOX0_HW_STATUS_OFF: + break; + case CPTRA_MBOX0_TARGET_USER_OFF: + s->target_user = val; + break; + case CPTRA_MBOX0_TARGET_USER_VAL_OFF: + s->target_user_valid = val; + break; + case CPTRA_MBOX0_CMD_OFF: + s->cmd = val; + break; + case CPTRA_MBOX0_DLEN_OFF: + if (val > CPTRA_MBOX0_SRAM_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: DLEN 0x%x exceeds SRAM size, clamped\n", + __func__, val); + val = CPTRA_MBOX0_SRAM_SIZE; + } + s->dlen = val; + break; + case CPTRA_MBOX0_EXECUTE_OFF: + if (val == 1 && s->execute != 1 && !s->command_pending) { + s->execute = 1; + s->cmd_status = CPTRA_MBOX0_STATUS_BUSY; + cptra_mbox_submit(s); + } else if (val == 0 && s->execute != 0) { + if (s->command_pending) { + s->release_pending = true; + } else { + cptra_mbox_clear(s); + } + } + break; + case CPTRA_MBOX0_TARGET_STATUS_OFF: + s->target_status = val; + break; + case CPTRA_MBOX0_CMD_STATUS_OFF: + s->cmd_status = val; + break; + default: + break; + } +} + +static const MemoryRegionOps cptra_mbox_csr_ops = { + .read = cptra_mbox_csr_read, + .write = cptra_mbox_csr_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void cptra_mbox_reset_hold(Object *obj, ResetType type) +{ + AspeedCptraMboxState *s = ASPEED_CPTRA_MBOX(obj); + + if (s->command_pending) { + /* Defer the clear until the in-flight command completes. */ + s->release_pending = true; + return; + } + cptra_mbox_clear(s); + + if (s->peer) { + CptraMboxPeerClass *pc = CPTRA_MBOX_PEER_GET_CLASS(s->peer); + if (pc->handle_reset) { + pc->handle_reset(s->peer); + } + } +} + +static void cptra_mbox_peer_check(const Object *obj, const char *name, + Object *val, Error **errp) +{ + CptraMboxPeer *peer; + + if (!val) { + return; + } + + peer = CPTRA_MBOX_PEER(val); + if (peer->intf) { + error_setg(errp, "Caliptra mailbox peer is already in use"); + } +} + +bool aspeed_cptra_mbox_set_peer(AspeedCptraMboxState *s, CptraMboxPeer *peer, + Error **errp) +{ + CptraMboxPeer *old_peer = s->peer; + + if (old_peer == peer) { + if (s->peer) { + s->peer->intf = CPTRA_MBOX_IF(s); + } + return true; + } + if (!object_property_set_link(OBJECT(s), "peer", + peer ? OBJECT(peer) : NULL, errp)) { + return false; + } + if (old_peer) { + old_peer->intf = NULL; + } + if (s->peer) { + s->peer->intf = CPTRA_MBOX_IF(s); + } + return true; +} + +static void cptra_mbox_init(Object *obj) +{ + AspeedCptraMboxState *s = ASPEED_CPTRA_MBOX(obj); + + object_property_add_link(obj, "peer", TYPE_CPTRA_MBOX_PEER, + (Object **)&s->peer, cptra_mbox_peer_check, + OBJ_PROP_LINK_STRONG); +} + +static void cptra_mbox_realize(DeviceState *dev, Error **errp) +{ + AspeedCptraMboxState *s = ASPEED_CPTRA_MBOX(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + if (s->peer) { + if (!aspeed_cptra_mbox_set_peer(s, s->peer, errp)) { + return; + } + } + + memory_region_init_io(&s->sram_mr, OBJECT(s), &cptra_mbox_sram_ops, s, + "cptra-mbox.sram", CPTRA_MBOX0_SRAM_SIZE); + sysbus_init_mmio(sbd, &s->sram_mr); + + memory_region_init_io(&s->csr_mr, OBJECT(s), &cptra_mbox_csr_ops, s, + "cptra-mbox.csr", CPTRA_MBOX0_CSR_SIZE); + sysbus_init_mmio(sbd, &s->csr_mr); +} + +static const VMStateDescription vmstate_cptra_mbox = { + .name = TYPE_ASPEED_CPTRA_MBOX, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(locked, AspeedCptraMboxState), + VMSTATE_UINT32(user, AspeedCptraMboxState), + VMSTATE_UINT32(target_user, AspeedCptraMboxState), + VMSTATE_UINT32(target_user_valid, AspeedCptraMboxState), + VMSTATE_UINT32(cmd, AspeedCptraMboxState), + VMSTATE_UINT32(dlen, AspeedCptraMboxState), + VMSTATE_UINT32(execute, AspeedCptraMboxState), + VMSTATE_UINT32(target_status, AspeedCptraMboxState), + VMSTATE_UINT32(cmd_status, AspeedCptraMboxState), + VMSTATE_UINT32(hw_status, AspeedCptraMboxState), + VMSTATE_UINT32_ARRAY(sram, AspeedCptraMboxState, + CPTRA_MBOX0_SRAM_WORDS), + VMSTATE_END_OF_LIST() + }, +}; + +static void cptra_mbox_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + CptraMboxIfClass *ic = CPTRA_MBOX_IF_CLASS(oc); + + dc->desc = "Caliptra mailbox host interface"; + dc->realize = cptra_mbox_realize; + dc->vmsd = &vmstate_cptra_mbox; + rc->phases.hold = cptra_mbox_reset_hold; + ic->complete = cptra_mbox_complete; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo cptra_mbox_types[] = { + { + .name = TYPE_CPTRA_MBOX_IF, + .parent = TYPE_INTERFACE, + .class_size = sizeof(CptraMboxIfClass), + }, + { + .name = TYPE_CPTRA_MBOX_PEER, + .parent = TYPE_DEVICE, + .instance_size = sizeof(CptraMboxPeer), + .class_size = sizeof(CptraMboxPeerClass), + .abstract = true, + }, + { + .name = TYPE_ASPEED_CPTRA_MBOX, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedCptraMboxState), + .instance_init = cptra_mbox_init, + .class_init = cptra_mbox_class_init, + .interfaces = (const InterfaceInfo[]) { + { TYPE_CPTRA_MBOX_IF }, + { } + }, + }, +}; + +DEFINE_TYPES(cptra_mbox_types) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -XXX,XX +XXX,XX @@ system_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true: files('pvpanic-pci.c')) system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c')) system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( + 'aspeed_cptra_mbox.c', 'aspeed_hace.c', 'aspeed_lpc.c', 'aspeed_ltpi.c', diff --git a/hw/misc/trace-events b/hw/misc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -XXX,XX +XXX,XX @@ aspeed_ast2700_scuio_read(uint64_t offset, unsigned size, uint32_t data) "To 0x% aspeed_ast1040_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_ast1040_scu_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 +# aspeed_cptra_mbox.c +cptra_mbox_execute(uint32_t cmd, uint32_t dlen) "EXECUTE cmd 0x%" PRIx32 " dlen 0x%" PRIx32 +cptra_mbox_complete(uint32_t status, uint32_t dlen) "complete status %u dlen 0x%" PRIx32 + # mps2-scc.c mps2_scc_read(uint64_t offset, uint64_t data, unsigned size) "MPS2 SCC read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" mps2_scc_write(uint64_t offset, uint64_t data, unsigned size) "MPS2 SCC write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" -- 2.43.0
Add a cptra-mbox-peer-extern backend that forwards mailbox execute requests to a chardev peer using a small framed protocol. Run the blocking chardev transaction from the thread pool and report completion back through the mailbox peer interface. Block migration while this external backend is realized because the peer state lives outside QEMU. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- MAINTAINERS | 1 + hw/misc/cptra_mbox_peer_extern.c | 318 +++++++++++++++++++++++++++++++ hw/misc/meson.build | 1 + 3 files changed, 320 insertions(+) create mode 100644 hw/misc/cptra_mbox_peer_extern.c diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ L: qemu-arm@nongnu.org S: Maintained F: hw/*/*aspeed* F: include/hw/*/*aspeed* +F: hw/misc/cptra_mbox_peer_extern.c F: hw/net/ftgmac100.c F: include/hw/net/ftgmac100.h F: docs/system/arm/aspeed.rst diff --git a/hw/misc/cptra_mbox_peer_extern.c b/hw/misc/cptra_mbox_peer_extern.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/misc/cptra_mbox_peer_extern.c @@ -XXX,XX +XXX,XX @@ +/* + * Caliptra mailbox external peer (backend). + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "block/thread-pool.h" +#include "chardev/char-fe.h" +#include "hw/misc/aspeed_cptra_mbox.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "migration/blocker.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "qemu/error-report.h" + +#define CPTRA_MBOX_PROTO_MAGIC 0x4D424F58u /* "MBOX" */ +#define CPTRA_MBOX_PROTO_VERSION 1u +#define CPTRA_MBOX_CMD_EXECUTE 1u +#define CPTRA_MBOX_CMD_RESPONSE 2u +#define CPTRA_MBOX_PROTO_MAX_PAYLOAD (8 + CPTRA_MBOX0_SRAM_SIZE) + +typedef struct QEMU_PACKED CptraMboxProtoHdr { + uint32_t magic; + uint16_t version; + uint16_t command; + uint32_t payload_len; +} CptraMboxProtoHdr; + +OBJECT_DECLARE_SIMPLE_TYPE(CptraMboxPeerExtern, CPTRA_MBOX_PEER_EXTERN) + +struct CptraMboxPeerExtern { + CptraMboxPeer parent; + + CharFrontend chr; + Error *migration_blocker; +}; + +typedef struct CptraMboxExternReq { + CptraMboxPeerExtern *p; + + uint32_t cmd; + uint32_t dlen; + uint8_t *req_data; + size_t req_len; + + uint32_t rsp_status; + uint32_t rsp_dlen; + uint8_t *rsp_data; + size_t rsp_len; + + char *error; +} CptraMboxExternReq; + +static size_t cptra_padded_len(uint32_t dlen) +{ + return (size_t)((dlen + 3) / 4) * 4; +} + +static int G_GNUC_PRINTF(2, 3) +cptra_req_fail(CptraMboxExternReq *req, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + g_free(req->error); + req->error = g_strdup_vprintf(fmt, ap); + va_end(ap); + + return -1; +} + +static int cptra_write_all(CptraMboxExternReq *req, const void *buf, size_t len) +{ + int ret; + + if (len > INT_MAX) { + return cptra_req_fail(req, "write too large: %zu", len); + } + ret = qemu_chr_fe_write_all(&req->p->chr, buf, len); + if (ret < 0 || (size_t)ret != len) { + return cptra_req_fail(req, "backend write failed"); + } + return 0; +} + +static int cptra_read_all(CptraMboxExternReq *req, void *buf, size_t len) +{ + int ret; + + if (len > INT_MAX) { + return cptra_req_fail(req, "read too large: %zu", len); + } + ret = qemu_chr_fe_read_all(&req->p->chr, buf, len); + if (ret < 0 || (size_t)ret != len) { + return cptra_req_fail(req, "backend read failed"); + } + return 0; +} + +/* + * Wire protocol (magic 0x4D424F58 "MBOX"): + * + * Header (12 bytes, little-endian): + * u32 magic = 0x4D424F58 + * u16 version = 1 + * u16 command + * u32 payload_len + * + * MBOX_EXECUTE (1) QEMU -> backend + * payload: u32 cmd, u32 dlen, u8 sram[ROUND_UP(dlen, 4)] + * + * MBOX_RESPONSE (2) backend -> QEMU + * payload: u32 status, u32 dlen, u8 sram[ROUND_UP(dlen, 4)] + */ +static int cptra_extern_worker(gpointer data) +{ + CptraMboxExternReq *req = data; + CptraMboxProtoHdr hdr; + size_t tx_payload_len = 8 + req->req_len; + g_autofree uint8_t *payload = NULL; + size_t rsp_data_len; + + if (!qemu_chr_fe_backend_open(&req->p->chr)) { + return cptra_req_fail(req, "backend is not connected"); + } + + payload = g_malloc0(tx_payload_len); + stl_le_p(payload, req->cmd); + stl_le_p(payload + 4, req->dlen); + if (req->req_len) { + memcpy(payload + 8, req->req_data, req->req_len); + } + + hdr.magic = cpu_to_le32(CPTRA_MBOX_PROTO_MAGIC); + hdr.version = cpu_to_le16(CPTRA_MBOX_PROTO_VERSION); + hdr.command = cpu_to_le16(CPTRA_MBOX_CMD_EXECUTE); + hdr.payload_len = cpu_to_le32(tx_payload_len); + + if (cptra_write_all(req, &hdr, sizeof(hdr)) < 0 || + cptra_write_all(req, payload, tx_payload_len) < 0) { + return -1; + } + + if (cptra_read_all(req, &hdr, sizeof(hdr)) < 0) { + return -1; + } + + hdr.magic = le32_to_cpu(hdr.magic); + hdr.version = le16_to_cpu(hdr.version); + hdr.command = le16_to_cpu(hdr.command); + hdr.payload_len = le32_to_cpu(hdr.payload_len); + + if (hdr.magic != CPTRA_MBOX_PROTO_MAGIC) { + return cptra_req_fail(req, "bad response magic 0x%08x", hdr.magic); + } + if (hdr.version != CPTRA_MBOX_PROTO_VERSION) { + return cptra_req_fail(req, "bad response version %u", hdr.version); + } + if (hdr.command != CPTRA_MBOX_CMD_RESPONSE) { + return cptra_req_fail(req, "unexpected response command %u", + hdr.command); + } + if (hdr.payload_len < 8 || hdr.payload_len > CPTRA_MBOX_PROTO_MAX_PAYLOAD) { + return cptra_req_fail(req, "invalid response payload length %u", + hdr.payload_len); + } + + g_free(payload); + payload = g_malloc(hdr.payload_len); + if (cptra_read_all(req, payload, hdr.payload_len) < 0) { + return -1; + } + + req->rsp_status = ldl_le_p(payload); + req->rsp_dlen = ldl_le_p(payload + 4); + if (req->rsp_dlen > CPTRA_MBOX0_SRAM_SIZE) { + return cptra_req_fail(req, "response DLEN 0x%x exceeds SRAM", + req->rsp_dlen); + } + + rsp_data_len = cptra_padded_len(req->rsp_dlen); + if (8 + rsp_data_len > hdr.payload_len) { + return cptra_req_fail(req, "short response payload for DLEN 0x%x", + req->rsp_dlen); + } + + if (rsp_data_len) { + req->rsp_data = g_malloc0(rsp_data_len); + memcpy(req->rsp_data, payload + 8, rsp_data_len); + req->rsp_len = rsp_data_len; + } + + return 0; +} + +static void cptra_extern_req_free(CptraMboxExternReq *req) +{ + g_free(req->req_data); + g_free(req->rsp_data); + g_free(req->error); + g_free(req); +} + +static void cptra_extern_complete(void *opaque, int ret) +{ + CptraMboxExternReq *req = opaque; + CptraMboxPeerExtern *p = req->p; + CptraMboxIf *intf = p->parent.intf; + CptraMboxIfClass *ic = intf ? CPTRA_MBOX_IF_GET_CLASS(intf) : NULL; + + if (req->error) { + error_report("cptra-mbox-peer-extern: %s", req->error); + } + + if (ic) { + if (ret == 0) { + ic->complete(intf, req->rsp_status, req->rsp_dlen, + req->rsp_data, req->rsp_len); + } else { + ic->complete(intf, CPTRA_MBOX0_STATUS_CMD_FAILURE, 0, NULL, 0); + } + } + + cptra_extern_req_free(req); + object_unref(OBJECT(p)); +} + +static void cptra_extern_handle_execute(CptraMboxPeer *peer, uint32_t cmd, + uint32_t dlen, const uint8_t *data, + uint32_t len) +{ + CptraMboxPeerExtern *p = CPTRA_MBOX_PEER_EXTERN(peer); + CptraMboxIf *intf = peer->intf; + CptraMboxExternReq *req; + + if (!qemu_chr_fe_backend_connected(&p->chr) || + !qemu_chr_fe_backend_open(&p->chr)) { + if (intf) { + CPTRA_MBOX_IF_GET_CLASS(intf)->complete( + intf, CPTRA_MBOX0_STATUS_CMD_FAILURE, 0, NULL, 0); + } + return; + } + + req = g_new0(CptraMboxExternReq, 1); + req->p = p; + req->cmd = cmd; + req->dlen = dlen; + if (len) { + req->req_data = g_memdup2(data, len); + req->req_len = len; + } + + object_ref(OBJECT(p)); + thread_pool_submit_aio(cptra_extern_worker, req, + cptra_extern_complete, req); +} + +static void cptra_extern_realize(DeviceState *dev, Error **errp) +{ + CptraMboxPeerExtern *p = CPTRA_MBOX_PEER_EXTERN(dev); + + if (!qemu_chr_fe_backend_connected(&p->chr)) { + error_setg(errp, "cptra-mbox-peer-extern requires a chardev attribute"); + return; + } + + qemu_chr_fe_set_open(&p->chr, true); + error_setg(&p->migration_blocker, + "Migration disabled: cptra-mbox-peer-extern chardev backend " + "state is external"); + if (migrate_add_blocker(&p->migration_blocker, errp) < 0) { + return; + } +} + +static void cptra_extern_finalize(Object *obj) +{ + CptraMboxPeerExtern *p = CPTRA_MBOX_PEER_EXTERN(obj); + + migrate_del_blocker(&p->migration_blocker); + qemu_chr_fe_deinit(&p->chr, false); +} + +static const Property cptra_extern_props[] = { + DEFINE_PROP_CHR("chardev", CptraMboxPeerExtern, chr), +}; + +static void cptra_extern_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + CptraMboxPeerClass *pc = CPTRA_MBOX_PEER_CLASS(oc); + + dc->desc = "Caliptra mailbox external (chardev) peer"; + dc->realize = cptra_extern_realize; + dc->hotpluggable = false; + device_class_set_props(dc, cptra_extern_props); + pc->handle_execute = cptra_extern_handle_execute; +} + +static const TypeInfo cptra_extern_type = { + .name = TYPE_CPTRA_MBOX_PEER_EXTERN, + .parent = TYPE_CPTRA_MBOX_PEER, + .instance_size = sizeof(CptraMboxPeerExtern), + .instance_finalize = cptra_extern_finalize, + .class_init = cptra_extern_class_init, +}; + +static void cptra_extern_register_types(void) +{ + type_register_static(&cptra_extern_type); +} + +type_init(cptra_extern_register_types) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -XXX,XX +XXX,XX @@ system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c')) system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( 'aspeed_cptra_mbox.c', + 'cptra_mbox_peer_extern.c', 'aspeed_hace.c', 'aspeed_lpc.c', 'aspeed_ltpi.c', -- 2.43.0
Instantiate the Caliptra mailbox in the AST1040 SoC and map its SRAM and CSR windows at the guest-visible MCI addresses. Add the 4 KiB MCI aperture controlled by the AST1040 SCU CPTRA page register, switch the SoC to the AST1040 SCU model, and expose a machine option that links an external mailbox peer after machine initialization. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- include/hw/arm/aspeed_soc.h | 11 ++++++ hw/arm/aspeed_ast1040.c | 60 ++++++++++++++++++++++++++++-- hw/arm/aspeed_ast1040_evb.c | 74 ++++++++++++++++++++++++++++++++++++- 3 files changed, 140 insertions(+), 5 deletions(-) diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -XXX,XX +XXX,XX @@ #include "hw/intc/arm_gicv3.h" #include "hw/misc/aspeed_ltpi.h" #include "hw/arm/aspeed_ast1700.h" +#include "hw/misc/aspeed_cptra_mbox.h" #define VBOOTROM_FILE_NAME "ast27x0_bootrom.bin" @@ -XXX,XX +XXX,XX @@ struct Aspeed10x0SoCState { #define TYPE_ASPEED10X0_SOC "aspeed10x0-soc" OBJECT_DECLARE_SIMPLE_TYPE(Aspeed10x0SoCState, ASPEED10X0_SOC) +struct Aspeed1040SoCState { + Aspeed10x0SoCState parent; + + AspeedCptraMboxState cptra_mbox; + MemoryRegion cptra_mci_window; +}; + +#define TYPE_ASPEED1040_SOC "ast1040-a0" +OBJECT_DECLARE_SIMPLE_TYPE(Aspeed1040SoCState, ASPEED1040_SOC) + struct AspeedSoCClass { DeviceClass parent_class; diff --git a/hw/arm/aspeed_ast1040.c b/hw/arm/aspeed_ast1040.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed_ast1040.c +++ b/hw/arm/aspeed_ast1040.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "qapi/error.h" -#include "system/address-spaces.h" #include "system/system.h" #include "hw/core/qdev-clock.h" #include "hw/misc/unimp.h" +#include "hw/misc/aspeed_cptra_mbox.h" #include "hw/arm/aspeed_soc.h" +#define AST1040_CPTRA_MCI_WINDOW_BASE 0x74200000 +#define AST1040_CPTRA_MCI_WINDOW_SIZE 0x1000 +#define AST1040_CPTRA_MBOX_SRAM_BASE 0x21400000 +#define AST1040_CPTRA_MBOX_CSR_BASE 0x21600000 + static const hwaddr aspeed_soc_ast1040_memmap[] = { [ASPEED_DEV_SRAM1] = 0x00000000, /* Hyper RAM */ [ASPEED_DEV_FMC] = 0x74000000, @@ -XXX,XX +XXX,XX @@ static qemu_irq aspeed_soc_ast1040_get_irq(AspeedSoCState *s, int dev) static void aspeed_soc_ast1040_init(Object *obj) { + Aspeed1040SoCState *a1040 = ASPEED1040_SOC(obj); Aspeed10x0SoCState *a = ASPEED10X0_SOC(obj); AspeedSoCState *s = ASPEED_SOC(obj); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast1040_init(Object *obj) s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0); - /* AST1040 uses the AST2700 SCUIO model */ - object_initialize_child(obj, "scu", &s->scu, TYPE_ASPEED_2700_SCUIO); + object_initialize_child(obj, "scu", &s->scu, TYPE_ASPEED_1040_SCU); qdev_prop_set_uint32(DEVICE(&s->scu), "silicon-rev", sc->silicon_rev); object_property_add_alias(obj, "hw-strap1", OBJECT(&s->scu), "hw-strap1"); object_property_add_alias(obj, "hw-strap2", OBJECT(&s->scu), "hw-strap2"); + object_initialize_child(obj, "cptra-mbox", &a1040->cptra_mbox, + TYPE_ASPEED_CPTRA_MBOX); for (i = 0; i < sc->uarts_num; i++) { object_initialize_child(obj, "uart[*]", &s->uart[i], TYPE_SERIAL_MM); @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast1040_init(Object *obj) TYPE_UNIMPLEMENTED_DEVICE); } +static bool aspeed_soc_ast1040_realize_cptra_mbox(Aspeed1040SoCState *a1040, + Error **errp) +{ + AspeedSoCState *s = ASPEED_SOC(a1040); + DeviceState *mbox = DEVICE(&a1040->cptra_mbox); + + /* + * Caliptra is an AST1040 integrated RoT IP. The external peer is + * optional; without a peer, the mailbox registers remain present and + * EXECUTE completes with CMD_FAILURE. + */ + if (!sysbus_realize(SYS_BUS_DEVICE(mbox), errp)) { + return false; + } + + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(mbox), 0, + AST1040_CPTRA_MBOX_SRAM_BASE); + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(mbox), 1, + AST1040_CPTRA_MBOX_CSR_BASE); + return true; +} + static void aspeed_soc_ast1040_realize(DeviceState *dev_soc, Error **errp) { + Aspeed1040SoCState *a1040 = ASPEED1040_SOC(dev_soc); Aspeed10x0SoCState *a = ASPEED10X0_SOC(dev_soc); AspeedSoCState *s = ASPEED_SOC(dev_soc); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast1040_realize(DeviceState *dev_soc, Error **errp) memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SRAM1], &s->sram[1]); + if (!aspeed_soc_ast1040_realize_cptra_mbox(a1040, errp)) { + return; + } + + /* + * The Caliptra MCI aperture is a 4 KiB alias whose target is selected by + * AST1040_SCU_CPTRA_PAGE_REG0. Firmware commonly switches it between + * mailbox SRAM (0x21400000) and mailbox CSR (0x21600000), then accesses + * the selected page through 0x74200000. + */ + memory_region_init_alias(&a1040->cptra_mci_window, OBJECT(s), + "aspeed.ast1040.cptra-mci-window", s->memory, + 0, AST1040_CPTRA_MCI_WINDOW_SIZE); + memory_region_add_subregion(s->memory, AST1040_CPTRA_MCI_WINDOW_BASE, + &a1040->cptra_mci_window); + /* The SCU drives the aperture's target offset via SCU_CPTRA_PAGE_REG0. */ + object_property_set_link(OBJECT(&s->scu), "cptra-page-window", + OBJECT(&a1040->cptra_mci_window), &error_abort); + object_property_set_uint(OBJECT(&s->scu), "cptra-page-window-base", + AST1040_CPTRA_MCI_WINDOW_BASE, &error_abort); + /* SCU */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->scu), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast1040_class_init(ObjectClass *klass, const void *data) static const TypeInfo aspeed_soc_ast1040_types[] = { { - .name = "ast1040-a0", + .name = TYPE_ASPEED1040_SOC, .parent = TYPE_ASPEED10X0_SOC, + .instance_size = sizeof(Aspeed1040SoCState), .instance_init = aspeed_soc_ast1040_init, .class_init = aspeed_soc_ast1040_class_init, } diff --git a/hw/arm/aspeed_ast1040_evb.c b/hw/arm/aspeed_ast1040_evb.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed_ast1040_evb.c +++ b/hw/arm/aspeed_ast1040_evb.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "qapi/error.h" +#include "qemu/error-report.h" #include "hw/arm/boot.h" #include "hw/arm/machines-qom.h" #include "hw/arm/aspeed.h" #include "hw/arm/aspeed_soc.h" #include "hw/core/qdev-clock.h" +#include "hw/misc/aspeed_cptra_mbox.h" #include "system/system.h" #define AST1040_INTERNAL_FLASH_SIZE (4 * MiB) /* Main SYSCLK frequency in Hz (400MHz) */ #define SYSCLK_FRQ 400000000ULL +#define TYPE_AST1040_EVB_MACHINE MACHINE_TYPE_NAME("ast1040-evb") +OBJECT_DECLARE_SIMPLE_TYPE(Ast1040EvbMachineState, AST1040_EVB_MACHINE) + +struct Ast1040EvbMachineState { + AspeedMachineState parent_obj; + + char *cptra_peer; + Notifier machine_done; +}; + +static void aspeed_bic_machine_done(Notifier *notifier, void *data) +{ + Ast1040EvbMachineState *m = container_of(notifier, + Ast1040EvbMachineState, + machine_done); + AspeedMachineState *bmc = ASPEED_MACHINE(m); + Aspeed1040SoCState *a1040 = ASPEED1040_SOC(bmc->soc); + bool ambiguous = false; + Object *peer; + Error *err = NULL; + + if (!m->cptra_peer) { + return; + } + + peer = object_resolve_path_type(m->cptra_peer, TYPE_CPTRA_MBOX_PEER, + &ambiguous); + if (!peer || ambiguous) { + error_report("cptra-peer: peer '%s' not found%s", + m->cptra_peer, ambiguous ? " (ambiguous)" : ""); + exit(1); + } + + if (!aspeed_cptra_mbox_set_peer(&a1040->cptra_mbox, + CPTRA_MBOX_PEER(peer), &err)) { + error_report_err(err); + exit(1); + } +} + static void aspeed_bic_machine_init(MachineState *machine) { AspeedMachineState *bmc = ASPEED_MACHINE(machine); @@ -XXX,XX +XXX,XX @@ static void aspeed_bic_machine_init(MachineState *machine) aspeed_connect_serial_hds_to_uarts(bmc); qdev_realize(DEVICE(bmc->soc), NULL, &error_abort); + if (AST1040_EVB_MACHINE(machine)->cptra_peer) { + AST1040_EVB_MACHINE(machine)->machine_done.notify = + aspeed_bic_machine_done; + qemu_add_machine_init_done_notifier( + &AST1040_EVB_MACHINE(machine)->machine_done); + } + armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, 0, AST1040_INTERNAL_FLASH_SIZE); } +static char *aspeed_bic_get_cptra_peer(Object *obj, Error **errp) +{ + Ast1040EvbMachineState *m = AST1040_EVB_MACHINE(obj); + + return g_strdup(m->cptra_peer); +} + +static void aspeed_bic_set_cptra_peer(Object *obj, const char *value, + Error **errp) +{ + Ast1040EvbMachineState *m = AST1040_EVB_MACHINE(obj); + + g_free(m->cptra_peer); + m->cptra_peer = g_strdup(value); +} + static void aspeed_machine_ast1040_evb_class_init(ObjectClass *oc, const void *data) { @@ -XXX,XX +XXX,XX @@ static void aspeed_machine_ast1040_evb_class_init(ObjectClass *oc, amc->macs_mask = 0; amc->uart_default = ASPEED_DEV_UART12; aspeed_machine_class_init_cpus_defaults(mc); + + object_class_property_add_str(oc, "cptra-peer", + aspeed_bic_get_cptra_peer, + aspeed_bic_set_cptra_peer); + object_class_property_set_description(oc, "cptra-peer", + "Caliptra mailbox peer object id"); } static const TypeInfo aspeed_ast1040_evb_types[] = { { - .name = MACHINE_TYPE_NAME("ast1040-evb"), + .name = TYPE_AST1040_EVB_MACHINE, .parent = TYPE_ASPEED_MACHINE, + .instance_size = sizeof(Ast1040EvbMachineState), .class_init = aspeed_machine_ast1040_evb_class_init, .interfaces = arm_machine_interfaces, } -- 2.43.0
Cover the AST1040 Caliptra mailbox execute flow through the external peer protocol and verify that the SCU CPTRA page register remaps the MCI aperture between CSR and SRAM pages. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- tests/qtest/aspeed_cptra_mbox-test.c | 223 +++++++++++++++++++++++++++ tests/qtest/meson.build | 3 +- 2 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/aspeed_cptra_mbox-test.c diff --git a/tests/qtest/aspeed_cptra_mbox-test.c b/tests/qtest/aspeed_cptra_mbox-test.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/aspeed_cptra_mbox-test.c @@ -XXX,XX +XXX,XX @@ +/* + * QTest for the ASPEED Caliptra mailbox on the ast1040-evb machine. + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest-single.h" +#include "qemu/sockets.h" + +/* AST1040 guest-visible mailbox bases. */ +#define SRAM_BASE 0x21400000ULL +#define CSR_BASE 0x21600000ULL + +#define CSR_LOCK 0x000 +#define CSR_USER 0x004 +#define CSR_CMD 0x010 +#define CSR_DLEN 0x014 +#define CSR_EXECUTE 0x018 +#define CSR_CMD_STATUS 0x020 + +#define STATUS_BUSY 0 +#define STATUS_COMPLETE 2 +#define STATUS_CMD_FAILURE 3 + +/* SCU CPTRA page-select register (SCU base 0x74C02000 + 0x120). */ +#define SCU_CPTRA_PAGE_REG0 0x74C02120ULL +/* MCI remap aperture. */ +#define MCI_WINDOW 0x74200000ULL + +/* Wire protocol. */ +#define PROTO_MAGIC 0x4D424F58u +#define PROTO_VERSION 1u +#define CMD_EXECUTE 1u +#define CMD_RESPONSE 2u + +static int peer_lfd = -1; +static int peer_fd = -1; + +static void peer_read(void *buf, size_t len) +{ + size_t off = 0; + + while (off < len) { + ssize_t r = read(peer_fd, (uint8_t *)buf + off, len - off); + g_assert_cmpint(r, >, 0); + off += r; + } +} + +static void peer_write(const void *buf, size_t len) +{ + size_t off = 0; + + while (off < len) { + ssize_t w = write(peer_fd, (const uint8_t *)buf + off, len - off); + g_assert_cmpint(w, >, 0); + off += w; + } +} + +static uint32_t ld32(const uint8_t *p) +{ + return p[0] | (p[1] << 8) | (p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void st32(uint8_t *p, uint32_t v) +{ + p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; +} + +/* + * Service one MBOX_EXECUTE: read the request, optionally check it, then reply + * with MBOX_RESPONSE carrying @rsp_status and @rsp_data. + */ +static void peer_serve_execute(uint32_t expect_cmd, uint32_t expect_dlen, + uint32_t rsp_status, + const uint8_t *rsp_data, uint32_t rsp_dlen) +{ + uint8_t hdr[12]; + uint32_t plen, cmd, dlen; + g_autofree uint8_t *payload = NULL; + uint32_t rsp_padded = (rsp_dlen + 3) & ~3u; + uint32_t rsp_plen = 8 + rsp_padded; + g_autofree uint8_t *rsp = g_malloc0(12 + rsp_plen); + + peer_read(hdr, sizeof(hdr)); + g_assert_cmpuint(ld32(hdr), ==, PROTO_MAGIC); + g_assert_cmpuint(hdr[4] | (hdr[5] << 8), ==, PROTO_VERSION); + g_assert_cmpuint(hdr[6] | (hdr[7] << 8), ==, CMD_EXECUTE); + plen = ld32(hdr + 8); + g_assert_cmpuint(plen, >=, 8); + + payload = g_malloc(plen); + peer_read(payload, plen); + cmd = ld32(payload); + dlen = ld32(payload + 4); + g_assert_cmpuint(cmd, ==, expect_cmd); + g_assert_cmpuint(dlen, ==, expect_dlen); + + st32(rsp, PROTO_MAGIC); + rsp[4] = PROTO_VERSION; rsp[5] = 0; + rsp[6] = CMD_RESPONSE; rsp[7] = 0; + st32(rsp + 8, rsp_plen); + st32(rsp + 12, rsp_status); + st32(rsp + 16, rsp_dlen); + if (rsp_dlen) { + memcpy(rsp + 20, rsp_data, rsp_dlen); + } + peer_write(rsp, 12 + rsp_plen); +} + +static uint32_t poll_cmd_status(void) +{ + uint32_t s = STATUS_BUSY; + int tries = 1000; + + while (tries--) { + s = qtest_readl(global_qtest, CSR_BASE + CSR_CMD_STATUS); + if (s != STATUS_BUSY) { + break; + } + } + return s; +} + +/* + * Realistic mailbox transaction: acquire the lock, run an EXECUTE roundtrip + * against the peer, verify the response lands in SRAM, then release. + */ +static void test_execute(void) +{ + static const uint8_t resp[] = { + 0x11, 0xf9, 0xff, 0xff, /* checksum */ + 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, /* inner dlen = 20 */ + 'C', 'a', 'l', 'i', 'p', 't', 'r', 'a', + '_', 'C', 'o', 'r', 'e', '_', 'v', '2', '.', '0', '.', '0', + }; + uint32_t status; + + /* + * Acquire: first read returns 0, USER reflects the SoC agent, busy after. + */ + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 0); + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_USER), ==, 1); + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 1); + + qtest_writel(global_qtest, SRAM_BASE + 0, 0xfffffec0); + qtest_writel(global_qtest, CSR_BASE + CSR_CMD, 0x4d465756); + qtest_writel(global_qtest, CSR_BASE + CSR_DLEN, 8); + qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 1); + + peer_serve_execute(0x4d465756, 8, STATUS_COMPLETE, resp, sizeof(resp)); + + status = poll_cmd_status(); + g_assert_cmpuint(status, ==, STATUS_COMPLETE); + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_DLEN), + ==, sizeof(resp)); + g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 0), ==, 0xfffff911); + g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 8), ==, 0x14); + g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 12), ==, 0x696c6143); + + /* Completing the transaction (EXECUTE 1->0) releases the lock. */ + qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 0); + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 0); + qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 0); +} + +/* SCU CPTRA_PAGE_REG0 selects the target page seen through the MCI window. */ +static void test_scu_remap(void) +{ + /* Window pointed at the CSR page exposes the CMD register. */ + qtest_writel(global_qtest, CSR_BASE + CSR_CMD, 0xdeadbeef); + qtest_writel(global_qtest, SCU_CPTRA_PAGE_REG0, CSR_BASE); + g_assert_cmpuint(qtest_readl(global_qtest, MCI_WINDOW + CSR_CMD), + ==, 0xdeadbeef); + + /* Re-point at the SRAM page. */ + qtest_writel(global_qtest, SRAM_BASE + 0, 0x12345678); + qtest_writel(global_qtest, SCU_CPTRA_PAGE_REG0, SRAM_BASE); + g_assert_cmpuint(qtest_readl(global_qtest, MCI_WINDOW + 0), + ==, 0x12345678); +} + +int main(int argc, char **argv) +{ + g_autofree char *sock_path = NULL; + g_autofree char *cmdline = NULL; + int ret; + + g_test_init(&argc, &argv, NULL); + + sock_path = g_strdup_printf("%s/cptra-mbox-%u.sock", + g_get_tmp_dir(), getpid()); + unlink(sock_path); + peer_lfd = qtest_socket_server(sock_path); + + cmdline = g_strdup_printf( + "-machine ast1040-evb,cptra-peer=peer0 " + "-chardev socket,id=cptra0,path=%s " + "-device cptra-mbox-peer-extern,id=peer0,chardev=cptra0", sock_path); + qtest_start(cmdline); + + peer_fd = accept(peer_lfd, NULL, NULL); + g_assert_cmpint(peer_fd, >=, 0); + + qtest_add_func("/cptra-mbox/execute", test_execute); + qtest_add_func("/cptra-mbox/scu-remap", test_scu_remap); + + ret = g_test_run(); + + qtest_end(); + if (peer_fd >= 0) { + close(peer_fd); + } + close(peer_lfd); + unlink(sock_path); + + return ret; +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -XXX,XX +XXX,XX @@ qtests_aspeed = \ ['aspeed_gpio-test', 'aspeed_hace-test', 'aspeed_scu-test', - 'aspeed_smc-test'] + 'aspeed_smc-test', + 'aspeed_cptra_mbox-test'] qtests_aspeed64 = \ ['ast2700-gpio-test', 'ast2700-hace-test', -- 2.43.0
Document the AST1040 Caliptra MCI mailbox windows, the SCU-controlled remap aperture, and how to connect the external mailbox peer through a chardev backend. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- docs/system/arm/aspeed.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/aspeed.rst +++ b/docs/system/arm/aspeed.rst @@ -XXX,XX +XXX,XX @@ Supported devices * ADC * Secure Boot Controller * PECI Controller (minimal) + * Caliptra MCI mailbox (AST1040 only) Missing devices @@ -XXX,XX +XXX,XX @@ To boot a kernel directly from a Zephyr build tree: $ qemu-system-arm -M ast1030-evb -nographic \ -kernel zephyr.bin + +Caliptra MCI mailbox (ast1040-evb) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The AST1040 chip provides a mechanism to map the Caliptra MCI mailbox +register space into the Cortex-M4F address space. The Caliptra MCI mailbox +SRAM and CSR are accessible at fixed addresses: + +- Mailbox SRAM at ``0x21400000`` (2 MiB, command/response payload) +- Mailbox CSR at ``0x21600000`` (4 KiB, LOCK/CMD/DLEN/EXECUTE/STATUS) + +The SCU ``CPTRA_PAGE_REG0`` register (at ``0x74C02120``) selects which +Caliptra MCI page (SRAM or CSR) is exposed through the 4 KiB aperture at +``0x74200000``, allowing firmware to access either window through a single +address. + +By default the mailbox has no Caliptra peer, so writing ``EXECUTE`` reports +``CMD_FAILURE``. To service commands with an external Caliptra simulator (for +example caliptra-server) over a UNIX socket, start the simulator first, then +create an external peer device and link it with the machine's ``cptra-peer`` +option: + +.. code-block:: bash + + $ qemu-system-arm -M ast1040-evb,cptra-peer=peer0 -nographic \ + -chardev socket,id=cptra0,path=/tmp/mcu_mbox.sock \ + -device cptra-mbox-peer-extern,id=peer0,chardev=cptra0 \ + -kernel zephyr.elf + +Writing ``EXECUTE`` bridges the command and the mailbox SRAM payload to the +peer over the socket, and writes the response back into the mailbox SRAM. -- 2.43.0
This series adds AST1040 Caliptra MCI mailbox support for the ast1040-evb machine. AST1040 firmware accesses Caliptra through an MCI page aperture. This series adds an AST1040 SCU model with the CPTRA page-select registers, a mailbox frontend that exposes the Caliptra SRAM and CSR windows, and an optional external chardev peer so a Caliptra simulator can service EXECUTE requests. The SoC wiring maps the mailbox SRAM, CSR window and 4 KiB remap aperture. The EVB machine also gets a cptra-peer option for linking the external peer after the machine is initialized. The external peer backend is intended for co-simulation with an external Caliptra model. The current bring-up backend used for manual testing is caliptra-server: https://github.com/stevenlee7189/caliptra-server/blob/main-2.x/README.md That README includes the server command line, the matching QEMU command line, and the guest-side MFWV smoke test sequence used to validate the MCI aperture, LOCK/CMD/DLEN/EXECUTE flow, and FirmwareVersion response. The qtest added by this series does not depend on that repository; it uses a socket-backed test peer to validate the QEMU-side protocol and mailbox flow. Resending because some reviewers did not receive the previous email. No changes since the prior posting. Patch layout: - patches 1-2 add the AST1040 SCU model and mailbox frontend - patch 3 adds the external chardev peer backend - patch 4 wires the mailbox into the AST1040 SoC and EVB machine - patches 5-6 add qtest coverage and documentation The qtest covers mailbox lock, execute and completion flow through a socket-backed peer, and verifies that SCU CPTRA_PAGE_REG0 remaps the MCI aperture between CSR and SRAM pages. Validation: - scripts/checkpatch.pl --branch origin/master..HEAD - qtest coverage for the AST1040 mailbox execute path with a socket-backed external peer - manual co-simulation bring-up with caliptra-server through cptra-mbox-peer-extern Steven Lee (6): hw/misc/aspeed_scu: Add AST1040 SCU model hw/misc: Add ASPEED Caliptra mailbox frontend hw/misc: Add external Caliptra mailbox peer hw/arm/aspeed_ast1040: Wire Caliptra mailbox tests/qtest: Add ASPEED Caliptra mailbox test docs/system/arm: Document AST1040 Caliptra mailbox MAINTAINERS | 1 + docs/system/arm/aspeed.rst | 32 +++ include/hw/arm/aspeed_soc.h | 11 + include/hw/misc/aspeed_cptra_mbox.h | 133 ++++++++++ include/hw/misc/aspeed_scu.h | 10 + hw/arm/aspeed_ast1040.c | 60 ++++- hw/arm/aspeed_ast1040_evb.c | 74 +++++- hw/misc/aspeed_cptra_mbox.c | 380 +++++++++++++++++++++++++++ hw/misc/aspeed_scu.c | 251 ++++++++++++++++++ hw/misc/cptra_mbox_peer_extern.c | 318 ++++++++++++++++++++++ tests/qtest/aspeed_cptra_mbox-test.c | 223 ++++++++++++++++ hw/misc/meson.build | 2 + hw/misc/trace-events | 6 + tests/qtest/meson.build | 3 +- 14 files changed, 1498 insertions(+), 6 deletions(-) create mode 100644 include/hw/misc/aspeed_cptra_mbox.h create mode 100644 hw/misc/aspeed_cptra_mbox.c create mode 100644 hw/misc/cptra_mbox_peer_extern.c create mode 100644 tests/qtest/aspeed_cptra_mbox-test.c -- 2.43.0
Add an AST1040 variant of the ASPEED SCU with reset values, APB clock calculation, clock-stop handling, and CPTRA page-select registers. Expose the Caliptra page aperture as a QOM link so the SoC model can wire the remap window without callbacks. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- include/hw/misc/aspeed_scu.h | 10 ++ hw/misc/aspeed_scu.c | 251 +++++++++++++++++++++++++++++++++++ hw/misc/trace-events | 2 + 3 files changed, 263 insertions(+) diff --git a/include/hw/misc/aspeed_scu.h b/include/hw/misc/aspeed_scu.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/misc/aspeed_scu.h +++ b/include/hw/misc/aspeed_scu.h @@ -XXX,XX +XXX,XX @@ OBJECT_DECLARE_TYPE(AspeedSCUState, AspeedSCUClass, ASPEED_SCU) #define TYPE_ASPEED_2700_SCU TYPE_ASPEED_SCU "-ast2700" #define TYPE_ASPEED_2700_SCUIO TYPE_ASPEED_SCU "io" "-ast2700" #define TYPE_ASPEED_1030_SCU TYPE_ASPEED_SCU "-ast1030" +#define TYPE_ASPEED_1040_SCU TYPE_ASPEED_SCU "-ast1040" #define ASPEED_SCU_NR_REGS (0x1A8 >> 2) #define ASPEED_AST2600_SCU_NR_REGS (0xE20 >> 2) @@ -XXX,XX +XXX,XX @@ struct AspeedSCUState { uint32_t hw_strap1; uint32_t hw_strap2; uint32_t hw_prot_key; + + /* + * AST1040 only: alias region (the Caliptra MCI page aperture) whose + * target offset is selected by SCU_CPTRA_PAGE_REG0, linked in by the SoC. + * cptra_page_window_base is its base in the address map, used to reject a + * self-referential remap. + */ + MemoryRegion *cptra_page_window; + uint64_t cptra_page_window_base; }; #define AST2400_A1_SILICON_REV 0x02010303U diff --git a/hw/misc/aspeed_scu.c b/hw/misc/aspeed_scu.c index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/aspeed_scu.c +++ b/hw/misc/aspeed_scu.c @@ -XXX,XX +XXX,XX @@ #include "qapi/visitor.h" #include "qemu/bitops.h" #include "qemu/log.h" +#include "qemu/range.h" #include "qemu/guest-random.h" #include "qemu/module.h" #include "trace.h" @@ -XXX,XX +XXX,XX @@ #define AST2700_SCUIO_CLK_DUTY_MEAS_RST TO_REG(0x388) #define AST2700_SCUIO_FREQ_CNT_CTL TO_REG(0x3A0) +/* AST1040 SCU */ +#define AST1040_SILICON_REV TO_REG(0x00) +#define AST1040_HW_STRAP1 TO_REG(0x10) +#define AST1040_HW_STRAP1_CLR TO_REG(0x14) +#define AST1040_HW_STRAP1_LOCK TO_REG(0x20) +#define AST1040_HW_STRAP1_SEC1 TO_REG(0x24) +#define AST1040_SCU_CPTRA_PAGE_REG0 TO_REG(0x120) +#define AST1040_SCU_CPTRA_PAGE_REG1 TO_REG(0x124) +#define AST1040_SCU_CPTRA_PAGE_REG2 TO_REG(0x128) +#define AST1040_SCU_CPTRA_PAGE_REG3 TO_REG(0x12C) +#define AST1040_SCU_CPTRA_PAGE_REG4 TO_REG(0x130) +#define AST1040_SCU_CPTRA_PAGE_REG5 TO_REG(0x134) +#define AST1040_SCU_CLK_STOP_CTL_1 TO_REG(0x240) +#define AST1040_SCU_CLK_STOP_CLR_1 TO_REG(0x244) +#define AST1040_SCU_CLK_STOP_CTL_2 TO_REG(0x260) +#define AST1040_SCU_CLK_STOP_CLR_2 TO_REG(0x264) +#define AST1040_SCU_CLK_SEL_1 TO_REG(0x280) +#define AST1040_SCU_CLK_SEL_2 TO_REG(0x284) +#define AST1040_SCU_HPLL_PARAM TO_REG(0x300) +#define AST1040_SCU_HPLL_EXT_PARAM TO_REG(0x304) +#define AST1040_SCU_APLL_PARAM TO_REG(0x310) +#define AST1040_SCU_APLL_EXT_PARAM TO_REG(0x314) +#define AST1040_SCU_DPLL_PARAM TO_REG(0x320) +#define AST1040_SCU_DPLL_EXT_PARAM TO_REG(0x324) +#define AST1040_SCU_DPLL_PARAM_READ TO_REG(0x328) +#define AST1040_SCU_DPLL_EXT_PARAM_READ TO_REG(0x32c) +#define AST1040_SCU_UARTCLK_GEN TO_REG(0x330) +#define AST1040_SCU_HUARTCLK_GEN TO_REG(0x334) +#define AST1040_SCU_CLK_DUTY_MEAS_RST TO_REG(0x388) +#define AST1040_SCU_FREQ_CNT_CTL TO_REG(0x3A0) +#define AST1040_SCU_CLK_GET_PCLK_DIV(x) (((x) >> 18) & 0x7) + +#define ASPEED_AST1040_SCU_NR_REGS (0xE20 >> 2) +#define AST1040_SCU_CPTRA_PAGE_SIZE 0x1000 + #define SCU_IO_REGION_SIZE 0x1000 static const uint32_t ast2400_a0_resets[ASPEED_SCU_NR_REGS] = { @@ -XXX,XX +XXX,XX @@ static const TypeInfo aspeed_1030_scu_info = { .class_init = aspeed_1030_scu_class_init, }; +/* AST1040 SCU */ + +static void aspeed_ast1040_scu_update_cptra_page(AspeedSCUState *s) +{ + hwaddr page_base = s->regs[AST1040_SCU_CPTRA_PAGE_REG0] & + ~(hwaddr)(AST1040_SCU_CPTRA_PAGE_SIZE - 1); + + if (!s->cptra_page_window) { + return; + } + + if (ranges_overlap(page_base, AST1040_SCU_CPTRA_PAGE_SIZE, + s->cptra_page_window_base, + AST1040_SCU_CPTRA_PAGE_SIZE)) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: refusing self-referential remap to 0x%" HWADDR_PRIx + "\n", __func__, page_base); + memory_region_set_enabled(s->cptra_page_window, false); + return; + } + + memory_region_set_alias_offset(s->cptra_page_window, page_base); + memory_region_set_enabled(s->cptra_page_window, true); +} + +static uint64_t aspeed_ast1040_scu_read(void *opaque, hwaddr offset, + unsigned size) +{ + AspeedSCUState *s = ASPEED_SCU(opaque); + int reg = TO_REG(offset); + + if (reg >= ASPEED_AST1040_SCU_NR_REGS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds read at offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + return 0; + } + + trace_aspeed_ast1040_scu_read(offset, size, s->regs[reg]); + return s->regs[reg]; +} + +static void aspeed_ast1040_scu_write(void *opaque, hwaddr offset, + uint64_t data64, unsigned size) +{ + AspeedSCUState *s = ASPEED_SCU(opaque); + int reg = TO_REG(offset); + uint32_t data = data64; + bool updated = false; + + if (reg >= ASPEED_AST1040_SCU_NR_REGS) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Out-of-bounds write at offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + return; + } + + trace_aspeed_ast1040_scu_write(offset, size, data); + + switch (reg) { + case AST1040_SCU_CLK_STOP_CTL_1: + case AST1040_SCU_CLK_STOP_CTL_2: + s->regs[reg] |= data; + updated = true; + break; + case AST1040_SCU_CLK_STOP_CLR_1: + case AST1040_SCU_CLK_STOP_CLR_2: + s->regs[reg - 1] &= ~data; + updated = true; + break; + case AST1040_SCU_FREQ_CNT_CTL: + s->regs[reg] = deposit32(s->regs[reg], 6, 1, !!(data & BIT(1))); + updated = true; + break; + case AST1040_SCU_CPTRA_PAGE_REG0: + s->regs[reg] = data; + aspeed_ast1040_scu_update_cptra_page(s); + updated = true; + break; + case AST1040_SCU_CPTRA_PAGE_REG1: + case AST1040_SCU_CPTRA_PAGE_REG2: + case AST1040_SCU_CPTRA_PAGE_REG3: + case AST1040_SCU_CPTRA_PAGE_REG4: + case AST1040_SCU_CPTRA_PAGE_REG5: + s->regs[reg] = data; + updated = true; + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Unhandled write at offset 0x%" HWADDR_PRIx "\n", + __func__, offset); + break; + } + + if (!updated) { + s->regs[reg] = data; + } +} + +static const MemoryRegionOps aspeed_ast1040_scu_ops = { + .read = aspeed_ast1040_scu_read, + .write = aspeed_ast1040_scu_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .impl.min_access_size = 4, + .impl.max_access_size = 4, + .valid.min_access_size = 1, + .valid.max_access_size = 8, + .valid.unaligned = false, +}; + +static uint32_t aspeed_1040_scu_calc_hpll(AspeedSCUState *s, uint32_t hpll_reg) +{ + uint32_t multiplier = 1; + uint32_t clkin = aspeed_scu_get_clkin(s); + + if (hpll_reg & SCU_AST2600_H_PLL_OFF) { + return 0; + } + + if (!(hpll_reg & SCU_AST2600_H_PLL_BYPASS_EN)) { + uint32_t p = (hpll_reg >> 19) & 0xf; + uint32_t n = (hpll_reg >> 13) & 0x3f; + uint32_t m = hpll_reg & 0x1fff; + + multiplier = ((m + 1) / (n + 1)) / (p + 1); + } + + return clkin * multiplier; +} + +static uint32_t aspeed_1040_scu_get_apb_freq(AspeedSCUState *s) +{ + AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(s); + uint32_t hpll = asc->calc_hpll(s, s->regs[AST1040_SCU_HPLL_PARAM]); + + return hpll / + (AST1040_SCU_CLK_GET_PCLK_DIV(s->regs[AST1040_SCU_CLK_SEL_1]) + 1) + / asc->apb_divider; +} + +static void aspeed_ast1040_scu_reset_hold(Object *obj, ResetType type) +{ + AspeedSCUState *s = ASPEED_SCU(obj); + AspeedSCUClass *asc = ASPEED_SCU_GET_CLASS(obj); + + memcpy(s->regs, asc->resets, asc->nr_regs * 4); + s->regs[AST1040_SILICON_REV] = s->silicon_rev; + s->regs[AST1040_HW_STRAP1] = s->hw_strap1; + aspeed_ast1040_scu_update_cptra_page(s); +} + +static const uint32_t ast1040_a0_scu_resets[ASPEED_AST1040_SCU_NR_REGS] = { + [AST1040_HW_STRAP1_CLR] = 0xFFF0FFF0, + [AST1040_HW_STRAP1_LOCK] = 0x00000FFF, + [AST1040_HW_STRAP1_SEC1] = 0x000000FF, + [AST1040_SCU_CLK_STOP_CTL_1] = 0xffff8400, + [AST1040_SCU_CLK_STOP_CTL_2] = 0x00005f30, + [AST1040_SCU_CLK_SEL_1] = 0x86900000, + [AST1040_SCU_CLK_SEL_2] = 0x00400000, + [AST1040_SCU_HPLL_PARAM] = 0x10000027, + [AST1040_SCU_HPLL_EXT_PARAM] = 0x80000014, + [AST1040_SCU_APLL_PARAM] = 0x1000001f, + [AST1040_SCU_APLL_EXT_PARAM] = 0x8000000f, + [AST1040_SCU_DPLL_PARAM] = 0x106e42ce, + [AST1040_SCU_DPLL_EXT_PARAM] = 0x80000167, + [AST1040_SCU_DPLL_PARAM_READ] = 0x106e42ce, + [AST1040_SCU_DPLL_EXT_PARAM_READ] = 0x80000167, + [AST1040_SCU_UARTCLK_GEN] = 0x00014506, + [AST1040_SCU_HUARTCLK_GEN] = 0x000145c0, + [AST1040_SCU_CLK_DUTY_MEAS_RST] = 0x0c9100d2, + [AST1040_SCU_FREQ_CNT_CTL] = 0x00000080, +}; + +static const Property aspeed_1040_scu_props[] = { + DEFINE_PROP_UINT64("cptra-page-window-base", AspeedSCUState, + cptra_page_window_base, 0), +}; + +static void aspeed_1040_scu_init(Object *obj) +{ + AspeedSCUState *s = ASPEED_SCU(obj); + + object_property_add_link(obj, "cptra-page-window", TYPE_MEMORY_REGION, + (Object **)&s->cptra_page_window, + qdev_prop_allow_set_link_before_realize, + OBJ_PROP_LINK_STRONG); +} + +static void aspeed_1040_scu_class_init(ObjectClass *klass, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ResettableClass *rc = RESETTABLE_CLASS(klass); + AspeedSCUClass *asc = ASPEED_SCU_CLASS(klass); + + dc->desc = "ASPEED 1040 System Control Unit"; + rc->phases.hold = aspeed_ast1040_scu_reset_hold; + device_class_set_props(dc, aspeed_1040_scu_props); + asc->resets = ast1040_a0_scu_resets; + asc->calc_hpll = aspeed_1040_scu_calc_hpll; + asc->get_apb = aspeed_1040_scu_get_apb_freq; + asc->apb_divider = 2; + asc->nr_regs = ASPEED_AST1040_SCU_NR_REGS; + asc->clkin_25Mhz = true; + asc->ops = &aspeed_ast1040_scu_ops; +} + +static const TypeInfo aspeed_1040_scu_info = { + .name = TYPE_ASPEED_1040_SCU, + .parent = TYPE_ASPEED_SCU, + .instance_size = sizeof(AspeedSCUState), + .instance_init = aspeed_1040_scu_init, + .class_init = aspeed_1040_scu_class_init, +}; + static void aspeed_scu_register_types(void) { type_register_static(&aspeed_scu_info); @@ -XXX,XX +XXX,XX @@ static void aspeed_scu_register_types(void) type_register_static(&aspeed_1030_scu_info); type_register_static(&aspeed_2700_scu_info); type_register_static(&aspeed_2700_scuio_info); + type_register_static(&aspeed_1040_scu_info); } type_init(aspeed_scu_register_types); diff --git a/hw/misc/trace-events b/hw/misc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -XXX,XX +XXX,XX @@ aspeed_ast2700_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" aspeed_ast2700_scu_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_ast2700_scuio_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_ast2700_scuio_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 +aspeed_ast1040_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 +aspeed_ast1040_scu_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 # mps2-scc.c mps2_scc_read(uint64_t offset, uint64_t data, unsigned size) "MPS2 SCC read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" -- 2.43.0
Add a Caliptra mailbox frontend device with SRAM and CSR regions for the AST1040 MCI mailbox window. Model the mailbox lock, command registers, execute flow and completion path through a pluggable peer interface so backends can service commands synchronously or asynchronously. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- include/hw/misc/aspeed_cptra_mbox.h | 133 ++++++++++ hw/misc/aspeed_cptra_mbox.c | 380 ++++++++++++++++++++++++++++ hw/misc/meson.build | 1 + hw/misc/trace-events | 4 + 4 files changed, 518 insertions(+) create mode 100644 include/hw/misc/aspeed_cptra_mbox.h create mode 100644 hw/misc/aspeed_cptra_mbox.c diff --git a/include/hw/misc/aspeed_cptra_mbox.h b/include/hw/misc/aspeed_cptra_mbox.h new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/include/hw/misc/aspeed_cptra_mbox.h @@ -XXX,XX +XXX,XX @@ +/* + * ASPEED Caliptra mailbox model (Caliptra 2.x subsystem mode) + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#ifndef HW_MISC_ASPEED_CPTRA_MBOX_H +#define HW_MISC_ASPEED_CPTRA_MBOX_H + +#include "qom/object.h" +#include "hw/core/sysbus.h" + +typedef struct Error Error; + +/* SRAM size: 2 MiB (matches MCU_MAILBOX0_SRAM_SIZE in caliptra-mcu-sw) */ +#define CPTRA_MBOX0_SRAM_SIZE (2u * 1024u * 1024u) +#define CPTRA_MBOX0_SRAM_WORDS (CPTRA_MBOX0_SRAM_SIZE / 4) + +/* CSR window: 4 KiB (covers all defined registers) */ +#define CPTRA_MBOX0_CSR_SIZE 0x1000u + +/* CSR register offsets (relative to CSR BAR) */ +#define CPTRA_MBOX0_LOCK_OFF 0x000 +#define CPTRA_MBOX0_USER_OFF 0x004 +#define CPTRA_MBOX0_TARGET_USER_OFF 0x008 +#define CPTRA_MBOX0_TARGET_USER_VAL_OFF 0x00C +#define CPTRA_MBOX0_CMD_OFF 0x010 +#define CPTRA_MBOX0_DLEN_OFF 0x014 +#define CPTRA_MBOX0_EXECUTE_OFF 0x018 +#define CPTRA_MBOX0_TARGET_STATUS_OFF 0x01C +#define CPTRA_MBOX0_CMD_STATUS_OFF 0x020 +#define CPTRA_MBOX0_HW_STATUS_OFF 0x024 + +/* CMD_STATUS values */ +#define CPTRA_MBOX0_STATUS_BUSY 0 +#define CPTRA_MBOX0_STATUS_DATA_READY 1 +#define CPTRA_MBOX0_STATUS_COMPLETE 2 +#define CPTRA_MBOX0_STATUS_CMD_FAILURE 3 + +/* SoC agent ID reported in the USER register when the lock is acquired */ +#define CPTRA_MBOX0_SOC_USER_ID 1u + +/* + * Caliptra mailbox interface (frontend), implemented as a QOM interface so + * that backends can deliver an asynchronous response without depending on the + * concrete frontend device type. + */ +#define TYPE_CPTRA_MBOX_IF "cptra-mbox-if" +typedef struct CptraMboxIfClass CptraMboxIfClass; +DECLARE_CLASS_CHECKERS(CptraMboxIfClass, CPTRA_MBOX_IF, TYPE_CPTRA_MBOX_IF) +typedef struct CptraMboxIf CptraMboxIf; +#define CPTRA_MBOX_IF(obj) \ + INTERFACE_CHECK(CptraMboxIf, (obj), TYPE_CPTRA_MBOX_IF) + +struct CptraMboxIfClass { + InterfaceClass parent; + + /* + * Called by the peer when a command submitted via handle_execute() has + * completed. @status is a CPTRA_MBOX0_STATUS_* value; @data/@len carry the + * response payload to be written back into the mailbox SRAM (@len bytes, + * @dlen is the reported DLEN). + */ + void (*complete)(CptraMboxIf *s, uint32_t status, uint32_t dlen, + const uint8_t *data, uint32_t len); +}; + +/* + * Caliptra mailbox peer (backend) base class. + */ +#define TYPE_CPTRA_MBOX_PEER "cptra-mbox-peer" +OBJECT_DECLARE_TYPE(CptraMboxPeer, CptraMboxPeerClass, CPTRA_MBOX_PEER) + +struct CptraMboxPeer { + DeviceState parent; + + /* Set by the frontend when this peer is linked to it. */ + CptraMboxIf *intf; +}; + +struct CptraMboxPeerClass { + DeviceClass parent; + + /* + * Process a command. @data/@len is a copy of the request payload from the + * mailbox SRAM. The peer must eventually report completion by calling the + * interface's complete() method (synchronously or asynchronously). + */ + void (*handle_execute)(CptraMboxPeer *p, uint32_t cmd, uint32_t dlen, + const uint8_t *data, uint32_t len); + + /* Optional: notify the peer of a mailbox reset. */ + void (*handle_reset)(CptraMboxPeer *p); +}; + +/* Concrete frontend device type. */ +#define TYPE_ASPEED_CPTRA_MBOX "aspeed-cptra-mbox" +OBJECT_DECLARE_SIMPLE_TYPE(AspeedCptraMboxState, ASPEED_CPTRA_MBOX) + +struct AspeedCptraMboxState { + SysBusDevice parent_obj; + + /* Linked backend; NULL means no Caliptra peer is present. */ + CptraMboxPeer *peer; + + bool locked; + bool command_pending; + bool release_pending; + uint32_t user; + uint32_t target_user; + uint32_t target_user_valid; + uint32_t cmd; + uint32_t dlen; + uint32_t execute; + uint32_t target_status; + uint32_t cmd_status; + uint32_t hw_status; + + uint32_t sram[CPTRA_MBOX0_SRAM_WORDS]; + + MemoryRegion sram_mr; + MemoryRegion csr_mr; +}; + +bool aspeed_cptra_mbox_set_peer(AspeedCptraMboxState *s, CptraMboxPeer *peer, + Error **errp); + +/* Concrete external (chardev) peer type. */ +#define TYPE_CPTRA_MBOX_PEER_EXTERN "cptra-mbox-peer-extern" + +#endif /* HW_MISC_ASPEED_CPTRA_MBOX_H */ diff --git a/hw/misc/aspeed_cptra_mbox.c b/hw/misc/aspeed_cptra_mbox.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/misc/aspeed_cptra_mbox.c @@ -XXX,XX +XXX,XX @@ +/* + * ASPEED Caliptra mailbox host interface (frontend) and peer base class. + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "hw/misc/aspeed_cptra_mbox.h" +#include "hw/core/qdev-properties.h" +#include "migration/vmstate.h" +#include "qapi/error.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "system/memory.h" +#include "trace.h" + +static size_t cptra_mbox_padded_len(uint32_t dlen) +{ + return (size_t)((dlen + 3) / 4) * 4; +} + +static void cptra_mbox_clear(AspeedCptraMboxState *s) +{ + s->locked = false; + s->release_pending = false; + s->user = 0; + s->target_user = 0; + s->target_user_valid = 0; + s->cmd = 0; + s->dlen = 0; + s->execute = 0; + s->target_status = 0; + s->cmd_status = 0; + s->hw_status = 0; + memset(s->sram, 0, sizeof(s->sram)); +} + +static void cptra_mbox_submit(AspeedCptraMboxState *s) +{ + CptraMboxPeerClass *pc; + g_autofree uint8_t *data = NULL; + size_t len; + + if (s->dlen > CPTRA_MBOX0_SRAM_SIZE) { + s->cmd_status = CPTRA_MBOX0_STATUS_CMD_FAILURE; + qemu_log_mask(LOG_GUEST_ERROR, + "%s: DLEN 0x%x exceeds SRAM size\n", __func__, s->dlen); + return; + } + + if (!s->peer) { + /* No Caliptra peer present: the command cannot be serviced. */ + s->cmd_status = CPTRA_MBOX0_STATUS_CMD_FAILURE; + return; + } + + len = cptra_mbox_padded_len(s->dlen); + if (len) { + data = g_malloc(len); + for (size_t i = 0; i < len / 4; i++) { + stl_le_p(data + i * 4, s->sram[i]); + } + } + + s->command_pending = true; + s->release_pending = false; + trace_cptra_mbox_execute(s->cmd, s->dlen); + + pc = CPTRA_MBOX_PEER_GET_CLASS(s->peer); + pc->handle_execute(s->peer, s->cmd, s->dlen, data, len); +} + +/* CptraMboxIf::complete - called by the peer when a command finishes. */ +static void cptra_mbox_complete(CptraMboxIf *iface, uint32_t status, + uint32_t dlen, const uint8_t *data, + uint32_t len) +{ + AspeedCptraMboxState *s = ASPEED_CPTRA_MBOX(iface); + + s->command_pending = false; + trace_cptra_mbox_complete(status, dlen); + + if (s->release_pending) { + cptra_mbox_clear(s); + return; + } + + /* + * Report the peer's status verbatim and write back whatever response + * payload it returned. The peer signals a transport/command failure with + * CPTRA_MBOX0_STATUS_CMD_FAILURE and no data. + */ + s->cmd_status = status; + s->dlen = dlen; + if (data && len) { + size_t words = MIN(len, CPTRA_MBOX0_SRAM_SIZE) / 4; + + for (size_t i = 0; i < words; i++) { + s->sram[i] = ldl_le_p(data + i * 4); + } + } +} + +static uint64_t cptra_mbox_sram_read(void *opaque, hwaddr offset, unsigned size) +{ + AspeedCptraMboxState *s = opaque; + uint32_t idx = offset / 4; + + if (idx >= CPTRA_MBOX0_SRAM_WORDS) { + return 0; + } + return s->sram[idx]; +} + +static void cptra_mbox_sram_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + AspeedCptraMboxState *s = opaque; + uint32_t idx = offset / 4; + + if (idx < CPTRA_MBOX0_SRAM_WORDS) { + s->sram[idx] = (uint32_t)value; + } +} + +static const MemoryRegionOps cptra_mbox_sram_ops = { + .read = cptra_mbox_sram_read, + .write = cptra_mbox_sram_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static uint64_t cptra_mbox_csr_read(void *opaque, hwaddr offset, unsigned size) +{ + AspeedCptraMboxState *s = opaque; + + switch (offset) { + case CPTRA_MBOX0_LOCK_OFF: + if (!s->locked) { + s->locked = true; + s->user = CPTRA_MBOX0_SOC_USER_ID; + return 0; + } + return 1; + case CPTRA_MBOX0_USER_OFF: + return s->user; + case CPTRA_MBOX0_TARGET_USER_OFF: + return s->target_user; + case CPTRA_MBOX0_TARGET_USER_VAL_OFF: + return s->target_user_valid; + case CPTRA_MBOX0_CMD_OFF: + return s->cmd; + case CPTRA_MBOX0_DLEN_OFF: + return s->dlen; + case CPTRA_MBOX0_EXECUTE_OFF: + return s->execute; + case CPTRA_MBOX0_TARGET_STATUS_OFF: + return s->target_status; + case CPTRA_MBOX0_CMD_STATUS_OFF: + return s->cmd_status; + case CPTRA_MBOX0_HW_STATUS_OFF: + return s->hw_status; + default: + return 0; + } +} + +static void cptra_mbox_csr_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + AspeedCptraMboxState *s = opaque; + uint32_t val = (uint32_t)value; + + switch (offset) { + case CPTRA_MBOX0_LOCK_OFF: + case CPTRA_MBOX0_USER_OFF: + case CPTRA_MBOX0_HW_STATUS_OFF: + break; + case CPTRA_MBOX0_TARGET_USER_OFF: + s->target_user = val; + break; + case CPTRA_MBOX0_TARGET_USER_VAL_OFF: + s->target_user_valid = val; + break; + case CPTRA_MBOX0_CMD_OFF: + s->cmd = val; + break; + case CPTRA_MBOX0_DLEN_OFF: + if (val > CPTRA_MBOX0_SRAM_SIZE) { + qemu_log_mask(LOG_GUEST_ERROR, + "%s: DLEN 0x%x exceeds SRAM size, clamped\n", + __func__, val); + val = CPTRA_MBOX0_SRAM_SIZE; + } + s->dlen = val; + break; + case CPTRA_MBOX0_EXECUTE_OFF: + if (val == 1 && s->execute != 1 && !s->command_pending) { + s->execute = 1; + s->cmd_status = CPTRA_MBOX0_STATUS_BUSY; + cptra_mbox_submit(s); + } else if (val == 0 && s->execute != 0) { + if (s->command_pending) { + s->release_pending = true; + } else { + cptra_mbox_clear(s); + } + } + break; + case CPTRA_MBOX0_TARGET_STATUS_OFF: + s->target_status = val; + break; + case CPTRA_MBOX0_CMD_STATUS_OFF: + s->cmd_status = val; + break; + default: + break; + } +} + +static const MemoryRegionOps cptra_mbox_csr_ops = { + .read = cptra_mbox_csr_read, + .write = cptra_mbox_csr_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid.min_access_size = 4, + .valid.max_access_size = 4, +}; + +static void cptra_mbox_reset_hold(Object *obj, ResetType type) +{ + AspeedCptraMboxState *s = ASPEED_CPTRA_MBOX(obj); + + if (s->command_pending) { + /* Defer the clear until the in-flight command completes. */ + s->release_pending = true; + return; + } + cptra_mbox_clear(s); + + if (s->peer) { + CptraMboxPeerClass *pc = CPTRA_MBOX_PEER_GET_CLASS(s->peer); + if (pc->handle_reset) { + pc->handle_reset(s->peer); + } + } +} + +static void cptra_mbox_peer_check(const Object *obj, const char *name, + Object *val, Error **errp) +{ + CptraMboxPeer *peer; + + if (!val) { + return; + } + + peer = CPTRA_MBOX_PEER(val); + if (peer->intf) { + error_setg(errp, "Caliptra mailbox peer is already in use"); + } +} + +bool aspeed_cptra_mbox_set_peer(AspeedCptraMboxState *s, CptraMboxPeer *peer, + Error **errp) +{ + CptraMboxPeer *old_peer = s->peer; + + if (old_peer == peer) { + if (s->peer) { + s->peer->intf = CPTRA_MBOX_IF(s); + } + return true; + } + if (!object_property_set_link(OBJECT(s), "peer", + peer ? OBJECT(peer) : NULL, errp)) { + return false; + } + if (old_peer) { + old_peer->intf = NULL; + } + if (s->peer) { + s->peer->intf = CPTRA_MBOX_IF(s); + } + return true; +} + +static void cptra_mbox_init(Object *obj) +{ + AspeedCptraMboxState *s = ASPEED_CPTRA_MBOX(obj); + + object_property_add_link(obj, "peer", TYPE_CPTRA_MBOX_PEER, + (Object **)&s->peer, cptra_mbox_peer_check, + OBJ_PROP_LINK_STRONG); +} + +static void cptra_mbox_realize(DeviceState *dev, Error **errp) +{ + AspeedCptraMboxState *s = ASPEED_CPTRA_MBOX(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + if (s->peer) { + if (!aspeed_cptra_mbox_set_peer(s, s->peer, errp)) { + return; + } + } + + memory_region_init_io(&s->sram_mr, OBJECT(s), &cptra_mbox_sram_ops, s, + "cptra-mbox.sram", CPTRA_MBOX0_SRAM_SIZE); + sysbus_init_mmio(sbd, &s->sram_mr); + + memory_region_init_io(&s->csr_mr, OBJECT(s), &cptra_mbox_csr_ops, s, + "cptra-mbox.csr", CPTRA_MBOX0_CSR_SIZE); + sysbus_init_mmio(sbd, &s->csr_mr); +} + +static const VMStateDescription vmstate_cptra_mbox = { + .name = TYPE_ASPEED_CPTRA_MBOX, + .version_id = 1, + .minimum_version_id = 1, + .fields = (const VMStateField[]) { + VMSTATE_BOOL(locked, AspeedCptraMboxState), + VMSTATE_UINT32(user, AspeedCptraMboxState), + VMSTATE_UINT32(target_user, AspeedCptraMboxState), + VMSTATE_UINT32(target_user_valid, AspeedCptraMboxState), + VMSTATE_UINT32(cmd, AspeedCptraMboxState), + VMSTATE_UINT32(dlen, AspeedCptraMboxState), + VMSTATE_UINT32(execute, AspeedCptraMboxState), + VMSTATE_UINT32(target_status, AspeedCptraMboxState), + VMSTATE_UINT32(cmd_status, AspeedCptraMboxState), + VMSTATE_UINT32(hw_status, AspeedCptraMboxState), + VMSTATE_UINT32_ARRAY(sram, AspeedCptraMboxState, + CPTRA_MBOX0_SRAM_WORDS), + VMSTATE_END_OF_LIST() + }, +}; + +static void cptra_mbox_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ResettableClass *rc = RESETTABLE_CLASS(oc); + CptraMboxIfClass *ic = CPTRA_MBOX_IF_CLASS(oc); + + dc->desc = "Caliptra mailbox host interface"; + dc->realize = cptra_mbox_realize; + dc->vmsd = &vmstate_cptra_mbox; + rc->phases.hold = cptra_mbox_reset_hold; + ic->complete = cptra_mbox_complete; + set_bit(DEVICE_CATEGORY_MISC, dc->categories); +} + +static const TypeInfo cptra_mbox_types[] = { + { + .name = TYPE_CPTRA_MBOX_IF, + .parent = TYPE_INTERFACE, + .class_size = sizeof(CptraMboxIfClass), + }, + { + .name = TYPE_CPTRA_MBOX_PEER, + .parent = TYPE_DEVICE, + .instance_size = sizeof(CptraMboxPeer), + .class_size = sizeof(CptraMboxPeerClass), + .abstract = true, + }, + { + .name = TYPE_ASPEED_CPTRA_MBOX, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(AspeedCptraMboxState), + .instance_init = cptra_mbox_init, + .class_init = cptra_mbox_class_init, + .interfaces = (const InterfaceInfo[]) { + { TYPE_CPTRA_MBOX_IF }, + { } + }, + }, +}; + +DEFINE_TYPES(cptra_mbox_types) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -XXX,XX +XXX,XX @@ system_ss.add(when: 'CONFIG_PVPANIC_PCI', if_true: files('pvpanic-pci.c')) system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c')) system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( + 'aspeed_cptra_mbox.c', 'aspeed_hace.c', 'aspeed_lpc.c', 'aspeed_ltpi.c', diff --git a/hw/misc/trace-events b/hw/misc/trace-events index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/trace-events +++ b/hw/misc/trace-events @@ -XXX,XX +XXX,XX @@ aspeed_ast2700_scuio_read(uint64_t offset, unsigned size, uint32_t data) "To 0x% aspeed_ast1040_scu_write(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 aspeed_ast1040_scu_read(uint64_t offset, unsigned size, uint32_t data) "To 0x%" PRIx64 " of size %u: 0x%" PRIx32 +# aspeed_cptra_mbox.c +cptra_mbox_execute(uint32_t cmd, uint32_t dlen) "EXECUTE cmd 0x%" PRIx32 " dlen 0x%" PRIx32 +cptra_mbox_complete(uint32_t status, uint32_t dlen) "complete status %u dlen 0x%" PRIx32 + # mps2-scc.c mps2_scc_read(uint64_t offset, uint64_t data, unsigned size) "MPS2 SCC read: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" mps2_scc_write(uint64_t offset, uint64_t data, unsigned size) "MPS2 SCC write: offset 0x%" PRIx64 " data 0x%" PRIx64 " size %u" -- 2.43.0
Add a cptra-mbox-peer-extern backend that forwards mailbox execute requests to a chardev peer using a small framed protocol. Run the blocking chardev transaction from the thread pool and report completion back through the mailbox peer interface. Block migration while this external backend is realized because the peer state lives outside QEMU. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- MAINTAINERS | 1 + hw/misc/cptra_mbox_peer_extern.c | 318 +++++++++++++++++++++++++++++++ hw/misc/meson.build | 1 + 3 files changed, 320 insertions(+) create mode 100644 hw/misc/cptra_mbox_peer_extern.c diff --git a/MAINTAINERS b/MAINTAINERS index XXXXXXX..XXXXXXX 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -XXX,XX +XXX,XX @@ L: qemu-arm@nongnu.org S: Maintained F: hw/*/*aspeed* F: include/hw/*/*aspeed* +F: hw/misc/cptra_mbox_peer_extern.c F: hw/net/ftgmac100.c F: include/hw/net/ftgmac100.h F: docs/system/arm/aspeed.rst diff --git a/hw/misc/cptra_mbox_peer_extern.c b/hw/misc/cptra_mbox_peer_extern.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/hw/misc/cptra_mbox_peer_extern.c @@ -XXX,XX +XXX,XX @@ +/* + * Caliptra mailbox external peer (backend). + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "block/thread-pool.h" +#include "chardev/char-fe.h" +#include "hw/misc/aspeed_cptra_mbox.h" +#include "hw/core/qdev-properties.h" +#include "hw/core/qdev-properties-system.h" +#include "migration/blocker.h" +#include "qapi/error.h" +#include "qemu/bswap.h" +#include "qemu/error-report.h" + +#define CPTRA_MBOX_PROTO_MAGIC 0x4D424F58u /* "MBOX" */ +#define CPTRA_MBOX_PROTO_VERSION 1u +#define CPTRA_MBOX_CMD_EXECUTE 1u +#define CPTRA_MBOX_CMD_RESPONSE 2u +#define CPTRA_MBOX_PROTO_MAX_PAYLOAD (8 + CPTRA_MBOX0_SRAM_SIZE) + +typedef struct QEMU_PACKED CptraMboxProtoHdr { + uint32_t magic; + uint16_t version; + uint16_t command; + uint32_t payload_len; +} CptraMboxProtoHdr; + +OBJECT_DECLARE_SIMPLE_TYPE(CptraMboxPeerExtern, CPTRA_MBOX_PEER_EXTERN) + +struct CptraMboxPeerExtern { + CptraMboxPeer parent; + + CharFrontend chr; + Error *migration_blocker; +}; + +typedef struct CptraMboxExternReq { + CptraMboxPeerExtern *p; + + uint32_t cmd; + uint32_t dlen; + uint8_t *req_data; + size_t req_len; + + uint32_t rsp_status; + uint32_t rsp_dlen; + uint8_t *rsp_data; + size_t rsp_len; + + char *error; +} CptraMboxExternReq; + +static size_t cptra_padded_len(uint32_t dlen) +{ + return (size_t)((dlen + 3) / 4) * 4; +} + +static int G_GNUC_PRINTF(2, 3) +cptra_req_fail(CptraMboxExternReq *req, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + g_free(req->error); + req->error = g_strdup_vprintf(fmt, ap); + va_end(ap); + + return -1; +} + +static int cptra_write_all(CptraMboxExternReq *req, const void *buf, size_t len) +{ + int ret; + + if (len > INT_MAX) { + return cptra_req_fail(req, "write too large: %zu", len); + } + ret = qemu_chr_fe_write_all(&req->p->chr, buf, len); + if (ret < 0 || (size_t)ret != len) { + return cptra_req_fail(req, "backend write failed"); + } + return 0; +} + +static int cptra_read_all(CptraMboxExternReq *req, void *buf, size_t len) +{ + int ret; + + if (len > INT_MAX) { + return cptra_req_fail(req, "read too large: %zu", len); + } + ret = qemu_chr_fe_read_all(&req->p->chr, buf, len); + if (ret < 0 || (size_t)ret != len) { + return cptra_req_fail(req, "backend read failed"); + } + return 0; +} + +/* + * Wire protocol (magic 0x4D424F58 "MBOX"): + * + * Header (12 bytes, little-endian): + * u32 magic = 0x4D424F58 + * u16 version = 1 + * u16 command + * u32 payload_len + * + * MBOX_EXECUTE (1) QEMU -> backend + * payload: u32 cmd, u32 dlen, u8 sram[ROUND_UP(dlen, 4)] + * + * MBOX_RESPONSE (2) backend -> QEMU + * payload: u32 status, u32 dlen, u8 sram[ROUND_UP(dlen, 4)] + */ +static int cptra_extern_worker(gpointer data) +{ + CptraMboxExternReq *req = data; + CptraMboxProtoHdr hdr; + size_t tx_payload_len = 8 + req->req_len; + g_autofree uint8_t *payload = NULL; + size_t rsp_data_len; + + if (!qemu_chr_fe_backend_open(&req->p->chr)) { + return cptra_req_fail(req, "backend is not connected"); + } + + payload = g_malloc0(tx_payload_len); + stl_le_p(payload, req->cmd); + stl_le_p(payload + 4, req->dlen); + if (req->req_len) { + memcpy(payload + 8, req->req_data, req->req_len); + } + + hdr.magic = cpu_to_le32(CPTRA_MBOX_PROTO_MAGIC); + hdr.version = cpu_to_le16(CPTRA_MBOX_PROTO_VERSION); + hdr.command = cpu_to_le16(CPTRA_MBOX_CMD_EXECUTE); + hdr.payload_len = cpu_to_le32(tx_payload_len); + + if (cptra_write_all(req, &hdr, sizeof(hdr)) < 0 || + cptra_write_all(req, payload, tx_payload_len) < 0) { + return -1; + } + + if (cptra_read_all(req, &hdr, sizeof(hdr)) < 0) { + return -1; + } + + hdr.magic = le32_to_cpu(hdr.magic); + hdr.version = le16_to_cpu(hdr.version); + hdr.command = le16_to_cpu(hdr.command); + hdr.payload_len = le32_to_cpu(hdr.payload_len); + + if (hdr.magic != CPTRA_MBOX_PROTO_MAGIC) { + return cptra_req_fail(req, "bad response magic 0x%08x", hdr.magic); + } + if (hdr.version != CPTRA_MBOX_PROTO_VERSION) { + return cptra_req_fail(req, "bad response version %u", hdr.version); + } + if (hdr.command != CPTRA_MBOX_CMD_RESPONSE) { + return cptra_req_fail(req, "unexpected response command %u", + hdr.command); + } + if (hdr.payload_len < 8 || hdr.payload_len > CPTRA_MBOX_PROTO_MAX_PAYLOAD) { + return cptra_req_fail(req, "invalid response payload length %u", + hdr.payload_len); + } + + g_free(payload); + payload = g_malloc(hdr.payload_len); + if (cptra_read_all(req, payload, hdr.payload_len) < 0) { + return -1; + } + + req->rsp_status = ldl_le_p(payload); + req->rsp_dlen = ldl_le_p(payload + 4); + if (req->rsp_dlen > CPTRA_MBOX0_SRAM_SIZE) { + return cptra_req_fail(req, "response DLEN 0x%x exceeds SRAM", + req->rsp_dlen); + } + + rsp_data_len = cptra_padded_len(req->rsp_dlen); + if (8 + rsp_data_len > hdr.payload_len) { + return cptra_req_fail(req, "short response payload for DLEN 0x%x", + req->rsp_dlen); + } + + if (rsp_data_len) { + req->rsp_data = g_malloc0(rsp_data_len); + memcpy(req->rsp_data, payload + 8, rsp_data_len); + req->rsp_len = rsp_data_len; + } + + return 0; +} + +static void cptra_extern_req_free(CptraMboxExternReq *req) +{ + g_free(req->req_data); + g_free(req->rsp_data); + g_free(req->error); + g_free(req); +} + +static void cptra_extern_complete(void *opaque, int ret) +{ + CptraMboxExternReq *req = opaque; + CptraMboxPeerExtern *p = req->p; + CptraMboxIf *intf = p->parent.intf; + CptraMboxIfClass *ic = intf ? CPTRA_MBOX_IF_GET_CLASS(intf) : NULL; + + if (req->error) { + error_report("cptra-mbox-peer-extern: %s", req->error); + } + + if (ic) { + if (ret == 0) { + ic->complete(intf, req->rsp_status, req->rsp_dlen, + req->rsp_data, req->rsp_len); + } else { + ic->complete(intf, CPTRA_MBOX0_STATUS_CMD_FAILURE, 0, NULL, 0); + } + } + + cptra_extern_req_free(req); + object_unref(OBJECT(p)); +} + +static void cptra_extern_handle_execute(CptraMboxPeer *peer, uint32_t cmd, + uint32_t dlen, const uint8_t *data, + uint32_t len) +{ + CptraMboxPeerExtern *p = CPTRA_MBOX_PEER_EXTERN(peer); + CptraMboxIf *intf = peer->intf; + CptraMboxExternReq *req; + + if (!qemu_chr_fe_backend_connected(&p->chr) || + !qemu_chr_fe_backend_open(&p->chr)) { + if (intf) { + CPTRA_MBOX_IF_GET_CLASS(intf)->complete( + intf, CPTRA_MBOX0_STATUS_CMD_FAILURE, 0, NULL, 0); + } + return; + } + + req = g_new0(CptraMboxExternReq, 1); + req->p = p; + req->cmd = cmd; + req->dlen = dlen; + if (len) { + req->req_data = g_memdup2(data, len); + req->req_len = len; + } + + object_ref(OBJECT(p)); + thread_pool_submit_aio(cptra_extern_worker, req, + cptra_extern_complete, req); +} + +static void cptra_extern_realize(DeviceState *dev, Error **errp) +{ + CptraMboxPeerExtern *p = CPTRA_MBOX_PEER_EXTERN(dev); + + if (!qemu_chr_fe_backend_connected(&p->chr)) { + error_setg(errp, "cptra-mbox-peer-extern requires a chardev attribute"); + return; + } + + qemu_chr_fe_set_open(&p->chr, true); + error_setg(&p->migration_blocker, + "Migration disabled: cptra-mbox-peer-extern chardev backend " + "state is external"); + if (migrate_add_blocker(&p->migration_blocker, errp) < 0) { + return; + } +} + +static void cptra_extern_finalize(Object *obj) +{ + CptraMboxPeerExtern *p = CPTRA_MBOX_PEER_EXTERN(obj); + + migrate_del_blocker(&p->migration_blocker); + qemu_chr_fe_deinit(&p->chr, false); +} + +static const Property cptra_extern_props[] = { + DEFINE_PROP_CHR("chardev", CptraMboxPeerExtern, chr), +}; + +static void cptra_extern_class_init(ObjectClass *oc, const void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + CptraMboxPeerClass *pc = CPTRA_MBOX_PEER_CLASS(oc); + + dc->desc = "Caliptra mailbox external (chardev) peer"; + dc->realize = cptra_extern_realize; + dc->hotpluggable = false; + device_class_set_props(dc, cptra_extern_props); + pc->handle_execute = cptra_extern_handle_execute; +} + +static const TypeInfo cptra_extern_type = { + .name = TYPE_CPTRA_MBOX_PEER_EXTERN, + .parent = TYPE_CPTRA_MBOX_PEER, + .instance_size = sizeof(CptraMboxPeerExtern), + .instance_finalize = cptra_extern_finalize, + .class_init = cptra_extern_class_init, +}; + +static void cptra_extern_register_types(void) +{ + type_register_static(&cptra_extern_type); +} + +type_init(cptra_extern_register_types) diff --git a/hw/misc/meson.build b/hw/misc/meson.build index XXXXXXX..XXXXXXX 100644 --- a/hw/misc/meson.build +++ b/hw/misc/meson.build @@ -XXX,XX +XXX,XX @@ system_ss.add(when: 'CONFIG_PVPANIC_MMIO', if_true: files('pvpanic-mmio.c')) system_ss.add(when: 'CONFIG_AUX', if_true: files('auxbus.c')) system_ss.add(when: 'CONFIG_ASPEED_SOC', if_true: files( 'aspeed_cptra_mbox.c', + 'cptra_mbox_peer_extern.c', 'aspeed_hace.c', 'aspeed_lpc.c', 'aspeed_ltpi.c', -- 2.43.0
Instantiate the Caliptra mailbox in the AST1040 SoC and map its SRAM and CSR windows at the guest-visible MCI addresses. Add the 4 KiB MCI aperture controlled by the AST1040 SCU CPTRA page register, switch the SoC to the AST1040 SCU model, and expose a machine option that links an external mailbox peer after machine initialization. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- include/hw/arm/aspeed_soc.h | 11 ++++++ hw/arm/aspeed_ast1040.c | 60 ++++++++++++++++++++++++++++-- hw/arm/aspeed_ast1040_evb.c | 74 ++++++++++++++++++++++++++++++++++++- 3 files changed, 140 insertions(+), 5 deletions(-) diff --git a/include/hw/arm/aspeed_soc.h b/include/hw/arm/aspeed_soc.h index XXXXXXX..XXXXXXX 100644 --- a/include/hw/arm/aspeed_soc.h +++ b/include/hw/arm/aspeed_soc.h @@ -XXX,XX +XXX,XX @@ #include "hw/intc/arm_gicv3.h" #include "hw/misc/aspeed_ltpi.h" #include "hw/arm/aspeed_ast1700.h" +#include "hw/misc/aspeed_cptra_mbox.h" #define VBOOTROM_FILE_NAME "ast27x0_bootrom.bin" @@ -XXX,XX +XXX,XX @@ struct Aspeed10x0SoCState { #define TYPE_ASPEED10X0_SOC "aspeed10x0-soc" OBJECT_DECLARE_SIMPLE_TYPE(Aspeed10x0SoCState, ASPEED10X0_SOC) +struct Aspeed1040SoCState { + Aspeed10x0SoCState parent; + + AspeedCptraMboxState cptra_mbox; + MemoryRegion cptra_mci_window; +}; + +#define TYPE_ASPEED1040_SOC "ast1040-a0" +OBJECT_DECLARE_SIMPLE_TYPE(Aspeed1040SoCState, ASPEED1040_SOC) + struct AspeedSoCClass { DeviceClass parent_class; diff --git a/hw/arm/aspeed_ast1040.c b/hw/arm/aspeed_ast1040.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed_ast1040.c +++ b/hw/arm/aspeed_ast1040.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "qapi/error.h" -#include "system/address-spaces.h" #include "system/system.h" #include "hw/core/qdev-clock.h" #include "hw/misc/unimp.h" +#include "hw/misc/aspeed_cptra_mbox.h" #include "hw/arm/aspeed_soc.h" +#define AST1040_CPTRA_MCI_WINDOW_BASE 0x74200000 +#define AST1040_CPTRA_MCI_WINDOW_SIZE 0x1000 +#define AST1040_CPTRA_MBOX_SRAM_BASE 0x21400000 +#define AST1040_CPTRA_MBOX_CSR_BASE 0x21600000 + static const hwaddr aspeed_soc_ast1040_memmap[] = { [ASPEED_DEV_SRAM1] = 0x00000000, /* Hyper RAM */ [ASPEED_DEV_FMC] = 0x74000000, @@ -XXX,XX +XXX,XX @@ static qemu_irq aspeed_soc_ast1040_get_irq(AspeedSoCState *s, int dev) static void aspeed_soc_ast1040_init(Object *obj) { + Aspeed1040SoCState *a1040 = ASPEED1040_SOC(obj); Aspeed10x0SoCState *a = ASPEED10X0_SOC(obj); AspeedSoCState *s = ASPEED_SOC(obj); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast1040_init(Object *obj) s->sysclk = qdev_init_clock_in(DEVICE(s), "sysclk", NULL, NULL, 0); - /* AST1040 uses the AST2700 SCUIO model */ - object_initialize_child(obj, "scu", &s->scu, TYPE_ASPEED_2700_SCUIO); + object_initialize_child(obj, "scu", &s->scu, TYPE_ASPEED_1040_SCU); qdev_prop_set_uint32(DEVICE(&s->scu), "silicon-rev", sc->silicon_rev); object_property_add_alias(obj, "hw-strap1", OBJECT(&s->scu), "hw-strap1"); object_property_add_alias(obj, "hw-strap2", OBJECT(&s->scu), "hw-strap2"); + object_initialize_child(obj, "cptra-mbox", &a1040->cptra_mbox, + TYPE_ASPEED_CPTRA_MBOX); for (i = 0; i < sc->uarts_num; i++) { object_initialize_child(obj, "uart[*]", &s->uart[i], TYPE_SERIAL_MM); @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast1040_init(Object *obj) TYPE_UNIMPLEMENTED_DEVICE); } +static bool aspeed_soc_ast1040_realize_cptra_mbox(Aspeed1040SoCState *a1040, + Error **errp) +{ + AspeedSoCState *s = ASPEED_SOC(a1040); + DeviceState *mbox = DEVICE(&a1040->cptra_mbox); + + /* + * Caliptra is an AST1040 integrated RoT IP. The external peer is + * optional; without a peer, the mailbox registers remain present and + * EXECUTE completes with CMD_FAILURE. + */ + if (!sysbus_realize(SYS_BUS_DEVICE(mbox), errp)) { + return false; + } + + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(mbox), 0, + AST1040_CPTRA_MBOX_SRAM_BASE); + aspeed_mmio_map(s->memory, SYS_BUS_DEVICE(mbox), 1, + AST1040_CPTRA_MBOX_CSR_BASE); + return true; +} + static void aspeed_soc_ast1040_realize(DeviceState *dev_soc, Error **errp) { + Aspeed1040SoCState *a1040 = ASPEED1040_SOC(dev_soc); Aspeed10x0SoCState *a = ASPEED10X0_SOC(dev_soc); AspeedSoCState *s = ASPEED_SOC(dev_soc); AspeedSoCClass *sc = ASPEED_SOC_GET_CLASS(s); @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast1040_realize(DeviceState *dev_soc, Error **errp) memory_region_add_subregion(s->memory, sc->memmap[ASPEED_DEV_SRAM1], &s->sram[1]); + if (!aspeed_soc_ast1040_realize_cptra_mbox(a1040, errp)) { + return; + } + + /* + * The Caliptra MCI aperture is a 4 KiB alias whose target is selected by + * AST1040_SCU_CPTRA_PAGE_REG0. Firmware commonly switches it between + * mailbox SRAM (0x21400000) and mailbox CSR (0x21600000), then accesses + * the selected page through 0x74200000. + */ + memory_region_init_alias(&a1040->cptra_mci_window, OBJECT(s), + "aspeed.ast1040.cptra-mci-window", s->memory, + 0, AST1040_CPTRA_MCI_WINDOW_SIZE); + memory_region_add_subregion(s->memory, AST1040_CPTRA_MCI_WINDOW_BASE, + &a1040->cptra_mci_window); + /* The SCU drives the aperture's target offset via SCU_CPTRA_PAGE_REG0. */ + object_property_set_link(OBJECT(&s->scu), "cptra-page-window", + OBJECT(&a1040->cptra_mci_window), &error_abort); + object_property_set_uint(OBJECT(&s->scu), "cptra-page-window-base", + AST1040_CPTRA_MCI_WINDOW_BASE, &error_abort); + /* SCU */ if (!sysbus_realize(SYS_BUS_DEVICE(&s->scu), errp)) { return; @@ -XXX,XX +XXX,XX @@ static void aspeed_soc_ast1040_class_init(ObjectClass *klass, const void *data) static const TypeInfo aspeed_soc_ast1040_types[] = { { - .name = "ast1040-a0", + .name = TYPE_ASPEED1040_SOC, .parent = TYPE_ASPEED10X0_SOC, + .instance_size = sizeof(Aspeed1040SoCState), .instance_init = aspeed_soc_ast1040_init, .class_init = aspeed_soc_ast1040_class_init, } diff --git a/hw/arm/aspeed_ast1040_evb.c b/hw/arm/aspeed_ast1040_evb.c index XXXXXXX..XXXXXXX 100644 --- a/hw/arm/aspeed_ast1040_evb.c +++ b/hw/arm/aspeed_ast1040_evb.c @@ -XXX,XX +XXX,XX @@ #include "qemu/osdep.h" #include "qapi/error.h" +#include "qemu/error-report.h" #include "hw/arm/boot.h" #include "hw/arm/machines-qom.h" #include "hw/arm/aspeed.h" #include "hw/arm/aspeed_soc.h" #include "hw/core/qdev-clock.h" +#include "hw/misc/aspeed_cptra_mbox.h" #include "system/system.h" #define AST1040_INTERNAL_FLASH_SIZE (4 * MiB) /* Main SYSCLK frequency in Hz (400MHz) */ #define SYSCLK_FRQ 400000000ULL +#define TYPE_AST1040_EVB_MACHINE MACHINE_TYPE_NAME("ast1040-evb") +OBJECT_DECLARE_SIMPLE_TYPE(Ast1040EvbMachineState, AST1040_EVB_MACHINE) + +struct Ast1040EvbMachineState { + AspeedMachineState parent_obj; + + char *cptra_peer; + Notifier machine_done; +}; + +static void aspeed_bic_machine_done(Notifier *notifier, void *data) +{ + Ast1040EvbMachineState *m = container_of(notifier, + Ast1040EvbMachineState, + machine_done); + AspeedMachineState *bmc = ASPEED_MACHINE(m); + Aspeed1040SoCState *a1040 = ASPEED1040_SOC(bmc->soc); + bool ambiguous = false; + Object *peer; + Error *err = NULL; + + if (!m->cptra_peer) { + return; + } + + peer = object_resolve_path_type(m->cptra_peer, TYPE_CPTRA_MBOX_PEER, + &ambiguous); + if (!peer || ambiguous) { + error_report("cptra-peer: peer '%s' not found%s", + m->cptra_peer, ambiguous ? " (ambiguous)" : ""); + exit(1); + } + + if (!aspeed_cptra_mbox_set_peer(&a1040->cptra_mbox, + CPTRA_MBOX_PEER(peer), &err)) { + error_report_err(err); + exit(1); + } +} + static void aspeed_bic_machine_init(MachineState *machine) { AspeedMachineState *bmc = ASPEED_MACHINE(machine); @@ -XXX,XX +XXX,XX @@ static void aspeed_bic_machine_init(MachineState *machine) aspeed_connect_serial_hds_to_uarts(bmc); qdev_realize(DEVICE(bmc->soc), NULL, &error_abort); + if (AST1040_EVB_MACHINE(machine)->cptra_peer) { + AST1040_EVB_MACHINE(machine)->machine_done.notify = + aspeed_bic_machine_done; + qemu_add_machine_init_done_notifier( + &AST1040_EVB_MACHINE(machine)->machine_done); + } + armv7m_load_kernel(ARM_CPU(first_cpu), machine->kernel_filename, 0, AST1040_INTERNAL_FLASH_SIZE); } +static char *aspeed_bic_get_cptra_peer(Object *obj, Error **errp) +{ + Ast1040EvbMachineState *m = AST1040_EVB_MACHINE(obj); + + return g_strdup(m->cptra_peer); +} + +static void aspeed_bic_set_cptra_peer(Object *obj, const char *value, + Error **errp) +{ + Ast1040EvbMachineState *m = AST1040_EVB_MACHINE(obj); + + g_free(m->cptra_peer); + m->cptra_peer = g_strdup(value); +} + static void aspeed_machine_ast1040_evb_class_init(ObjectClass *oc, const void *data) { @@ -XXX,XX +XXX,XX @@ static void aspeed_machine_ast1040_evb_class_init(ObjectClass *oc, amc->macs_mask = 0; amc->uart_default = ASPEED_DEV_UART12; aspeed_machine_class_init_cpus_defaults(mc); + + object_class_property_add_str(oc, "cptra-peer", + aspeed_bic_get_cptra_peer, + aspeed_bic_set_cptra_peer); + object_class_property_set_description(oc, "cptra-peer", + "Caliptra mailbox peer object id"); } static const TypeInfo aspeed_ast1040_evb_types[] = { { - .name = MACHINE_TYPE_NAME("ast1040-evb"), + .name = TYPE_AST1040_EVB_MACHINE, .parent = TYPE_ASPEED_MACHINE, + .instance_size = sizeof(Ast1040EvbMachineState), .class_init = aspeed_machine_ast1040_evb_class_init, .interfaces = arm_machine_interfaces, } -- 2.43.0
Cover the AST1040 Caliptra mailbox execute flow through the external peer protocol and verify that the SCU CPTRA page register remaps the MCI aperture between CSR and SRAM pages. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- tests/qtest/aspeed_cptra_mbox-test.c | 223 +++++++++++++++++++++++++++ tests/qtest/meson.build | 3 +- 2 files changed, 225 insertions(+), 1 deletion(-) create mode 100644 tests/qtest/aspeed_cptra_mbox-test.c diff --git a/tests/qtest/aspeed_cptra_mbox-test.c b/tests/qtest/aspeed_cptra_mbox-test.c new file mode 100644 index XXXXXXX..XXXXXXX --- /dev/null +++ b/tests/qtest/aspeed_cptra_mbox-test.c @@ -XXX,XX +XXX,XX @@ +/* + * QTest for the ASPEED Caliptra mailbox on the ast1040-evb machine. + * + * Copyright (C) 2026 ASPEED Technology Inc. + * + * SPDX-License-Identifier: GPL-2.0-or-later + */ + +#include "qemu/osdep.h" +#include "libqtest-single.h" +#include "qemu/sockets.h" + +/* AST1040 guest-visible mailbox bases. */ +#define SRAM_BASE 0x21400000ULL +#define CSR_BASE 0x21600000ULL + +#define CSR_LOCK 0x000 +#define CSR_USER 0x004 +#define CSR_CMD 0x010 +#define CSR_DLEN 0x014 +#define CSR_EXECUTE 0x018 +#define CSR_CMD_STATUS 0x020 + +#define STATUS_BUSY 0 +#define STATUS_COMPLETE 2 +#define STATUS_CMD_FAILURE 3 + +/* SCU CPTRA page-select register (SCU base 0x74C02000 + 0x120). */ +#define SCU_CPTRA_PAGE_REG0 0x74C02120ULL +/* MCI remap aperture. */ +#define MCI_WINDOW 0x74200000ULL + +/* Wire protocol. */ +#define PROTO_MAGIC 0x4D424F58u +#define PROTO_VERSION 1u +#define CMD_EXECUTE 1u +#define CMD_RESPONSE 2u + +static int peer_lfd = -1; +static int peer_fd = -1; + +static void peer_read(void *buf, size_t len) +{ + size_t off = 0; + + while (off < len) { + ssize_t r = read(peer_fd, (uint8_t *)buf + off, len - off); + g_assert_cmpint(r, >, 0); + off += r; + } +} + +static void peer_write(const void *buf, size_t len) +{ + size_t off = 0; + + while (off < len) { + ssize_t w = write(peer_fd, (const uint8_t *)buf + off, len - off); + g_assert_cmpint(w, >, 0); + off += w; + } +} + +static uint32_t ld32(const uint8_t *p) +{ + return p[0] | (p[1] << 8) | (p[2] << 16) | ((uint32_t)p[3] << 24); +} + +static void st32(uint8_t *p, uint32_t v) +{ + p[0] = v; p[1] = v >> 8; p[2] = v >> 16; p[3] = v >> 24; +} + +/* + * Service one MBOX_EXECUTE: read the request, optionally check it, then reply + * with MBOX_RESPONSE carrying @rsp_status and @rsp_data. + */ +static void peer_serve_execute(uint32_t expect_cmd, uint32_t expect_dlen, + uint32_t rsp_status, + const uint8_t *rsp_data, uint32_t rsp_dlen) +{ + uint8_t hdr[12]; + uint32_t plen, cmd, dlen; + g_autofree uint8_t *payload = NULL; + uint32_t rsp_padded = (rsp_dlen + 3) & ~3u; + uint32_t rsp_plen = 8 + rsp_padded; + g_autofree uint8_t *rsp = g_malloc0(12 + rsp_plen); + + peer_read(hdr, sizeof(hdr)); + g_assert_cmpuint(ld32(hdr), ==, PROTO_MAGIC); + g_assert_cmpuint(hdr[4] | (hdr[5] << 8), ==, PROTO_VERSION); + g_assert_cmpuint(hdr[6] | (hdr[7] << 8), ==, CMD_EXECUTE); + plen = ld32(hdr + 8); + g_assert_cmpuint(plen, >=, 8); + + payload = g_malloc(plen); + peer_read(payload, plen); + cmd = ld32(payload); + dlen = ld32(payload + 4); + g_assert_cmpuint(cmd, ==, expect_cmd); + g_assert_cmpuint(dlen, ==, expect_dlen); + + st32(rsp, PROTO_MAGIC); + rsp[4] = PROTO_VERSION; rsp[5] = 0; + rsp[6] = CMD_RESPONSE; rsp[7] = 0; + st32(rsp + 8, rsp_plen); + st32(rsp + 12, rsp_status); + st32(rsp + 16, rsp_dlen); + if (rsp_dlen) { + memcpy(rsp + 20, rsp_data, rsp_dlen); + } + peer_write(rsp, 12 + rsp_plen); +} + +static uint32_t poll_cmd_status(void) +{ + uint32_t s = STATUS_BUSY; + int tries = 1000; + + while (tries--) { + s = qtest_readl(global_qtest, CSR_BASE + CSR_CMD_STATUS); + if (s != STATUS_BUSY) { + break; + } + } + return s; +} + +/* + * Realistic mailbox transaction: acquire the lock, run an EXECUTE roundtrip + * against the peer, verify the response lands in SRAM, then release. + */ +static void test_execute(void) +{ + static const uint8_t resp[] = { + 0x11, 0xf9, 0xff, 0xff, /* checksum */ + 0x00, 0x00, 0x00, 0x00, + 0x14, 0x00, 0x00, 0x00, /* inner dlen = 20 */ + 'C', 'a', 'l', 'i', 'p', 't', 'r', 'a', + '_', 'C', 'o', 'r', 'e', '_', 'v', '2', '.', '0', '.', '0', + }; + uint32_t status; + + /* + * Acquire: first read returns 0, USER reflects the SoC agent, busy after. + */ + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 0); + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_USER), ==, 1); + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 1); + + qtest_writel(global_qtest, SRAM_BASE + 0, 0xfffffec0); + qtest_writel(global_qtest, CSR_BASE + CSR_CMD, 0x4d465756); + qtest_writel(global_qtest, CSR_BASE + CSR_DLEN, 8); + qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 1); + + peer_serve_execute(0x4d465756, 8, STATUS_COMPLETE, resp, sizeof(resp)); + + status = poll_cmd_status(); + g_assert_cmpuint(status, ==, STATUS_COMPLETE); + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_DLEN), + ==, sizeof(resp)); + g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 0), ==, 0xfffff911); + g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 8), ==, 0x14); + g_assert_cmpuint(qtest_readl(global_qtest, SRAM_BASE + 12), ==, 0x696c6143); + + /* Completing the transaction (EXECUTE 1->0) releases the lock. */ + qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 0); + g_assert_cmpuint(qtest_readl(global_qtest, CSR_BASE + CSR_LOCK), ==, 0); + qtest_writel(global_qtest, CSR_BASE + CSR_EXECUTE, 0); +} + +/* SCU CPTRA_PAGE_REG0 selects the target page seen through the MCI window. */ +static void test_scu_remap(void) +{ + /* Window pointed at the CSR page exposes the CMD register. */ + qtest_writel(global_qtest, CSR_BASE + CSR_CMD, 0xdeadbeef); + qtest_writel(global_qtest, SCU_CPTRA_PAGE_REG0, CSR_BASE); + g_assert_cmpuint(qtest_readl(global_qtest, MCI_WINDOW + CSR_CMD), + ==, 0xdeadbeef); + + /* Re-point at the SRAM page. */ + qtest_writel(global_qtest, SRAM_BASE + 0, 0x12345678); + qtest_writel(global_qtest, SCU_CPTRA_PAGE_REG0, SRAM_BASE); + g_assert_cmpuint(qtest_readl(global_qtest, MCI_WINDOW + 0), + ==, 0x12345678); +} + +int main(int argc, char **argv) +{ + g_autofree char *sock_path = NULL; + g_autofree char *cmdline = NULL; + int ret; + + g_test_init(&argc, &argv, NULL); + + sock_path = g_strdup_printf("%s/cptra-mbox-%u.sock", + g_get_tmp_dir(), getpid()); + unlink(sock_path); + peer_lfd = qtest_socket_server(sock_path); + + cmdline = g_strdup_printf( + "-machine ast1040-evb,cptra-peer=peer0 " + "-chardev socket,id=cptra0,path=%s " + "-device cptra-mbox-peer-extern,id=peer0,chardev=cptra0", sock_path); + qtest_start(cmdline); + + peer_fd = accept(peer_lfd, NULL, NULL); + g_assert_cmpint(peer_fd, >=, 0); + + qtest_add_func("/cptra-mbox/execute", test_execute); + qtest_add_func("/cptra-mbox/scu-remap", test_scu_remap); + + ret = g_test_run(); + + qtest_end(); + if (peer_fd >= 0) { + close(peer_fd); + } + close(peer_lfd); + unlink(sock_path); + + return ret; +} diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build index XXXXXXX..XXXXXXX 100644 --- a/tests/qtest/meson.build +++ b/tests/qtest/meson.build @@ -XXX,XX +XXX,XX @@ qtests_aspeed = \ ['aspeed_gpio-test', 'aspeed_hace-test', 'aspeed_scu-test', - 'aspeed_smc-test'] + 'aspeed_smc-test', + 'aspeed_cptra_mbox-test'] qtests_aspeed64 = \ ['ast2700-gpio-test', 'ast2700-hace-test', -- 2.43.0
Document the AST1040 Caliptra MCI mailbox windows, the SCU-controlled remap aperture, and how to connect the external mailbox peer through a chardev backend. Signed-off-by: Steven Lee <steven_lee@aspeedtech.com> --- docs/system/arm/aspeed.rst | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/docs/system/arm/aspeed.rst b/docs/system/arm/aspeed.rst index XXXXXXX..XXXXXXX 100644 --- a/docs/system/arm/aspeed.rst +++ b/docs/system/arm/aspeed.rst @@ -XXX,XX +XXX,XX @@ Supported devices * ADC * Secure Boot Controller * PECI Controller (minimal) + * Caliptra MCI mailbox (AST1040 only) Missing devices @@ -XXX,XX +XXX,XX @@ To boot a kernel directly from a Zephyr build tree: $ qemu-system-arm -M ast1030-evb -nographic \ -kernel zephyr.bin + +Caliptra MCI mailbox (ast1040-evb) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The AST1040 chip provides a mechanism to map the Caliptra MCI mailbox +register space into the Cortex-M4F address space. The Caliptra MCI mailbox +SRAM and CSR are accessible at fixed addresses: + +- Mailbox SRAM at ``0x21400000`` (2 MiB, command/response payload) +- Mailbox CSR at ``0x21600000`` (4 KiB, LOCK/CMD/DLEN/EXECUTE/STATUS) + +The SCU ``CPTRA_PAGE_REG0`` register (at ``0x74C02120``) selects which +Caliptra MCI page (SRAM or CSR) is exposed through the 4 KiB aperture at +``0x74200000``, allowing firmware to access either window through a single +address. + +By default the mailbox has no Caliptra peer, so writing ``EXECUTE`` reports +``CMD_FAILURE``. To service commands with an external Caliptra simulator (for +example caliptra-server) over a UNIX socket, start the simulator first, then +create an external peer device and link it with the machine's ``cptra-peer`` +option: + +.. code-block:: bash + + $ qemu-system-arm -M ast1040-evb,cptra-peer=peer0 -nographic \ + -chardev socket,id=cptra0,path=/tmp/mcu_mbox.sock \ + -device cptra-mbox-peer-extern,id=peer0,chardev=cptra0 \ + -kernel zephyr.elf + +Writing ``EXECUTE`` bridges the command and the mailbox SRAM payload to the +peer over the socket, and writes the response back into the mailbox SRAM. -- 2.43.0