QEMU-Devel Archive on lore.kernel.org
 help / color / mirror / Atom feed
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, &notif, 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



  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