From: Wadim Mueller <wafgo01@gmail.com>
To: qemu-devel@nongnu.org
Cc: qemu-arm@nongnu.org, "Peter Maydell" <peter.maydell@linaro.org>,
"Philippe Mathieu-Daudé" <philmd@mailo.com>,
"Bin Meng" <bmeng.cn@gmail.com>,
"Paolo Bonzini" <pbonzini@redhat.com>,
"Fabiano Rosas" <farosas@suse.de>,
"Wadim Mueller" <wafgo01@gmail.com>
Subject: [RFC PATCH v2 10/14] hw/misc: add TI DMSC (TI-SCI system controller) model
Date: Thu, 20 Aug 2026 14:48:10 +0200 [thread overview]
Message-ID: <20260820124824.618671-11-wafgo01@gmail.com> (raw)
In-Reply-To: <20260820124824.618671-1-wafgo01@gmail.com>
On real K3 silicon the device management and security controller runs the
TI system firmware (SYSFW) and answers TI-SCI requests from all other
cores. Rather than executing the vendor firmware blob, model the DMSC as a
TI-SCI endpoint on top of the secure proxy.
Implemented message classes:
- version and firmware capability queries
- device state, device reset and clock management (set/get state,
get/set frequency, query frequency, clock parents)
- processor boot control: request/release, set config, set control,
boot vector capture, wait_proc_boot_status and handover, which is what
actually starts the A53 and M4F cores
- firewall configuration and SA2UL/secure-revision queries needed by
TF-A and OP-TEE
- system reset
Unknown messages are NAKed and traced, which keeps guests that probe for
optional features working.
Signed-off-by: Wadim Mueller <wafgo01@gmail.com>
---
hw/misc/Kconfig | 3 +
hw/misc/meson.build | 1 +
hw/misc/ti-dmsc.c | 1725 +++++++++++++++++++++++++++++++++++++
hw/misc/trace-events | 32 +
include/hw/misc/ti-dmsc.h | 661 ++++++++++++++
5 files changed, 2422 insertions(+)
create mode 100644 hw/misc/ti-dmsc.c
create mode 100644 include/hw/misc/ti-dmsc.h
diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index bea8e9341f..ebdbda1962 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -143,6 +143,9 @@ config MPS2_SCC
bool
select LED
+config TI_DMSC
+ bool
+
config TI_RAT
bool
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index a8a40ea2a7..124d55502d 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -123,6 +123,7 @@ system_ss.add(when: 'CONFIG_STM32L4X5_RCC', if_true: files('stm32l4x5_rcc.c'))
system_ss.add(when: 'CONFIG_MPS2_FPGAIO', if_true: files('mps2-fpgaio.c'))
system_ss.add(when: 'CONFIG_MPS2_SCC', if_true: files('mps2-scc.c'))
+system_ss.add(when: 'CONFIG_TI_DMSC', if_true: files('ti-dmsc.c'))
system_ss.add(when: 'CONFIG_TI_RAT', if_true: files('ti-rat.c'))
system_ss.add(when: 'CONFIG_TI_K3_CTRLMMR', if_true: files('ti-k3-ctrlmmr.c'))
system_ss.add(when: 'CONFIG_TI_K3_GTC', if_true: files('ti-k3-gtc.c'))
diff --git a/hw/misc/ti-dmsc.c b/hw/misc/ti-dmsc.c
new file mode 100644
index 0000000000..3b5b32830e
--- /dev/null
+++ b/hw/misc/ti-dmsc.c
@@ -0,0 +1,1725 @@
+/*
+ * TI DMSC emulator (minimal TISCI service) as QOM device
+ *
+ * This is used together with a TI SEC_PROXY model. The SEC_PROXY stays the
+ * transport/queue/data-window part, this device handles SYSFW/TISCI logic.
+ *
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/core/qdev-properties.h"
+#include "hw/core/qdev.h"
+#include "hw/core/resettable.h"
+#include "system/reset.h"
+#include "system/runstate.h"
+#include "target/arm/arm-powerctl.h"
+#include "qemu/main-loop.h"
+#include "hw/misc/ti-dmsc.h"
+#include "trace.h"
+
+/*
+ * Responses, which go through sec-proxy, have one word less payload than the
+ * slot size: word 0 is reserved by ti_sec_proxy_push_msg().
+ * TI_DMSC_MAX_WORDS is the TISCI request limit, not transport capacity.
+ */
+#define TI_DMSC_SEC_PROXY_PAYLOAD_MAX \
+ ((SEC_PROXY_MSG_MAX_WORDS - 1) * sizeof(uint32_t))
+
+static const char *ti_dmsc_proc_name_from_id(uint32_t proc_id)
+{
+ switch (proc_id) {
+ case SCICLIENT_PROCID_A53_CL0_C0:
+ return "A53_CL0_C0";
+ case SCICLIENT_PROCID_A53_CL0_C1:
+ return "A53_CL0_C1";
+
+ case SCICLIENT_PROCID_R5_CL0_C0:
+ return "R5_CL0_C0";
+ case SCICLIENT_PROCID_R5_CL0_C1:
+ return "R5_CL0_C1";
+ case SCICLIENT_PROCID_R5_CL1_C0:
+ return "R5_CL1_C0";
+ case SCICLIENT_PROCID_R5_CL1_C1:
+ return "R5_CL1_C1";
+
+ case SCICLIENT_PROCID_MCU_M4FSS0_C0:
+ return "MCU_M4FSS0_C0";
+
+ default:
+ return "UNKNOWN_PROC";
+ }
+}
+
+static const char *ti_dmsc_host_name_from_id(uint32_t host_id)
+{
+ switch (host_id) {
+ case TISCI_HOST_ID_DMSC:
+ return "DMSC";
+
+ case TISCI_HOST_ID_MAIN_0_R5_0:
+ return "MAIN_0_R5_0";
+ case TISCI_HOST_ID_MAIN_0_R5_1:
+ return "MAIN_0_R5_1";
+ case TISCI_HOST_ID_MAIN_0_R5_2:
+ return "MAIN_0_R5_2";
+ case TISCI_HOST_ID_MAIN_0_R5_3:
+ return "MAIN_0_R5_3";
+
+ case TISCI_HOST_ID_A53_0:
+ return "A53_0";
+ case TISCI_HOST_ID_A53_1:
+ return "A53_1";
+ case TISCI_HOST_ID_A53_2:
+ return "A53_2";
+ case TISCI_HOST_ID_A53_3:
+ return "A53_3";
+ case TISCI_HOST_ID_A53_4:
+ return "A53_4";
+
+ case TISCI_HOST_ID_M4_0:
+ return "M4_0";
+
+ case TISCI_HOST_ID_MAIN_1_R5_0:
+ return "MAIN_1_R5_0";
+ case TISCI_HOST_ID_MAIN_1_R5_1:
+ return "MAIN_1_R5_1";
+ case TISCI_HOST_ID_MAIN_1_R5_2:
+ return "MAIN_1_R5_2";
+ case TISCI_HOST_ID_MAIN_1_R5_3:
+ return "MAIN_1_R5_3";
+
+ case TISCI_HOST_ID_ICSSG_0:
+ return "ICSSG_0";
+ case TISCI_HOST_ID_ICSSG_1:
+ return "ICSSG_1";
+
+ default:
+ return "UNKNOWN_HOST";
+ }
+}
+
+static const char *ti_dmsc_device_state_to_str(uint8_t state)
+{
+ switch (state) {
+ case TISCI_MSG_VALUE_DEVICE_SW_STATE_AUTO_OFF:
+ return "AUTO";
+ case TISCI_MSG_VALUE_DEVICE_SW_STATE_RETENTION:
+ return "RETENTION";
+ case TISCI_MSG_VALUE_DEVICE_SW_STATE_ON:
+ return "ON";
+ default:
+ return "UNKNOWN_STATE";
+ }
+}
+
+static const char *ti_dmsc_device_name_from_id(uint32_t dev_id)
+{
+ switch (dev_id) {
+ case TISCI_DEV_ADC0:
+ return "ADC0";
+ case TISCI_DEV_CMP_EVENT_INTROUTER0:
+ return "CMP_EVENT_INTROUTER0";
+ case TISCI_DEV_DBGSUSPENDROUTER0:
+ return "DBGSUSPENDROUTER0";
+ case TISCI_DEV_MAIN_GPIOMUX_INTROUTER0:
+ return "MAIN_GPIOMUX_INTROUTER0";
+ case TISCI_DEV_MCU_MCU_GPIOMUX_INTROUTER0:
+ return "MCU_MCU_GPIOMUX_INTROUTER0";
+ case TISCI_DEV_TIMESYNC_EVENT_INTROUTER0:
+ return "TIMESYNC_EVENT_INTROUTER0";
+ case TISCI_DEV_MCU_M4FSS0:
+ return "MCU_M4FSS0";
+ case TISCI_DEV_MCU_M4FSS0_CBASS_0:
+ return "MCU_M4FSS0_CBASS_0";
+ case TISCI_DEV_MCU_M4FSS0_CORE0:
+ return "MCU_M4FSS0_CORE0";
+ case TISCI_DEV_CPSW0:
+ return "CPSW0";
+ case TISCI_DEV_CPT2_AGGR0:
+ return "CPT2_AGGR0";
+ case TISCI_DEV_STM0:
+ return "STM0";
+ case TISCI_DEV_DCC0:
+ return "DCC0";
+ case TISCI_DEV_DCC1:
+ return "DCC1";
+ case TISCI_DEV_DCC2:
+ return "DCC2";
+ case TISCI_DEV_DCC3:
+ return "DCC3";
+ case TISCI_DEV_DCC4:
+ return "DCC4";
+ case TISCI_DEV_DCC5:
+ return "DCC5";
+ case TISCI_DEV_DMSC0:
+ return "DMSC0";
+ case TISCI_DEV_MCU_DCC0:
+ return "MCU_DCC0";
+ case TISCI_DEV_DEBUGSS_WRAP0:
+ return "DEBUGSS_WRAP0";
+ case TISCI_DEV_DMASS0:
+ return "DMASS0";
+ case TISCI_DEV_DMASS0_BCDMA_0:
+ return "DMASS0_BCDMA_0";
+ case TISCI_DEV_DMASS0_CBASS_0:
+ return "DMASS0_CBASS_0";
+ case TISCI_DEV_DMASS0_INTAGGR_0:
+ return "DMASS0_INTAGGR_0";
+ case TISCI_DEV_DMASS0_IPCSS_0:
+ return "DMASS0_IPCSS_0";
+ case TISCI_DEV_DMASS0_PKTDMA_0:
+ return "DMASS0_PKTDMA_0";
+ case TISCI_DEV_DMASS0_RINGACC_0:
+ return "DMASS0_RINGACC_0";
+ case TISCI_DEV_MCU_TIMER0:
+ return "MCU_TIMER0";
+ case TISCI_DEV_TIMER0:
+ return "TIMER0";
+ case TISCI_DEV_TIMER1:
+ return "TIMER1";
+ case TISCI_DEV_TIMER2:
+ return "TIMER2";
+ case TISCI_DEV_TIMER3:
+ return "TIMER3";
+ case TISCI_DEV_TIMER4:
+ return "TIMER4";
+ case TISCI_DEV_TIMER5:
+ return "TIMER5";
+ case TISCI_DEV_TIMER6:
+ return "TIMER6";
+ case TISCI_DEV_TIMER7:
+ return "TIMER7";
+ case TISCI_DEV_TIMER8:
+ return "TIMER8";
+ case TISCI_DEV_TIMER9:
+ return "TIMER9";
+ case TISCI_DEV_TIMER10:
+ return "TIMER10";
+ case TISCI_DEV_TIMER11:
+ return "TIMER11";
+ case TISCI_DEV_MCU_TIMER1:
+ return "MCU_TIMER1";
+ case TISCI_DEV_MCU_TIMER2:
+ return "MCU_TIMER2";
+ case TISCI_DEV_MCU_TIMER3:
+ return "MCU_TIMER3";
+ case TISCI_DEV_ECAP0:
+ return "ECAP0";
+ case TISCI_DEV_ECAP1:
+ return "ECAP1";
+ case TISCI_DEV_ECAP2:
+ return "ECAP2";
+ case TISCI_DEV_ELM0:
+ return "ELM0";
+ case TISCI_DEV_EMIF_DATA_0_VD:
+ return "EMIF_DATA_0_VD";
+ case TISCI_DEV_MMCSD0:
+ return "MMCSD0";
+ case TISCI_DEV_MMCSD1:
+ return "MMCSD1";
+ case TISCI_DEV_EQEP0:
+ return "EQEP0";
+ case TISCI_DEV_EQEP1:
+ return "EQEP1";
+ case TISCI_DEV_GTC0:
+ return "GTC0";
+ case TISCI_DEV_EQEP2:
+ return "EQEP2";
+ case TISCI_DEV_ESM0:
+ return "ESM0";
+ case TISCI_DEV_MCU_ESM0:
+ return "MCU_ESM0";
+ case TISCI_DEV_FSIRX0:
+ return "FSIRX0";
+ case TISCI_DEV_FSIRX1:
+ return "FSIRX1";
+ case TISCI_DEV_FSIRX2:
+ return "FSIRX2";
+ case TISCI_DEV_FSIRX3:
+ return "FSIRX3";
+ case TISCI_DEV_FSIRX4:
+ return "FSIRX4";
+ case TISCI_DEV_FSIRX5:
+ return "FSIRX5";
+ case TISCI_DEV_FSITX0:
+ return "FSITX0";
+ case TISCI_DEV_FSITX1:
+ return "FSITX1";
+ case TISCI_DEV_FSS0:
+ return "FSS0";
+ case TISCI_DEV_FSS0_FSAS_0:
+ return "FSS0_FSAS_0";
+ case TISCI_DEV_FSS0_OSPI_0:
+ return "FSS0_OSPI_0";
+ case TISCI_DEV_GICSS0:
+ return "GICSS0";
+ case TISCI_DEV_GPIO0:
+ return "GPIO0";
+ case TISCI_DEV_GPIO1:
+ return "GPIO1";
+ case TISCI_DEV_MCU_GPIO0:
+ return "MCU_GPIO0";
+ case TISCI_DEV_GPMC0:
+ return "GPMC0";
+ case TISCI_DEV_PRU_ICSSG0:
+ return "PRU_ICSSG0";
+ case TISCI_DEV_PRU_ICSSG1:
+ return "PRU_ICSSG1";
+ case TISCI_DEV_LED0:
+ return "LED0";
+ case TISCI_DEV_CPTS0:
+ return "CPTS0";
+ case TISCI_DEV_DDPA0:
+ return "DDPA0";
+ case TISCI_DEV_EPWM0:
+ return "EPWM0";
+ case TISCI_DEV_EPWM1:
+ return "EPWM1";
+ case TISCI_DEV_EPWM2:
+ return "EPWM2";
+ case TISCI_DEV_EPWM3:
+ return "EPWM3";
+ case TISCI_DEV_EPWM4:
+ return "EPWM4";
+ case TISCI_DEV_EPWM5:
+ return "EPWM5";
+ case TISCI_DEV_EPWM6:
+ return "EPWM6";
+ case TISCI_DEV_EPWM7:
+ return "EPWM7";
+ case TISCI_DEV_EPWM8:
+ return "EPWM8";
+ case TISCI_DEV_VTM0:
+ return "VTM0";
+ case TISCI_DEV_MAILBOX0:
+ return "MAILBOX0";
+ case TISCI_DEV_MAIN2MCU_VD:
+ return "MAIN2MCU_VD";
+ case TISCI_DEV_MCAN0:
+ return "MCAN0";
+ case TISCI_DEV_MCAN1:
+ return "MCAN1";
+ case TISCI_DEV_MCU_MCRC64_0:
+ return "MCU_MCRC64_0";
+ case TISCI_DEV_MCU2MAIN_VD:
+ return "MCU2MAIN_VD";
+ case TISCI_DEV_I2C0:
+ return "I2C0";
+ case TISCI_DEV_I2C1:
+ return "I2C1";
+ case TISCI_DEV_I2C2:
+ return "I2C2";
+ case TISCI_DEV_I2C3:
+ return "I2C3";
+ case TISCI_DEV_MCU_I2C0:
+ return "MCU_I2C0";
+ case TISCI_DEV_MCU_I2C1:
+ return "MCU_I2C1";
+ case TISCI_DEV_PCIE0:
+ return "PCIE0";
+ case TISCI_DEV_R5FSS0:
+ return "R5FSS0";
+ case TISCI_DEV_R5FSS1:
+ return "R5FSS1";
+ case TISCI_DEV_R5FSS0_CORE0:
+ return "R5FSS0_CORE0";
+ case TISCI_DEV_R5FSS0_CORE1:
+ return "R5FSS0_CORE1";
+ case TISCI_DEV_R5FSS1_CORE0:
+ return "R5FSS1_CORE0";
+ case TISCI_DEV_R5FSS1_CORE1:
+ return "R5FSS1_CORE1";
+ case TISCI_DEV_RTI0:
+ return "RTI0";
+ case TISCI_DEV_RTI1:
+ return "RTI1";
+ case TISCI_DEV_RTI8:
+ return "RTI8";
+ case TISCI_DEV_RTI9:
+ return "RTI9";
+ case TISCI_DEV_RTI10:
+ return "RTI10";
+ case TISCI_DEV_RTI11:
+ return "RTI11";
+ case TISCI_DEV_MCU_RTI0:
+ return "MCU_RTI0";
+ case TISCI_DEV_SA2_UL0:
+ return "SA2_UL0";
+ case TISCI_DEV_COMPUTE_CLUSTER0:
+ return "COMPUTE_CLUSTER0";
+ case TISCI_DEV_A53SS0_CORE_0:
+ return "A53SS0_CORE_0";
+ case TISCI_DEV_A53SS0_CORE_1:
+ return "A53SS0_CORE_1";
+ case TISCI_DEV_A53SS0:
+ return "A53SS0";
+ case TISCI_DEV_DDR16SS0:
+ return "DDR16SS0";
+ case TISCI_DEV_PSC0:
+ return "PSC0";
+ case TISCI_DEV_MCU_PSC0:
+ return "MCU_PSC0";
+ case TISCI_DEV_MCSPI0:
+ return "MCSPI0";
+ case TISCI_DEV_MCSPI1:
+ return "MCSPI1";
+ case TISCI_DEV_MCSPI2:
+ return "MCSPI2";
+ case TISCI_DEV_MCSPI3:
+ return "MCSPI3";
+ case TISCI_DEV_MCSPI4:
+ return "MCSPI4";
+ case TISCI_DEV_UART0:
+ return "UART0";
+ case TISCI_DEV_MCU_MCSPI0:
+ return "MCU_MCSPI0";
+ case TISCI_DEV_MCU_MCSPI1:
+ return "MCU_MCSPI1";
+ case TISCI_DEV_MCU_UART0:
+ return "MCU_UART0";
+ case TISCI_DEV_SPINLOCK0:
+ return "SPINLOCK0";
+ case TISCI_DEV_TIMERMGR0:
+ return "TIMERMGR0";
+ case TISCI_DEV_UART1:
+ return "UART1";
+ case TISCI_DEV_UART2:
+ return "UART2";
+ case TISCI_DEV_UART3:
+ return "UART3";
+ case TISCI_DEV_UART4:
+ return "UART4";
+ case TISCI_DEV_UART5:
+ return "UART5";
+ case TISCI_DEV_BOARD0:
+ return "BOARD0";
+ case TISCI_DEV_UART6:
+ return "UART6";
+ case TISCI_DEV_MCU_UART1:
+ return "MCU_UART1";
+ case TISCI_DEV_USB0:
+ return "USB0";
+ case TISCI_DEV_SERDES_10G0:
+ return "SERDES_10G0";
+ case TISCI_DEV_PBIST0:
+ return "PBIST0";
+ case TISCI_DEV_PBIST1:
+ return "PBIST1";
+ case TISCI_DEV_PBIST2:
+ return "PBIST2";
+ case TISCI_DEV_PBIST3:
+ return "PBIST3";
+ case TISCI_DEV_COMPUTE_CLUSTER0_PBIST_0:
+ return "COMPUTE_CLUSTER0_PBIST_0";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+static void ti_dmsc_init_device_states(TIDmscState *s)
+{
+ for (size_t i = 0; i < TISCI_DEV_ID_MAX; i++) {
+ s->dev_hw_state[i] = TISCI_MSG_VALUE_DEVICE_HW_STATE_ON;
+ s->dev_prog_state[i] = TISCI_MSG_VALUE_DEVICE_HW_STATE_ON;
+ }
+
+ s->dev_hw_state[TISCI_DEV_MCU_M4FSS0_CORE0] =
+ TISCI_MSG_VALUE_DEVICE_HW_STATE_OFF;
+ s->dev_prog_state[TISCI_DEV_MCU_M4FSS0_CORE0] =
+ TISCI_MSG_VALUE_DEVICE_HW_STATE_OFF;
+ s->m4_running = false;
+ memset(s->proc_bootvector, 0, sizeof(s->proc_bootvector));
+}
+
+static const char *ti_dmsc_message_name_from_id(uint16_t msg_id)
+{
+ switch (msg_id) {
+ case TISCI_MSG_GET_DEVICE:
+ return "GET_DEVICE";
+ case TISCI_MSG_SET_DEVICE:
+ return "SET_DEVICE";
+ case TISCI_MSG_SET_DEVICE_RESETS:
+ return "SET_DEVICE_RESETS";
+ case TISCI_MSG_DEVICE_DROP_POWERUP_REF:
+ return "DEVICE_DROP_POWERUP_REF";
+ case TISCI_MSG_PREPARE_SLEEP:
+ return "PREPARE_SLEEP";
+ case TISCI_MSG_ENTER_SLEEP:
+ return "ENTER_SLEEP";
+ case TISCI_MSG_VERSION:
+ return "VERSION";
+ case TISCI_MSG_BOOT_NOTIFICATION:
+ return "BOOT_NOTIFICATION";
+ case TISCI_MSG_BOARD_CONFIG:
+ return "BOARD_CONFIG";
+ case TISCI_MSG_BOARD_CONFIG_RM:
+ return "BOARD_CONFIG_RM";
+ case TISCI_MSG_BOARD_CONFIG_SECURITY:
+ return "BOARD_CONFIG_SECURITY";
+ case TISCI_MSG_BOARD_CONFIG_PM:
+ return "BOARD_CONFIG_PM";
+ case TISCI_MSG_ENABLE_WDT:
+ return "ENABLE_WDT";
+ case TISCI_MSG_WAKE_RESET:
+ return "WAKE_RESET";
+ case TISCI_MSG_WAKE_REASON:
+ return "WAKE_REASON";
+ case TISCI_MSG_GOODBYE:
+ return "GOODBYE";
+ case TISCI_MSG_SYS_RESET:
+ return "SYS_RESET";
+ case TISCI_MSG_QUERY_MSMC:
+ return "QUERY_MSMC";
+ case TISCI_MSG_GET_TRACE_CONFIG:
+ return "GET_TRACE_CONFIG";
+ case TISCI_MSG_QUERY_FW_CAPS:
+ return "QUERY_FW_CAPS";
+ case TISCI_MSG_SET_CLOCK:
+ return "SET_CLOCK";
+ case TISCI_MSG_GET_CLOCK:
+ return "GET_CLOCK";
+ case TISCI_MSG_SET_CLOCK_PARENT:
+ return "SET_CLOCK_PARENT";
+ case TISCI_MSG_GET_CLOCK_PARENT:
+ return "GET_CLOCK_PARENT";
+ case TISCI_MSG_GET_NUM_CLOCK_PARENTS:
+ return "GET_NUM_CLOCK_PARENTS";
+ case TISCI_MSG_SET_FREQ:
+ return "SET_FREQ";
+ case TISCI_MSG_QUERY_FREQ:
+ return "QUERY_FREQ";
+ case TISCI_MSG_GET_FREQ:
+ return "GET_FREQ";
+ case TISCI_MSG_PROC_REQUEST:
+ return "PROC_REQUEST";
+ case TISCI_MSG_PROC_RELEASE:
+ return "PROC_RELEASE";
+ case TISCI_MSG_PROC_HANDOVER:
+ return "PROC_HANDOVER";
+ case TISCI_MSG_SET_CONFIG:
+ return "SET_CONFIG";
+ case TISCI_MSG_SET_CTRL:
+ return "SET_CTRL";
+ case TISCI_MSG_GET_STATUS:
+ return "GET_STATUS";
+ case TISCI_MSG_WAIT_PROC_BOOT_STATUS:
+ return "WAIT_PROC_BOOT_STATUS";
+ case TISCI_MSG_FWL_SET:
+ return "FWL_SET";
+ case TISCI_MSG_FWL_GET:
+ return "FWL_GET";
+ case TISCI_MSG_FWL_CHANGE_OWNER:
+ return "FWL_CHANGE_OWNER";
+ case TISCI_MSG_SA2UL_GET_DKEK:
+ return "SA2UL_GET_DKEK";
+ case TISCI_MSG_READ_SWREV:
+ return "READ_SWREV";
+ case TISCI_MSG_READ_KEYCNT_KEYREV:
+ return "READ_KEYCNT_KEYREV";
+ default:
+ return "UNKNOWN";
+ }
+}
+
+/*
+ * The DMSC has no MMIO. Sec-proxy calls us, when a client commits the last
+ * word of a request data window.
+ */
+static void ti_dmsc_handle_one(TIDmscClient *client, uint16_t thread_id,
+ const uint32_t *words, size_t nwords);
+
+/*
+ * Push a response to the client's TX thread. Secure hosts carry a 4-byte
+ * {u16 checksum; u16 reserved} prefix before TISciMsgHdr. The checksum word
+ * is present in the transport, but not validated by this model.
+ *
+ * TI_SCI_FLAG_REQ_GENERIC_NORESPONSE is handled here too. The handlers run
+ * still for side effects, but replies are suppressed unless AOP was set.
+ */
+static size_t ti_dmsc_client_respond(TIDmscClient *client, const void *msg,
+ size_t nbytes)
+{
+ TIDmscState *s = client->dmsc;
+ const uint32_t *words = msg;
+
+ if (!client->cur_req_wants_resp) {
+ return nbytes;
+ }
+
+ if (client->secure) {
+ uint32_t buf[TI_DMSC_MAX_WORDS + 1] = {0};
+
+ /*
+ * Control the sec-proxy payload limit, not only the local scratch
+ * buffer. The scratch buffer has the extra secure-prefix word.
+ */
+ if (nbytes + sizeof(uint32_t) > TI_DMSC_SEC_PROXY_PAYLOAD_MAX) {
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "ti-dmsc: secure response too large (%zu bytes), dropping\n",
+ nbytes);
+ return 0;
+ }
+
+ memcpy((uint8_t *)buf + sizeof(uint32_t), words, nbytes);
+ return ti_sec_proxy_push_msg(s->sec_proxy, client->tx_thread_id, buf,
+ nbytes + sizeof(uint32_t));
+ }
+
+ if (nbytes > TI_DMSC_SEC_PROXY_PAYLOAD_MAX) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: response too large (%zu bytes), dropping\n",
+ nbytes);
+ return 0;
+ }
+
+ return ti_sec_proxy_push_msg(s->sec_proxy, client->tx_thread_id, words,
+ nbytes);
+}
+
+/*
+ * Queue TISCI_MSG_BOOT_NOTIFICATION for secure boot hosts. SYSFW sends this
+ * unsolicited message once it is ready on the host response thread.
+ *
+ * Re-arm it on every reset: sec-proxy reset leaves thread slots alone. Reset
+ * the outbound counter first, since pushes only increment it.
+ */
+static void ti_dmsc_send_boot_notification(TIDmscClient *client)
+{
+ TISciMsgHdr notif = {0};
+
+ notif.type = TISCI_MSG_BOOT_NOTIFICATION;
+ notif.host = TISCI_HOST_ID_DMSC;
+ notif.seq = 0;
+ notif.flags = 0;
+
+ ti_sec_proxy_reset_thread_count(client->dmsc->sec_proxy,
+ client->tx_thread_id);
+
+ /*
+ * This is unsolicited, so ignore no-response state of the previous
+ * request and force delivery.
+ */
+ client->cur_req_wants_resp = true;
+
+ if (!ti_dmsc_client_respond(client, ¬if, sizeof(notif))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push BOOT_NOTIFICATION into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/* Bottom half: handle pending message outside of MMIO context */
+static void ti_dmsc_bh(void *opaque)
+{
+ TIDmscState *s = opaque;
+ uint32_t local_words[TI_DMSC_MAX_WORDS];
+
+ while (true) {
+ TIDmscClient *client = NULL;
+ size_t local_nwords = 0;
+ uint16_t tid = 0;
+
+ qemu_mutex_lock(&s->lock);
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ if (s->clients[i].pending) {
+ client = &s->clients[i];
+ break;
+ }
+ }
+ if (!client) {
+ qemu_mutex_unlock(&s->lock);
+ return;
+ }
+
+ tid = client->rx_thread_id;
+ local_nwords = client->pending_nwords;
+ if (local_nwords > TI_DMSC_MAX_WORDS) {
+ local_nwords = TI_DMSC_MAX_WORDS;
+ }
+ memcpy(local_words, client->pending_words,
+ local_nwords * sizeof(uint32_t));
+ client->pending = false;
+ client->pending_nwords = 0;
+ qemu_mutex_unlock(&s->lock);
+
+ ti_dmsc_handle_one(client, tid, local_words, local_nwords);
+ }
+}
+
+/*
+ * Sec-proxy calls this, when a client commits a message on our RX thread.
+ */
+static void ti_dmsc_sec_proxy_cb(void *opaque, uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TIDmscClient *client = opaque;
+ TIDmscState *s = client->dmsc;
+
+ if (thread_id != client->rx_thread_id) {
+ return;
+ }
+
+ qemu_mutex_lock(&s->lock);
+ client->pending = true;
+
+ if (nwords > TI_DMSC_MAX_WORDS) {
+ nwords = TI_DMSC_MAX_WORDS;
+ }
+ memcpy(client->pending_words, words, nwords * sizeof(uint32_t));
+ client->pending_nwords = nwords;
+ qemu_mutex_unlock(&s->lock);
+
+ qemu_bh_schedule(s->bh);
+}
+
+/* Dispatch one committed TISCI message. */
+static void ti_dmsc_handle_one(TIDmscClient *client, uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+
+ if (!s->sec_proxy) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: No sec-proxy linked, dropping message\n");
+ return;
+ }
+
+ size_t hdr_words = sizeof(TISciMsgHdr) / sizeof(uint32_t);
+ size_t min_words = hdr_words + (client->secure ? 1 : 0);
+
+ if (nwords < min_words) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Short message (words=%zu), dropping\n", nwords);
+ return;
+ }
+
+ if (client->secure) {
+ /*
+ * Secure R5 clients prepend a checksum/reserved word before
+ * TISciMsgHdr. Drop it before we cast payloads.
+ */
+ words += 1;
+ nwords -= 1;
+ }
+
+ TISciMsgHdr hdr = {0};
+ memcpy(&hdr, words, MIN(sizeof(hdr), nwords * sizeof(uint32_t)));
+
+ /*
+ * Remember AOP before dispatch. ti_dmsc_client_respond() uses it to
+ * suppress replies for requests with TI_SCI_FLAG_REQ_GENERIC_NORESPONSE.
+ */
+ client->cur_req_wants_resp = (hdr.flags & TISCI_MSG_FLAG_AOP) != 0;
+
+ if (hdr.type < ARRAY_SIZE(s->msg_handler) && s->msg_handler[hdr.type]) {
+ trace_dmsc_new_message_received(
+ hdr.type, ti_dmsc_message_name_from_id(hdr.type),
+ ti_dmsc_host_name_from_id(hdr.host), thread_id);
+ s->msg_handler[hdr.type](client, &hdr, thread_id, words, nwords);
+ return;
+ } else {
+ TISciMsgHdr resp = hdr;
+
+ trace_dmsc_unsupported_message(
+ ti_dmsc_message_name_from_id(hdr.type), hdr.type,
+ ti_dmsc_host_name_from_id(hdr.host), thread_id);
+ qemu_log_mask(
+ LOG_GUEST_ERROR,
+ "ti-dmsc: No handler for message type=0x%04x (%s), dropping\n",
+ hdr.type, ti_dmsc_message_name_from_id(hdr.type));
+
+ /*
+ * Header-only NAKs unblock callers, which wait for a response,
+ * instead of leaving them to time out.
+ */
+ resp.flags = 0;
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push NAK response into sec-proxy "
+ "thread=%u\n",
+ client->tx_thread_id);
+ }
+ }
+}
+
+static void ti_dmsc_reset_hold(Object *obj, ResetType type)
+{
+ TIDmscState *s = TI_DMSC(obj);
+
+ qemu_mutex_lock(&s->lock);
+ ti_dmsc_init_device_states(s);
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ s->clients[i].pending = false;
+ s->clients[i].pending_nwords = 0;
+ memset(s->clients[i].pending_words, 0,
+ sizeof(s->clients[i].pending_words));
+ }
+ /*
+ * msg_handler contains realize-time function pointers, not guest state.
+ * Clearing it during reset disables all TISCI handlers after the first
+ * cold reset.
+ */
+ qemu_mutex_unlock(&s->lock);
+
+ /*
+ * Secure clients get a fresh boot notification after every reset.
+ */
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ if (s->clients[i].secure) {
+ ti_dmsc_send_boot_notification(&s->clients[i]);
+ }
+ }
+}
+
+static TISciMsgHdr ti_dmsc_set_resp_flags(TISciMsgHdr *req_hdr, int add_flags)
+{
+ TISciMsgHdr resp = *req_hdr;
+ resp.flags =
+ ((req_hdr->flags & TISCI_MSG_FLAG_AOP) ? TISCI_MSG_FLAG_ACK : 0) |
+ add_flags;
+ return resp;
+}
+
+static void ti_dmsc_handle_set_clock(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgSetClockReq *req = (struct TisciMsgSetClockReq *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_set_clock(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device),
+ req->clk);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_CLOCK response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SET_CLOCK_PARENT stores no clock tree state. The model only returns the
+ * generic TISCI ACK/NAK header.
+ */
+static void ti_dmsc_handle_set_clock_parent(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgSetClockParentReq *req =
+ (struct TisciMsgSetClockParentReq *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_set_clock_parent(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->dev_id),
+ req->clk_id, req->parent_id);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_CLOCK_PARENT response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_set_freq(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_FREQ response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_query_freq(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgQueryFreqReq *req = (struct TisciMsgQueryFreqReq *)words;
+ struct TisciMsgQueryFreqResp resp = {0};
+
+ trace_dmsc_handle_query_freq(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device),
+ req->clk, req->clk32, req->target_freq_hz);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.freq_hz = req->target_freq_hz;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push QUERY_FREQ response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_freq(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgGetFreqReq *req = (struct TisciMsgGetFreqReq *)words;
+ struct TisciMsgQueryFreqResp resp = {0};
+
+ trace_dmsc_handle_get_freq(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device),
+ req->clk);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ /*
+ * Clock rates are not modelled per device. Return the fixed 200 MHz
+ * rate used by the AM64x board model for generic clocks.
+ */
+ resp.freq_hz = 200000000ULL;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_FREQ response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_clock_parents(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgGetNumClockParentsReq *req =
+ (struct TisciMsgGetNumClockParentsReq *)words;
+ struct TisciMsgGetNumClockParentsResp resp = {0};
+
+ trace_dmsc_handle_get_clock_parents(
+ ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device), req->clk, req->clk32);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ /*
+ * Clock parent topology is not modelled. Report two possible parents,
+ * which is the minimum topology where SET_CLOCK_PARENT has a meaning.
+ */
+ resp.num_parents = 2;
+ resp.num_parentint32_t = UINT_MAX;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_CLOCK_PARENTS response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * Clock muxes are not modelled. Report parent 0 as actual parent for all
+ * clocks.
+ */
+static void ti_dmsc_handle_get_clock_parent(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgGetClockParentResp resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.parent = 0;
+ resp.parent32 = 0;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_CLOCK_PARENT response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_clock(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgGetClockReq *req = (struct TisciMsgGetClockReq *)words;
+ struct TisciMsgGetClockResp resp = {0};
+
+ trace_dmsc_handle_get_clock(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->device),
+ req->clk, req->clk32);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.current_state = resp.programmed_state =
+ TISCI_MSG_VALUE_DEVICE_HW_STATE_ON;
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_CLOCK response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_stop_proc(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TiSciMsgReqProcRelease *req = (struct TiSciMsgReqProcRelease *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+ trace_dmsc_stop_proc(ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id,
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (req->processor_id == SCICLIENT_PROCID_MCU_M4FSS0_C0) {
+ s->m4_running = false;
+ }
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push PROC_RELEASE response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_start_proc(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TiSciMsgReqProcRequest *req = (struct TiSciMsgReqProcRequest *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_start_proc(ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id,
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push PROC_REQUEST response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SET_CTRL carries processor control flags, e.g. halt and reset-vector
+ * latch. Boot vectors are captured by SET_CONFIG and release happens via
+ * SET_DEVICE, so here is no processor-control state modeled.
+ */
+static void ti_dmsc_handle_proc_set_ctrl(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_CTRL response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * PROC_HANDOVER transfers a processor to another TISCI host. Ownership is
+ * not modeled; the request gets only the generic ACK/NAK header.
+ */
+static void ti_dmsc_handover_proc(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TiSciMsgReqProcHandover *req =
+ (struct TiSciMsgReqProcHandover *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handover_proc(ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id,
+ ti_dmsc_host_name_from_id(req->host_id),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push PROC_HANDOVER response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SYS_RESET is a TISCI system-reset command and is normally sent without
+ * AOP. Request a QEMU system reset; an AOP caller gets the bare ACK first.
+ */
+static void ti_dmsc_handle_sys_reset(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_sys_reset(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ ti_dmsc_client_respond(client, &resp, sizeof(resp));
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+static void ti_dmsc_query_hw_caps(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TiSciMsgQueryFwCapsResp resp = {0};
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.fw_caps = MSG_FLAG_CAPS_GENERIC;
+ trace_dmsc_get_fw_caps(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push FW CAPABILITIES response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_get_version(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TiSciMsgVersionResp resp = {0};
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.firmware_revision = 0x000a;
+ resp.abi_major = 4;
+ resp.abi_minor = 0;
+ snprintf(resp.firmware_description, sizeof(resp.firmware_description),
+ "QEMU_TI_DMSC (Wadims DMSC)");
+ trace_dmsc_get_version(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ resp.firmware_description);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push VERSION response into sec-proxy "
+ "thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_device(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TisciMsgGetDeviceReq *req = (struct TisciMsgGetDeviceReq *)words;
+ struct TisciMsgGetDeviceResp resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ if (req->id < TISCI_DEV_ID_MAX) {
+ resp.current_state = s->dev_hw_state[req->id];
+ resp.programmed_state = s->dev_prog_state[req->id];
+ } else {
+ resp.current_state = resp.programmed_state =
+ TISCI_MSG_VALUE_DEVICE_HW_STATE_ON;
+ }
+
+ trace_dmsc_handle_get_device(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->id),
+ resp.programmed_state, resp.current_state);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_DEVICE response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_get_status(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TisciMsgProcGetStatusReq *req =
+ (struct TisciMsgProcGetStatusReq *)words;
+ struct TisciMsgProcGetStatusResp resp = {0};
+
+ trace_dmsc_handle_get_status(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id);
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.processor_id = req->processor_id;
+ resp.bootvector_lo = 0;
+ resp.bootvector_hi = 0;
+ resp.config_flags_1 = 0;
+ resp.control_flags_1 = 0;
+ resp.status_flags_1 = 0;
+
+ if (req->processor_id == SCICLIENT_PROCID_A53_CL0_C0 ||
+ req->processor_id == SCICLIENT_PROCID_A53_CL0_C1) {
+ uint64_t bv =
+ s->proc_bootvector[req->processor_id - SCICLIENT_PROCID_A53_CL0_C0];
+
+ resp.bootvector_lo = (uint32_t)bv;
+ resp.bootvector_hi = (uint32_t)(bv >> 32);
+ }
+
+ if (req->processor_id == SCICLIENT_PROCID_MCU_M4FSS0_C0) {
+ resp.status_flags_1 |= TISCI_MSG_VAL_PROC_BOOT_STATUS_FLAG_M4F_WFI;
+ }
+
+ trace_dmsc_get_status_resp(ti_dmsc_proc_name_from_id(req->processor_id),
+ req->processor_id, resp.status_flags_1,
+ s->m4_running);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push GET_STATUS response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * WAIT_PROC_BOOT_STATUS waits for processor WFE/WFI status bits. Per-core
+ * polling is not modeled; no-response requests complete without a reply.
+ */
+static void ti_dmsc_handle_wait_proc_boot_status(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgReqWaitProcBootStatus *req =
+ (struct TisciMsgReqWaitProcBootStatus *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_wait_proc_boot_status(
+ ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_proc_name_from_id(req->processor_id), req->processor_id);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push WAIT_PROC_BOOT_STATUS response "
+ "into sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_set_device_state(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TisciMsgSetDeviceReq *req = (struct TisciMsgSetDeviceReq *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_set_device_state(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->id),
+ ti_dmsc_device_state_to_str(req->state));
+
+ if (req->id < TISCI_DEV_ID_MAX) {
+ s->dev_hw_state[req->id] = s->dev_prog_state[req->id] = req->state;
+ }
+
+ if (req->id == TISCI_DEV_MCU_M4FSS0_CORE0 &&
+ req->state != TISCI_MSG_VALUE_DEVICE_SW_STATE_ON) {
+ s->m4_running = false;
+ }
+
+ /*
+ * SET_DEVICE ON releases an A53 core at the vector captured by
+ * SET_CONFIG. Start the vCPU in EL3/AArch64; ALREADY_ON is harmless on
+ * repeated handoffs.
+ */
+ if (req->id == TISCI_DEV_A53SS0_CORE_0 ||
+ req->id == TISCI_DEV_A53SS0_CORE_1) {
+ int core = req->id - TISCI_DEV_A53SS0_CORE_0;
+ uint64_t cpuid = s->a53_cpu_id_base + core;
+
+ if (req->state == TISCI_MSG_VALUE_DEVICE_SW_STATE_ON) {
+ uint64_t entry = s->proc_bootvector[core];
+
+ trace_dmsc_a53_start(core, entry);
+ arm_set_cpu_on(cpuid, entry, 0, /* target_el */ 3,
+ /* target_aa64 */ true);
+ } else if (req->state == TISCI_MSG_VALUE_DEVICE_SW_STATE_AUTO_OFF) {
+ trace_dmsc_a53_stop(core);
+ arm_set_cpu_off(cpuid);
+ }
+ }
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_DEVICE response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_set_device_resets(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ struct TisciMsgSetDeviceResetsReq *req =
+ (struct TisciMsgSetDeviceResetsReq *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_set_device_resets(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ ti_dmsc_device_name_from_id(req->id),
+ req->resets);
+
+ if (req->id == TISCI_DEV_MCU_M4FSS0_CORE0) {
+ s->m4_running = !(req->resets);
+ if (req->resets == 1) {
+ arm_set_cpu_off(s->m4_cpu_id);
+ } else {
+ arm_set_cpu_on_and_reset(s->m4_cpu_id);
+ }
+ }
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_DEVICE_RESETS response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * BOARD_CONFIG messages carry SYSFW board-configuration blobs. This model
+ * accepts them, but does not consume or store the blobs.
+ */
+static void ti_dmsc_handle_board_config(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push BOARD_CONFIG response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SET_CONFIG carries the A53 boot vector used later by SET_DEVICE. The
+ * packed TISCI request puts bootvector_low at byte offset 9, so read it
+ * with byte loads, not with a struct cast.
+ */
+static void ti_dmsc_handle_proc_set_config(TIDmscClient *client,
+ TISciMsgHdr *hdr, uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ TIDmscState *s = client->dmsc;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_proc_set_config(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (nwords * sizeof(uint32_t) >= sizeof(TISciMsgHdr) + 9) {
+ /* Packed payload right after the 8-byte header. */
+ const uint8_t *p = (const uint8_t *)words + sizeof(TISciMsgHdr);
+ uint8_t proc_id = p[0];
+ uint64_t bv = (uint64_t)(uint32_t)ldl_le_p(p + 1) |
+ ((uint64_t)(uint32_t)ldl_le_p(p + 5) << 32);
+
+ if (proc_id == SCICLIENT_PROCID_A53_CL0_C0 ||
+ proc_id == SCICLIENT_PROCID_A53_CL0_C1) {
+ s->proc_bootvector[proc_id - SCICLIENT_PROCID_A53_CL0_C0] = bv;
+ trace_dmsc_a53_bootvector(proc_id, bv);
+ }
+ }
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SET_CONFIG response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * FWL_SET programs a firewall region. Firewall state is not modelled, so the
+ * request is accepted and only a generic ACK/NAK is returned.
+ */
+static void ti_dmsc_handle_fwl_set(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgReqFwlSetFirewallRegion *req =
+ (struct TisciMsgReqFwlSetFirewallRegion *)words;
+ TISciMsgHdr resp = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_fwl_set(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host), req->fwl_id,
+ req->region);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push FWL_SET response into sec-proxy "
+ "thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * FWL_GET returns one firewall region descriptor. The model echoes fwl_id
+ * and region, and leaves control/permission fields zeroed.
+ */
+static void ti_dmsc_handle_fwl_get(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgReqFwlGetFirewallRegion *req =
+ (struct TisciMsgReqFwlGetFirewallRegion *)words;
+ struct TisciMsgRespFwlGetFirewallRegion resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.fwl_id = req->fwl_id;
+ resp.region = req->region;
+
+ trace_dmsc_handle_fwl_get(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host), req->fwl_id,
+ req->region);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push FWL_GET response into sec-proxy "
+ "thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * FWL_CHANGE_OWNER changes the owner of a firewall region. Ownership is not
+ * tracked; echo the IDs and owner index back.
+ */
+static void ti_dmsc_handle_fwl_change_owner(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgReqFwlChangeOwnerInfo *req =
+ (struct TisciMsgReqFwlChangeOwnerInfo *)words;
+ struct TisciMsgRespFwlChangeOwnerInfo resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+ resp.fwl_id = req->fwl_id;
+ resp.region = req->region;
+ resp.owner_index = req->owner_index;
+
+ trace_dmsc_handle_fwl_change_owner(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ req->fwl_id, req->region,
+ req->owner_index);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push FWL_CHANGE_OWNER response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * SA2UL_GET_DKEK returns derived key data from SA2UL. No key ladder is
+ * modelled; the response contains zero bytes and is no secret material.
+ */
+static void ti_dmsc_handle_sa2ul_get_dkek(TIDmscClient *client,
+ TISciMsgHdr *hdr, uint16_t thread_id,
+ const uint32_t *words, size_t nwords)
+{
+ struct TisciMsgReqSa2ulGetDkek *req =
+ (struct TisciMsgReqSa2ulGetDkek *)words;
+ struct TisciMsgRespSa2ulGetDkek resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_sa2ul_get_dkek(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host),
+ req->sa2ul_instance);
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push SA2UL_GET_DKEK response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+/*
+ * READ_SWREV and READ_KEYCNT_KEYREV are security status queries. The status
+ * words are not modelled and read back as zero.
+ */
+static void ti_dmsc_handle_read_swrev(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgRespReadSwrev resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_read_swrev(ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push READ_SWREV response into "
+ "sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_handle_read_keycnt_keyrev(TIDmscClient *client,
+ TISciMsgHdr *hdr,
+ uint16_t thread_id,
+ const uint32_t *words,
+ size_t nwords)
+{
+ struct TisciMsgRespReadKeycntKeyrev resp = {0};
+
+ resp.hdr = ti_dmsc_set_resp_flags(hdr, 0);
+
+ trace_dmsc_handle_read_keycnt_keyrev(
+ ti_dmsc_message_name_from_id(hdr->type),
+ ti_dmsc_host_name_from_id(hdr->host));
+
+ if (!ti_dmsc_client_respond(client, &resp, sizeof(resp))) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: Failed to push READ_KEYCNT_KEYREV response "
+ "into sec-proxy thread=%u\n",
+ client->tx_thread_id);
+ }
+}
+
+static void ti_dmsc_realize(DeviceState *dev, Error **errp)
+{
+ ERRP_GUARD();
+ TIDmscState *s = TI_DMSC(dev);
+
+ if (!s->sec_proxy) {
+ error_setg(errp, "ti-dmsc: 'sec-proxy' link not set");
+ return;
+ }
+
+ s->msg_words = ti_sec_proxy_get_msg_words(s->sec_proxy);
+ if (s->msg_words == 0) {
+ /* Fallback if sec-proxy does not provide it yet. */
+ s->msg_words = TI_DMSC_MAX_WORDS;
+ }
+
+ if (s->msg_words > TI_DMSC_MAX_WORDS) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "ti-dmsc: msg_words=%u too large, clamping to %u\n",
+ s->msg_words, TI_DMSC_MAX_WORDS);
+ s->msg_words = TI_DMSC_MAX_WORDS;
+ }
+
+ if (s->num_rx_threads || s->num_tx_threads) {
+ if (s->num_rx_threads != s->num_tx_threads) {
+ error_setg(
+ errp,
+ "ti-dmsc: rx-threads and tx-threads must have the same length");
+ return;
+ }
+ if (s->num_rx_threads == 0) {
+ error_setg(errp, "ti-dmsc: rx-threads list is empty");
+ return;
+ }
+ s->num_clients = s->num_rx_threads;
+ s->clients = g_new0(TIDmscClient, s->num_clients);
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ s->clients[i].dmsc = s;
+ s->clients[i].rx_thread_id = s->rx_thread_ids[i];
+ s->clients[i].tx_thread_id = s->tx_thread_ids[i];
+ }
+ } else {
+ s->num_clients = 1;
+ s->clients = g_new0(TIDmscClient, s->num_clients);
+ s->clients[0].dmsc = s;
+ s->clients[0].rx_thread_id = s->rx_thread_id;
+ s->clients[0].tx_thread_id = s->tx_thread_id;
+ }
+
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ for (uint32_t j = 0; j < s->num_secure_rx_threads; j++) {
+ if (s->clients[i].rx_thread_id == s->secure_rx_threads[j]) {
+ s->clients[i].secure = true;
+ break;
+ }
+ }
+ }
+
+ s->msg_handler[TISCI_MSG_PROC_RELEASE] = ti_dmsc_stop_proc;
+ s->msg_handler[TISCI_MSG_PROC_REQUEST] = ti_dmsc_start_proc;
+ s->msg_handler[TISCI_MSG_PROC_HANDOVER] = ti_dmsc_handover_proc;
+ s->msg_handler[TISCI_MSG_SET_CTRL] = ti_dmsc_handle_proc_set_ctrl;
+ s->msg_handler[TISCI_MSG_SYS_RESET] = ti_dmsc_handle_sys_reset;
+ s->msg_handler[TISCI_MSG_QUERY_FW_CAPS] = ti_dmsc_query_hw_caps;
+ s->msg_handler[TISCI_MSG_VERSION] = ti_dmsc_get_version;
+ s->msg_handler[TISCI_MSG_GET_DEVICE] = ti_dmsc_handle_get_device;
+ s->msg_handler[TISCI_MSG_SET_DEVICE] = ti_dmsc_handle_set_device_state;
+ s->msg_handler[TISCI_MSG_GET_STATUS] = ti_dmsc_handle_get_status;
+ s->msg_handler[TISCI_MSG_WAIT_PROC_BOOT_STATUS] =
+ ti_dmsc_handle_wait_proc_boot_status;
+ s->msg_handler[TISCI_MSG_SET_DEVICE_RESETS] =
+ ti_dmsc_handle_set_device_resets;
+ s->msg_handler[TISCI_MSG_GET_CLOCK] = ti_dmsc_handle_get_clock;
+ s->msg_handler[TISCI_MSG_SET_CLOCK] = ti_dmsc_handle_set_clock;
+ s->msg_handler[TISCI_MSG_GET_CLOCK_PARENT] =
+ ti_dmsc_handle_get_clock_parent;
+ s->msg_handler[TISCI_MSG_GET_NUM_CLOCK_PARENTS] =
+ ti_dmsc_handle_get_clock_parents;
+ s->msg_handler[TISCI_MSG_SET_CLOCK_PARENT] =
+ ti_dmsc_handle_set_clock_parent;
+ s->msg_handler[TISCI_MSG_QUERY_FREQ] = ti_dmsc_handle_query_freq;
+ s->msg_handler[TISCI_MSG_GET_FREQ] = ti_dmsc_handle_get_freq;
+ s->msg_handler[TISCI_MSG_SET_FREQ] = ti_dmsc_handle_set_freq;
+ s->msg_handler[TISCI_MSG_SET_CONFIG] = ti_dmsc_handle_proc_set_config;
+ s->msg_handler[TISCI_MSG_BOARD_CONFIG] = ti_dmsc_handle_board_config;
+ s->msg_handler[TISCI_MSG_BOARD_CONFIG_RM] = ti_dmsc_handle_board_config;
+ s->msg_handler[TISCI_MSG_BOARD_CONFIG_SECURITY] =
+ ti_dmsc_handle_board_config;
+ s->msg_handler[TISCI_MSG_BOARD_CONFIG_PM] = ti_dmsc_handle_board_config;
+ s->msg_handler[TISCI_MSG_FWL_SET] = ti_dmsc_handle_fwl_set;
+ s->msg_handler[TISCI_MSG_FWL_GET] = ti_dmsc_handle_fwl_get;
+ s->msg_handler[TISCI_MSG_FWL_CHANGE_OWNER] =
+ ti_dmsc_handle_fwl_change_owner;
+ s->msg_handler[TISCI_MSG_SA2UL_GET_DKEK] = ti_dmsc_handle_sa2ul_get_dkek;
+ s->msg_handler[TISCI_MSG_READ_SWREV] = ti_dmsc_handle_read_swrev;
+ s->msg_handler[TISCI_MSG_READ_KEYCNT_KEYREV] =
+ ti_dmsc_handle_read_keycnt_keyrev;
+
+ for (uint32_t i = 0; i < s->num_clients; i++) {
+ ti_sec_proxy_register_msg_cb(s->sec_proxy, s->clients[i].rx_thread_id,
+ ti_dmsc_sec_proxy_cb, &s->clients[i]);
+ }
+
+ ti_dmsc_init_device_states(s);
+
+ /*
+ * ti-dmsc is a pure QOM child with no bus/MMIO reset path. Register it
+ * with the global reset container, so cold boot and system_reset both
+ * requeue the DMSC boot notification.
+ */
+ qemu_register_resettable(OBJECT(dev));
+}
+
+static void ti_dmsc_init(Object *obj)
+{
+ TIDmscState *s = TI_DMSC(obj);
+
+ qemu_mutex_init(&s->lock);
+ s->bh = qemu_bh_new(ti_dmsc_bh, s);
+ s->num_rx_threads = 0;
+ s->rx_thread_ids = NULL;
+ s->num_tx_threads = 0;
+ s->tx_thread_ids = NULL;
+ s->num_secure_rx_threads = 0;
+ s->secure_rx_threads = NULL;
+ s->num_clients = 0;
+ s->clients = NULL;
+
+ /*
+ * Legacy single-client defaults; machines pass normally thread arrays.
+ */
+ /* Legacy M4 response thread default: 16. */
+
+ s->msg_words = TI_DMSC_MAX_WORDS;
+ object_property_add_link(
+ obj, "sec-proxy", TYPE_TI_SEC_PROXY, (Object **)&s->sec_proxy,
+ qdev_prop_allow_set_link_before_realize, OBJ_PROP_LINK_STRONG);
+}
+
+static void ti_dmsc_finalize(Object *obj)
+{
+ TIDmscState *s = TI_DMSC(obj);
+
+ if (s->bh) {
+ qemu_bh_delete(s->bh);
+ s->bh = NULL;
+ }
+ g_free(s->clients);
+ s->clients = NULL;
+ s->num_clients = 0;
+ g_free(s->rx_thread_ids);
+ s->rx_thread_ids = NULL;
+ s->num_rx_threads = 0;
+ g_free(s->tx_thread_ids);
+ s->tx_thread_ids = NULL;
+ s->num_tx_threads = 0;
+ g_free(s->secure_rx_threads);
+ s->secure_rx_threads = NULL;
+ s->num_secure_rx_threads = 0;
+ qemu_mutex_destroy(&s->lock);
+}
+
+static const Property ti_dmsc_props[] = {
+ DEFINE_PROP_UINT16("rx-thread", TIDmscState, rx_thread_id, 17),
+ DEFINE_PROP_UINT16("tx-thread", TIDmscState, tx_thread_id, 16),
+ DEFINE_PROP_ARRAY("rx-threads", TIDmscState, num_rx_threads, rx_thread_ids,
+ qdev_prop_uint16, uint16_t),
+ DEFINE_PROP_ARRAY("tx-threads", TIDmscState, num_tx_threads, tx_thread_ids,
+ qdev_prop_uint16, uint16_t),
+ DEFINE_PROP_ARRAY("secure-rx-threads", TIDmscState, num_secure_rx_threads,
+ secure_rx_threads, qdev_prop_uint16, uint16_t),
+ DEFINE_PROP_UINT64("m4-cpu-id", TIDmscState, m4_cpu_id, 0),
+ DEFINE_PROP_UINT64("a53-cpu-id-base", TIDmscState, a53_cpu_id_base, 0),
+};
+
+static void ti_dmsc_class_init(ObjectClass *klass, const void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = ti_dmsc_realize;
+ rc->phases.hold = ti_dmsc_reset_hold;
+ device_class_set_props(dc, ti_dmsc_props);
+}
+
+static const TypeInfo ti_dmsc_info = {
+ .name = TYPE_TI_DMSC,
+ .parent = TYPE_DEVICE,
+ .instance_size = sizeof(TIDmscState),
+ .instance_init = ti_dmsc_init,
+ .instance_finalize = ti_dmsc_finalize,
+ .class_init = ti_dmsc_class_init,
+};
+
+static void ti_dmsc_types(void)
+{
+ type_register_static(&ti_dmsc_info);
+}
+
+type_init(ti_dmsc_types)
diff --git a/hw/misc/trace-events b/hw/misc/trace-events
index dfa2d04117..d6fac538b4 100644
--- a/hw/misc/trace-events
+++ b/hw/misc/trace-events
@@ -456,6 +456,38 @@ iommu_testdev_dma_verify(uint32_t expected, uint32_t actual) "expected=0x%x actu
iommu_testdev_dma_result(uint32_t result) "DMA completed result=0x%x"
iommu_testdev_dma_armed(bool armed) "armed=%d"
+# ti-dmsc.c
+dmsc_handle_set_clock(const char *type, const char *host_name, const char *device_name, int clk) "%s received from host %s, device %s, clk: %i"
+dmsc_handle_get_device(const char *type, const char *host_name, const char *device_name, uint8_t prog_state, uint8_t current_state) "%s received from host %s, device %s, response: prog_state: %i, current_state: %i"
+dmsc_handle_get_clock(const char *type, const char *host_name, const char *device_name, uint8_t clk, uint32_t clk32) "%s received from host %s, device %s, clk %u, clk32 %u"
+dmsc_handle_get_clock_parents(const char *type, const char *host_name, const char *device_name, uint8_t clk, uint32_t clk32) "%s received from host %s, device %s, clk %u, clk32 %u"
+dmsc_handle_set_clock_parent(const char *type, const char *host_name, const char *device_name, uint8_t clk_id, uint8_t parent_id) "%s received from host %s, device %s, clk_id %u, parent_id %u"
+dmsc_handle_query_freq(const char *type, const char *host_name, const char *device_name, uint8_t clk, uint32_t clk32, uint64_t target_freq) "%s received from host %s, device %s, clk %u, clk32 %u, target_frequency %"PRIu64
+dmsc_handle_get_freq(const char *type, const char *host_name, const char *device_name, uint8_t clk) "%s received from host %s, device %s, clk %u"
+dmsc_handle_proc_set_config(const char *type, const char *host_name) "%s received from host %s"
+dmsc_handle_set_device_state(const char *type, const char *host_name, const char *device_name, const char *state) "%s received from host %s, device %s, state %s"
+dmsc_handle_set_device_resets(const char *type, const char *host_name, const char *device_name, uint32_t resets) "%s received from host %s, device %s, resets 0x%08x"
+dmsc_handle_get_status(const char *type, const char *host_name, const char *proc_name, uint8_t proc_id) "%s received from host %s, proc %s (%u)"
+dmsc_handle_wait_proc_boot_status(const char *type, const char *host_name, const char *proc_name, uint8_t proc_id) "%s received from host %s, proc %s (%u)"
+dmsc_get_status_resp(const char *proc_name, uint8_t proc_id, uint32_t status_flags, int m4_running) "GET_STATUS response for %s (%u): status_flags=0x%08x, m4_running=%d"
+dmsc_unsupported_message(const char *msg, int type, const char *host, int thread_id) "No handler for message type=%s (%i) from host %s ... (thread id: %i) dropping"
+dmsc_new_message_received(int type, const char *msg, const char *host, int thread_id) "Handling message type=0x%04x (%s) from host=%s, thread=%u"
+dmsc_get_version(const char* msg, const char *host, const char *description) "Get Version received %s from host %s. Description returned %s"
+dmsc_get_fw_caps(const char* msg, const char *host) "Get FW Caps Request received %s from host %s"
+dmsc_start_proc(const char* proc_name, int proc_id, const char *host) "Requested to start Processor %s (%i) from host %s"
+dmsc_stop_proc(const char* proc_name, int proc_id, const char *host) "Requested to stop Processor %s (%i) from host %s"
+dmsc_handover_proc(const char* proc_name, int proc_id, const char *new_host, const char *host) "Requested to hand over Processor %s (%i) to host %s, from host %s"
+dmsc_a53_bootvector(uint8_t proc_id, uint64_t bootvector) "Captured boot vector for A53 proc %u: 0x%" PRIx64
+dmsc_a53_start(int core, uint64_t entry) "Cold-starting A53 core %d at entry 0x%" PRIx64 " (EL3/AArch64)"
+dmsc_a53_stop(int core) "Powering off A53 core %d"
+dmsc_handle_sys_reset(const char *type, const char *host_name) "%s received from host %s, requesting machine reset"
+dmsc_handle_fwl_set(const char *type, const char *host_name, uint16_t fwl_id, uint16_t region) "%s received from host %s, fwl_id %u, region %u"
+dmsc_handle_fwl_get(const char *type, const char *host_name, uint16_t fwl_id, uint16_t region) "%s received from host %s, fwl_id %u, region %u"
+dmsc_handle_fwl_change_owner(const char *type, const char *host_name, uint16_t fwl_id, uint16_t region, uint8_t owner_index) "%s received from host %s, fwl_id %u, region %u, owner_index %u"
+dmsc_handle_sa2ul_get_dkek(const char *type, const char *host_name, uint8_t sa2ul_instance) "%s received from host %s, sa2ul_instance %u (returning zeroed DKEK)"
+dmsc_handle_read_swrev(const char *type, const char *host_name) "%s received from host %s (returning swrev=0)"
+dmsc_handle_read_keycnt_keyrev(const char *type, const char *host_name) "%s received from host %s (returning keycnt=keyrev=0)"
+
# ti-rat.c
rat_enable_region(int idx, uint64_t size, uint64_t source, uint64_t dest) "Enabling RAT Region %u: size 0x%"PRIx64" map 0x%"PRIx64" -> 0x%"PRIx64
rat_disable_region(int idx) "Disabling RAT Region %u"
diff --git a/include/hw/misc/ti-dmsc.h b/include/hw/misc/ti-dmsc.h
new file mode 100644
index 0000000000..f918890180
--- /dev/null
+++ b/include/hw/misc/ti-dmsc.h
@@ -0,0 +1,661 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (c) 2026 CMBLU Energy AG
+ * Author: Wadim Mueller <wafgo01@gmail.com>
+ *
+ * TI DMSC emulator device (minimal TISCI service)
+ *
+ * This device links to a TI SEC_PROXY instance and provides the minimal
+ * DMSC/TISCI request/response handler.
+ */
+
+#ifndef HW_MISC_TI_DMSC_H
+#define HW_MISC_TI_DMSC_H
+
+#include "qemu/compiler.h"
+#include "hw/core/qdev.h"
+#include "hw/misc/ti-sec-proxy.h"
+
+#define TISCI_MSG_VALUE_DEVICE_SW_STATE_AUTO_OFF 0
+
+/** Used by TISCI_MSG_SET_DEVICE to disable device but keep in retention. */
+#define TISCI_MSG_VALUE_DEVICE_SW_STATE_RETENTION 1
+
+/** Used by TISCI_MSG_SET_DEVICE to turn device on for usage. */
+#define TISCI_MSG_VALUE_DEVICE_SW_STATE_ON 2
+
+/* Device is off in TISCI_MSG_GET_DEVICE response. */
+#define TISCI_MSG_VALUE_DEVICE_HW_STATE_OFF 0
+
+/* Device is on in TISCI_MSG_GET_DEVICE response. */
+#define TISCI_MSG_VALUE_DEVICE_HW_STATE_ON 1
+
+/*
+ * Device is changing state. The state may remain until dependent hardware,
+ * e.g. pending IRQ handshakes, allows to complete the transition.
+ */
+#define TISCI_MSG_VALUE_DEVICE_HW_STATE_TRANS 2
+
+/** DMSC(Secure): Device Management and Security Control */
+#define TISCI_HOST_ID_DMSC (0U)
+/** MAIN_0_R5_0(Secure): Cortex R5_0 context 0 on Main island(BOOT) */
+#define TISCI_HOST_ID_MAIN_0_R5_0 (35U)
+/** MAIN_0_R5_1(Non Secure): Cortex R5_0 context 1 on Main island */
+#define TISCI_HOST_ID_MAIN_0_R5_1 (36U)
+/** MAIN_0_R5_2(Secure): Cortex R5_0 context 2 on Main island */
+#define TISCI_HOST_ID_MAIN_0_R5_2 (37U)
+/** MAIN_0_R5_3(Non Secure): Cortex R5_0 context 3 on Main island */
+#define TISCI_HOST_ID_MAIN_0_R5_3 (38U)
+/** A53_0(Secure): Cortex a53 context 0 on Main island */
+#define TISCI_HOST_ID_A53_0 (10U)
+/** A53_1(Secure): Cortex A53 context 1 on Main island */
+#define TISCI_HOST_ID_A53_1 (11U)
+/** A53_2(Non Secure): Cortex A53 context 2 on Main island */
+#define TISCI_HOST_ID_A53_2 (12U)
+/** A53_3(Non Secure): Cortex A53 context 3 on Main island */
+#define TISCI_HOST_ID_A53_3 (13U)
+/** M4_0(Non Secure): M4 */
+#define TISCI_HOST_ID_M4_0 (30U)
+/** MAIN_1_R5_0(Secure): Cortex R5_1 context 0 on Main island */
+#define TISCI_HOST_ID_MAIN_1_R5_0 (40U)
+/** MAIN_1_R5_1(Non Secure): Cortex R5_1 context 1 on Main island */
+#define TISCI_HOST_ID_MAIN_1_R5_1 (41U)
+/** MAIN_1_R5_2(Secure): Cortex R5_1 context 2 on Main island */
+#define TISCI_HOST_ID_MAIN_1_R5_2 (42U)
+/** MAIN_1_R5_3(Non Secure): Cortex R5_1 context 3 on Main island */
+#define TISCI_HOST_ID_MAIN_1_R5_3 (43U)
+/** A53_4(Non Secure): Cortex A53 context 1 on Main island */
+#define TISCI_HOST_ID_A53_4 (14U)
+/** ICSSG_0(Non Secure): ICSSG context 0 on Main island */
+#define TISCI_HOST_ID_ICSSG_0 (50U)
+/** ICSSG_1(Non Secure): ICSSG context 1 on Main island */
+#define TISCI_HOST_ID_ICSSG_1 (51U)
+
+/* Catch-all host for board-config resource assignments. */
+#define TISCI_HOST_ID_ALL (128U)
+
+/** Number of unique hosts on the SoC */
+#define TISCI_HOST_ID_CNT (17U)
+
+#define TISCI_DEV_ADC0 0U
+#define TISCI_DEV_CMP_EVENT_INTROUTER0 1U
+#define TISCI_DEV_DBGSUSPENDROUTER0 2U
+#define TISCI_DEV_MAIN_GPIOMUX_INTROUTER0 3U
+#define TISCI_DEV_MCU_MCU_GPIOMUX_INTROUTER0 5U
+#define TISCI_DEV_TIMESYNC_EVENT_INTROUTER0 6U
+#define TISCI_DEV_MCU_M4FSS0 7U
+#define TISCI_DEV_MCU_M4FSS0_CBASS_0 8U
+#define TISCI_DEV_MCU_M4FSS0_CORE0 9U
+#define TISCI_DEV_CPSW0 13U
+#define TISCI_DEV_CPT2_AGGR0 14U
+#define TISCI_DEV_STM0 15U
+#define TISCI_DEV_DCC0 16U
+#define TISCI_DEV_DCC1 17U
+#define TISCI_DEV_DCC2 18U
+#define TISCI_DEV_DCC3 19U
+#define TISCI_DEV_DCC4 20U
+#define TISCI_DEV_DCC5 21U
+#define TISCI_DEV_DMSC0 22U
+#define TISCI_DEV_MCU_DCC0 23U
+#define TISCI_DEV_DEBUGSS_WRAP0 24U
+#define TISCI_DEV_DMASS0 25U
+#define TISCI_DEV_DMASS0_BCDMA_0 26U
+#define TISCI_DEV_DMASS0_CBASS_0 27U
+#define TISCI_DEV_DMASS0_INTAGGR_0 28U
+#define TISCI_DEV_DMASS0_IPCSS_0 29U
+#define TISCI_DEV_DMASS0_PKTDMA_0 30U
+#define TISCI_DEV_DMASS0_RINGACC_0 33U
+#define TISCI_DEV_MCU_TIMER0 35U
+#define TISCI_DEV_TIMER0 36U
+#define TISCI_DEV_TIMER1 37U
+#define TISCI_DEV_TIMER2 38U
+#define TISCI_DEV_TIMER3 39U
+#define TISCI_DEV_TIMER4 40U
+#define TISCI_DEV_TIMER5 41U
+#define TISCI_DEV_TIMER6 42U
+#define TISCI_DEV_TIMER7 43U
+#define TISCI_DEV_TIMER8 44U
+#define TISCI_DEV_TIMER9 45U
+#define TISCI_DEV_TIMER10 46U
+#define TISCI_DEV_TIMER11 47U
+#define TISCI_DEV_MCU_TIMER1 48U
+#define TISCI_DEV_MCU_TIMER2 49U
+#define TISCI_DEV_MCU_TIMER3 50U
+#define TISCI_DEV_ECAP0 51U
+#define TISCI_DEV_ECAP1 52U
+#define TISCI_DEV_ECAP2 53U
+#define TISCI_DEV_ELM0 54U
+#define TISCI_DEV_EMIF_DATA_0_VD 55U
+#define TISCI_DEV_MMCSD0 57U
+#define TISCI_DEV_MMCSD1 58U
+#define TISCI_DEV_EQEP0 59U
+#define TISCI_DEV_EQEP1 60U
+#define TISCI_DEV_GTC0 61U
+#define TISCI_DEV_EQEP2 62U
+#define TISCI_DEV_ESM0 63U
+#define TISCI_DEV_MCU_ESM0 64U
+#define TISCI_DEV_FSIRX0 65U
+#define TISCI_DEV_FSIRX1 66U
+#define TISCI_DEV_FSIRX2 67U
+#define TISCI_DEV_FSIRX3 68U
+#define TISCI_DEV_FSIRX4 69U
+#define TISCI_DEV_FSIRX5 70U
+#define TISCI_DEV_FSITX0 71U
+#define TISCI_DEV_FSITX1 72U
+#define TISCI_DEV_FSS0 73U
+#define TISCI_DEV_FSS0_FSAS_0 74U
+#define TISCI_DEV_FSS0_OSPI_0 75U
+#define TISCI_DEV_GICSS0 76U
+#define TISCI_DEV_GPIO0 77U
+#define TISCI_DEV_GPIO1 78U
+#define TISCI_DEV_MCU_GPIO0 79U
+#define TISCI_DEV_GPMC0 80U
+#define TISCI_DEV_PRU_ICSSG0 81U
+#define TISCI_DEV_PRU_ICSSG1 82U
+#define TISCI_DEV_LED0 83U
+#define TISCI_DEV_CPTS0 84U
+#define TISCI_DEV_DDPA0 85U
+#define TISCI_DEV_EPWM0 86U
+#define TISCI_DEV_EPWM1 87U
+#define TISCI_DEV_EPWM2 88U
+#define TISCI_DEV_EPWM3 89U
+#define TISCI_DEV_EPWM4 90U
+#define TISCI_DEV_EPWM5 91U
+#define TISCI_DEV_EPWM6 92U
+#define TISCI_DEV_EPWM7 93U
+#define TISCI_DEV_EPWM8 94U
+#define TISCI_DEV_VTM0 95U
+#define TISCI_DEV_MAILBOX0 96U
+#define TISCI_DEV_MAIN2MCU_VD 97U
+#define TISCI_DEV_MCAN0 98U
+#define TISCI_DEV_MCAN1 99U
+#define TISCI_DEV_MCU_MCRC64_0 100U
+#define TISCI_DEV_MCU2MAIN_VD 101U
+#define TISCI_DEV_I2C0 102U
+#define TISCI_DEV_I2C1 103U
+#define TISCI_DEV_I2C2 104U
+#define TISCI_DEV_I2C3 105U
+#define TISCI_DEV_MCU_I2C0 106U
+#define TISCI_DEV_MCU_I2C1 107U
+#define TISCI_DEV_PCIE0 114U
+#define TISCI_DEV_R5FSS0 119U
+#define TISCI_DEV_R5FSS1 120U
+#define TISCI_DEV_R5FSS0_CORE0 121U
+#define TISCI_DEV_R5FSS0_CORE1 122U
+#define TISCI_DEV_R5FSS1_CORE0 123U
+#define TISCI_DEV_R5FSS1_CORE1 124U
+#define TISCI_DEV_RTI0 125U
+#define TISCI_DEV_RTI1 126U
+#define TISCI_DEV_RTI8 127U
+#define TISCI_DEV_RTI9 128U
+#define TISCI_DEV_RTI10 130U
+#define TISCI_DEV_RTI11 131U
+#define TISCI_DEV_MCU_RTI0 132U
+#define TISCI_DEV_SA2_UL0 133U
+#define TISCI_DEV_COMPUTE_CLUSTER0 134U
+#define TISCI_DEV_A53SS0_CORE_0 135U
+#define TISCI_DEV_A53SS0_CORE_1 136U
+#define TISCI_DEV_A53SS0 137U
+#define TISCI_DEV_DDR16SS0 138U
+#define TISCI_DEV_PSC0 139U
+#define TISCI_DEV_MCU_PSC0 140U
+#define TISCI_DEV_MCSPI0 141U
+#define TISCI_DEV_MCSPI1 142U
+#define TISCI_DEV_MCSPI2 143U
+#define TISCI_DEV_MCSPI3 144U
+#define TISCI_DEV_MCSPI4 145U
+#define TISCI_DEV_UART0 146U
+#define TISCI_DEV_MCU_MCSPI0 147U
+#define TISCI_DEV_MCU_MCSPI1 148U
+#define TISCI_DEV_MCU_UART0 149U
+#define TISCI_DEV_SPINLOCK0 150U
+#define TISCI_DEV_TIMERMGR0 151U
+#define TISCI_DEV_UART1 152U
+#define TISCI_DEV_UART2 153U
+#define TISCI_DEV_UART3 154U
+#define TISCI_DEV_UART4 155U
+#define TISCI_DEV_UART5 156U
+#define TISCI_DEV_BOARD0 157U
+#define TISCI_DEV_UART6 158U
+#define TISCI_DEV_MCU_UART1 160U
+#define TISCI_DEV_USB0 161U
+#define TISCI_DEV_SERDES_10G0 162U
+#define TISCI_DEV_PBIST0 163U
+#define TISCI_DEV_PBIST1 164U
+#define TISCI_DEV_PBIST2 165U
+#define TISCI_DEV_PBIST3 166U
+#define TISCI_DEV_COMPUTE_CLUSTER0_PBIST_0 167U
+#define TISCI_DEV_ID_MAX 168U
+
+#define TISCI_MSG_FLAG_RESERVED0 BIT(0)
+/*
+ * ACK-on-processed: request a response after handling, ACK on success and
+ * NAK otherwise.
+ */
+#define TISCI_MSG_FLAG_AOP BIT(1)
+
+/** Indicate that this message is marked secure */
+#define TISCI_MSG_FLAG_SEC BIT(2)
+
+/* Response success flag; missing one means NAK. */
+#define TISCI_MSG_FLAG_ACK BIT(1)
+
+/* TISCI Message IDs */
+#define TISCI_MSG_VERSION (0x0002U)
+#define TISCI_MSG_BOOT_NOTIFICATION (0x000AU)
+#define TISCI_MSG_BOARD_CONFIG (0x000BU)
+#define TISCI_MSG_BOARD_CONFIG_RM (0x000CU)
+#define TISCI_MSG_BOARD_CONFIG_SECURITY (0x000DU)
+#define TISCI_MSG_BOARD_CONFIG_PM (0x000EU)
+
+#define TISCI_MSG_ENABLE_WDT (0x0000U)
+#define TISCI_MSG_WAKE_RESET (0x0001U)
+#define TISCI_MSG_WAKE_REASON (0x0003U)
+#define TISCI_MSG_GOODBYE (0x0004U)
+#define TISCI_MSG_SYS_RESET (0x0005U)
+
+#define TISCI_MSG_QUERY_MSMC (0x0020U)
+#define TISCI_MSG_GET_TRACE_CONFIG (0x0021U)
+#define TISCI_MSG_QUERY_FW_CAPS (0x0022U)
+
+#define TISCI_MSG_SET_CLOCK (0x0100U)
+#define TISCI_MSG_GET_CLOCK (0x0101U)
+#define TISCI_MSG_SET_CLOCK_PARENT (0x0102U)
+#define TISCI_MSG_GET_CLOCK_PARENT (0x0103U)
+#define TISCI_MSG_GET_NUM_CLOCK_PARENTS (0x0104U)
+#define TISCI_MSG_SET_FREQ (0x010cU)
+#define TISCI_MSG_QUERY_FREQ (0x010dU)
+#define TISCI_MSG_GET_FREQ (0x010eU)
+
+#define TISCI_MSG_SET_DEVICE (0x0200U)
+#define TISCI_MSG_GET_DEVICE (0x0201U)
+
+#define TISCI_MSG_SET_DEVICE_RESETS (0x0202U)
+#define TISCI_MSG_DEVICE_DROP_POWERUP_REF (0x0203U)
+
+#define TISCI_MSG_PREPARE_SLEEP (0x0300U)
+#define TISCI_MSG_ENTER_SLEEP (0x0301U)
+
+#define TISCI_MSG_PROC_REQUEST (0xc000U)
+#define TISCI_MSG_PROC_RELEASE (0xc001U)
+#define TISCI_MSG_PROC_HANDOVER (0xc005U)
+#define TISCI_MSG_SET_CONFIG (0xc100U)
+#define TISCI_MSG_SET_CTRL (0xc101U)
+#define TISCI_MSG_GET_STATUS (0xc400U)
+#define TISCI_MSG_WAIT_PROC_BOOT_STATUS (0xc401U)
+
+/*
+ * Security message IDs for K3 SA2UL, OTP and secure-boot TI-SCI services.
+ * Layouts match the U-Boot/Zephyr TI-SCI protocol headers.
+ */
+#define TISCI_MSG_FWL_SET (0x9000U)
+#define TISCI_MSG_FWL_GET (0x9001U)
+#define TISCI_MSG_FWL_CHANGE_OWNER (0x9002U)
+#define TISCI_MSG_SA2UL_GET_DKEK (0x9029U)
+#define TISCI_MSG_READ_SWREV (0x9033U)
+#define TISCI_MSG_READ_KEYCNT_KEYREV (0x9034U)
+
+#define TISCI_MSG_MAX_ID (0xc500U)
+
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 9 Processor 0) */
+#define SCICLIENT_PROCID_A53_CL0_C0 (0x20U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 9 Processor 1) */
+#define SCICLIENT_PROCID_A53_CL0_C1 (0x21U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 0 Processor 0) */
+#define SCICLIENT_PROCID_R5_CL0_C0 (0x01U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 0 Processor 1) */
+#define SCICLIENT_PROCID_R5_CL0_C1 (0x02U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 1 Processor 0) */
+#define SCICLIENT_PROCID_R5_CL1_C0 (0x06U)
+/** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 1 Processor 1) */
+#define SCICLIENT_PROCID_R5_CL1_C1 (0x07U)
+/*** AM64_MAIN_SEC_MMR_MAIN_0: (Cluster 16 Processor 0) */
+#define SCICLIENT_PROCID_MCU_M4FSS0_C0 (0x18U)
+
+#define TYPE_TI_DMSC "ti-dmsc"
+
+OBJECT_DECLARE_SIMPLE_TYPE(TIDmscState, TI_DMSC)
+
+/* Default: 64 bytes -> 16 words */
+#define TI_DMSC_MAX_WORDS 16
+
+/*
+ * Minimal TISCI wire structs. Keep them packed and model only fields this
+ * device actually consumes or returns.
+ */
+typedef struct TISciMsgHdr {
+ uint16_t type;
+ uint8_t host;
+ uint8_t seq;
+ uint32_t flags;
+} QEMU_PACKED TISciMsgHdr;
+
+struct TiSciMsgReqProcRequest {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+} QEMU_PACKED;
+
+struct TiSciMsgReqProcRelease {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+} QEMU_PACKED;
+
+/*
+ * TISCI_MSG_PROC_HANDOVER request. Response is only TISciMsgHdr ACK/NAK.
+ */
+struct TiSciMsgReqProcHandover {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+ uint8_t host_id;
+} QEMU_PACKED;
+
+#define TISCI_MSG_VAL_PROC_BOOT_STATUS_FLAG_M4F_WFI (0x00000002U)
+
+struct TisciMsgProcGetStatusReq {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+} QEMU_PACKED;
+
+struct TisciMsgProcGetStatusResp {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+ uint32_t bootvector_lo;
+ uint32_t bootvector_hi;
+ uint32_t config_flags_1;
+ uint32_t control_flags_1;
+ uint32_t status_flags_1;
+} QEMU_PACKED;
+
+/*
+ * WAIT_PROC_BOOT_STATUS request. Only processor_id is consumed by the no-op
+ * handler, so the trailing wait/status fields are left out.
+ */
+struct TisciMsgReqWaitProcBootStatus {
+ TISciMsgHdr hdr;
+ uint8_t processor_id;
+} QEMU_PACKED;
+
+/*
+ * SET_DEVICE matches the TISCI ABI layout: the reserved u32 before state is
+ * on the wire and keeps the state byte aligned to SYSFW.
+ */
+struct TisciMsgSetDeviceReq {
+ TISciMsgHdr hdr;
+ uint32_t id;
+ uint32_t reserved;
+ uint8_t state;
+} QEMU_PACKED;
+
+struct TisciMsgSetDeviceResetsReq {
+ TISciMsgHdr hdr;
+ uint32_t id;
+ uint32_t resets;
+} QEMU_PACKED;
+
+struct TiSciMsgQueryFwCapsResp {
+ TISciMsgHdr hdr;
+#define MSG_FLAG_CAPS_GENERIC BIT(0)
+#define MSG_FLAG_CAPS_LPM_DEEP_SLEEP BIT(1)
+#define MSG_FLAG_CAPS_LPM_MCU_ONLY BIT(2)
+#define MSG_FLAG_CAPS_LPM_STANDBY BIT(3)
+#define MSG_FLAG_CAPS_LPM_PARTIAL_IO BIT(4)
+#define MSG_FLAG_CAPS_LPM_DM_MANAGED BIT(5)
+ uint64_t fw_caps;
+} QEMU_PACKED;
+
+struct TiSciMsgVersionResp {
+ TISciMsgHdr hdr;
+ char firmware_description[32];
+ uint16_t firmware_revision;
+ uint8_t abi_major;
+ uint8_t abi_minor;
+} QEMU_PACKED;
+
+struct TisciMsgSetFreqReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint64_t min_freq_hz;
+ uint64_t target_freq_hz;
+ uint64_t max_freq_hz;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgQueryFreqReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint64_t min_freq_hz;
+ uint64_t target_freq_hz;
+ uint64_t max_freq_hz;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgQueryFreqResp {
+ TISciMsgHdr hdr;
+ uint64_t freq_hz;
+} QEMU_PACKED;
+
+/*
+ * GET_FREQ has only device/clock in the request. The response is hdr plus
+ * freq_hz, same payload as QUERY_FREQ.
+ */
+struct TisciMsgGetFreqReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+} QEMU_PACKED;
+
+struct TisciMsgSetClockReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+ uint8_t state;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgGetNumClockParentsReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgGetNumClockParentsResp {
+ TISciMsgHdr hdr;
+ uint8_t num_parents;
+ uint32_t num_parentint32_t;
+} QEMU_PACKED;
+
+struct TisciMsgGetClockParentReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgGetClockParentResp {
+ TISciMsgHdr hdr;
+ uint8_t parent;
+ uint32_t parent32;
+} QEMU_PACKED;
+
+/*
+ * TISCI_MSG_SET_CLOCK_PARENT request. Response is bare TISciMsgHdr ACK/NAK.
+ */
+struct TisciMsgSetClockParentReq {
+ TISciMsgHdr hdr;
+ uint32_t dev_id;
+ uint8_t clk_id;
+ uint8_t parent_id;
+} QEMU_PACKED;
+
+struct TisciMsgGetClockReq {
+ TISciMsgHdr hdr;
+ uint32_t device;
+ uint8_t clk;
+ uint32_t clk32;
+} QEMU_PACKED;
+
+struct TisciMsgGetClockResp {
+ TISciMsgHdr hdr;
+ uint8_t programmed_state;
+ uint8_t current_state;
+} QEMU_PACKED;
+
+struct TisciMsgGetDeviceReq {
+ TISciMsgHdr hdr;
+ uint32_t id;
+} QEMU_PACKED;
+
+struct TisciMsgGetDeviceResp {
+ TISciMsgHdr hdr;
+ uint32_t context_loss_count;
+ uint32_t resets;
+ uint8_t programmed_state;
+ uint8_t current_state;
+} QEMU_PACKED;
+
+/*
+ * Security message layouts for K3 SA2UL, OTP and secure-boot services. They
+ * match the U-Boot and Zephyr TI-SCI protocol headers.
+ */
+#define FWL_MAX_PRIVID_SLOTS 3U
+
+struct TisciMsgReqFwlSetFirewallRegion {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint32_t n_permission_regs;
+ uint32_t control;
+ uint32_t permissions[FWL_MAX_PRIVID_SLOTS];
+ uint64_t start_address;
+ uint64_t end_address;
+} QEMU_PACKED;
+
+/* TISCI_MSG_FWL_SET response is bare generic ACK/NACK (TISciMsgHdr). */
+
+struct TisciMsgReqFwlGetFirewallRegion {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint32_t n_permission_regs;
+} QEMU_PACKED;
+
+struct TisciMsgRespFwlGetFirewallRegion {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint32_t n_permission_regs;
+ uint32_t control;
+ uint32_t permissions[FWL_MAX_PRIVID_SLOTS];
+ uint64_t start_address;
+ uint64_t end_address;
+} QEMU_PACKED;
+
+struct TisciMsgReqFwlChangeOwnerInfo {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint8_t owner_index;
+} QEMU_PACKED;
+
+struct TisciMsgRespFwlChangeOwnerInfo {
+ TISciMsgHdr hdr;
+ uint16_t fwl_id;
+ uint16_t region;
+ uint8_t owner_index;
+ uint8_t owner_privid;
+ uint16_t owner_permission_bits;
+} QEMU_PACKED;
+
+#define SA2UL_DKEK_KEY_LEN 32
+#define KDF_LABEL_AND_CONTEXT_LEN_MAX 41
+
+struct TisciMsgReqSa2ulGetDkek {
+ TISciMsgHdr hdr;
+ uint8_t sa2ul_instance;
+ uint8_t kdf_label_len;
+ uint8_t kdf_context_len;
+ uint8_t kdf_label_and_context[KDF_LABEL_AND_CONTEXT_LEN_MAX];
+} QEMU_PACKED;
+
+struct TisciMsgRespSa2ulGetDkek {
+ TISciMsgHdr hdr;
+ uint8_t dkek[SA2UL_DKEK_KEY_LEN];
+} QEMU_PACKED;
+
+struct TisciMsgRespReadSwrev {
+ TISciMsgHdr hdr;
+ uint32_t swrev;
+} QEMU_PACKED;
+
+struct TisciMsgRespReadKeycntKeyrev {
+ TISciMsgHdr hdr;
+ uint32_t keycnt;
+ uint32_t keyrev;
+} QEMU_PACKED;
+
+typedef struct TIDmscClient TIDmscClient;
+
+typedef void (*TiDmscMsgHandler)(TIDmscClient *client, TISciMsgHdr *hdr,
+ uint16_t thread_id, const uint32_t *words,
+ size_t nwords);
+
+struct TIDmscClient {
+ TIDmscState *dmsc;
+ uint16_t rx_thread_id;
+ uint16_t tx_thread_id;
+ bool pending;
+ /*
+ * Secure R5 clients add a 4-byte checksum/reserved word before the
+ * normal TISciMsgHdr on requests and responses.
+ */
+ bool secure;
+ uint32_t pending_words[TI_DMSC_MAX_WORDS];
+ size_t pending_nwords;
+
+ /*
+ * Set from the actual request's AOP bit before dispatch. The bottom
+ * half handles one message at a time, so no extra locking is needed.
+ */
+ bool cur_req_wants_resp;
+};
+
+struct TIDmscState {
+ DeviceState parent_obj;
+
+ /* QOM link to SEC_PROXY */
+ TISecProxyState *sec_proxy;
+
+ /* Config */
+ uint16_t rx_thread_id; /* e.g. M4_0_WRITE_THREAD */
+ uint16_t tx_thread_id; /* e.g. M4_0_READ_RESPONSE_THREAD */
+ uint32_t num_rx_threads;
+ uint16_t *rx_thread_ids;
+ uint32_t num_tx_threads;
+ uint16_t *tx_thread_ids;
+ /* rx threads of clients, which use secure R5 transport framing */
+ uint32_t num_secure_rx_threads;
+ uint16_t *secure_rx_threads;
+ uint64_t m4_cpu_id; /* QEMU CPU index used for MCU M4 */
+ uint64_t a53_cpu_id_base; /* MP affinity of A53 core 0 (core 1 = +1) */
+
+ uint32_t msg_words; /* usually 16 */
+
+ /* Optional async handling */
+ QEMUBH *bh;
+ QemuMutex lock;
+
+ TiDmscMsgHandler msg_handler[TISCI_MSG_MAX_ID];
+ uint32_t num_clients;
+ TIDmscClient *clients;
+
+ uint8_t dev_hw_state[TISCI_DEV_ID_MAX];
+ uint8_t dev_prog_state[TISCI_DEV_ID_MAX];
+ bool m4_running;
+ /* A53 boot vectors captured from TISCI_MSG_SET_CONFIG. */
+ uint64_t proc_bootvector[2];
+};
+
+#endif /* HW_MISC_TI_DMSC_H */
--
2.43.0
next prev parent reply other threads:[~2026-08-20 12:50 UTC|newest]
Thread overview: 24+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-20 12:48 [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 01/14] hw/i2c/omap_i2c: add a dedicated CONFIG_OMAP_I2C symbol Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 02/14] hw/i2c/omap_i2c: implement soft reset and NACK reporting Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 03/14] hw/sd/sdhci: complete non-interrupt ADMA descriptor chains in one pass Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 04/14] hw/char: add TI AM64x UART model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 05/14] hw/timer: add TI K3 DMTimer model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 06/14] hw/misc: add TI K3 CTRL_MMR, GTC, DDRSS, SDHCI PHY and TRNG models Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 07/14] hw/misc: add TI RAT (region address translation) model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 08/14] hw/misc: add TI mailbox (IPC) model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 09/14] hw/misc: add TI K3 secure proxy model Wadim Mueller
2026-08-20 12:48 ` Wadim Mueller [this message]
2026-08-20 12:48 ` [RFC PATCH v2 11/14] hw/arm: add TI K3 combined boot image parser Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 12/14] hw/arm: add TI AM64x SoC model Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 13/14] hw/arm: add the am64-virt machine Wadim Mueller
2026-08-20 12:48 ` [RFC PATCH v2 14/14] tests: add AM64x unit, qtest and functional tests Wadim Mueller
2026-08-20 16:25 ` Alex Bennée
2026-08-22 21:04 ` Wadim Mueller
2026-08-21 10:26 ` [RFC PATCH v2 00/14] hw/arm: add TI AM64x SoC and am64-virt machine Alex Bennée
2026-08-22 21:06 ` Wadim Mueller
2026-08-21 16:35 ` Nick Huang
2026-08-22 21:07 ` Wadim Mueller
2026-08-22 5:22 ` Bin Meng
2026-08-22 21:13 ` Wadim Mueller
2026-08-24 11:51 ` Nick Huang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260820124824.618671-11-wafgo01@gmail.com \
--to=wafgo01@gmail.com \
--cc=bmeng.cn@gmail.com \
--cc=farosas@suse.de \
--cc=pbonzini@redhat.com \
--cc=peter.maydell@linaro.org \
--cc=philmd@mailo.com \
--cc=qemu-arm@nongnu.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox