diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index 50f7d14650..a5f6b9621a 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -35,6 +35,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_AON', if_true: files('sifive_e_aon.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_U_OTP', if_true: files('sifive_u_otp.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_U_PRCI', if_true: files('sifive_u_prci.c'))
 specific_ss.add(when: 'CONFIG_RISCV_IOPMP', if_true: files('riscv_iopmp.c'))
+specific_ss.add(when: 'CONFIG_RISCV_IOPMP', if_true: files('riscv_iopmp_dispatcher.c'))
 
 subdir('macio')
 
diff --git a/hw/misc/riscv_iopmp_dispatcher.c b/hw/misc/riscv_iopmp_dispatcher.c
new file mode 100644
index 0000000000..3d52806ab7
--- /dev/null
+++ b/hw/misc/riscv_iopmp_dispatcher.c
@@ -0,0 +1,139 @@
+/*
+ * QEMU RISC-V IOPMP dispatcher
+ *
+ * Receives transaction information from the requestor and forwards it to the
+ * corresponding IOPMP device.
+ *
+ * Copyright (c) 2023-2025 Andes Tech. Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "trace.h"
+#include "exec/exec-all.h"
+#include "exec/address-spaces.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "hw/misc/riscv_iopmp_dispatcher.h"
+#include "memory.h"
+#include "hw/irq.h"
+#include "hw/misc/riscv_iopmp_txn_info.h"
+
+static void riscv_iopmp_dispatcher_realize(DeviceState *dev, Error **errp)
+{
+    int i;
+    RISCVIOPMPDispState *s = RISCV_IOPMP_DISP(dev);
+
+    s->SinkMemMap = g_new(SinkMemMapEntry *, s->stage_num);
+    for (i = 0; i < s->stage_num; i++) {
+        s->SinkMemMap[i] = g_new(SinkMemMapEntry, s->target_num);
+    }
+
+    object_initialize_child(OBJECT(s), "iopmp_dispatcher_txn_info",
+                            &s->txn_info_sink,
+                            TYPE_RISCV_IOPMP_DISP_SS);
+}
+
+static const Property iopmp_dispatcher_properties[] = {
+    DEFINE_PROP_UINT32("stage-num", RISCVIOPMPDispState, stage_num, 2),
+    DEFINE_PROP_UINT32("target-num", RISCVIOPMPDispState, target_num, 10),
+};
+
+static void riscv_iopmp_dispatcher_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    device_class_set_props(dc, iopmp_dispatcher_properties);
+    dc->realize = riscv_iopmp_dispatcher_realize;
+}
+
+static const TypeInfo riscv_iopmp_dispatcher_info = {
+    .name = TYPE_RISCV_IOPMP_DISP,
+    .parent = TYPE_DEVICE,
+    .instance_size = sizeof(RISCVIOPMPDispState),
+    .class_init = riscv_iopmp_dispatcher_class_init,
+};
+
+static size_t dispatcher_txn_info_push(StreamSink *txn_info_sink,
+                                       unsigned char *buf,
+                                       size_t len, bool eop)
+{
+    uint64_t addr;
+    uint32_t stage;
+    int i, j;
+    riscv_iopmp_disp_ss *ss =
+        RISCV_IOPMP_DISP_SS(txn_info_sink);
+    RISCVIOPMPDispState *s = RISCV_IOPMP_DISP(container_of(ss,
+        RISCVIOPMPDispState, txn_info_sink));
+    RISCVIOPMPTxnInfo *signal;
+    if (len != sizeof(RISCVIOPMPTxnInfo)) {
+        return 0;
+    }
+    signal = (RISCVIOPMPTxnInfo *)buf;
+    addr = signal->start_addr;
+    stage = signal->stage;
+    for (i = stage; i < s->stage_num; i++) {
+        for (j = 0; j < s->target_num; j++) {
+            if (s->SinkMemMap[i][j].map.base <= addr &&
+                addr < s->SinkMemMap[i][j].map.base
+                + s->SinkMemMap[i][j].map.size) {
+                    return stream_push(s->SinkMemMap[i][j].sink, buf, len, eop);
+            }
+        }
+    }
+    /* Always pass if target is not protected by IOPMP*/
+    return 1;
+}
+
+static void riscv_iopmp_disp_ss_class_init(
+    ObjectClass *klass, void *data)
+{
+    StreamSinkClass *ssc = STREAM_SINK_CLASS(klass);
+    ssc->push = dispatcher_txn_info_push;
+}
+
+static const TypeInfo riscv_iopmp_disp_ss_info = {
+    .name = TYPE_RISCV_IOPMP_DISP_SS,
+    .parent = TYPE_OBJECT,
+    .instance_size = sizeof(riscv_iopmp_disp_ss),
+    .class_init = riscv_iopmp_disp_ss_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_STREAM_SINK },
+        { }
+    },
+};
+
+void iopmp_dispatcher_add_target(DeviceState *dev, StreamSink *sink,
+    uint64_t base, uint64_t size, uint32_t stage, uint32_t id)
+{
+    RISCVIOPMPDispState *s = RISCV_IOPMP_DISP(dev);
+    if (stage < s->stage_num && id < s->target_num) {
+        s->SinkMemMap[stage][id].map.base = base;
+        s->SinkMemMap[stage][id].map.size = size;
+        s->SinkMemMap[stage][id].sink = sink;
+    }
+}
+
+static void
+iopmp_dispatcher_register_types(void)
+{
+    type_register_static(&riscv_iopmp_dispatcher_info);
+    type_register_static(&riscv_iopmp_disp_ss_info);
+}
+
+type_init(iopmp_dispatcher_register_types);
+
diff --git a/include/hw/misc/riscv_iopmp_dispatcher.h b/include/hw/misc/riscv_iopmp_dispatcher.h
new file mode 100644
index 0000000000..bbaa76507b
--- /dev/null
+++ b/include/hw/misc/riscv_iopmp_dispatcher.h
@@ -0,0 +1,61 @@
+/*
+ * QEMU RISC-V IOPMP dispatcher
+ *
+ * Receives transaction information from the requestor and forwards it to the
+ * corresponding IOPMP device.
+ *
+ * Copyright (c) 2023-2024 Andes Tech. Corp.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2 or later, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef RISCV_IOPMP_DISPATCHER_H
+#define RISCV_IOPMP_DISPATCHER_H
+
+#include "hw/sysbus.h"
+#include "qemu/typedefs.h"
+#include "memory.h"
+#include "hw/stream.h"
+#include "hw/misc/riscv_iopmp_txn_info.h"
+#include "exec/hwaddr.h"
+
+#define TYPE_RISCV_IOPMP_DISP "riscv-iopmp-dispatcher"
+OBJECT_DECLARE_SIMPLE_TYPE(RISCVIOPMPDispState, RISCV_IOPMP_DISP)
+
+#define TYPE_RISCV_IOPMP_DISP_SS "riscv-iopmp-dispatcher-streamsink"
+OBJECT_DECLARE_SIMPLE_TYPE(riscv_iopmp_disp_ss, RISCV_IOPMP_DISP_SS)
+
+typedef struct riscv_iopmp_disp_ss {
+    Object parent;
+} riscv_iopmp_disp_ss;
+
+typedef struct SinkMemMapEntry {
+    StreamSink *sink;
+    MemMapEntry map;
+} SinkMemMapEntry;
+
+typedef struct RISCVIOPMPDispState {
+    SysBusDevice parent_obj;
+    riscv_iopmp_disp_ss txn_info_sink;
+    SinkMemMapEntry **SinkMemMap;
+    /* The maximum number of cascading stages of IOPMP */
+    uint32_t stage_num;
+    /* The maximum number of parallel IOPMP devices within a single stage. */
+    uint32_t target_num;
+} RISCVIOPMPDispState;
+
+void iopmp_dispatcher_add_target(DeviceState *dev, StreamSink *sink,
+    uint64_t base, uint64_t size, uint32_t stage, uint32_t id);
+#endif
