All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH 0/6] Support RISC-V IOPMP
@ 2023-10-25  5:14 ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Ethan Chen

This series implements IOPMP specification v1.0.0-draft4 rapid-k model:
https://github.com/riscv-non-isa/iopmp-spec/blob/main/riscv_iopmp_specification.pdf

When IOPMP is enabled, a DMA device ATCDMAC300 is added to RISC-V virt platform.
This DMA devce is connected to the IOPMP and has the functionalities required
by IOPMP, including:
- Support specify source-id (SID)
- Support asynchronous I/O to handle stall transcations

Ethan Chen (6):
  exec/memory: Introduce the translate_size function within the IOMMU
    class
  system/physmem: IOMMU: Invoke the translate_size function if it is
    implemented
  exec/memattrs: Add iopmp source id to MemTxAttrs
  Add RISC-V IOPMP support
  hw/dma: Add Andes ATCDMAC300 support
  hw/riscv/virt: Add IOPMP support

 hw/dma/Kconfig                |   3 +
 hw/dma/atcdmac300.c           | 435 +++++++++++++++++
 hw/dma/meson.build            |   1 +
 hw/misc/Kconfig               |   3 +
 hw/misc/meson.build           |   1 +
 hw/misc/riscv_iopmp.c         | 881 ++++++++++++++++++++++++++++++++++
 hw/riscv/Kconfig              |   2 +
 hw/riscv/virt.c               |  72 ++-
 include/exec/memattrs.h       |   3 +
 include/exec/memory.h         |  19 +
 include/hw/dma/atcdmac300.h   | 171 +++++++
 include/hw/misc/riscv_iopmp.h | 322 +++++++++++++
 include/hw/riscv/virt.h       |  10 +-
 system/physmem.c              |   9 +-
 14 files changed, 1927 insertions(+), 5 deletions(-)
 create mode 100644 hw/dma/atcdmac300.c
 create mode 100644 hw/misc/riscv_iopmp.c
 create mode 100644 include/hw/dma/atcdmac300.h
 create mode 100644 include/hw/misc/riscv_iopmp.h

-- 
2.34.1



^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH 0/6] Support RISC-V IOPMP
@ 2023-10-25  5:14 ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Ethan Chen

This series implements IOPMP specification v1.0.0-draft4 rapid-k model:
https://github.com/riscv-non-isa/iopmp-spec/blob/main/riscv_iopmp_specification.pdf

When IOPMP is enabled, a DMA device ATCDMAC300 is added to RISC-V virt platform.
This DMA devce is connected to the IOPMP and has the functionalities required
by IOPMP, including:
- Support specify source-id (SID)
- Support asynchronous I/O to handle stall transcations

Ethan Chen (6):
  exec/memory: Introduce the translate_size function within the IOMMU
    class
  system/physmem: IOMMU: Invoke the translate_size function if it is
    implemented
  exec/memattrs: Add iopmp source id to MemTxAttrs
  Add RISC-V IOPMP support
  hw/dma: Add Andes ATCDMAC300 support
  hw/riscv/virt: Add IOPMP support

 hw/dma/Kconfig                |   3 +
 hw/dma/atcdmac300.c           | 435 +++++++++++++++++
 hw/dma/meson.build            |   1 +
 hw/misc/Kconfig               |   3 +
 hw/misc/meson.build           |   1 +
 hw/misc/riscv_iopmp.c         | 881 ++++++++++++++++++++++++++++++++++
 hw/riscv/Kconfig              |   2 +
 hw/riscv/virt.c               |  72 ++-
 include/exec/memattrs.h       |   3 +
 include/exec/memory.h         |  19 +
 include/hw/dma/atcdmac300.h   | 171 +++++++
 include/hw/misc/riscv_iopmp.h | 322 +++++++++++++
 include/hw/riscv/virt.h       |  10 +-
 system/physmem.c              |   9 +-
 14 files changed, 1927 insertions(+), 5 deletions(-)
 create mode 100644 hw/dma/atcdmac300.c
 create mode 100644 hw/misc/riscv_iopmp.c
 create mode 100644 include/hw/dma/atcdmac300.h
 create mode 100644 include/hw/misc/riscv_iopmp.h

-- 
2.34.1



^ permalink raw reply	[flat|nested] 34+ messages in thread

* [PATCH 1/6] exec/memory: Introduce the translate_size function within the IOMMU class
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel
  Cc: Ethan Chen, Paolo Bonzini, Peter Xu, David Hildenbrand,
	Philippe Mathieu-Daudé

IOMMU have size information during translation.

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 include/exec/memory.h | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/include/exec/memory.h b/include/exec/memory.h
index 9087d02769..5520b7c8c0 100644
--- a/include/exec/memory.h
+++ b/include/exec/memory.h
@@ -396,6 +396,25 @@ struct IOMMUMemoryRegionClass {
      */
     IOMMUTLBEntry (*translate)(IOMMUMemoryRegion *iommu, hwaddr addr,
                                IOMMUAccessFlags flag, int iommu_idx);
+    /**
+     * @translate_size:
+     *
+     * Return a TLB entry that contains a given address and size.
+     *
+     * @iommu: the IOMMUMemoryRegion
+     *
+     * @hwaddr: address to be translated within the memory region
+     *
+     * @size: size to indicate the scope of the entire transaction
+     *
+     * @flag: requested access permission
+     *
+     * @iommu_idx: IOMMU index for the translation
+     */
+    IOMMUTLBEntry (*translate_size)(IOMMUMemoryRegion *iommu, hwaddr addr,
+                                    hwaddr size, IOMMUAccessFlags flag,
+                                    int iommu_idx);
+
     /**
      * @get_min_page_size:
      *
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 1/6] exec/memory: Introduce the translate_size function within the IOMMU class
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel
  Cc: Ethan Chen, Paolo Bonzini, Peter Xu, David Hildenbrand,
	Philippe Mathieu-Daudé

IOMMU have size information during translation.

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 include/exec/memory.h | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/include/exec/memory.h b/include/exec/memory.h
index 9087d02769..5520b7c8c0 100644
--- a/include/exec/memory.h
+++ b/include/exec/memory.h
@@ -396,6 +396,25 @@ struct IOMMUMemoryRegionClass {
      */
     IOMMUTLBEntry (*translate)(IOMMUMemoryRegion *iommu, hwaddr addr,
                                IOMMUAccessFlags flag, int iommu_idx);
+    /**
+     * @translate_size:
+     *
+     * Return a TLB entry that contains a given address and size.
+     *
+     * @iommu: the IOMMUMemoryRegion
+     *
+     * @hwaddr: address to be translated within the memory region
+     *
+     * @size: size to indicate the scope of the entire transaction
+     *
+     * @flag: requested access permission
+     *
+     * @iommu_idx: IOMMU index for the translation
+     */
+    IOMMUTLBEntry (*translate_size)(IOMMUMemoryRegion *iommu, hwaddr addr,
+                                    hwaddr size, IOMMUAccessFlags flag,
+                                    int iommu_idx);
+
     /**
      * @get_min_page_size:
      *
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel
  Cc: Ethan Chen, Paolo Bonzini, Peter Xu, David Hildenbrand,
	Philippe Mathieu-Daudé

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 system/physmem.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/system/physmem.c b/system/physmem.c
index fc2b0fee01..53b6ab735c 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -432,8 +432,13 @@ static MemoryRegionSection address_space_translate_iommu(IOMMUMemoryRegion *iomm
             iommu_idx = imrc->attrs_to_index(iommu_mr, attrs);
         }
 
-        iotlb = imrc->translate(iommu_mr, addr, is_write ?
-                                IOMMU_WO : IOMMU_RO, iommu_idx);
+        if (imrc->translate_size) {
+            iotlb = imrc->translate_size(iommu_mr, addr, *plen_out, is_write ?
+                                         IOMMU_WO : IOMMU_RO, iommu_idx);
+        } else {
+            iotlb = imrc->translate(iommu_mr, addr, is_write ?
+                                    IOMMU_WO : IOMMU_RO, iommu_idx);
+        }
 
         if (!(iotlb.perm & (1 << is_write))) {
             goto unassigned;
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel
  Cc: Ethan Chen, Paolo Bonzini, Peter Xu, David Hildenbrand,
	Philippe Mathieu-Daudé

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 system/physmem.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/system/physmem.c b/system/physmem.c
index fc2b0fee01..53b6ab735c 100644
--- a/system/physmem.c
+++ b/system/physmem.c
@@ -432,8 +432,13 @@ static MemoryRegionSection address_space_translate_iommu(IOMMUMemoryRegion *iomm
             iommu_idx = imrc->attrs_to_index(iommu_mr, attrs);
         }
 
-        iotlb = imrc->translate(iommu_mr, addr, is_write ?
-                                IOMMU_WO : IOMMU_RO, iommu_idx);
+        if (imrc->translate_size) {
+            iotlb = imrc->translate_size(iommu_mr, addr, *plen_out, is_write ?
+                                         IOMMU_WO : IOMMU_RO, iommu_idx);
+        } else {
+            iotlb = imrc->translate(iommu_mr, addr, is_write ?
+                                    IOMMU_WO : IOMMU_RO, iommu_idx);
+        }
 
         if (!(iotlb.perm & (1 << is_write))) {
             goto unassigned;
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 3/6] exec/memattrs: Add iopmp source id to MemTxAttrs
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Ethan Chen

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 include/exec/memattrs.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h
index d04170aa27..e27b4fab00 100644
--- a/include/exec/memattrs.h
+++ b/include/exec/memattrs.h
@@ -64,6 +64,9 @@ typedef struct MemTxAttrs {
     unsigned int target_tlb_bit0 : 1;
     unsigned int target_tlb_bit1 : 1;
     unsigned int target_tlb_bit2 : 1;
+
+    /* IOPMP support up to 65535 sources */
+    unsigned int iopmp_sid:16;
 } MemTxAttrs;
 
 /* Bus masters which don't specify any attributes will get this,
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 3/6] exec/memattrs: Add iopmp source id to MemTxAttrs
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Ethan Chen

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 include/exec/memattrs.h | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/include/exec/memattrs.h b/include/exec/memattrs.h
index d04170aa27..e27b4fab00 100644
--- a/include/exec/memattrs.h
+++ b/include/exec/memattrs.h
@@ -64,6 +64,9 @@ typedef struct MemTxAttrs {
     unsigned int target_tlb_bit0 : 1;
     unsigned int target_tlb_bit1 : 1;
     unsigned int target_tlb_bit2 : 1;
+
+    /* IOPMP support up to 65535 sources */
+    unsigned int iopmp_sid:16;
 } MemTxAttrs;
 
 /* Bus masters which don't specify any attributes will get this,
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 4/6] Add RISC-V IOPMP support
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Ethan Chen, Paolo Bonzini

Support specification Version 1.0.0-draft4 rapid-k model.

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 hw/misc/Kconfig               |   3 +
 hw/misc/meson.build           |   1 +
 hw/misc/riscv_iopmp.c         | 881 ++++++++++++++++++++++++++++++++++
 include/hw/misc/riscv_iopmp.h | 322 +++++++++++++
 4 files changed, 1207 insertions(+)
 create mode 100644 hw/misc/riscv_iopmp.c
 create mode 100644 include/hw/misc/riscv_iopmp.h

diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index dba41afe67..8c63eb4cef 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -197,4 +197,7 @@ config DJMEMC
 config IOSB
     bool
 
+config RISCV_IOPMP
+    bool
+
 source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index f60de33f9a..7826ed9b7b 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -35,6 +35,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c'))
 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'))
 
 subdir('macio')
 
diff --git a/hw/misc/riscv_iopmp.c b/hw/misc/riscv_iopmp.c
new file mode 100644
index 0000000000..5f9535501a
--- /dev/null
+++ b/hw/misc/riscv_iopmp.c
@@ -0,0 +1,881 @@
+/*
+ * QEMU RISC-V IOPMP (Input Output Physical Memory Protection)
+ *
+ * Copyright (c) 2023 Andes Tech. Corp.
+ *
+ * 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.h"
+#include "memory.h"
+#include "hw/irq.h"
+
+#define TYPE_IOPMP_IOMMU_MEMORY_REGION "iopmp-iommu-memory-region"
+
+#define LOGGE(x...) qemu_log_mask(LOG_GUEST_ERROR, x)
+#define xLOG(x...)
+#define yLOG(x...) qemu_log(x)
+#ifdef DEBUG_RISCV_IOPMP
+  #define LOG(x...) yLOG(x)
+#else
+  #define LOG(x...) xLOG(x)
+#endif
+
+#define MEMTX_IOPMP_STALL (1 << 3)
+
+
+static void iopmp_decode_napot(target_ulong a, target_ulong *sa,
+                               target_ulong *ea)
+{
+    /*
+     * aaaa...aaa0   8-byte NAPOT range
+     * aaaa...aa01   16-byte NAPOT range
+     * aaaa...a011   32-byte NAPOT range
+     * ...
+     * aa01...1111   2^XLEN-byte NAPOT range
+     * a011...1111   2^(XLEN+1)-byte NAPOT range
+     * 0111...1111   2^(XLEN+2)-byte NAPOT range
+     *  1111...1111   Reserved
+     */
+
+    a = (a << 2) | 0x3;
+    *sa = a & (a + 1);
+    *ea = a | (a + 1);
+}
+
+static void iopmp_update_rule(IopmpState *s, uint32_t entry_index)
+{
+    uint8_t this_cfg = s->regs.entry[entry_index].cfg_reg;
+    target_ulong this_addr = s->regs.entry[entry_index].addr_reg;
+    target_ulong prev_addr = 0u;
+    target_ulong sa = 0u;
+    target_ulong ea = 0u;
+
+    if (entry_index >= 1u) {
+        prev_addr = s->regs.entry[entry_index - 1].addr_reg;
+    }
+
+    switch (iopmp_get_field(this_cfg, ENTRY_CFG_A)) {
+    case IOPMP_AMATCH_OFF:
+        sa = 0u;
+        ea = -1;
+        break;
+
+    case IOPMP_AMATCH_TOR:
+        sa = (prev_addr) << 2; /* shift up from [xx:0] to [xx+2:2] */
+        ea = ((this_addr) << 2) - 1u;
+        if (sa > ea) {
+            sa = ea = 0u;
+        }
+        break;
+
+    case IOPMP_AMATCH_NA4:
+        sa = this_addr << 2; /* shift up from [xx:0] to [xx+2:2] */
+        ea = (sa + 4u) - 1u;
+        break;
+
+    case IOPMP_AMATCH_NAPOT:
+        iopmp_decode_napot(this_addr, &sa, &ea);
+        break;
+
+    default:
+        sa = 0u;
+        ea = 0u;
+        break;
+    }
+
+    s->entry_addr[entry_index].sa = sa;
+    s->entry_addr[entry_index].ea = ea;
+}
+
+static uint64_t
+iopmp_read(void *opaque, hwaddr addr, unsigned size)
+{
+    IopmpState *s = IOPMP(opaque);
+    uint32_t rz = 0;
+    uint32_t is_stall = 0;
+    uint32_t sid;
+    switch (addr) {
+    case IOPMP_VERSION ... IOPMP_USER_CFG0 + 16 * (IOPMP_MAX_ENTRY_NUM - 1):
+        switch (addr) {
+        case IOPMP_VERSION:
+            rz = VENDER_ANDES << VERSION_VENDOR |
+                 SPECVER_1_0_0_DRAFT4 << VERSION_SPECVER;
+            break;
+        case IOPMP_IMP:
+            rz = IMPID_1_0_0_DRAFT4_0;
+            break;
+        case IOPMP_HWCFG0: /* RO */
+            rz = s->sid_num << HWCFG0_SID_NUM |
+                 s->entry_num << HWCFG0_ENTRY_NUM;
+            break;
+        case IOPMP_HWCFG1:
+            rz = s->model << HWCFG1_MODEL | TOR_EN << HWCFG1_TOR_EN |
+                 s->sps_en << HWCFG1_SPS_EN |
+                 USER_CFG_EN << HWCFG1_USER_CFG_EN |
+                 s->prient_prog << HWCFG1_PRIENT_PROG |
+                 s->sid_transl_en << HWCFG1_SID_TRANSL_EN |
+                 s->sid_transl_prog << HWCFG1_SID_TRANSL_PROG |
+                 s->md_num << HWCFG1_MD_NUM |
+                 s->enable << HWCFG1_ENABLE;
+            break;
+        case IOPMP_HWCFG2:
+            rz = s->prio_entry << HWCFG2_PRIO_ENTRY |
+                 s->sid_transl << HWCFG2_SID_TRANSL;
+            break;
+        case IOPMP_ENTRYOFFSET:
+            rz = IOPMP_ENTRY_ADDR0;
+            break;
+        case IOPMP_ERRREACT:
+            rz = s->regs.errreact;
+            break;
+        case IOPMP_MDSTALL:
+            if (s->md_stall_stat) {
+                is_stall = 1;
+            }
+            rz = iopmp_get_field(s->regs.mdstall, MDSTALL_MD) | is_stall;
+            break;
+        case IOPMP_MDSTALLH:
+            rz = s->regs.mdstall >> 32;
+            break;
+        case IOPMP_SIDSCP:
+            sid = iopmp_get_field(s->regs.sidscp, SIDSCP_SID);
+            if (sid < s->sid_num) {
+                rz = sid | (s->sidscp_op[sid]) << SIDSCP_STAT;
+            } else {
+                rz = sid | 3 << SIDSCP_STAT;
+            }
+            break;
+        case IOPMP_MDLCK:
+            rz = s->regs.mdlck & UINT32_MAX;
+            break;
+        case IOPMP_MDLCKH:
+            rz = s->regs.mdlck >> 32;
+            break;
+        case IOPMP_MDCFGLCK:
+            rz = s->regs.mdcfglck;
+            break;
+        case IOPMP_ENTRYLCK:
+            rz = s->regs.entrylck;
+            break;
+        case IOPMP_ERR_REQADDR:
+            rz = s->regs.err_reqaddr & UINT32_MAX;
+            break;
+        case IOPMP_ERR_REQADDRH:
+            rz = s->regs.err_reqaddr >> 32;
+            break;
+        case IOPMP_ERR_REQSID:
+            rz = s->regs.err_reqsid;
+            break;
+        case IOPMP_ERR_REQINFO:
+            rz = s->regs.err_reqinfo;
+            break;
+
+        default:
+            if (addr >= IOPMP_MDCFG0 &&
+                addr < IOPMP_MDCFG0 + 4 * (s->md_num - 1)) {
+                int offset = addr - IOPMP_MDCFG0;
+                int idx = offset >> 2;
+                if (idx == 0) {
+                    if (offset == 0) {
+                        rz = s->regs.mdcfg[idx];
+                    } else {
+                        LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                    }
+                } else {
+                    /* Only MDCFG0 is implemented */
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+            } else if (addr >= IOPMP_SRCMD_EN0 &&
+                       addr < IOPMP_SRCMD_WH0 + 32 * (s->sid_num - 1)) {
+                int offset = addr - IOPMP_SRCMD_EN0;
+                int idx = offset >> 5;
+                offset &= 0x1f;
+                if (offset == 0) {
+                    rz = s->regs.srcmd_en[idx] & UINT32_MAX;
+                } else if (offset == 4) {
+                    rz = s->regs.srcmd_en[idx] >> 32;
+                } else if (offset == 8) {
+                    rz = s->regs.srcmd_r[idx] & UINT32_MAX;
+                } else if (offset == 12) {
+                    rz = s->regs.srcmd_r[idx] >> 32;
+                } else if (offset == 16) {
+                    rz = s->regs.srcmd_w[idx] & UINT32_MAX;
+                } else if (offset == 24) {
+                    rz = s->regs.srcmd_w[idx] >> 32;
+                } else {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+            } else if (addr >= IOPMP_ENTRY_ADDR0 &&
+                       addr < IOPMP_USER_CFG0 + 16 * (s->entry_num - 1)) {
+                int offset = addr - IOPMP_ENTRY_ADDR0;
+                int idx = offset >> 4;
+                offset &= 0xf;
+                if (offset == 0) {
+                    rz = s->regs.entry[idx].addr_reg & UINT32_MAX;
+                } else if (offset == 4) {
+                    rz = s->regs.entry[idx].addr_reg >> 32;
+                } else if (offset == 8) {
+                    rz = s->regs.entry[idx].cfg_reg;
+                } else if (offset == 12) {
+                    /* Not support user customized permission*/
+                    rz = 0;
+                } else {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+            } else {
+                LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+            }
+            break;
+        }
+        LOG("\e[95m%s: addr %08x, value %08x\e[0m\n", __func__, (int)addr,
+            (int)rz);
+        break;
+    default:
+        LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+    }
+
+    return rz;
+}
+
+static void
+iopmp_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
+{
+    IopmpState *s = IOPMP(opaque);
+    int value_f;
+    int reg_f;
+    uint32_t sid, op;
+
+    switch (addr) {
+    case IOPMP_VERSION ... IOPMP_USER_CFG0 + 16 * (IOPMP_MAX_ENTRY_NUM - 1):
+        switch (addr) {
+        case IOPMP_VERSION: /* RO */
+            break;
+        case IOPMP_IMP: /* RO */
+            break;
+        case IOPMP_HWCFG0: /* RO */
+            break;
+        case IOPMP_HWCFG1:
+            if (iopmp_get_field(value, HWCFG1_PRIENT_PROG)) {
+                /* W1C */
+                s->prient_prog = 0;
+            }
+            if (iopmp_get_field(value, HWCFG1_SID_TRANSL_PROG)) {
+                /* W1C */
+                s->sid_transl_prog = 0;
+            }
+            if (iopmp_get_field(value, HWCFG1_ENABLE)) {
+                /* W1S */
+                s->enable = 1;
+            }
+            break;
+        case IOPMP_HWCFG2:
+            if (s->prient_prog) {
+                s->prio_entry = iopmp_get_field(value, HWCFG2_PRIO_ENTRY);
+            }
+            if (s->sid_transl_en && s->sid_transl_prog) {
+                s->sid_transl = iopmp_get_field(value, HWCFG2_SID_TRANSL);
+            }
+            break;
+        case IOPMP_ERRREACT:
+            if (!iopmp_get_field(s->regs.errreact, ERRREACT_L)) {
+                    iopmp_set_field32(&s->regs.errreact, ERRREACT_L,
+                                    iopmp_get_field(value, ERRREACT_L));
+                if (iopmp_get_field(value, ERRREACT_IP)) {
+                    iopmp_set_field32(&s->regs.errreact, ERRREACT_IP, 0);
+                }
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_IE,
+                                iopmp_get_field(value, ERRREACT_IE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_IRE,
+                                iopmp_get_field(value, ERRREACT_IRE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_RRE,
+                                iopmp_get_field(value, ERRREACT_RRE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_IWE,
+                                iopmp_get_field(value, ERRREACT_IWE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_RWE,
+                                iopmp_get_field(value, ERRREACT_RWE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_PEE,
+                                iopmp_get_field(value, ERRREACT_PEE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_RPE,
+                                iopmp_get_field(value, ERRREACT_RPE));
+            } else {
+                if (iopmp_get_field(value, ERRREACT_IP)) {
+                    iopmp_set_field32(&s->regs.errreact, ERRREACT_IP, 0);
+                }
+            }
+            break;
+        case IOPMP_MDSTALL:
+            iopmp_set_field64(&s->regs.mdstall, MDSTALL, value);
+            break;
+        case IOPMP_MDSTALLH:
+            iopmp_set_field64(&s->regs.mdstall, MDSTALLH, value);
+            break;
+        case IOPMP_SIDSCP:
+            sid = iopmp_get_field(value, SIDSCP_SID);
+            op = iopmp_get_field(value, SIDSCP_OP);
+            if (sid < s->sid_num) {
+                if (op != SIDSCP_OP_QUERY) {
+                    s->sidscp_op[sid] = op;
+                    s->regs.sidscp = value;
+                }
+            } else {
+                s->regs.sidscp = sid | (0x3 << SIDSCP_OP);
+            }
+            break;
+        case IOPMP_MDLCK:
+            if (!(s->regs.mdlck & (1 << MDLCK_L))) {
+                s->regs.mdlck = value |
+                                (s->regs.mdstall & ~(uint64_t)UINT32_MAX);
+            }
+            break;
+        case IOPMP_MDLCKH:
+             if (!(s->regs.mdlck & (1 << MDLCK_L))) {
+                s->regs.mdlck = (uint64_t)value << 32 |
+                                (s->regs.mdstall & UINT32_MAX);
+            }
+            break;
+        case IOPMP_MDCFGLCK:
+            if (!iopmp_get_field(s->regs.mdcfglck, MDCFGLCK_L)) {
+                value_f = iopmp_get_field(value, MDCFGLCK_F);
+                reg_f = iopmp_get_field(s->regs.mdcfglck, MDCFGLCK_F);
+                if (value_f > reg_f) {
+                    iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_F, value_f);
+                }
+                iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_L,
+                          iopmp_get_field(value, MDCFGLCK_L));
+            }
+            break;
+        case IOPMP_ENTRYLCK:
+            if (!(iopmp_get_field(s->regs.entrylck, ENTRYLCK_L))) {
+                value_f = iopmp_get_field(value, ENTRYLCK_F);
+                reg_f = iopmp_get_field(s->regs.entrylck, ENTRYLCK_F);
+                if (value_f > reg_f) {
+                    iopmp_set_field32(&s->regs.entrylck, ENTRYLCK_F, value_f);
+                }
+                iopmp_set_field32(&s->regs.entrylck, ENTRYLCK_F,
+                          iopmp_get_field(value, ENTRYLCK_F));
+            }
+        case IOPMP_ERR_REQADDR: /* RO */
+            break;
+        case IOPMP_ERR_REQADDRH: /* RO */
+            break;
+        case IOPMP_ERR_REQSID: /* RO */
+            break;
+        case IOPMP_ERR_REQINFO: /* RO */
+            break;
+
+        default:
+            if (addr >= IOPMP_MDCFG0 &&
+                addr < IOPMP_MDCFG0 + 4 * (s->md_num - 1)) {
+                int offset = addr - IOPMP_MDCFG0;
+                int idx = offset >> 2;
+                /* RO in rapid-k model */
+                if (idx > 0) {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+            } else if (addr >= IOPMP_SRCMD_EN0 &&
+                       addr < IOPMP_SRCMD_WH0 + 32 * (s->sid_num - 1)) {
+                int offset = addr - IOPMP_SRCMD_EN0;
+                int idx = offset >> 5;
+                offset &= 0x1f;
+                if (offset % 4) {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                } else if (iopmp_get_field(s->regs.srcmd_en[idx],
+                                           SRCMD_EN_L) == 0) {
+                    if (offset == 0) {
+                        iopmp_set_field64(&s->regs.srcmd_en[idx], SRCMD_EN_MD,
+                                        iopmp_get_field(value, SRCMD_EN_MD));
+                        iopmp_set_field64(&s->regs.srcmd_en[idx], SRCMD_EN_L,
+                                        iopmp_get_field(value, SRCMD_EN_L));
+                    } else if (offset == 4) {
+                        iopmp_set_field64(&s->regs.srcmd_en[idx], SRCMD_ENH_MDH,
+                                        value);
+                    } else if (offset == 8 && s->sps_en) {
+                        iopmp_set_field64(&s->regs.srcmd_r[idx], SRCMD_R_MD,
+                                            iopmp_get_field(value, SRCMD_R_MD));
+                    } else if (offset == 12 && s->sps_en) {
+                        iopmp_set_field64(&s->regs.srcmd_r[idx], SRCMD_RH_MDH,
+                                        value);
+                    } else if (offset == 16 && s->sps_en) {
+                        iopmp_set_field64(&s->regs.srcmd_w[idx], SRCMD_W_MD,
+                                        iopmp_get_field(value, SRCMD_W_MD));
+                    } else if (offset == 24 && s->sps_en) {
+                        iopmp_set_field64(&s->regs.srcmd_w[idx], SRCMD_WH_MDH,
+                                          value);
+                    }
+                }
+            } else if (addr >= IOPMP_ENTRY_ADDR0 &&
+                       addr < IOPMP_USER_CFG0 + 16 * (s->entry_num - 1)) {
+                int offset = addr - IOPMP_ENTRY_ADDR0;
+                int idx = offset >> 4;
+                offset &= 0xf;
+                if (offset == 0) {
+                    iopmp_set_field64(&s->regs.entry[idx].addr_reg,
+                                      ENTRY_ADDR_ADDR, value);
+                } else if (offset == 4) {
+                    iopmp_set_field64(&s->regs.entry[idx].addr_reg,
+                                      ENTRY_ADDRH_ADDRH, value);
+                } else if (offset == 8) {
+                    s->regs.entry[idx].cfg_reg = value;
+                } else if (offset == 12) {
+                    /* Not support user customized permission*/
+                    ;
+                } else {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+                iopmp_update_rule(s, idx);
+            } else {
+                LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+            }
+            /* If IOPMP permission of any addr has been changed, */
+            /* flush TLB pages. */
+            tlb_flush_all_cpus_synced(current_cpu);
+            break;
+        }
+        LOG("\e[95m%s: addr %08x, value %08x\e[0m\n", __func__, (int)addr,
+            (int)rz);
+        break;
+    default:
+        LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+    }
+}
+
+/* Match entry in memory domain */
+static int match_entry_md(IopmpState *s, int md_idx, hwaddr s_addr,
+                          hwaddr e_addr, int *entry_idx)
+{
+    int entry_idx_s, entry_idx_e;
+    int result = ENTRY_NO_HIT;
+    int i = 0;
+    entry_idx_s = md_idx * s->regs.mdcfg[0];
+    entry_idx_e = (md_idx + 1) * s->regs.mdcfg[0];
+    if (entry_idx_s >= s->entry_num) {
+        return result;
+    }
+    if (entry_idx_e > s->entry_num) {
+        entry_idx_e = s->entry_num;
+    }
+    i = entry_idx_s;
+    while (i < entry_idx_e) {
+        if (s_addr >= s->entry_addr[i].sa && s_addr <= s->entry_addr[i].ea) {
+            if (e_addr >= s->entry_addr[i].sa &&
+                e_addr <= s->entry_addr[i].ea) {
+                *entry_idx = i;
+                return ENTRY_HIT;
+            } else if (i >= s->prio_entry) {
+                result = ENTRY_PAR_HIT;
+                continue;
+            } else {
+                return ENTRY_PAR_HIT;
+            }
+        }
+        i++;
+    }
+    return result;
+}
+
+static int match_entry(IopmpState *s, int sid, hwaddr s_addr, hwaddr e_addr,
+                       int *match_md_idx, int *match_entry_idx)
+{
+    int cur_result = ENTRY_NO_HIT;
+    int result = ENTRY_NO_HIT;
+    uint64_t srcmd_en = s->regs.srcmd_en[sid] >> 1;
+    for (int md_idx = 0; md_idx < s->md_num; md_idx++) {
+        if (srcmd_en & (1ULL << md_idx)) {
+            cur_result = match_entry_md(s, md_idx, s_addr, e_addr,
+                                    match_entry_idx);
+            if (cur_result == ENTRY_HIT) {
+                *match_md_idx = md_idx;
+                return cur_result;
+            }
+            if (cur_result > result) {
+                result = cur_result;
+            }
+        }
+    }
+    return result;
+}
+
+static bool check_md_stall(IopmpState *s, int md_idx)
+{
+    uint64_t md_selected = iopmp_get_field(s->regs.mdstall, MDSTALL_MD) &
+                           (1 << md_idx);
+    if (iopmp_get_field(s->regs.mdstall, MDSTALL_EXEMPT)) {
+        return !md_selected;
+    } else {
+        return md_selected;
+    }
+}
+
+static inline bool check_sidscp_stall(IopmpState *s, int sid)
+{
+    return s->sidscp_op[sid] == SIDSCP_OP_STALL;
+}
+
+static void iopmp_error_reaction(IopmpState *s, uint32_t id, hwaddr addr,
+                                 hwaddr size, uint32_t info)
+{
+    if (addr > s->prev_error[id].addr_start &&
+        addr + size == s->prev_error[id].addr_end &&
+        info == s->prev_error[id].reqinfo) {
+            /* skip following error */
+            ;
+    } else {
+        s->prev_error[id].addr_start = addr;
+        s->prev_error[id].addr_end = addr + size;
+        s->prev_error[id].reqinfo = info;
+        if (!iopmp_get_field(s->regs.errreact, ERRREACT_IP)) {
+            iopmp_set_field32(&s->regs.errreact, ERRREACT_IP, 1);
+            s->regs.err_reqsid = id;
+            s->regs.err_reqaddr = addr;
+            s->regs.err_reqinfo = info;
+
+            if (iopmp_get_field(info, ERR_REQINFO_TYPE) == ERR_REQINFO_TYPE_READ
+               && iopmp_get_field(s->regs.errreact, ERRREACT_IE) &&
+               iopmp_get_field(s->regs.errreact, ERRREACT_IRE)) {
+                qemu_set_irq(s->irq, 1);
+            }
+            if (iopmp_get_field(info, ERR_REQINFO_TYPE) ==
+                ERR_REQINFO_TYPE_WRITE &&
+                iopmp_get_field(s->regs.errreact, ERRREACT_IE) &&
+                iopmp_get_field(s->regs.errreact, ERRREACT_IWE)) {
+                qemu_set_irq(s->irq, 1);
+            }
+        }
+    }
+}
+
+static IOMMUTLBEntry iopmp_translate_size(IOMMUMemoryRegion *iommu,
+                                          hwaddr addr, hwaddr size,
+                                          IOMMUAccessFlags flags,
+                                          int sid)
+{
+    bool is_stalled = false;
+    IopmpState *s = IOPMP(container_of(iommu, IopmpState, iommu));
+    IOMMUTLBEntry entry = {
+        .target_as = &s->downstream_as,
+        .iova = addr,
+        .translated_addr = addr,
+        .addr_mask = (~(hwaddr)0),
+        .perm = IOMMU_NONE,
+    };
+    int entry_idx = -1;
+    int md_idx = -1;
+    int result = match_entry(s, sid, addr, addr + size - 1,
+                             &md_idx, &entry_idx);
+    int srcmd_rw;
+    if (result == ENTRY_HIT) {
+        is_stalled = check_md_stall(s, md_idx) || check_sidscp_stall(s, sid);
+        if (is_stalled) {
+            s->md_stall_stat |= (1 << md_idx);
+            entry.target_as = &s->stall_io_as;
+            entry.perm = IOMMU_RW;
+            return entry;
+        } else {
+            s->md_stall_stat &= ~(1 << md_idx);
+        }
+        entry.perm = s->regs.entry[entry_idx].cfg_reg & 0x7;
+        if (s->sps_en) {
+            /* do not affect x permission */
+            srcmd_rw = 0x4 | ((s->regs.srcmd_r[sid] >>
+                              (md_idx + SRCMD_R_MD)) & 0x1);
+            srcmd_rw |= ((s->regs.srcmd_w[sid] >>
+                         (md_idx + SRCMD_W_MD)) & 0x1) << 1;
+            entry.perm &= srcmd_rw;
+        }
+        if ((entry.perm & flags) == 0) {
+            /* permission denied */
+            iopmp_error_reaction(s, sid, addr, size,
+                                 (entry_idx << ERR_REQINFO_EID) |
+                                 ((flags - 1) << ERR_REQINFO_TYPE));
+            entry.target_as = &s->blocked_io_as;
+            entry.perm = IOMMU_RW;
+        } else {
+            entry.addr_mask = s->entry_addr[entry_idx].ea -
+                              s->entry_addr[entry_idx].sa;
+            if (s->sid_transl_en) {
+                /* next iopmp */
+                if (s->next_iommu) {
+                    return iopmp_translate_size(s->next_iommu, addr, size,
+                                                flags, s->sid_transl);
+                } else {
+                    error_report("Next iopmp is not found.");
+                    exit(1);
+                }
+            }
+        }
+    } else {
+        if (result == ENTRY_PAR_HIT) {
+            iopmp_error_reaction(s, sid, addr, size,
+                                 (1 << ERR_REQINFO_PAR_HIT) |
+                                 ((flags - 1) << ERR_REQINFO_TYPE));
+        } else {
+            iopmp_error_reaction(s, sid, addr, size,
+                                 (1 << ERR_REQINFO_NO_HIT) |
+                                 ((flags - 1) << ERR_REQINFO_TYPE));
+        }
+        entry.target_as = &s->blocked_io_as;
+        entry.perm = IOMMU_RW;
+    }
+    return entry;
+}
+
+static const MemoryRegionOps iopmp_ops = {
+    .read = iopmp_read,
+    .write = iopmp_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {.min_access_size = 4, .max_access_size = 4}
+};
+
+static MemTxResult iopmp_block_write(void *opaque, hwaddr addr, uint64_t value,
+                                     unsigned size, MemTxAttrs attrs)
+{
+    IopmpState *s = IOPMP(opaque);
+
+    switch (iopmp_get_field(s->regs.errreact, ERRREACT_RWE)) {
+    case RWE_BUS_ERROR:
+        return MEMTX_ERROR;
+        break;
+    case RWE_DECODE_ERROR:
+        return MEMTX_DECODE_ERROR;
+        break;
+    case RWE_SUCCESS:
+        return MEMTX_OK;
+        break;
+    default:
+        break;
+    }
+    return MEMTX_OK;
+}
+
+static MemTxResult iopmp_block_read(void *opaque, hwaddr addr, uint64_t *pdata,
+                                    unsigned size, MemTxAttrs attrs)
+{
+    IopmpState *s = IOPMP(opaque);
+
+    switch (iopmp_get_field(s->regs.errreact, ERRREACT_RRE)) {
+    case RRE_BUS_ERROR:
+        return MEMTX_ERROR;
+        break;
+    case RRE_DECODE_ERROR:
+        return MEMTX_DECODE_ERROR;
+        break;
+    case RRE_SUCCESS_ZEROS:
+        *pdata = 0;
+        return MEMTX_OK;
+        break;
+    case RRE_SUCCESS_ONES:
+        *pdata = UINT64_MAX;
+        return MEMTX_OK;
+        break;
+    default:
+        break;
+    }
+    return MEMTX_OK;
+}
+
+static const MemoryRegionOps iopmp_block_io_ops = {
+    .read_with_attrs = iopmp_block_read,
+    .write_with_attrs = iopmp_block_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {.min_access_size = 1, .max_access_size = 8},
+};
+
+
+static MemTxResult iopmp_handle_stall(IopmpState *s, hwaddr addr,
+                                      MemTxAttrs attrs)
+{
+    return MEMTX_IOPMP_STALL;
+}
+
+static MemTxResult iopmp_stall_write(void *opaque, hwaddr addr, uint64_t value,
+                                     unsigned size, MemTxAttrs attrs)
+{
+    IopmpState *s = IOPMP(opaque);
+
+    return iopmp_handle_stall(s, addr, attrs);
+}
+
+static MemTxResult iopmp_stall_read(void *opaque, hwaddr addr, uint64_t *pdata,
+                                    unsigned size, MemTxAttrs attrs)
+{
+    IopmpState *s = IOPMP(opaque);
+
+    *pdata = 0;
+    return iopmp_handle_stall(s, addr, attrs);
+}
+
+
+static const MemoryRegionOps iopmp_stall_io_ops = {
+    .read_with_attrs = iopmp_stall_read,
+    .write_with_attrs = iopmp_stall_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {.min_access_size = 1, .max_access_size = 8},
+};
+
+static void iopmp_realize(DeviceState *dev, Error **errp)
+{
+    Object *obj = OBJECT(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    IopmpState *s = IOPMP(dev);
+    s->downstream = get_system_memory();
+    uint64_t size = memory_region_size(s->downstream);
+
+    s->prient_prog = 1;
+    s->sid_num = MIN(s->sid_num, IOPMP_MAX_SID_NUM);
+    s->md_num = MIN(s->md_num, IOPMP_MAX_MD_NUM);
+    s->entry_num = MIN(s->entry_num, IOPMP_MAX_ENTRY_NUM);
+    if (s->sid_transl_en) {
+        s->sid_transl_prog = 1;
+    }
+    if (!s->model_str || strcmp(s->model_str, "rapidk") == 0) {
+        /* apply default model */
+        s->model = IOPMP_MODEL_RAPIDK;
+        iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_F, s->md_num);
+        iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_L, 1);
+        s->regs.mdcfg[0] = s->k;
+    } else {
+        error_report("IOPMP model %s is not supported. "
+                     "Vailid values are full, rapidk, dynamick,"
+                     "isolation and compactk.", s->model_str);
+        exit(1);
+    }
+    memory_region_init_iommu(&s->iommu, sizeof(s->iommu),
+                             TYPE_IOPMP_IOMMU_MEMORY_REGION,
+                             obj, "iopmp-iommu", UINT64_MAX);
+    address_space_init(&s->iopmp_as, MEMORY_REGION(&s->iommu), "iommu");
+    memory_region_init_io(&s->mmio, obj, &iopmp_ops,
+                          s, "iopmp-regs", 0x100000);
+    sysbus_init_mmio(sbd, &s->mmio);
+    memory_region_init_io(&s->blocked_io, obj, &iopmp_block_io_ops,
+                          s, "iopmp-blocked-io", size);
+    address_space_init(&s->downstream_as, s->downstream,
+                       "iopmp-downstream-as");
+    address_space_init(&s->blocked_io_as, &s->blocked_io,
+                       "iopmp-blocked-io-as");
+
+    memory_region_init_io(&s->stall_io, obj, &iopmp_stall_io_ops,
+                          s, "iopmp-stall-io", size);
+    address_space_init(&s->stall_io_as, &s->stall_io,
+                       "iopmp-stall-io-as");
+}
+
+static void iopmp_reset(DeviceState *dev)
+{
+    IopmpState *s = IOPMP(dev);
+    qemu_set_irq(s->irq, 0);
+    memset(&s->regs, 0, sizeof(iopmp_regs));
+    memset(&s->entry_addr, 0, IOPMP_MAX_ENTRY_NUM * sizeof(iopmp_addr_t));
+    if (s->model == IOPMP_MODEL_RAPIDK) {
+        iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_F, s->md_num);
+        iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_L, 1);
+        s->regs.mdcfg[0] = s->k;
+    }
+    s->regs.errreact = 0;
+
+    s->prient_prog = 1;
+    if (s->sid_transl_en) {
+        s->sid_transl_prog = 1;
+    }
+}
+
+static int iopmp_attrs_to_index(IOMMUMemoryRegion *iommu, MemTxAttrs attrs)
+{
+    /* Get source id(SID) */
+    return attrs.iopmp_sid;
+}
+
+
+static void iopmp_iommu_memory_region_class_init(ObjectClass *klass, void *data)
+{
+    IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);
+
+    imrc->translate_size = iopmp_translate_size;
+    imrc->attrs_to_index = iopmp_attrs_to_index;
+}
+
+static Property iopmp_property[] = {
+    DEFINE_PROP_STRING("model", IopmpState, model_str),
+    DEFINE_PROP_BOOL("sps_en", IopmpState, sps_en, false),
+    DEFINE_PROP_BOOL("sid_transl_en", IopmpState, sid_transl_en, false),
+    DEFINE_PROP_UINT32("k", IopmpState, k, IOPMP_MODEL_K),
+    DEFINE_PROP_UINT32("prio_entry", IopmpState, prio_entry, PRIO_ENTRY),
+    DEFINE_PROP_UINT32("sid_num", IopmpState, sid_num, IOPMP_MAX_SID_NUM),
+    DEFINE_PROP_UINT32("md_num", IopmpState, md_num, IOPMP_MAX_MD_NUM),
+    DEFINE_PROP_UINT32("entry_num", IopmpState, entry_num, IOPMP_MAX_ENTRY_NUM),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void iopmp_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    device_class_set_props(dc, iopmp_property);
+    dc->realize = iopmp_realize;
+    dc->reset = iopmp_reset;
+}
+
+static void iopmp_init(Object *obj)
+{
+    IopmpState *s = IOPMP(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    sysbus_init_irq(sbd, &s->irq);
+}
+
+static const TypeInfo iopmp_info = {
+    .name = TYPE_IOPMP,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(IopmpState),
+    .instance_init = iopmp_init,
+    .class_init = iopmp_class_init,
+};
+
+static const TypeInfo
+iopmp_iommu_memory_region_info = {
+    .name = TYPE_IOPMP_IOMMU_MEMORY_REGION,
+    .parent = TYPE_IOMMU_MEMORY_REGION,
+    .class_init = iopmp_iommu_memory_region_class_init,
+};
+
+DeviceState *
+iopmp_create(hwaddr addr, qemu_irq irq)
+{
+    LOG("%s:\n", __func__);
+    DeviceState *iopmp_device = sysbus_create_varargs(TYPE_IOPMP, addr, irq,
+                                                      NULL);
+    return iopmp_device;
+}
+
+void cascade_iopmp(DeviceState *cur_dev, DeviceState *next_dev)
+{
+    IopmpState *s = IOPMP(cur_dev);
+    s->sid_transl_en = true;
+    IopmpState *next_s = IOPMP(next_dev);
+    s->next_iommu = &next_s->iommu;
+}
+
+static void
+iopmp_register_types(void)
+{
+    type_register_static(&iopmp_info);
+    type_register_static(&iopmp_iommu_memory_region_info);
+}
+
+type_init(iopmp_register_types);
diff --git a/include/hw/misc/riscv_iopmp.h b/include/hw/misc/riscv_iopmp.h
new file mode 100644
index 0000000000..b4e79ef0da
--- /dev/null
+++ b/include/hw/misc/riscv_iopmp.h
@@ -0,0 +1,322 @@
+/*
+ * QEMU RISC-V IOPMP (Input Output Physical Memory Protection)
+ *
+ * Copyright (c) 2023 Andes Tech. Corp.
+ *
+ * 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_H
+#define RISCV_IOPMP_H
+
+#include "hw/sysbus.h"
+#include "qemu/typedefs.h"
+#include "memory.h"
+
+#define TYPE_IOPMP "iopmp"
+#define IOPMP(obj) OBJECT_CHECK(IopmpState, (obj), TYPE_IOPMP)
+
+#define iopmp_get_field(reg, name) (((reg) & (name ## _FIELD)) >> (name))
+#define iopmp_set_field32(reg, name, newval) do { \
+    uint32_t val = *reg; \
+    val &= ~name##_FIELD; \
+    val |= ((newval) << name) & name##_FIELD; \
+    *reg = val; \
+    } while (0)
+#define iopmp_set_field64(reg, name, newval) do { \
+    uint64_t val = *reg; \
+    val &= ~name##_FIELD; \
+    val |= ((newval) << name) & name##_FIELD; \
+    *reg = val; \
+    } while (0)
+
+
+#define IOPMP_MAX_MD_NUM    63
+#define IOPMP_MAX_SID_NUM   256
+#define IOPMP_MAX_ENTRY_NUM 512
+
+#define IOPMP_VERSION      0x0
+#define IOPMP_IMP          0x4
+#define IOPMP_HWCFG0       0x8
+#define IOPMP_HWCFG1       0xC
+#define IOPMP_HWCFG2       0x10
+#define IOPMP_ENTRYOFFSET  0x20
+#define IOPMP_ERRREACT     0x28
+#define IOPMP_MDSTALL      0x30
+#define IOPMP_MDSTALLH     0x34
+#define IOPMP_SIDSCP       0x38
+#define IOPMP_MDLCK        0x40
+#define IOPMP_MDLCKH       0x44
+#define IOPMP_MDCFGLCK     0x48
+#define IOPMP_ENTRYLCK     0x4C
+
+#define IOPMP_ERR_REQADDR  0x60
+#define IOPMP_ERR_REQADDRH 0x64
+#define IOPMP_ERR_REQSID   0x68
+#define IOPMP_ERR_REQINFO  0x6C
+
+#define IOPMP_MDCFG0       0x800
+#define IOPMP_SRCMD_EN0    0x1000
+#define IOPMP_SRCMD_ENH0   0x1004
+#define IOPMP_SRCMD_R0     0x1008
+#define IOPMP_SRCMD_RH0    0x100C
+#define IOPMP_SRCMD_W0     0x1010
+#define IOPMP_SRCMD_WH0    0x1014
+
+#define IOPMP_ENTRY_ADDR0  0x4000
+#define IOPMP_ENTRY_ADDRH0 0x4004
+#define IOPMP_ENTRY_CFG0   0x4008
+#define IOPMP_USER_CFG0    0x400C
+
+#define VERSION_VENDOR       0
+#define VERSION_SPECVER      24
+#define VENDER_ANDES         6533
+#define SPECVER_1_0_0_DRAFT4 4
+
+#define IMPID_1_0_0_DRAFT4_0 10040
+
+#define HWCFG0_SID_NUM     0
+#define HWCFG0_ENTRY_NUM   16
+
+#define HWCFG1_MODEL            0
+#define HWCFG1_TOR_EN           4
+#define HWCFG1_SPS_EN           5
+#define HWCFG1_USER_CFG_EN      6
+#define HWCFG1_PRIENT_PROG      7
+#define HWCFG1_SID_TRANSL_EN    8
+#define HWCFG1_SID_TRANSL_PROG  9
+#define HWCFG1_MD_NUM           24
+#define HWCFG1_ENABLE           31
+
+#define HWCFG1_SPS_EN_FIELD          (1 << HWCFG1_SPS_EN)
+#define HWCFG1_PRIENT_PROG_FIELD     (1 << HWCFG1_PRIENT_PROG)
+#define HWCFG1_SID_TRANSL_PROG_FIELD (1 << HWCFG1_SID_TRANSL_PROG)
+#define HWCFG1_ENABLE_FIELD          (1 << HWCFG1_ENABLE)
+
+#define HWCFG2_PRIO_ENTRY       0
+#define HWCFG2_SID_TRANSL      16
+
+#define HWCFG2_PRIO_ENTRY_FIELD (0xFFFF << HWCFG2_PRIO_ENTRY)
+#define HWCFG2_SID_TRANSL_FIELD (0xFFFF << HWCFG2_SID_TRANSL)
+
+#define ERRREACT_L          0
+#define ERRREACT_IE         1
+#define ERRREACT_IP         2
+#define ERRREACT_IRE        4
+#define ERRREACT_RRE        5
+#define ERRREACT_IWE        8
+#define ERRREACT_RWE        9
+#define ERRREACT_PEE        28
+#define ERRREACT_RPE        29
+
+#define ERRREACT_L_FIELD    (0x1 << ERRREACT_L)
+#define ERRREACT_IE_FIELD   (0x1 << ERRREACT_IE)
+#define ERRREACT_IP_FIELD   (0x1 << ERRREACT_IP)
+#define ERRREACT_IRE_FIELD  (0x1 << ERRREACT_IRE)
+#define ERRREACT_RRE_FIELD  (0x7 << ERRREACT_RRE)
+#define ERRREACT_IWE_FIELD  (0x1 << ERRREACT_IWE)
+#define ERRREACT_RWE_FIELD  (0x7 << ERRREACT_RWE)
+#define ERRREACT_PEE_FIELD  (0x1 << ERRREACT_PEE)
+#define ERRREACT_RPE_FIELD  (0x7 << ERRREACT_RPE)
+
+#define RRE_BUS_ERROR       0
+#define RRE_DECODE_ERROR    1
+#define RRE_SUCCESS_ZEROS   2
+#define RRE_SUCCESS_ONES    3
+
+#define RWE_BUS_ERROR       0
+#define RWE_DECODE_ERROR    1
+#define RWE_SUCCESS         2
+
+#define MDSTALL               0
+#define MDSTALLH              32
+#define MDSTALL_FIELD         UINT32_MAX
+#define MDSTALLH_FIELD        (UINT64_MAX << MDSTALLH)
+#define MDSTALL_EXEMPT        0
+#define MDSTALL_EXEMPT_FIELD  (1 << MDSTALL_EXEMPT)
+#define MDSTALL_ISSTALLED     0
+#define MDSTALL_MD            1
+#define MDSTALL_MD_FIELD      (0x7FFFFFFFFFFFFFFF << MDSTALL_MD)
+
+#define SIDSCP_SID         0
+#define SIDSCP_STAT        30
+#define SIDSCP_OP          30
+#define SIDSCP_SID_FIELD   (0xFFFF << SIDSCP_SID)
+#define SIDSCP_STAT_FIELD  (0x3 << SIDSCP_STAT)
+#define SIDSCP_OP_FIELD    (0x3 << SIDSCP_OP)
+#define SIDSCP_OP_QUERY    0
+#define SIDSCP_OP_STALL    1
+#define SIDSCP_OP_NOTSTALL 2
+
+#define MDLCK_L            0
+#define MDLCK_MD           1
+
+#define MDCFGLCK_L         0
+#define MDCFGLCK_L_FIELD   (0x1 << MDCFGLCK_L)
+#define MDCFGLCK_F         1
+#define MDCFGLCK_F_FIELD   (0x7F << MDCFGLCK_F)
+
+#define ENTRYLCK_L         0
+#define ENTRYLCK_L_FIELD   (0x1 << MDCFGLCK_L)
+#define ENTRYLCK_F         1
+#define ENTRYLCK_F_FIELD   (0xFFFF << ENTRYLCK_F)
+
+#define ERR_REQINFO_NO_HIT  0
+#define ERR_REQINFO_PAR_HIT 1
+#define ERR_REQINFO_TYPE    8
+#define ERR_REQINFO_EID     16
+
+#define ERR_REQINFO_NO_HIT_FIELD  (0x1 << ERR_REQINFO_NO_HIT)
+#define ERR_REQINFO_PAR_HIT_FIELD (0x1 << ERR_REQINFO_PAR_HIT)
+#define ERR_REQINFO_TYPE_FIELD    (0x3 << ERR_REQINFO_TYPE)
+#define ERR_REQINFO_EID_FIELD     (0xFFFF << ERR_REQINFO_EID)
+
+#define ERR_REQINFO_TYPE_READ  0
+#define ERR_REQINFO_TYPE_WRITE 1
+#define ERR_REQINFO_TYPE_USER  3
+
+#define SRCMD_EN_L         0
+#define SRCMD_EN_MD        1
+#define SRCMD_EN_L_FIELD   (0x1 << SRCMD_EN_L)
+#define SRCMD_EN_MD_FIELD  (0x7FFFFFFF << SRCMD_EN_MD)
+#define SRCMD_ENH_MDH        32
+#define SRCMD_ENH_MDH_FIELD (0xFFFFFFFFUL << SRCMD_ENH_MDH)
+
+#define SRCMD_R_MD        1
+#define SRCMD_R_MD_FIELD  (0x7FFFFFFF << SRCMD_R_MD)
+#define SRCMD_RH_MDH        32
+#define SRCMD_RH_MDH_FIELD (0xFFFFFFFFUL << SRCMD_RH_MDH)
+#define SRCMD_W_MD        1
+#define SRCMD_W_MD_FIELD  (0x7FFFFFFF << SRCMD_W_MD)
+#define SRCMD_WH_MDH        32
+#define SRCMD_WH_MDH_FIELD (0xFFFFFFFFUL << SRCMD_WH_MDH)
+
+#define ENTRY_ADDR_ADDR         0
+#define ENTRY_ADDR_ADDR_FIELD   0xFFFFFFFF
+#define ENTRY_ADDRH_ADDRH       32
+#define ENTRY_ADDRH_ADDRH_FIELD (0xFFFFFFFFUL << ENTRY_ADDRH_ADDRH)
+
+#define ENTRY_CFG_R            0
+#define ENTRY_CFG_W            1
+#define ENTRY_CFG_X            2
+#define ENTRY_CFG_A            3
+#define ENTRY_CFG_A_FIELD      (0x3 << ENTRY_CFG_A)
+
+#define IOPMP_MODEL_FULL       0
+#define IOPMP_MODEL_RAPIDK     0x1
+#define IOPMP_MODEL_DYNAMICK   0x2
+#define IOPMP_MODEL_ISOLATION  0x3
+#define IOPMP_MODEL_COMPACTK   0x4
+#define IOPMP_MODEL_K          8
+
+#define TOR_EN 1
+#define SPS_EN 0
+#define USER_CFG_EN   0
+#define PROG_PRIENT   1
+#define PRIO_ENTRY    IOPMP_MAX_ENTRY_NUM
+#define SID_TRANSL_EN 0
+#define SID_TRANSL    0
+
+#define ENTRY_NO_HIT      0
+#define ENTRY_PAR_HIT     1
+#define ENTRY_HIT         2
+
+typedef enum {
+    IOPMP_READ      = 1 << 0,
+    IOPMP_WRITE     = 1 << 1,
+    IOPMP_EXEC      = 1 << 2,
+    IOPMP_ADDRMODE  = 1 << 3,
+} iopmp_priv_t;
+
+typedef enum {
+    IOPMP_AMATCH_OFF,  /* Null (off)                            */
+    IOPMP_AMATCH_TOR,  /* Top of Range                          */
+    IOPMP_AMATCH_NA4,  /* Naturally aligned four-byte region    */
+    IOPMP_AMATCH_NAPOT /* Naturally aligned power-of-two region */
+} iopmp_am_t;
+
+typedef struct {
+    uint64_t addr_reg;
+    uint32_t  cfg_reg;
+} iopmp_entry_t;
+
+typedef struct {
+    target_ulong sa;
+    target_ulong ea;
+} iopmp_addr_t;
+
+typedef struct {
+    uint64_t srcmd_en[IOPMP_MAX_SID_NUM];
+    uint64_t srcmd_r[IOPMP_MAX_SID_NUM];
+    uint64_t srcmd_w[IOPMP_MAX_SID_NUM];
+    uint32_t mdcfg[IOPMP_MAX_MD_NUM];
+    iopmp_entry_t entry[IOPMP_MAX_ENTRY_NUM];
+    uint64_t mdmsk;
+    uint64_t mdlck;
+    uint32_t entrylck;
+    uint32_t mdcfglck;
+    uint32_t arrlck;
+    uint64_t mdstall;
+    uint32_t sidscp;
+    uint32_t errreact;
+    uint64_t err_reqaddr;
+    uint32_t err_reqsid;
+    uint32_t err_reqinfo;
+} iopmp_regs;
+
+/* To verfiy the same transcation */
+typedef struct iopmp_error_detail {
+    uint32_t reqinfo;
+    target_ulong addr_start;
+    target_ulong addr_end;
+} iopmp_error_detail;
+
+typedef struct IopmpState {
+    SysBusDevice parent_obj;
+    iopmp_addr_t entry_addr[IOPMP_MAX_ENTRY_NUM];
+    iopmp_error_detail prev_error[IOPMP_MAX_SID_NUM];
+    MemoryRegion mmio;
+    IOMMUMemoryRegion iommu;
+    IOMMUMemoryRegion *next_iommu;
+    iopmp_regs regs;
+    MemoryRegion *downstream;
+    MemoryRegion blocked_io;
+    MemoryRegion stall_io;
+    char *model_str;
+    uint32_t model;
+    uint32_t k;
+    bool sps_en;
+    bool sid_transl_prog;
+    bool prient_prog;
+    bool sid_transl_en;
+    uint32_t sid_transl;
+
+    AddressSpace iopmp_as;
+    AddressSpace downstream_as;
+    AddressSpace blocked_io_as;
+    AddressSpace stall_io_as;
+    qemu_irq irq;
+    bool enable;
+    uint32_t sidscp_op[IOPMP_MAX_SID_NUM];
+    uint64_t md_stall_stat;
+    uint32_t prio_entry;
+
+    uint32_t sid_num;
+    uint32_t md_num;
+    uint32_t entry_num;
+} IopmpState;
+
+DeviceState *iopmp_create(hwaddr addr, qemu_irq irq);
+void cascade_iopmp(DeviceState *cur_dev, DeviceState *next_dev);
+
+#endif
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 4/6] Add RISC-V IOPMP support
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Ethan Chen, Paolo Bonzini

Support specification Version 1.0.0-draft4 rapid-k model.

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 hw/misc/Kconfig               |   3 +
 hw/misc/meson.build           |   1 +
 hw/misc/riscv_iopmp.c         | 881 ++++++++++++++++++++++++++++++++++
 include/hw/misc/riscv_iopmp.h | 322 +++++++++++++
 4 files changed, 1207 insertions(+)
 create mode 100644 hw/misc/riscv_iopmp.c
 create mode 100644 include/hw/misc/riscv_iopmp.h

diff --git a/hw/misc/Kconfig b/hw/misc/Kconfig
index dba41afe67..8c63eb4cef 100644
--- a/hw/misc/Kconfig
+++ b/hw/misc/Kconfig
@@ -197,4 +197,7 @@ config DJMEMC
 config IOSB
     bool
 
+config RISCV_IOPMP
+    bool
+
 source macio/Kconfig
diff --git a/hw/misc/meson.build b/hw/misc/meson.build
index f60de33f9a..7826ed9b7b 100644
--- a/hw/misc/meson.build
+++ b/hw/misc/meson.build
@@ -35,6 +35,7 @@ system_ss.add(when: 'CONFIG_SIFIVE_E_PRCI', if_true: files('sifive_e_prci.c'))
 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'))
 
 subdir('macio')
 
diff --git a/hw/misc/riscv_iopmp.c b/hw/misc/riscv_iopmp.c
new file mode 100644
index 0000000000..5f9535501a
--- /dev/null
+++ b/hw/misc/riscv_iopmp.c
@@ -0,0 +1,881 @@
+/*
+ * QEMU RISC-V IOPMP (Input Output Physical Memory Protection)
+ *
+ * Copyright (c) 2023 Andes Tech. Corp.
+ *
+ * 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.h"
+#include "memory.h"
+#include "hw/irq.h"
+
+#define TYPE_IOPMP_IOMMU_MEMORY_REGION "iopmp-iommu-memory-region"
+
+#define LOGGE(x...) qemu_log_mask(LOG_GUEST_ERROR, x)
+#define xLOG(x...)
+#define yLOG(x...) qemu_log(x)
+#ifdef DEBUG_RISCV_IOPMP
+  #define LOG(x...) yLOG(x)
+#else
+  #define LOG(x...) xLOG(x)
+#endif
+
+#define MEMTX_IOPMP_STALL (1 << 3)
+
+
+static void iopmp_decode_napot(target_ulong a, target_ulong *sa,
+                               target_ulong *ea)
+{
+    /*
+     * aaaa...aaa0   8-byte NAPOT range
+     * aaaa...aa01   16-byte NAPOT range
+     * aaaa...a011   32-byte NAPOT range
+     * ...
+     * aa01...1111   2^XLEN-byte NAPOT range
+     * a011...1111   2^(XLEN+1)-byte NAPOT range
+     * 0111...1111   2^(XLEN+2)-byte NAPOT range
+     *  1111...1111   Reserved
+     */
+
+    a = (a << 2) | 0x3;
+    *sa = a & (a + 1);
+    *ea = a | (a + 1);
+}
+
+static void iopmp_update_rule(IopmpState *s, uint32_t entry_index)
+{
+    uint8_t this_cfg = s->regs.entry[entry_index].cfg_reg;
+    target_ulong this_addr = s->regs.entry[entry_index].addr_reg;
+    target_ulong prev_addr = 0u;
+    target_ulong sa = 0u;
+    target_ulong ea = 0u;
+
+    if (entry_index >= 1u) {
+        prev_addr = s->regs.entry[entry_index - 1].addr_reg;
+    }
+
+    switch (iopmp_get_field(this_cfg, ENTRY_CFG_A)) {
+    case IOPMP_AMATCH_OFF:
+        sa = 0u;
+        ea = -1;
+        break;
+
+    case IOPMP_AMATCH_TOR:
+        sa = (prev_addr) << 2; /* shift up from [xx:0] to [xx+2:2] */
+        ea = ((this_addr) << 2) - 1u;
+        if (sa > ea) {
+            sa = ea = 0u;
+        }
+        break;
+
+    case IOPMP_AMATCH_NA4:
+        sa = this_addr << 2; /* shift up from [xx:0] to [xx+2:2] */
+        ea = (sa + 4u) - 1u;
+        break;
+
+    case IOPMP_AMATCH_NAPOT:
+        iopmp_decode_napot(this_addr, &sa, &ea);
+        break;
+
+    default:
+        sa = 0u;
+        ea = 0u;
+        break;
+    }
+
+    s->entry_addr[entry_index].sa = sa;
+    s->entry_addr[entry_index].ea = ea;
+}
+
+static uint64_t
+iopmp_read(void *opaque, hwaddr addr, unsigned size)
+{
+    IopmpState *s = IOPMP(opaque);
+    uint32_t rz = 0;
+    uint32_t is_stall = 0;
+    uint32_t sid;
+    switch (addr) {
+    case IOPMP_VERSION ... IOPMP_USER_CFG0 + 16 * (IOPMP_MAX_ENTRY_NUM - 1):
+        switch (addr) {
+        case IOPMP_VERSION:
+            rz = VENDER_ANDES << VERSION_VENDOR |
+                 SPECVER_1_0_0_DRAFT4 << VERSION_SPECVER;
+            break;
+        case IOPMP_IMP:
+            rz = IMPID_1_0_0_DRAFT4_0;
+            break;
+        case IOPMP_HWCFG0: /* RO */
+            rz = s->sid_num << HWCFG0_SID_NUM |
+                 s->entry_num << HWCFG0_ENTRY_NUM;
+            break;
+        case IOPMP_HWCFG1:
+            rz = s->model << HWCFG1_MODEL | TOR_EN << HWCFG1_TOR_EN |
+                 s->sps_en << HWCFG1_SPS_EN |
+                 USER_CFG_EN << HWCFG1_USER_CFG_EN |
+                 s->prient_prog << HWCFG1_PRIENT_PROG |
+                 s->sid_transl_en << HWCFG1_SID_TRANSL_EN |
+                 s->sid_transl_prog << HWCFG1_SID_TRANSL_PROG |
+                 s->md_num << HWCFG1_MD_NUM |
+                 s->enable << HWCFG1_ENABLE;
+            break;
+        case IOPMP_HWCFG2:
+            rz = s->prio_entry << HWCFG2_PRIO_ENTRY |
+                 s->sid_transl << HWCFG2_SID_TRANSL;
+            break;
+        case IOPMP_ENTRYOFFSET:
+            rz = IOPMP_ENTRY_ADDR0;
+            break;
+        case IOPMP_ERRREACT:
+            rz = s->regs.errreact;
+            break;
+        case IOPMP_MDSTALL:
+            if (s->md_stall_stat) {
+                is_stall = 1;
+            }
+            rz = iopmp_get_field(s->regs.mdstall, MDSTALL_MD) | is_stall;
+            break;
+        case IOPMP_MDSTALLH:
+            rz = s->regs.mdstall >> 32;
+            break;
+        case IOPMP_SIDSCP:
+            sid = iopmp_get_field(s->regs.sidscp, SIDSCP_SID);
+            if (sid < s->sid_num) {
+                rz = sid | (s->sidscp_op[sid]) << SIDSCP_STAT;
+            } else {
+                rz = sid | 3 << SIDSCP_STAT;
+            }
+            break;
+        case IOPMP_MDLCK:
+            rz = s->regs.mdlck & UINT32_MAX;
+            break;
+        case IOPMP_MDLCKH:
+            rz = s->regs.mdlck >> 32;
+            break;
+        case IOPMP_MDCFGLCK:
+            rz = s->regs.mdcfglck;
+            break;
+        case IOPMP_ENTRYLCK:
+            rz = s->regs.entrylck;
+            break;
+        case IOPMP_ERR_REQADDR:
+            rz = s->regs.err_reqaddr & UINT32_MAX;
+            break;
+        case IOPMP_ERR_REQADDRH:
+            rz = s->regs.err_reqaddr >> 32;
+            break;
+        case IOPMP_ERR_REQSID:
+            rz = s->regs.err_reqsid;
+            break;
+        case IOPMP_ERR_REQINFO:
+            rz = s->regs.err_reqinfo;
+            break;
+
+        default:
+            if (addr >= IOPMP_MDCFG0 &&
+                addr < IOPMP_MDCFG0 + 4 * (s->md_num - 1)) {
+                int offset = addr - IOPMP_MDCFG0;
+                int idx = offset >> 2;
+                if (idx == 0) {
+                    if (offset == 0) {
+                        rz = s->regs.mdcfg[idx];
+                    } else {
+                        LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                    }
+                } else {
+                    /* Only MDCFG0 is implemented */
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+            } else if (addr >= IOPMP_SRCMD_EN0 &&
+                       addr < IOPMP_SRCMD_WH0 + 32 * (s->sid_num - 1)) {
+                int offset = addr - IOPMP_SRCMD_EN0;
+                int idx = offset >> 5;
+                offset &= 0x1f;
+                if (offset == 0) {
+                    rz = s->regs.srcmd_en[idx] & UINT32_MAX;
+                } else if (offset == 4) {
+                    rz = s->regs.srcmd_en[idx] >> 32;
+                } else if (offset == 8) {
+                    rz = s->regs.srcmd_r[idx] & UINT32_MAX;
+                } else if (offset == 12) {
+                    rz = s->regs.srcmd_r[idx] >> 32;
+                } else if (offset == 16) {
+                    rz = s->regs.srcmd_w[idx] & UINT32_MAX;
+                } else if (offset == 24) {
+                    rz = s->regs.srcmd_w[idx] >> 32;
+                } else {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+            } else if (addr >= IOPMP_ENTRY_ADDR0 &&
+                       addr < IOPMP_USER_CFG0 + 16 * (s->entry_num - 1)) {
+                int offset = addr - IOPMP_ENTRY_ADDR0;
+                int idx = offset >> 4;
+                offset &= 0xf;
+                if (offset == 0) {
+                    rz = s->regs.entry[idx].addr_reg & UINT32_MAX;
+                } else if (offset == 4) {
+                    rz = s->regs.entry[idx].addr_reg >> 32;
+                } else if (offset == 8) {
+                    rz = s->regs.entry[idx].cfg_reg;
+                } else if (offset == 12) {
+                    /* Not support user customized permission*/
+                    rz = 0;
+                } else {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+            } else {
+                LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+            }
+            break;
+        }
+        LOG("\e[95m%s: addr %08x, value %08x\e[0m\n", __func__, (int)addr,
+            (int)rz);
+        break;
+    default:
+        LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+    }
+
+    return rz;
+}
+
+static void
+iopmp_write(void *opaque, hwaddr addr, uint64_t value, unsigned size)
+{
+    IopmpState *s = IOPMP(opaque);
+    int value_f;
+    int reg_f;
+    uint32_t sid, op;
+
+    switch (addr) {
+    case IOPMP_VERSION ... IOPMP_USER_CFG0 + 16 * (IOPMP_MAX_ENTRY_NUM - 1):
+        switch (addr) {
+        case IOPMP_VERSION: /* RO */
+            break;
+        case IOPMP_IMP: /* RO */
+            break;
+        case IOPMP_HWCFG0: /* RO */
+            break;
+        case IOPMP_HWCFG1:
+            if (iopmp_get_field(value, HWCFG1_PRIENT_PROG)) {
+                /* W1C */
+                s->prient_prog = 0;
+            }
+            if (iopmp_get_field(value, HWCFG1_SID_TRANSL_PROG)) {
+                /* W1C */
+                s->sid_transl_prog = 0;
+            }
+            if (iopmp_get_field(value, HWCFG1_ENABLE)) {
+                /* W1S */
+                s->enable = 1;
+            }
+            break;
+        case IOPMP_HWCFG2:
+            if (s->prient_prog) {
+                s->prio_entry = iopmp_get_field(value, HWCFG2_PRIO_ENTRY);
+            }
+            if (s->sid_transl_en && s->sid_transl_prog) {
+                s->sid_transl = iopmp_get_field(value, HWCFG2_SID_TRANSL);
+            }
+            break;
+        case IOPMP_ERRREACT:
+            if (!iopmp_get_field(s->regs.errreact, ERRREACT_L)) {
+                    iopmp_set_field32(&s->regs.errreact, ERRREACT_L,
+                                    iopmp_get_field(value, ERRREACT_L));
+                if (iopmp_get_field(value, ERRREACT_IP)) {
+                    iopmp_set_field32(&s->regs.errreact, ERRREACT_IP, 0);
+                }
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_IE,
+                                iopmp_get_field(value, ERRREACT_IE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_IRE,
+                                iopmp_get_field(value, ERRREACT_IRE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_RRE,
+                                iopmp_get_field(value, ERRREACT_RRE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_IWE,
+                                iopmp_get_field(value, ERRREACT_IWE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_RWE,
+                                iopmp_get_field(value, ERRREACT_RWE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_PEE,
+                                iopmp_get_field(value, ERRREACT_PEE));
+                iopmp_set_field32(&s->regs.errreact, ERRREACT_RPE,
+                                iopmp_get_field(value, ERRREACT_RPE));
+            } else {
+                if (iopmp_get_field(value, ERRREACT_IP)) {
+                    iopmp_set_field32(&s->regs.errreact, ERRREACT_IP, 0);
+                }
+            }
+            break;
+        case IOPMP_MDSTALL:
+            iopmp_set_field64(&s->regs.mdstall, MDSTALL, value);
+            break;
+        case IOPMP_MDSTALLH:
+            iopmp_set_field64(&s->regs.mdstall, MDSTALLH, value);
+            break;
+        case IOPMP_SIDSCP:
+            sid = iopmp_get_field(value, SIDSCP_SID);
+            op = iopmp_get_field(value, SIDSCP_OP);
+            if (sid < s->sid_num) {
+                if (op != SIDSCP_OP_QUERY) {
+                    s->sidscp_op[sid] = op;
+                    s->regs.sidscp = value;
+                }
+            } else {
+                s->regs.sidscp = sid | (0x3 << SIDSCP_OP);
+            }
+            break;
+        case IOPMP_MDLCK:
+            if (!(s->regs.mdlck & (1 << MDLCK_L))) {
+                s->regs.mdlck = value |
+                                (s->regs.mdstall & ~(uint64_t)UINT32_MAX);
+            }
+            break;
+        case IOPMP_MDLCKH:
+             if (!(s->regs.mdlck & (1 << MDLCK_L))) {
+                s->regs.mdlck = (uint64_t)value << 32 |
+                                (s->regs.mdstall & UINT32_MAX);
+            }
+            break;
+        case IOPMP_MDCFGLCK:
+            if (!iopmp_get_field(s->regs.mdcfglck, MDCFGLCK_L)) {
+                value_f = iopmp_get_field(value, MDCFGLCK_F);
+                reg_f = iopmp_get_field(s->regs.mdcfglck, MDCFGLCK_F);
+                if (value_f > reg_f) {
+                    iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_F, value_f);
+                }
+                iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_L,
+                          iopmp_get_field(value, MDCFGLCK_L));
+            }
+            break;
+        case IOPMP_ENTRYLCK:
+            if (!(iopmp_get_field(s->regs.entrylck, ENTRYLCK_L))) {
+                value_f = iopmp_get_field(value, ENTRYLCK_F);
+                reg_f = iopmp_get_field(s->regs.entrylck, ENTRYLCK_F);
+                if (value_f > reg_f) {
+                    iopmp_set_field32(&s->regs.entrylck, ENTRYLCK_F, value_f);
+                }
+                iopmp_set_field32(&s->regs.entrylck, ENTRYLCK_F,
+                          iopmp_get_field(value, ENTRYLCK_F));
+            }
+        case IOPMP_ERR_REQADDR: /* RO */
+            break;
+        case IOPMP_ERR_REQADDRH: /* RO */
+            break;
+        case IOPMP_ERR_REQSID: /* RO */
+            break;
+        case IOPMP_ERR_REQINFO: /* RO */
+            break;
+
+        default:
+            if (addr >= IOPMP_MDCFG0 &&
+                addr < IOPMP_MDCFG0 + 4 * (s->md_num - 1)) {
+                int offset = addr - IOPMP_MDCFG0;
+                int idx = offset >> 2;
+                /* RO in rapid-k model */
+                if (idx > 0) {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+            } else if (addr >= IOPMP_SRCMD_EN0 &&
+                       addr < IOPMP_SRCMD_WH0 + 32 * (s->sid_num - 1)) {
+                int offset = addr - IOPMP_SRCMD_EN0;
+                int idx = offset >> 5;
+                offset &= 0x1f;
+                if (offset % 4) {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                } else if (iopmp_get_field(s->regs.srcmd_en[idx],
+                                           SRCMD_EN_L) == 0) {
+                    if (offset == 0) {
+                        iopmp_set_field64(&s->regs.srcmd_en[idx], SRCMD_EN_MD,
+                                        iopmp_get_field(value, SRCMD_EN_MD));
+                        iopmp_set_field64(&s->regs.srcmd_en[idx], SRCMD_EN_L,
+                                        iopmp_get_field(value, SRCMD_EN_L));
+                    } else if (offset == 4) {
+                        iopmp_set_field64(&s->regs.srcmd_en[idx], SRCMD_ENH_MDH,
+                                        value);
+                    } else if (offset == 8 && s->sps_en) {
+                        iopmp_set_field64(&s->regs.srcmd_r[idx], SRCMD_R_MD,
+                                            iopmp_get_field(value, SRCMD_R_MD));
+                    } else if (offset == 12 && s->sps_en) {
+                        iopmp_set_field64(&s->regs.srcmd_r[idx], SRCMD_RH_MDH,
+                                        value);
+                    } else if (offset == 16 && s->sps_en) {
+                        iopmp_set_field64(&s->regs.srcmd_w[idx], SRCMD_W_MD,
+                                        iopmp_get_field(value, SRCMD_W_MD));
+                    } else if (offset == 24 && s->sps_en) {
+                        iopmp_set_field64(&s->regs.srcmd_w[idx], SRCMD_WH_MDH,
+                                          value);
+                    }
+                }
+            } else if (addr >= IOPMP_ENTRY_ADDR0 &&
+                       addr < IOPMP_USER_CFG0 + 16 * (s->entry_num - 1)) {
+                int offset = addr - IOPMP_ENTRY_ADDR0;
+                int idx = offset >> 4;
+                offset &= 0xf;
+                if (offset == 0) {
+                    iopmp_set_field64(&s->regs.entry[idx].addr_reg,
+                                      ENTRY_ADDR_ADDR, value);
+                } else if (offset == 4) {
+                    iopmp_set_field64(&s->regs.entry[idx].addr_reg,
+                                      ENTRY_ADDRH_ADDRH, value);
+                } else if (offset == 8) {
+                    s->regs.entry[idx].cfg_reg = value;
+                } else if (offset == 12) {
+                    /* Not support user customized permission*/
+                    ;
+                } else {
+                    LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+                }
+                iopmp_update_rule(s, idx);
+            } else {
+                LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+            }
+            /* If IOPMP permission of any addr has been changed, */
+            /* flush TLB pages. */
+            tlb_flush_all_cpus_synced(current_cpu);
+            break;
+        }
+        LOG("\e[95m%s: addr %08x, value %08x\e[0m\n", __func__, (int)addr,
+            (int)rz);
+        break;
+    default:
+        LOGGE("%s: Bad addr %x\n", __func__, (int)addr);
+    }
+}
+
+/* Match entry in memory domain */
+static int match_entry_md(IopmpState *s, int md_idx, hwaddr s_addr,
+                          hwaddr e_addr, int *entry_idx)
+{
+    int entry_idx_s, entry_idx_e;
+    int result = ENTRY_NO_HIT;
+    int i = 0;
+    entry_idx_s = md_idx * s->regs.mdcfg[0];
+    entry_idx_e = (md_idx + 1) * s->regs.mdcfg[0];
+    if (entry_idx_s >= s->entry_num) {
+        return result;
+    }
+    if (entry_idx_e > s->entry_num) {
+        entry_idx_e = s->entry_num;
+    }
+    i = entry_idx_s;
+    while (i < entry_idx_e) {
+        if (s_addr >= s->entry_addr[i].sa && s_addr <= s->entry_addr[i].ea) {
+            if (e_addr >= s->entry_addr[i].sa &&
+                e_addr <= s->entry_addr[i].ea) {
+                *entry_idx = i;
+                return ENTRY_HIT;
+            } else if (i >= s->prio_entry) {
+                result = ENTRY_PAR_HIT;
+                continue;
+            } else {
+                return ENTRY_PAR_HIT;
+            }
+        }
+        i++;
+    }
+    return result;
+}
+
+static int match_entry(IopmpState *s, int sid, hwaddr s_addr, hwaddr e_addr,
+                       int *match_md_idx, int *match_entry_idx)
+{
+    int cur_result = ENTRY_NO_HIT;
+    int result = ENTRY_NO_HIT;
+    uint64_t srcmd_en = s->regs.srcmd_en[sid] >> 1;
+    for (int md_idx = 0; md_idx < s->md_num; md_idx++) {
+        if (srcmd_en & (1ULL << md_idx)) {
+            cur_result = match_entry_md(s, md_idx, s_addr, e_addr,
+                                    match_entry_idx);
+            if (cur_result == ENTRY_HIT) {
+                *match_md_idx = md_idx;
+                return cur_result;
+            }
+            if (cur_result > result) {
+                result = cur_result;
+            }
+        }
+    }
+    return result;
+}
+
+static bool check_md_stall(IopmpState *s, int md_idx)
+{
+    uint64_t md_selected = iopmp_get_field(s->regs.mdstall, MDSTALL_MD) &
+                           (1 << md_idx);
+    if (iopmp_get_field(s->regs.mdstall, MDSTALL_EXEMPT)) {
+        return !md_selected;
+    } else {
+        return md_selected;
+    }
+}
+
+static inline bool check_sidscp_stall(IopmpState *s, int sid)
+{
+    return s->sidscp_op[sid] == SIDSCP_OP_STALL;
+}
+
+static void iopmp_error_reaction(IopmpState *s, uint32_t id, hwaddr addr,
+                                 hwaddr size, uint32_t info)
+{
+    if (addr > s->prev_error[id].addr_start &&
+        addr + size == s->prev_error[id].addr_end &&
+        info == s->prev_error[id].reqinfo) {
+            /* skip following error */
+            ;
+    } else {
+        s->prev_error[id].addr_start = addr;
+        s->prev_error[id].addr_end = addr + size;
+        s->prev_error[id].reqinfo = info;
+        if (!iopmp_get_field(s->regs.errreact, ERRREACT_IP)) {
+            iopmp_set_field32(&s->regs.errreact, ERRREACT_IP, 1);
+            s->regs.err_reqsid = id;
+            s->regs.err_reqaddr = addr;
+            s->regs.err_reqinfo = info;
+
+            if (iopmp_get_field(info, ERR_REQINFO_TYPE) == ERR_REQINFO_TYPE_READ
+               && iopmp_get_field(s->regs.errreact, ERRREACT_IE) &&
+               iopmp_get_field(s->regs.errreact, ERRREACT_IRE)) {
+                qemu_set_irq(s->irq, 1);
+            }
+            if (iopmp_get_field(info, ERR_REQINFO_TYPE) ==
+                ERR_REQINFO_TYPE_WRITE &&
+                iopmp_get_field(s->regs.errreact, ERRREACT_IE) &&
+                iopmp_get_field(s->regs.errreact, ERRREACT_IWE)) {
+                qemu_set_irq(s->irq, 1);
+            }
+        }
+    }
+}
+
+static IOMMUTLBEntry iopmp_translate_size(IOMMUMemoryRegion *iommu,
+                                          hwaddr addr, hwaddr size,
+                                          IOMMUAccessFlags flags,
+                                          int sid)
+{
+    bool is_stalled = false;
+    IopmpState *s = IOPMP(container_of(iommu, IopmpState, iommu));
+    IOMMUTLBEntry entry = {
+        .target_as = &s->downstream_as,
+        .iova = addr,
+        .translated_addr = addr,
+        .addr_mask = (~(hwaddr)0),
+        .perm = IOMMU_NONE,
+    };
+    int entry_idx = -1;
+    int md_idx = -1;
+    int result = match_entry(s, sid, addr, addr + size - 1,
+                             &md_idx, &entry_idx);
+    int srcmd_rw;
+    if (result == ENTRY_HIT) {
+        is_stalled = check_md_stall(s, md_idx) || check_sidscp_stall(s, sid);
+        if (is_stalled) {
+            s->md_stall_stat |= (1 << md_idx);
+            entry.target_as = &s->stall_io_as;
+            entry.perm = IOMMU_RW;
+            return entry;
+        } else {
+            s->md_stall_stat &= ~(1 << md_idx);
+        }
+        entry.perm = s->regs.entry[entry_idx].cfg_reg & 0x7;
+        if (s->sps_en) {
+            /* do not affect x permission */
+            srcmd_rw = 0x4 | ((s->regs.srcmd_r[sid] >>
+                              (md_idx + SRCMD_R_MD)) & 0x1);
+            srcmd_rw |= ((s->regs.srcmd_w[sid] >>
+                         (md_idx + SRCMD_W_MD)) & 0x1) << 1;
+            entry.perm &= srcmd_rw;
+        }
+        if ((entry.perm & flags) == 0) {
+            /* permission denied */
+            iopmp_error_reaction(s, sid, addr, size,
+                                 (entry_idx << ERR_REQINFO_EID) |
+                                 ((flags - 1) << ERR_REQINFO_TYPE));
+            entry.target_as = &s->blocked_io_as;
+            entry.perm = IOMMU_RW;
+        } else {
+            entry.addr_mask = s->entry_addr[entry_idx].ea -
+                              s->entry_addr[entry_idx].sa;
+            if (s->sid_transl_en) {
+                /* next iopmp */
+                if (s->next_iommu) {
+                    return iopmp_translate_size(s->next_iommu, addr, size,
+                                                flags, s->sid_transl);
+                } else {
+                    error_report("Next iopmp is not found.");
+                    exit(1);
+                }
+            }
+        }
+    } else {
+        if (result == ENTRY_PAR_HIT) {
+            iopmp_error_reaction(s, sid, addr, size,
+                                 (1 << ERR_REQINFO_PAR_HIT) |
+                                 ((flags - 1) << ERR_REQINFO_TYPE));
+        } else {
+            iopmp_error_reaction(s, sid, addr, size,
+                                 (1 << ERR_REQINFO_NO_HIT) |
+                                 ((flags - 1) << ERR_REQINFO_TYPE));
+        }
+        entry.target_as = &s->blocked_io_as;
+        entry.perm = IOMMU_RW;
+    }
+    return entry;
+}
+
+static const MemoryRegionOps iopmp_ops = {
+    .read = iopmp_read,
+    .write = iopmp_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {.min_access_size = 4, .max_access_size = 4}
+};
+
+static MemTxResult iopmp_block_write(void *opaque, hwaddr addr, uint64_t value,
+                                     unsigned size, MemTxAttrs attrs)
+{
+    IopmpState *s = IOPMP(opaque);
+
+    switch (iopmp_get_field(s->regs.errreact, ERRREACT_RWE)) {
+    case RWE_BUS_ERROR:
+        return MEMTX_ERROR;
+        break;
+    case RWE_DECODE_ERROR:
+        return MEMTX_DECODE_ERROR;
+        break;
+    case RWE_SUCCESS:
+        return MEMTX_OK;
+        break;
+    default:
+        break;
+    }
+    return MEMTX_OK;
+}
+
+static MemTxResult iopmp_block_read(void *opaque, hwaddr addr, uint64_t *pdata,
+                                    unsigned size, MemTxAttrs attrs)
+{
+    IopmpState *s = IOPMP(opaque);
+
+    switch (iopmp_get_field(s->regs.errreact, ERRREACT_RRE)) {
+    case RRE_BUS_ERROR:
+        return MEMTX_ERROR;
+        break;
+    case RRE_DECODE_ERROR:
+        return MEMTX_DECODE_ERROR;
+        break;
+    case RRE_SUCCESS_ZEROS:
+        *pdata = 0;
+        return MEMTX_OK;
+        break;
+    case RRE_SUCCESS_ONES:
+        *pdata = UINT64_MAX;
+        return MEMTX_OK;
+        break;
+    default:
+        break;
+    }
+    return MEMTX_OK;
+}
+
+static const MemoryRegionOps iopmp_block_io_ops = {
+    .read_with_attrs = iopmp_block_read,
+    .write_with_attrs = iopmp_block_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {.min_access_size = 1, .max_access_size = 8},
+};
+
+
+static MemTxResult iopmp_handle_stall(IopmpState *s, hwaddr addr,
+                                      MemTxAttrs attrs)
+{
+    return MEMTX_IOPMP_STALL;
+}
+
+static MemTxResult iopmp_stall_write(void *opaque, hwaddr addr, uint64_t value,
+                                     unsigned size, MemTxAttrs attrs)
+{
+    IopmpState *s = IOPMP(opaque);
+
+    return iopmp_handle_stall(s, addr, attrs);
+}
+
+static MemTxResult iopmp_stall_read(void *opaque, hwaddr addr, uint64_t *pdata,
+                                    unsigned size, MemTxAttrs attrs)
+{
+    IopmpState *s = IOPMP(opaque);
+
+    *pdata = 0;
+    return iopmp_handle_stall(s, addr, attrs);
+}
+
+
+static const MemoryRegionOps iopmp_stall_io_ops = {
+    .read_with_attrs = iopmp_stall_read,
+    .write_with_attrs = iopmp_stall_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {.min_access_size = 1, .max_access_size = 8},
+};
+
+static void iopmp_realize(DeviceState *dev, Error **errp)
+{
+    Object *obj = OBJECT(dev);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+    IopmpState *s = IOPMP(dev);
+    s->downstream = get_system_memory();
+    uint64_t size = memory_region_size(s->downstream);
+
+    s->prient_prog = 1;
+    s->sid_num = MIN(s->sid_num, IOPMP_MAX_SID_NUM);
+    s->md_num = MIN(s->md_num, IOPMP_MAX_MD_NUM);
+    s->entry_num = MIN(s->entry_num, IOPMP_MAX_ENTRY_NUM);
+    if (s->sid_transl_en) {
+        s->sid_transl_prog = 1;
+    }
+    if (!s->model_str || strcmp(s->model_str, "rapidk") == 0) {
+        /* apply default model */
+        s->model = IOPMP_MODEL_RAPIDK;
+        iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_F, s->md_num);
+        iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_L, 1);
+        s->regs.mdcfg[0] = s->k;
+    } else {
+        error_report("IOPMP model %s is not supported. "
+                     "Vailid values are full, rapidk, dynamick,"
+                     "isolation and compactk.", s->model_str);
+        exit(1);
+    }
+    memory_region_init_iommu(&s->iommu, sizeof(s->iommu),
+                             TYPE_IOPMP_IOMMU_MEMORY_REGION,
+                             obj, "iopmp-iommu", UINT64_MAX);
+    address_space_init(&s->iopmp_as, MEMORY_REGION(&s->iommu), "iommu");
+    memory_region_init_io(&s->mmio, obj, &iopmp_ops,
+                          s, "iopmp-regs", 0x100000);
+    sysbus_init_mmio(sbd, &s->mmio);
+    memory_region_init_io(&s->blocked_io, obj, &iopmp_block_io_ops,
+                          s, "iopmp-blocked-io", size);
+    address_space_init(&s->downstream_as, s->downstream,
+                       "iopmp-downstream-as");
+    address_space_init(&s->blocked_io_as, &s->blocked_io,
+                       "iopmp-blocked-io-as");
+
+    memory_region_init_io(&s->stall_io, obj, &iopmp_stall_io_ops,
+                          s, "iopmp-stall-io", size);
+    address_space_init(&s->stall_io_as, &s->stall_io,
+                       "iopmp-stall-io-as");
+}
+
+static void iopmp_reset(DeviceState *dev)
+{
+    IopmpState *s = IOPMP(dev);
+    qemu_set_irq(s->irq, 0);
+    memset(&s->regs, 0, sizeof(iopmp_regs));
+    memset(&s->entry_addr, 0, IOPMP_MAX_ENTRY_NUM * sizeof(iopmp_addr_t));
+    if (s->model == IOPMP_MODEL_RAPIDK) {
+        iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_F, s->md_num);
+        iopmp_set_field32(&s->regs.mdcfglck, MDCFGLCK_L, 1);
+        s->regs.mdcfg[0] = s->k;
+    }
+    s->regs.errreact = 0;
+
+    s->prient_prog = 1;
+    if (s->sid_transl_en) {
+        s->sid_transl_prog = 1;
+    }
+}
+
+static int iopmp_attrs_to_index(IOMMUMemoryRegion *iommu, MemTxAttrs attrs)
+{
+    /* Get source id(SID) */
+    return attrs.iopmp_sid;
+}
+
+
+static void iopmp_iommu_memory_region_class_init(ObjectClass *klass, void *data)
+{
+    IOMMUMemoryRegionClass *imrc = IOMMU_MEMORY_REGION_CLASS(klass);
+
+    imrc->translate_size = iopmp_translate_size;
+    imrc->attrs_to_index = iopmp_attrs_to_index;
+}
+
+static Property iopmp_property[] = {
+    DEFINE_PROP_STRING("model", IopmpState, model_str),
+    DEFINE_PROP_BOOL("sps_en", IopmpState, sps_en, false),
+    DEFINE_PROP_BOOL("sid_transl_en", IopmpState, sid_transl_en, false),
+    DEFINE_PROP_UINT32("k", IopmpState, k, IOPMP_MODEL_K),
+    DEFINE_PROP_UINT32("prio_entry", IopmpState, prio_entry, PRIO_ENTRY),
+    DEFINE_PROP_UINT32("sid_num", IopmpState, sid_num, IOPMP_MAX_SID_NUM),
+    DEFINE_PROP_UINT32("md_num", IopmpState, md_num, IOPMP_MAX_MD_NUM),
+    DEFINE_PROP_UINT32("entry_num", IopmpState, entry_num, IOPMP_MAX_ENTRY_NUM),
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void iopmp_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    device_class_set_props(dc, iopmp_property);
+    dc->realize = iopmp_realize;
+    dc->reset = iopmp_reset;
+}
+
+static void iopmp_init(Object *obj)
+{
+    IopmpState *s = IOPMP(obj);
+    SysBusDevice *sbd = SYS_BUS_DEVICE(obj);
+    sysbus_init_irq(sbd, &s->irq);
+}
+
+static const TypeInfo iopmp_info = {
+    .name = TYPE_IOPMP,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(IopmpState),
+    .instance_init = iopmp_init,
+    .class_init = iopmp_class_init,
+};
+
+static const TypeInfo
+iopmp_iommu_memory_region_info = {
+    .name = TYPE_IOPMP_IOMMU_MEMORY_REGION,
+    .parent = TYPE_IOMMU_MEMORY_REGION,
+    .class_init = iopmp_iommu_memory_region_class_init,
+};
+
+DeviceState *
+iopmp_create(hwaddr addr, qemu_irq irq)
+{
+    LOG("%s:\n", __func__);
+    DeviceState *iopmp_device = sysbus_create_varargs(TYPE_IOPMP, addr, irq,
+                                                      NULL);
+    return iopmp_device;
+}
+
+void cascade_iopmp(DeviceState *cur_dev, DeviceState *next_dev)
+{
+    IopmpState *s = IOPMP(cur_dev);
+    s->sid_transl_en = true;
+    IopmpState *next_s = IOPMP(next_dev);
+    s->next_iommu = &next_s->iommu;
+}
+
+static void
+iopmp_register_types(void)
+{
+    type_register_static(&iopmp_info);
+    type_register_static(&iopmp_iommu_memory_region_info);
+}
+
+type_init(iopmp_register_types);
diff --git a/include/hw/misc/riscv_iopmp.h b/include/hw/misc/riscv_iopmp.h
new file mode 100644
index 0000000000..b4e79ef0da
--- /dev/null
+++ b/include/hw/misc/riscv_iopmp.h
@@ -0,0 +1,322 @@
+/*
+ * QEMU RISC-V IOPMP (Input Output Physical Memory Protection)
+ *
+ * Copyright (c) 2023 Andes Tech. Corp.
+ *
+ * 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_H
+#define RISCV_IOPMP_H
+
+#include "hw/sysbus.h"
+#include "qemu/typedefs.h"
+#include "memory.h"
+
+#define TYPE_IOPMP "iopmp"
+#define IOPMP(obj) OBJECT_CHECK(IopmpState, (obj), TYPE_IOPMP)
+
+#define iopmp_get_field(reg, name) (((reg) & (name ## _FIELD)) >> (name))
+#define iopmp_set_field32(reg, name, newval) do { \
+    uint32_t val = *reg; \
+    val &= ~name##_FIELD; \
+    val |= ((newval) << name) & name##_FIELD; \
+    *reg = val; \
+    } while (0)
+#define iopmp_set_field64(reg, name, newval) do { \
+    uint64_t val = *reg; \
+    val &= ~name##_FIELD; \
+    val |= ((newval) << name) & name##_FIELD; \
+    *reg = val; \
+    } while (0)
+
+
+#define IOPMP_MAX_MD_NUM    63
+#define IOPMP_MAX_SID_NUM   256
+#define IOPMP_MAX_ENTRY_NUM 512
+
+#define IOPMP_VERSION      0x0
+#define IOPMP_IMP          0x4
+#define IOPMP_HWCFG0       0x8
+#define IOPMP_HWCFG1       0xC
+#define IOPMP_HWCFG2       0x10
+#define IOPMP_ENTRYOFFSET  0x20
+#define IOPMP_ERRREACT     0x28
+#define IOPMP_MDSTALL      0x30
+#define IOPMP_MDSTALLH     0x34
+#define IOPMP_SIDSCP       0x38
+#define IOPMP_MDLCK        0x40
+#define IOPMP_MDLCKH       0x44
+#define IOPMP_MDCFGLCK     0x48
+#define IOPMP_ENTRYLCK     0x4C
+
+#define IOPMP_ERR_REQADDR  0x60
+#define IOPMP_ERR_REQADDRH 0x64
+#define IOPMP_ERR_REQSID   0x68
+#define IOPMP_ERR_REQINFO  0x6C
+
+#define IOPMP_MDCFG0       0x800
+#define IOPMP_SRCMD_EN0    0x1000
+#define IOPMP_SRCMD_ENH0   0x1004
+#define IOPMP_SRCMD_R0     0x1008
+#define IOPMP_SRCMD_RH0    0x100C
+#define IOPMP_SRCMD_W0     0x1010
+#define IOPMP_SRCMD_WH0    0x1014
+
+#define IOPMP_ENTRY_ADDR0  0x4000
+#define IOPMP_ENTRY_ADDRH0 0x4004
+#define IOPMP_ENTRY_CFG0   0x4008
+#define IOPMP_USER_CFG0    0x400C
+
+#define VERSION_VENDOR       0
+#define VERSION_SPECVER      24
+#define VENDER_ANDES         6533
+#define SPECVER_1_0_0_DRAFT4 4
+
+#define IMPID_1_0_0_DRAFT4_0 10040
+
+#define HWCFG0_SID_NUM     0
+#define HWCFG0_ENTRY_NUM   16
+
+#define HWCFG1_MODEL            0
+#define HWCFG1_TOR_EN           4
+#define HWCFG1_SPS_EN           5
+#define HWCFG1_USER_CFG_EN      6
+#define HWCFG1_PRIENT_PROG      7
+#define HWCFG1_SID_TRANSL_EN    8
+#define HWCFG1_SID_TRANSL_PROG  9
+#define HWCFG1_MD_NUM           24
+#define HWCFG1_ENABLE           31
+
+#define HWCFG1_SPS_EN_FIELD          (1 << HWCFG1_SPS_EN)
+#define HWCFG1_PRIENT_PROG_FIELD     (1 << HWCFG1_PRIENT_PROG)
+#define HWCFG1_SID_TRANSL_PROG_FIELD (1 << HWCFG1_SID_TRANSL_PROG)
+#define HWCFG1_ENABLE_FIELD          (1 << HWCFG1_ENABLE)
+
+#define HWCFG2_PRIO_ENTRY       0
+#define HWCFG2_SID_TRANSL      16
+
+#define HWCFG2_PRIO_ENTRY_FIELD (0xFFFF << HWCFG2_PRIO_ENTRY)
+#define HWCFG2_SID_TRANSL_FIELD (0xFFFF << HWCFG2_SID_TRANSL)
+
+#define ERRREACT_L          0
+#define ERRREACT_IE         1
+#define ERRREACT_IP         2
+#define ERRREACT_IRE        4
+#define ERRREACT_RRE        5
+#define ERRREACT_IWE        8
+#define ERRREACT_RWE        9
+#define ERRREACT_PEE        28
+#define ERRREACT_RPE        29
+
+#define ERRREACT_L_FIELD    (0x1 << ERRREACT_L)
+#define ERRREACT_IE_FIELD   (0x1 << ERRREACT_IE)
+#define ERRREACT_IP_FIELD   (0x1 << ERRREACT_IP)
+#define ERRREACT_IRE_FIELD  (0x1 << ERRREACT_IRE)
+#define ERRREACT_RRE_FIELD  (0x7 << ERRREACT_RRE)
+#define ERRREACT_IWE_FIELD  (0x1 << ERRREACT_IWE)
+#define ERRREACT_RWE_FIELD  (0x7 << ERRREACT_RWE)
+#define ERRREACT_PEE_FIELD  (0x1 << ERRREACT_PEE)
+#define ERRREACT_RPE_FIELD  (0x7 << ERRREACT_RPE)
+
+#define RRE_BUS_ERROR       0
+#define RRE_DECODE_ERROR    1
+#define RRE_SUCCESS_ZEROS   2
+#define RRE_SUCCESS_ONES    3
+
+#define RWE_BUS_ERROR       0
+#define RWE_DECODE_ERROR    1
+#define RWE_SUCCESS         2
+
+#define MDSTALL               0
+#define MDSTALLH              32
+#define MDSTALL_FIELD         UINT32_MAX
+#define MDSTALLH_FIELD        (UINT64_MAX << MDSTALLH)
+#define MDSTALL_EXEMPT        0
+#define MDSTALL_EXEMPT_FIELD  (1 << MDSTALL_EXEMPT)
+#define MDSTALL_ISSTALLED     0
+#define MDSTALL_MD            1
+#define MDSTALL_MD_FIELD      (0x7FFFFFFFFFFFFFFF << MDSTALL_MD)
+
+#define SIDSCP_SID         0
+#define SIDSCP_STAT        30
+#define SIDSCP_OP          30
+#define SIDSCP_SID_FIELD   (0xFFFF << SIDSCP_SID)
+#define SIDSCP_STAT_FIELD  (0x3 << SIDSCP_STAT)
+#define SIDSCP_OP_FIELD    (0x3 << SIDSCP_OP)
+#define SIDSCP_OP_QUERY    0
+#define SIDSCP_OP_STALL    1
+#define SIDSCP_OP_NOTSTALL 2
+
+#define MDLCK_L            0
+#define MDLCK_MD           1
+
+#define MDCFGLCK_L         0
+#define MDCFGLCK_L_FIELD   (0x1 << MDCFGLCK_L)
+#define MDCFGLCK_F         1
+#define MDCFGLCK_F_FIELD   (0x7F << MDCFGLCK_F)
+
+#define ENTRYLCK_L         0
+#define ENTRYLCK_L_FIELD   (0x1 << MDCFGLCK_L)
+#define ENTRYLCK_F         1
+#define ENTRYLCK_F_FIELD   (0xFFFF << ENTRYLCK_F)
+
+#define ERR_REQINFO_NO_HIT  0
+#define ERR_REQINFO_PAR_HIT 1
+#define ERR_REQINFO_TYPE    8
+#define ERR_REQINFO_EID     16
+
+#define ERR_REQINFO_NO_HIT_FIELD  (0x1 << ERR_REQINFO_NO_HIT)
+#define ERR_REQINFO_PAR_HIT_FIELD (0x1 << ERR_REQINFO_PAR_HIT)
+#define ERR_REQINFO_TYPE_FIELD    (0x3 << ERR_REQINFO_TYPE)
+#define ERR_REQINFO_EID_FIELD     (0xFFFF << ERR_REQINFO_EID)
+
+#define ERR_REQINFO_TYPE_READ  0
+#define ERR_REQINFO_TYPE_WRITE 1
+#define ERR_REQINFO_TYPE_USER  3
+
+#define SRCMD_EN_L         0
+#define SRCMD_EN_MD        1
+#define SRCMD_EN_L_FIELD   (0x1 << SRCMD_EN_L)
+#define SRCMD_EN_MD_FIELD  (0x7FFFFFFF << SRCMD_EN_MD)
+#define SRCMD_ENH_MDH        32
+#define SRCMD_ENH_MDH_FIELD (0xFFFFFFFFUL << SRCMD_ENH_MDH)
+
+#define SRCMD_R_MD        1
+#define SRCMD_R_MD_FIELD  (0x7FFFFFFF << SRCMD_R_MD)
+#define SRCMD_RH_MDH        32
+#define SRCMD_RH_MDH_FIELD (0xFFFFFFFFUL << SRCMD_RH_MDH)
+#define SRCMD_W_MD        1
+#define SRCMD_W_MD_FIELD  (0x7FFFFFFF << SRCMD_W_MD)
+#define SRCMD_WH_MDH        32
+#define SRCMD_WH_MDH_FIELD (0xFFFFFFFFUL << SRCMD_WH_MDH)
+
+#define ENTRY_ADDR_ADDR         0
+#define ENTRY_ADDR_ADDR_FIELD   0xFFFFFFFF
+#define ENTRY_ADDRH_ADDRH       32
+#define ENTRY_ADDRH_ADDRH_FIELD (0xFFFFFFFFUL << ENTRY_ADDRH_ADDRH)
+
+#define ENTRY_CFG_R            0
+#define ENTRY_CFG_W            1
+#define ENTRY_CFG_X            2
+#define ENTRY_CFG_A            3
+#define ENTRY_CFG_A_FIELD      (0x3 << ENTRY_CFG_A)
+
+#define IOPMP_MODEL_FULL       0
+#define IOPMP_MODEL_RAPIDK     0x1
+#define IOPMP_MODEL_DYNAMICK   0x2
+#define IOPMP_MODEL_ISOLATION  0x3
+#define IOPMP_MODEL_COMPACTK   0x4
+#define IOPMP_MODEL_K          8
+
+#define TOR_EN 1
+#define SPS_EN 0
+#define USER_CFG_EN   0
+#define PROG_PRIENT   1
+#define PRIO_ENTRY    IOPMP_MAX_ENTRY_NUM
+#define SID_TRANSL_EN 0
+#define SID_TRANSL    0
+
+#define ENTRY_NO_HIT      0
+#define ENTRY_PAR_HIT     1
+#define ENTRY_HIT         2
+
+typedef enum {
+    IOPMP_READ      = 1 << 0,
+    IOPMP_WRITE     = 1 << 1,
+    IOPMP_EXEC      = 1 << 2,
+    IOPMP_ADDRMODE  = 1 << 3,
+} iopmp_priv_t;
+
+typedef enum {
+    IOPMP_AMATCH_OFF,  /* Null (off)                            */
+    IOPMP_AMATCH_TOR,  /* Top of Range                          */
+    IOPMP_AMATCH_NA4,  /* Naturally aligned four-byte region    */
+    IOPMP_AMATCH_NAPOT /* Naturally aligned power-of-two region */
+} iopmp_am_t;
+
+typedef struct {
+    uint64_t addr_reg;
+    uint32_t  cfg_reg;
+} iopmp_entry_t;
+
+typedef struct {
+    target_ulong sa;
+    target_ulong ea;
+} iopmp_addr_t;
+
+typedef struct {
+    uint64_t srcmd_en[IOPMP_MAX_SID_NUM];
+    uint64_t srcmd_r[IOPMP_MAX_SID_NUM];
+    uint64_t srcmd_w[IOPMP_MAX_SID_NUM];
+    uint32_t mdcfg[IOPMP_MAX_MD_NUM];
+    iopmp_entry_t entry[IOPMP_MAX_ENTRY_NUM];
+    uint64_t mdmsk;
+    uint64_t mdlck;
+    uint32_t entrylck;
+    uint32_t mdcfglck;
+    uint32_t arrlck;
+    uint64_t mdstall;
+    uint32_t sidscp;
+    uint32_t errreact;
+    uint64_t err_reqaddr;
+    uint32_t err_reqsid;
+    uint32_t err_reqinfo;
+} iopmp_regs;
+
+/* To verfiy the same transcation */
+typedef struct iopmp_error_detail {
+    uint32_t reqinfo;
+    target_ulong addr_start;
+    target_ulong addr_end;
+} iopmp_error_detail;
+
+typedef struct IopmpState {
+    SysBusDevice parent_obj;
+    iopmp_addr_t entry_addr[IOPMP_MAX_ENTRY_NUM];
+    iopmp_error_detail prev_error[IOPMP_MAX_SID_NUM];
+    MemoryRegion mmio;
+    IOMMUMemoryRegion iommu;
+    IOMMUMemoryRegion *next_iommu;
+    iopmp_regs regs;
+    MemoryRegion *downstream;
+    MemoryRegion blocked_io;
+    MemoryRegion stall_io;
+    char *model_str;
+    uint32_t model;
+    uint32_t k;
+    bool sps_en;
+    bool sid_transl_prog;
+    bool prient_prog;
+    bool sid_transl_en;
+    uint32_t sid_transl;
+
+    AddressSpace iopmp_as;
+    AddressSpace downstream_as;
+    AddressSpace blocked_io_as;
+    AddressSpace stall_io_as;
+    qemu_irq irq;
+    bool enable;
+    uint32_t sidscp_op[IOPMP_MAX_SID_NUM];
+    uint64_t md_stall_stat;
+    uint32_t prio_entry;
+
+    uint32_t sid_num;
+    uint32_t md_num;
+    uint32_t entry_num;
+} IopmpState;
+
+DeviceState *iopmp_create(hwaddr addr, qemu_irq irq);
+void cascade_iopmp(DeviceState *cur_dev, DeviceState *next_dev);
+
+#endif
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 5/6] hw/dma: Add Andes ATCDMAC300 support
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Ethan Chen, Paolo Bonzini

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 hw/dma/Kconfig              |   3 +
 hw/dma/atcdmac300.c         | 435 ++++++++++++++++++++++++++++++++++++
 hw/dma/meson.build          |   1 +
 include/hw/dma/atcdmac300.h | 171 ++++++++++++++
 4 files changed, 610 insertions(+)
 create mode 100644 hw/dma/atcdmac300.c
 create mode 100644 include/hw/dma/atcdmac300.h

diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig
index 98fbb1bb04..b3f4edc873 100644
--- a/hw/dma/Kconfig
+++ b/hw/dma/Kconfig
@@ -30,3 +30,6 @@ config SIFIVE_PDMA
 config XLNX_CSU_DMA
     bool
     select REGISTER
+
+config ATCDMAC300
+    bool
diff --git a/hw/dma/atcdmac300.c b/hw/dma/atcdmac300.c
new file mode 100644
index 0000000000..4cc37060d6
--- /dev/null
+++ b/hw/dma/atcdmac300.c
@@ -0,0 +1,435 @@
+/*
+ * Andes ATCDMAC300 (Andes Technology DMA Controller)
+ *
+ * Copyright (c) 2022 Andes Tech. Corp.
+ *
+ * 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 "qapi/error.h"
+#include "hw/dma/atcdmac300.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/memattrs.h"
+#include "exec/address-spaces.h"
+
+/* #define DEBUG_ANDES_ATCDMAC300 */
+#define LOGGE(x...) qemu_log_mask(LOG_GUEST_ERROR, x)
+#define xLOG(x...)
+#define yLOG(x...) qemu_log(x)
+#ifdef DEBUG_ANDES_ATCDMAC300
+  #define LOG(x...) yLOG(x)
+#else
+  #define LOG(x...) xLOG(x)
+#endif
+
+#define MEMTX_IOPMP_STALL (1 << 3)
+
+static void atcdmac300_dma_int_stat_update(ATCDMAC300State *s, int status,
+                                           int ch)
+{
+    s->IntStatus |= (1 << (status + ch));
+}
+
+static void atcdmac300_dma_reset_chan(ATCDMAC300State *s, int ch)
+{
+    if (s) {
+        s->chan[ch].ChnCtrl &= ~(1 << CHAN_CTL_ENABLE);
+        s->ChEN &= ~(1 << ch);
+    }
+}
+
+static void atcdmac300_dma_reset(ATCDMAC300State *s)
+{
+    int ch;
+    for (ch = 0; ch < ATCDMAC300_MAX_CHAN; ch++) {
+        atcdmac300_dma_reset_chan(s, ch);
+    }
+}
+
+static uint64_t atcdmac300_read(void *opaque, hwaddr offset, unsigned size)
+{
+    ATCDMAC300State *s = opaque;
+    int ch = 0;
+    uint64_t result = 0;
+
+    if (offset >= 0x40) {
+        ch = ATCDMAC300_GET_CHAN(offset);
+        offset = ATCDMAC300_GET_OFF(offset, ch);
+    }
+
+    switch (offset) {
+    case ATCDMAC300_DMA_CFG:
+        result = s->DMACfg;
+        break;
+    case ATCDMAC300_DMAC_CTRL:
+        break;
+    case ATCDMAC300_CHN_ABT:
+        break;
+    case ATCDMAC300_INT_STATUS:
+        result = s->IntStatus;
+        break;
+    case ATCDMAC300_CHAN_ENABLE:
+        result = s->ChEN;
+        break;
+    case ATCDMAC300_CHAN_CTL:
+        result = s->chan[ch].ChnCtrl;
+        break;
+    default:
+        LOGGE("%s: Bad offset 0x%" HWADDR_PRIX "\n",
+              __func__, offset);
+        break;
+    }
+
+    LOG("### atcdmac300_read()=0x%lx, val=0x%lx\n", offset, result);
+    return result;
+}
+
+static MemTxResult dma_iopmp_read(ATCDMAC300State *s, hwaddr addr, void *buf,
+                                  hwaddr len)
+{
+    if (s->iopmp_as) {
+        MemTxAttrs dma_attrs = {.iopmp_sid = s->sid};
+        return address_space_rw(s->iopmp_as, addr, dma_attrs,
+                                buf, len, false);
+    }
+    cpu_physical_memory_read(addr, buf, len);
+    return MEMTX_OK;
+}
+
+static MemTxResult dma_iopmp_write(ATCDMAC300State *s, hwaddr addr, void *buf,
+                                   hwaddr len)
+{
+    if (s->iopmp_as) {
+        MemTxAttrs dma_attrs = {.iopmp_sid = s->sid};
+        return address_space_rw(s->iopmp_as, addr, dma_attrs,
+                                buf, len, true);
+    }
+    cpu_physical_memory_write(addr, buf, len);
+    return MEMTX_OK;
+}
+
+static void atcdmac300_co_run(void *opaque)
+{
+    ATCDMAC300State *s = opaque;
+    int64_t remain_size, burst_remain_size, copy_size, copy_size_dst;
+    uint64_t src_addr, dst_addr, src_width, dst_width, burst_size,
+             src_addr_ctl, dst_addr_ctl, int_tc_mask, int_err_mask,
+             int_abort_mask;
+    uint64_t buf[ATCDMAC300_MAX_BURST_SIZE];
+    int result = 0;
+
+    while (1) {
+        for (int ch = 0; ch < ATCDMAC300_MAX_CHAN; ch++) {
+            if (((s->chan[ch].ChnCtrl >> CHAN_CTL_ENABLE) & 0x1) == 0x1) {
+                src_width = (s->chan[ch].ChnCtrl >> CHAN_CTL_SRC_WIDTH) &
+                            CHAN_CTL_SRC_WIDTH_MASK;
+                dst_width = (s->chan[ch].ChnCtrl >> CHAN_CTL_DST_WIDTH) &
+                            CHAN_CTL_DST_WIDTH_MASK;
+                burst_size = (s->chan[ch].ChnCtrl >> CHAN_CTL_SRC_BURST_SZ) &
+                             CHAN_CTL_SRC_BURST_SZ_MASK;
+                src_addr = (s->chan[ch].ChnSrcAddrH << 32) |
+                           s->chan[ch].ChnSrcAddr;
+                dst_addr = (s->chan[ch].ChnDstAddrH << 32) |
+                           s->chan[ch].ChnDstAddr;
+                src_addr_ctl = (s->chan[ch].ChnCtrl >> CHAN_CTL_SRC_ADDR_CTL) &
+                               CHAN_CTL_SRC_ADDR_CTL_MASK;
+                dst_addr_ctl = (s->chan[ch].ChnCtrl >> CHAN_CTL_DST_ADDR_CTL) &
+                               CHAN_CTL_DST_ADDR_CTL_MASK;
+
+                src_width = 1 << src_width;
+                dst_width = 1 << dst_width;
+                burst_size = 1 << burst_size;
+                remain_size = s->chan[ch].ChnTranSize * src_width;
+                int_tc_mask = (s->chan[ch].ChnCtrl >> CHAN_CTL_INT_TC_MASK_POS)
+                              & 0x1;
+                int_err_mask = (s->chan[ch].ChnCtrl >>
+                                CHAN_CTL_INT_ERR_MASK_POS) & 0x1;
+                int_abort_mask = (s->chan[ch].ChnCtrl >>
+                                  CHAN_CTL_INT_ABT_MASK_POS) & 0x1;
+                if (remain_size && burst_size < (1 << 11) &&
+                    src_width < (1 << 6) && dst_width < (1 << 6) &&
+                    (src_addr & (src_width - 1)) == 0 &&
+                    (dst_addr & (dst_width - 1)) == 0 &&
+                    (remain_size & (dst_width - 1)) == 0 &&
+                    (burst_size * src_width & (dst_width - 1)) == 0) {
+
+                    while (remain_size > 0) {
+                        /* check current abort status before a brust start*/
+                        if (s->ChAbort & (1 << ch)) {
+                            result = DMA_ABT_RESULT;
+                            s->ChAbort &= ~(1 << ch);
+                            atcdmac300_dma_reset_chan(s, ch);
+                            atcdmac300_dma_int_stat_update(s, INT_STATUS_ABT,
+                                                           ch);
+                            if (!int_abort_mask) {
+                                qemu_irq_raise(s->irq);
+                            }
+                            break;
+                        }
+
+                        int i;
+                        for (i = 0; i < burst_size; i++) {
+                            if (remain_size <= 0) {
+                                break;
+                            }
+
+                            memset(buf, 0, sizeof(buf));
+
+                            copy_size = MIN(remain_size, src_width);
+                            result = dma_iopmp_read(s, src_addr, buf,
+                                                    copy_size);
+                            while (result == MEMTX_IOPMP_STALL) {
+                                qemu_coroutine_yield();
+                                result = dma_iopmp_read(s, src_addr, buf,
+                                                        copy_size);
+                            }
+                            burst_remain_size = copy_size;
+                            uint64_t *curr_ptr = buf;
+                            while (burst_remain_size > 0) {
+                                copy_size_dst = MIN(burst_remain_size,
+                                                    dst_width);
+                                result = dma_iopmp_write(s, dst_addr, curr_ptr,
+                                                         copy_size_dst);
+                                while (result == MEMTX_IOPMP_STALL) {
+                                    qemu_coroutine_yield();
+                                    result = dma_iopmp_read(s, src_addr, buf,
+                                                            copy_size);
+                                }
+                                curr_ptr += copy_size_dst;
+                                burst_remain_size -= copy_size_dst;
+                            }
+
+                            if (result != MEMTX_OK) {
+                                break;
+                            }
+
+                            remain_size -= copy_size;
+
+                            if (src_addr_ctl == 0) {
+                                src_addr += copy_size;
+                            }
+                            if (src_addr_ctl == 1) {
+                                src_addr -= copy_size;
+                            }
+                            if (dst_addr_ctl == 0) {
+                                dst_addr += copy_size;
+                            }
+                            if (dst_addr_ctl == 1) {
+                                dst_addr -= copy_size;
+                            }
+                        }
+                        if (result != MEMTX_OK) {
+                                break;
+                        }
+                        qemu_coroutine_yield();
+                    }
+                    if (result == MEMTX_OK) {
+                        s->ChAbort &= ~(1 << ch);
+                        atcdmac300_dma_reset_chan(s, ch);
+                        atcdmac300_dma_int_stat_update(s, INT_STATUS_TC, ch);
+                        if (!int_tc_mask) {
+                            qemu_irq_raise(s->irq);
+                        }
+                    } else if (result != DMA_ABT_RESULT) {
+                        s->ChAbort &= ~(1 << ch);
+                        atcdmac300_dma_int_stat_update(s, INT_STATUS_ERR, ch);
+                        atcdmac300_dma_reset_chan(s, ch);
+                        if (!int_err_mask) {
+                            qemu_irq_raise(s->irq);
+                        }
+                    }
+                } else {
+                    s->ChAbort &= ~(1 << ch);
+                    atcdmac300_dma_int_stat_update(s, INT_STATUS_ERR, ch);
+                    atcdmac300_dma_reset_chan(s, ch);
+                    if (!int_err_mask) {
+                        qemu_irq_raise(s->irq);
+                    }
+                }
+            }
+        }
+        qemu_coroutine_yield();
+    }
+}
+
+static void atcdmac300_bh_cb(void *opaque)
+{
+    ATCDMAC300State *s = opaque;
+
+    int rearm = 0;
+    if (s->running) {
+        rearm = 1;
+        goto out;
+    } else {
+        s->running = 1;
+    }
+
+    AioContext *ctx = qemu_get_current_aio_context();
+    aio_co_enter(ctx, s->co);
+
+    s->running = 0;
+out:
+    if (rearm) {
+        qemu_bh_schedule_idle(s->bh);
+        s->dma_bh_scheduled = true;
+    }
+    qemu_bh_schedule_idle(s->bh);
+    s->dma_bh_scheduled = true;
+    s->running = 0;
+}
+
+static void atcdmac300_write(void *opaque, hwaddr offset, uint64_t value,
+                             unsigned size)
+{
+    ATCDMAC300State *s = opaque;
+    int ch = 0;
+
+    LOG("@@@ atcdmac300_write()=0x%lx, value=0x%lx\n", offset, value);
+
+    if (offset >= 0x40) {
+        ch = ATCDMAC300_GET_CHAN(offset);
+        offset = ATCDMAC300_GET_OFF(offset, ch);
+    }
+
+    switch (offset) {
+    case ATCDMAC300_INT_STATUS:
+        /* Write 1 to clear */
+        s->IntStatus = 0;
+        break;
+    case ATCDMAC300_DMAC_CTRL:
+        atcdmac300_dma_reset(s);
+        break;
+    case ATCDMAC300_CHN_ABT:
+        for (int i = 0; i < ATCDMAC300_MAX_CHAN; i++) {
+            if (value & 0x1 && (s->chan[i].ChnCtrl & (1 << CHAN_CTL_ENABLE))) {
+                s->ChAbort |= (0x1 << i);
+            }
+            value >>= 1;
+        }
+        break;
+    case ATCDMAC300_CHAN_CTL:
+        s->chan[ch].ChnCtrl = value;
+        qemu_bh_schedule_idle(s->bh);
+        break;
+    case ATCDMAC300_CHAN_TRAN_SZ:
+        s->chan[ch].ChnTranSize = value;
+        break;
+    case ATCDMAC300_CHAN_SRC_ADDR:
+        s->chan[ch].ChnSrcAddr = value;
+        break;
+    case ATCDMAC300_CHAN_SRC_ADDR_H:
+        s->chan[ch].ChnSrcAddrH = value;
+        break;
+    case ATCDMAC300_CHAN_DST_ADDR:
+        s->chan[ch].ChnDstAddr = value;
+        break;
+    case ATCDMAC300_CHAN_DST_ADDR_H:
+        s->chan[ch].ChnDstAddrH = value;
+        break;
+    case ATCDMAC300_CHAN_LL_POINTER:
+        s->chan[ch].ChnLLPointer = value;
+        break;
+    case ATCDMAC300_CHAN_LL_POINTER_H:
+        s->chan[ch].ChnLLPointerH = value;
+        break;
+    default:
+        LOGGE("%s: Bad offset 0x%" HWADDR_PRIX "\n",
+              __func__, offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps atcdmac300_ops = {
+    .read = atcdmac300_read,
+    .write = atcdmac300_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8
+    }
+};
+
+static void atcdmac300_init(Object *obj)
+{
+    ATCDMAC300State *s = ATCDMAC300(obj);
+    SysBusDevice *sbus = SYS_BUS_DEVICE(obj);
+
+    sysbus_init_irq(sbus, &s->irq);
+    memory_region_init_io(&s->mmio, obj, &atcdmac300_ops, s, TYPE_ATCDMAC300,
+                          s->mmio_size);
+    sysbus_init_mmio(sbus, &s->mmio);
+    if (s->iothread) {
+        s->ctx = iothread_get_aio_context(s->iothread);
+    } else {
+        s->ctx = qemu_get_aio_context();
+    }
+    s->bh = aio_bh_new(s->ctx, atcdmac300_bh_cb, s);
+    s->co = qemu_coroutine_create(atcdmac300_co_run, s);
+}
+
+static Property atcdmac300_properties[] = {
+    DEFINE_PROP_UINT32("mmio-size", ATCDMAC300State, mmio_size, 0x100000),
+    DEFINE_PROP_UINT32("id-and-revision", ATCDMAC300State, IdRev,
+                       (ATCDMAC300_PRODUCT_ID  << 8) |
+                       ((ATCDMAC300_PRODUCT_ID & 0x7) << 4) |
+                       ((ATCDMAC300_PRODUCT_ID & 0x7))),
+    DEFINE_PROP_UINT32("inturrupt-status", ATCDMAC300State, IntStatus, 0),
+    DEFINE_PROP_UINT32("dmac-configuration", ATCDMAC300State,
+                       DMACfg, 0xc3404108),
+    DEFINE_PROP_LINK("iothread", ATCDMAC300State, iothread,
+                     TYPE_IOTHREAD, IOThread *),
+
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void atcdmac300_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *k = DEVICE_CLASS(klass);
+    device_class_set_props(k, atcdmac300_properties);
+}
+
+static const TypeInfo atcdmac300_info = {
+    .name          = TYPE_ATCDMAC300,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ATCDMAC300State),
+    .class_init    = atcdmac300_class_init,
+    .instance_init = atcdmac300_init,
+};
+
+DeviceState *
+atcdmac300_create(const char *name, hwaddr addr, hwaddr mmio_size, qemu_irq irq)
+{
+    DeviceState *dev;
+    dev = sysbus_create_varargs(TYPE_ATCDMAC300, addr, irq, NULL);
+    return dev;
+}
+
+static void atcdmac300_register_types(void)
+{
+    type_register_static(&atcdmac300_info);
+}
+
+void atcdmac300_connect_iopmp_as(DeviceState *dev, AddressSpace *iopmp_as,
+                                 uint32_t sid)
+{
+    ATCDMAC300State *s = ATCDMAC300(dev);
+    s->iopmp_as = iopmp_as;
+    s->sid = sid;
+}
+
+type_init(atcdmac300_register_types)
diff --git a/hw/dma/meson.build b/hw/dma/meson.build
index a96c1be2c8..dfe37de32d 100644
--- a/hw/dma/meson.build
+++ b/hw/dma/meson.build
@@ -14,3 +14,4 @@ system_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_dma.c'))
 system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c'))
 system_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c'))
+system_ss.add(when: 'CONFIG_ATCDMAC300', if_true: files('atcdmac300.c'))
\ No newline at end of file
diff --git a/include/hw/dma/atcdmac300.h b/include/hw/dma/atcdmac300.h
new file mode 100644
index 0000000000..c2688bc18f
--- /dev/null
+++ b/include/hw/dma/atcdmac300.h
@@ -0,0 +1,171 @@
+/*
+ * Andes ATCDMAC300 (Andes Technology DMA Controller)
+ *
+ * Copyright (c) 2022 Andes Tech. Corp.
+ *
+ * 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 ATCDMAC300_H
+#define ATCDMAC300_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+#include "qemu/coroutine.h"
+#include "block/aio.h"
+#include "sysemu/iothread.h"
+#include "sysemu/dma.h"
+
+#define TYPE_ATCDMAC300 "atcdmac300"
+OBJECT_DECLARE_SIMPLE_TYPE(ATCDMAC300State, ATCDMAC300)
+
+#define ATCDMAC300_IOPMP_SID            0
+
+#define ATCDMAC300_PRODUCT_ID           0x010230
+#define ATCDMAC300_REV_MAJOR            0x0
+#define ATCDMAC300_REV_MINOR            0x1
+
+/* DMAC Configuration Register (Offset 0x10)  */
+#define ATCDMAC300_DMA_CFG              0x10
+#define DMA_CFG_CHAIN_XFR               31
+#define DMA_CFG_REQ_SYNC                30
+#define DMA_CFG_DATA_WITDTH             24
+#define DMA_CFG_ADDR_WIDTH              17
+#define DMA_CFG_CORE_NUM                16
+#define DMA_CFG_BUS_NUM                 15
+#define DMA_CFG_REQ_NUM                 10
+#define DMA_CFG_FIFO_DEPTH              4
+#define DMA_CFG_CHAN_NUM                0
+
+/* Interrupt Status Register (Offset 0x20) */
+#define ATCDMAC300_DMAC_CTRL            0x20
+
+/* Channel Abort Register (Offset 0x24) */
+#define ATCDMAC300_CHN_ABT              0x24
+
+/* Interrupt Status Register (Offset 0x30) */
+#define ATCDMAC300_INT_STATUS           0x30
+#define INT_STATUS_TC                   16
+#define INT_STATUS_ABT                  8
+#define INT_STATUS_ERR                  0
+
+/* Interrupt Status Register (Offset 0x34) */
+#define ATCDMAC300_CHAN_ENABLE          0x34
+
+/* Channel n Control Register (Offset 0x40 + n*0x20) */
+#define CHAN_CTL_SRC_BUS_IDX            31
+#define CHAN_CTL_DST_BUS_IDX            30
+#define CHAN_CTL_PRIORITY               29
+#define CHAN_CTL_SRC_BURST_SZ           24
+#define CHAN_CTL_SRC_WIDTH              21
+#define CHAN_CTL_DST_WIDTH              18
+#define CHAN_CTL_SRC_MODE               17
+#define CHAN_CTL_DST_MODE               16
+#define CHAN_CTL_SRC_ADDR_CTL           14
+#define CHAN_CTL_DST_ADDR_CTL           12
+#define CHAN_CTL_SRC_REQ_SEL            8
+#define CHAN_CTL_DST_REQ_SEL            4
+#define CHAN_CTL_INT_ABT_MASK_POS       3
+#define CHAN_CTL_INT_ERR_MASK_POS       2
+#define CHAN_CTL_INT_TC_MASK_POS        1
+#define CHAN_CTL_ENABLE                 0
+
+#define CHAN_CTL_SRC_WIDTH_MASK         0x7
+#define CHAN_CTL_DST_WIDTH_MASK         0x7
+#define CHAN_CTL_SRC_BURST_SZ_MASK      0xf
+#define CHAN_CTL_SRC_ADDR_CTL_MASK      0x3
+#define CHAN_CTL_DST_ADDR_CTL_MASK      0x3
+
+#define ATCDMAC300_CHAN_CTL             0x40
+#define ATCDMAC300_CHAN_TRAN_SZ         0x44
+#define ATCDMAC300_CHAN_SRC_ADDR        0x48
+#define ATCDMAC300_CHAN_SRC_ADDR_H      0x4C
+#define ATCDMAC300_CHAN_DST_ADDR        0x50
+#define ATCDMAC300_CHAN_DST_ADDR_H      0x54
+#define ATCDMAC300_CHAN_LL_POINTER      0x58
+#define ATCDMAC300_CHAN_LL_POINTER_H    0x5C
+
+#define ATCDMAC300_IRQ_START            0x40
+#define ATCDMAC300_IRQ_END              (ATCDMAC300_IRQ_START + \
+                                         ATCDMAC300_MAX_CHAN)
+
+#define ATCDMAC300_MAX_BURST_SIZE       1024
+#define ATCDMAC300_MAX_CHAN             0x8
+
+#define PER_CHAN_OFFSET                 0x20
+#define ATCDMAC300_FIRST_CHAN_BASE      ATCDMAC300_CHAN_CTL
+#define ATCDMAC300_GET_CHAN(reg)        (((reg - ATCDMAC300_FIRST_CHAN_BASE) / \
+                                            PER_CHAN_OFFSET))
+#define ATCDMAC300_GET_OFF(reg, ch)     (reg - (ch * PER_CHAN_OFFSET))
+
+#define DMA_ABT_RESULT (1 << 3)
+
+typedef struct {
+    qemu_irq irq;
+
+    /* Channel control registers (n=0~7) */
+    uint32_t ChnCtrl;
+    uint32_t ChnTranSize;
+    uint32_t ChnSrcAddr;
+    uint64_t ChnSrcAddrH;
+    uint32_t ChnDstAddr;
+    uint64_t ChnDstAddrH;
+    uint32_t ChnLLPointer;
+    uint32_t ChnLLPointerH;
+} ATCDMAC300Chan;
+
+struct ATCDMAC300State {
+    /*< private >*/
+    SysBusDevice busdev;
+    /*< public >*/
+
+    qemu_irq irq;
+    MemoryRegion mmio;
+    uint32_t mmio_size;
+
+    /* ID and revision register */
+    uint32_t IdRev;
+
+    /* Configuration register */
+    uint32_t DMACfg;
+
+    /* Global control registers */
+    uint32_t DMACtrl;
+    uint32_t ChAbort;
+
+    /* Channel status registers */
+    uint32_t IntStatus;
+    uint32_t ChEN;
+
+    ATCDMAC300Chan chan[ATCDMAC300_MAX_CHAN];
+
+    /* To support iopmp */
+    AddressSpace *iopmp_as;
+    uint32_t sid;
+
+    Coroutine *co;
+    QEMUBH *bh;
+    bool running;
+    bool dma_bh_scheduled;
+    AioContext *ctx;
+    IOThread *iothread;
+};
+
+DeviceState *atcdmac300_create(const char *name, hwaddr addr, hwaddr mmio_size,
+                               qemu_irq irq);
+
+void atcdmac300_connect_iopmp_as(DeviceState *dev, AddressSpace *iopmp_as,
+                                 uint32_t sid);
+
+#endif /* ATCDMAC300_H */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 5/6] hw/dma: Add Andes ATCDMAC300 support
@ 2023-10-25  5:14   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel; +Cc: Ethan Chen, Paolo Bonzini

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 hw/dma/Kconfig              |   3 +
 hw/dma/atcdmac300.c         | 435 ++++++++++++++++++++++++++++++++++++
 hw/dma/meson.build          |   1 +
 include/hw/dma/atcdmac300.h | 171 ++++++++++++++
 4 files changed, 610 insertions(+)
 create mode 100644 hw/dma/atcdmac300.c
 create mode 100644 include/hw/dma/atcdmac300.h

diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig
index 98fbb1bb04..b3f4edc873 100644
--- a/hw/dma/Kconfig
+++ b/hw/dma/Kconfig
@@ -30,3 +30,6 @@ config SIFIVE_PDMA
 config XLNX_CSU_DMA
     bool
     select REGISTER
+
+config ATCDMAC300
+    bool
diff --git a/hw/dma/atcdmac300.c b/hw/dma/atcdmac300.c
new file mode 100644
index 0000000000..4cc37060d6
--- /dev/null
+++ b/hw/dma/atcdmac300.c
@@ -0,0 +1,435 @@
+/*
+ * Andes ATCDMAC300 (Andes Technology DMA Controller)
+ *
+ * Copyright (c) 2022 Andes Tech. Corp.
+ *
+ * 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 "qapi/error.h"
+#include "hw/dma/atcdmac300.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "exec/memattrs.h"
+#include "exec/address-spaces.h"
+
+/* #define DEBUG_ANDES_ATCDMAC300 */
+#define LOGGE(x...) qemu_log_mask(LOG_GUEST_ERROR, x)
+#define xLOG(x...)
+#define yLOG(x...) qemu_log(x)
+#ifdef DEBUG_ANDES_ATCDMAC300
+  #define LOG(x...) yLOG(x)
+#else
+  #define LOG(x...) xLOG(x)
+#endif
+
+#define MEMTX_IOPMP_STALL (1 << 3)
+
+static void atcdmac300_dma_int_stat_update(ATCDMAC300State *s, int status,
+                                           int ch)
+{
+    s->IntStatus |= (1 << (status + ch));
+}
+
+static void atcdmac300_dma_reset_chan(ATCDMAC300State *s, int ch)
+{
+    if (s) {
+        s->chan[ch].ChnCtrl &= ~(1 << CHAN_CTL_ENABLE);
+        s->ChEN &= ~(1 << ch);
+    }
+}
+
+static void atcdmac300_dma_reset(ATCDMAC300State *s)
+{
+    int ch;
+    for (ch = 0; ch < ATCDMAC300_MAX_CHAN; ch++) {
+        atcdmac300_dma_reset_chan(s, ch);
+    }
+}
+
+static uint64_t atcdmac300_read(void *opaque, hwaddr offset, unsigned size)
+{
+    ATCDMAC300State *s = opaque;
+    int ch = 0;
+    uint64_t result = 0;
+
+    if (offset >= 0x40) {
+        ch = ATCDMAC300_GET_CHAN(offset);
+        offset = ATCDMAC300_GET_OFF(offset, ch);
+    }
+
+    switch (offset) {
+    case ATCDMAC300_DMA_CFG:
+        result = s->DMACfg;
+        break;
+    case ATCDMAC300_DMAC_CTRL:
+        break;
+    case ATCDMAC300_CHN_ABT:
+        break;
+    case ATCDMAC300_INT_STATUS:
+        result = s->IntStatus;
+        break;
+    case ATCDMAC300_CHAN_ENABLE:
+        result = s->ChEN;
+        break;
+    case ATCDMAC300_CHAN_CTL:
+        result = s->chan[ch].ChnCtrl;
+        break;
+    default:
+        LOGGE("%s: Bad offset 0x%" HWADDR_PRIX "\n",
+              __func__, offset);
+        break;
+    }
+
+    LOG("### atcdmac300_read()=0x%lx, val=0x%lx\n", offset, result);
+    return result;
+}
+
+static MemTxResult dma_iopmp_read(ATCDMAC300State *s, hwaddr addr, void *buf,
+                                  hwaddr len)
+{
+    if (s->iopmp_as) {
+        MemTxAttrs dma_attrs = {.iopmp_sid = s->sid};
+        return address_space_rw(s->iopmp_as, addr, dma_attrs,
+                                buf, len, false);
+    }
+    cpu_physical_memory_read(addr, buf, len);
+    return MEMTX_OK;
+}
+
+static MemTxResult dma_iopmp_write(ATCDMAC300State *s, hwaddr addr, void *buf,
+                                   hwaddr len)
+{
+    if (s->iopmp_as) {
+        MemTxAttrs dma_attrs = {.iopmp_sid = s->sid};
+        return address_space_rw(s->iopmp_as, addr, dma_attrs,
+                                buf, len, true);
+    }
+    cpu_physical_memory_write(addr, buf, len);
+    return MEMTX_OK;
+}
+
+static void atcdmac300_co_run(void *opaque)
+{
+    ATCDMAC300State *s = opaque;
+    int64_t remain_size, burst_remain_size, copy_size, copy_size_dst;
+    uint64_t src_addr, dst_addr, src_width, dst_width, burst_size,
+             src_addr_ctl, dst_addr_ctl, int_tc_mask, int_err_mask,
+             int_abort_mask;
+    uint64_t buf[ATCDMAC300_MAX_BURST_SIZE];
+    int result = 0;
+
+    while (1) {
+        for (int ch = 0; ch < ATCDMAC300_MAX_CHAN; ch++) {
+            if (((s->chan[ch].ChnCtrl >> CHAN_CTL_ENABLE) & 0x1) == 0x1) {
+                src_width = (s->chan[ch].ChnCtrl >> CHAN_CTL_SRC_WIDTH) &
+                            CHAN_CTL_SRC_WIDTH_MASK;
+                dst_width = (s->chan[ch].ChnCtrl >> CHAN_CTL_DST_WIDTH) &
+                            CHAN_CTL_DST_WIDTH_MASK;
+                burst_size = (s->chan[ch].ChnCtrl >> CHAN_CTL_SRC_BURST_SZ) &
+                             CHAN_CTL_SRC_BURST_SZ_MASK;
+                src_addr = (s->chan[ch].ChnSrcAddrH << 32) |
+                           s->chan[ch].ChnSrcAddr;
+                dst_addr = (s->chan[ch].ChnDstAddrH << 32) |
+                           s->chan[ch].ChnDstAddr;
+                src_addr_ctl = (s->chan[ch].ChnCtrl >> CHAN_CTL_SRC_ADDR_CTL) &
+                               CHAN_CTL_SRC_ADDR_CTL_MASK;
+                dst_addr_ctl = (s->chan[ch].ChnCtrl >> CHAN_CTL_DST_ADDR_CTL) &
+                               CHAN_CTL_DST_ADDR_CTL_MASK;
+
+                src_width = 1 << src_width;
+                dst_width = 1 << dst_width;
+                burst_size = 1 << burst_size;
+                remain_size = s->chan[ch].ChnTranSize * src_width;
+                int_tc_mask = (s->chan[ch].ChnCtrl >> CHAN_CTL_INT_TC_MASK_POS)
+                              & 0x1;
+                int_err_mask = (s->chan[ch].ChnCtrl >>
+                                CHAN_CTL_INT_ERR_MASK_POS) & 0x1;
+                int_abort_mask = (s->chan[ch].ChnCtrl >>
+                                  CHAN_CTL_INT_ABT_MASK_POS) & 0x1;
+                if (remain_size && burst_size < (1 << 11) &&
+                    src_width < (1 << 6) && dst_width < (1 << 6) &&
+                    (src_addr & (src_width - 1)) == 0 &&
+                    (dst_addr & (dst_width - 1)) == 0 &&
+                    (remain_size & (dst_width - 1)) == 0 &&
+                    (burst_size * src_width & (dst_width - 1)) == 0) {
+
+                    while (remain_size > 0) {
+                        /* check current abort status before a brust start*/
+                        if (s->ChAbort & (1 << ch)) {
+                            result = DMA_ABT_RESULT;
+                            s->ChAbort &= ~(1 << ch);
+                            atcdmac300_dma_reset_chan(s, ch);
+                            atcdmac300_dma_int_stat_update(s, INT_STATUS_ABT,
+                                                           ch);
+                            if (!int_abort_mask) {
+                                qemu_irq_raise(s->irq);
+                            }
+                            break;
+                        }
+
+                        int i;
+                        for (i = 0; i < burst_size; i++) {
+                            if (remain_size <= 0) {
+                                break;
+                            }
+
+                            memset(buf, 0, sizeof(buf));
+
+                            copy_size = MIN(remain_size, src_width);
+                            result = dma_iopmp_read(s, src_addr, buf,
+                                                    copy_size);
+                            while (result == MEMTX_IOPMP_STALL) {
+                                qemu_coroutine_yield();
+                                result = dma_iopmp_read(s, src_addr, buf,
+                                                        copy_size);
+                            }
+                            burst_remain_size = copy_size;
+                            uint64_t *curr_ptr = buf;
+                            while (burst_remain_size > 0) {
+                                copy_size_dst = MIN(burst_remain_size,
+                                                    dst_width);
+                                result = dma_iopmp_write(s, dst_addr, curr_ptr,
+                                                         copy_size_dst);
+                                while (result == MEMTX_IOPMP_STALL) {
+                                    qemu_coroutine_yield();
+                                    result = dma_iopmp_read(s, src_addr, buf,
+                                                            copy_size);
+                                }
+                                curr_ptr += copy_size_dst;
+                                burst_remain_size -= copy_size_dst;
+                            }
+
+                            if (result != MEMTX_OK) {
+                                break;
+                            }
+
+                            remain_size -= copy_size;
+
+                            if (src_addr_ctl == 0) {
+                                src_addr += copy_size;
+                            }
+                            if (src_addr_ctl == 1) {
+                                src_addr -= copy_size;
+                            }
+                            if (dst_addr_ctl == 0) {
+                                dst_addr += copy_size;
+                            }
+                            if (dst_addr_ctl == 1) {
+                                dst_addr -= copy_size;
+                            }
+                        }
+                        if (result != MEMTX_OK) {
+                                break;
+                        }
+                        qemu_coroutine_yield();
+                    }
+                    if (result == MEMTX_OK) {
+                        s->ChAbort &= ~(1 << ch);
+                        atcdmac300_dma_reset_chan(s, ch);
+                        atcdmac300_dma_int_stat_update(s, INT_STATUS_TC, ch);
+                        if (!int_tc_mask) {
+                            qemu_irq_raise(s->irq);
+                        }
+                    } else if (result != DMA_ABT_RESULT) {
+                        s->ChAbort &= ~(1 << ch);
+                        atcdmac300_dma_int_stat_update(s, INT_STATUS_ERR, ch);
+                        atcdmac300_dma_reset_chan(s, ch);
+                        if (!int_err_mask) {
+                            qemu_irq_raise(s->irq);
+                        }
+                    }
+                } else {
+                    s->ChAbort &= ~(1 << ch);
+                    atcdmac300_dma_int_stat_update(s, INT_STATUS_ERR, ch);
+                    atcdmac300_dma_reset_chan(s, ch);
+                    if (!int_err_mask) {
+                        qemu_irq_raise(s->irq);
+                    }
+                }
+            }
+        }
+        qemu_coroutine_yield();
+    }
+}
+
+static void atcdmac300_bh_cb(void *opaque)
+{
+    ATCDMAC300State *s = opaque;
+
+    int rearm = 0;
+    if (s->running) {
+        rearm = 1;
+        goto out;
+    } else {
+        s->running = 1;
+    }
+
+    AioContext *ctx = qemu_get_current_aio_context();
+    aio_co_enter(ctx, s->co);
+
+    s->running = 0;
+out:
+    if (rearm) {
+        qemu_bh_schedule_idle(s->bh);
+        s->dma_bh_scheduled = true;
+    }
+    qemu_bh_schedule_idle(s->bh);
+    s->dma_bh_scheduled = true;
+    s->running = 0;
+}
+
+static void atcdmac300_write(void *opaque, hwaddr offset, uint64_t value,
+                             unsigned size)
+{
+    ATCDMAC300State *s = opaque;
+    int ch = 0;
+
+    LOG("@@@ atcdmac300_write()=0x%lx, value=0x%lx\n", offset, value);
+
+    if (offset >= 0x40) {
+        ch = ATCDMAC300_GET_CHAN(offset);
+        offset = ATCDMAC300_GET_OFF(offset, ch);
+    }
+
+    switch (offset) {
+    case ATCDMAC300_INT_STATUS:
+        /* Write 1 to clear */
+        s->IntStatus = 0;
+        break;
+    case ATCDMAC300_DMAC_CTRL:
+        atcdmac300_dma_reset(s);
+        break;
+    case ATCDMAC300_CHN_ABT:
+        for (int i = 0; i < ATCDMAC300_MAX_CHAN; i++) {
+            if (value & 0x1 && (s->chan[i].ChnCtrl & (1 << CHAN_CTL_ENABLE))) {
+                s->ChAbort |= (0x1 << i);
+            }
+            value >>= 1;
+        }
+        break;
+    case ATCDMAC300_CHAN_CTL:
+        s->chan[ch].ChnCtrl = value;
+        qemu_bh_schedule_idle(s->bh);
+        break;
+    case ATCDMAC300_CHAN_TRAN_SZ:
+        s->chan[ch].ChnTranSize = value;
+        break;
+    case ATCDMAC300_CHAN_SRC_ADDR:
+        s->chan[ch].ChnSrcAddr = value;
+        break;
+    case ATCDMAC300_CHAN_SRC_ADDR_H:
+        s->chan[ch].ChnSrcAddrH = value;
+        break;
+    case ATCDMAC300_CHAN_DST_ADDR:
+        s->chan[ch].ChnDstAddr = value;
+        break;
+    case ATCDMAC300_CHAN_DST_ADDR_H:
+        s->chan[ch].ChnDstAddrH = value;
+        break;
+    case ATCDMAC300_CHAN_LL_POINTER:
+        s->chan[ch].ChnLLPointer = value;
+        break;
+    case ATCDMAC300_CHAN_LL_POINTER_H:
+        s->chan[ch].ChnLLPointerH = value;
+        break;
+    default:
+        LOGGE("%s: Bad offset 0x%" HWADDR_PRIX "\n",
+              __func__, offset);
+        break;
+    }
+}
+
+static const MemoryRegionOps atcdmac300_ops = {
+    .read = atcdmac300_read,
+    .write = atcdmac300_write,
+    .endianness = DEVICE_NATIVE_ENDIAN,
+    .valid = {
+        .min_access_size = 4,
+        .max_access_size = 8
+    }
+};
+
+static void atcdmac300_init(Object *obj)
+{
+    ATCDMAC300State *s = ATCDMAC300(obj);
+    SysBusDevice *sbus = SYS_BUS_DEVICE(obj);
+
+    sysbus_init_irq(sbus, &s->irq);
+    memory_region_init_io(&s->mmio, obj, &atcdmac300_ops, s, TYPE_ATCDMAC300,
+                          s->mmio_size);
+    sysbus_init_mmio(sbus, &s->mmio);
+    if (s->iothread) {
+        s->ctx = iothread_get_aio_context(s->iothread);
+    } else {
+        s->ctx = qemu_get_aio_context();
+    }
+    s->bh = aio_bh_new(s->ctx, atcdmac300_bh_cb, s);
+    s->co = qemu_coroutine_create(atcdmac300_co_run, s);
+}
+
+static Property atcdmac300_properties[] = {
+    DEFINE_PROP_UINT32("mmio-size", ATCDMAC300State, mmio_size, 0x100000),
+    DEFINE_PROP_UINT32("id-and-revision", ATCDMAC300State, IdRev,
+                       (ATCDMAC300_PRODUCT_ID  << 8) |
+                       ((ATCDMAC300_PRODUCT_ID & 0x7) << 4) |
+                       ((ATCDMAC300_PRODUCT_ID & 0x7))),
+    DEFINE_PROP_UINT32("inturrupt-status", ATCDMAC300State, IntStatus, 0),
+    DEFINE_PROP_UINT32("dmac-configuration", ATCDMAC300State,
+                       DMACfg, 0xc3404108),
+    DEFINE_PROP_LINK("iothread", ATCDMAC300State, iothread,
+                     TYPE_IOTHREAD, IOThread *),
+
+    DEFINE_PROP_END_OF_LIST(),
+};
+
+static void atcdmac300_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *k = DEVICE_CLASS(klass);
+    device_class_set_props(k, atcdmac300_properties);
+}
+
+static const TypeInfo atcdmac300_info = {
+    .name          = TYPE_ATCDMAC300,
+    .parent        = TYPE_SYS_BUS_DEVICE,
+    .instance_size = sizeof(ATCDMAC300State),
+    .class_init    = atcdmac300_class_init,
+    .instance_init = atcdmac300_init,
+};
+
+DeviceState *
+atcdmac300_create(const char *name, hwaddr addr, hwaddr mmio_size, qemu_irq irq)
+{
+    DeviceState *dev;
+    dev = sysbus_create_varargs(TYPE_ATCDMAC300, addr, irq, NULL);
+    return dev;
+}
+
+static void atcdmac300_register_types(void)
+{
+    type_register_static(&atcdmac300_info);
+}
+
+void atcdmac300_connect_iopmp_as(DeviceState *dev, AddressSpace *iopmp_as,
+                                 uint32_t sid)
+{
+    ATCDMAC300State *s = ATCDMAC300(dev);
+    s->iopmp_as = iopmp_as;
+    s->sid = sid;
+}
+
+type_init(atcdmac300_register_types)
diff --git a/hw/dma/meson.build b/hw/dma/meson.build
index a96c1be2c8..dfe37de32d 100644
--- a/hw/dma/meson.build
+++ b/hw/dma/meson.build
@@ -14,3 +14,4 @@ system_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_dma.c'))
 system_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c'))
 system_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c'))
 system_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c'))
+system_ss.add(when: 'CONFIG_ATCDMAC300', if_true: files('atcdmac300.c'))
\ No newline at end of file
diff --git a/include/hw/dma/atcdmac300.h b/include/hw/dma/atcdmac300.h
new file mode 100644
index 0000000000..c2688bc18f
--- /dev/null
+++ b/include/hw/dma/atcdmac300.h
@@ -0,0 +1,171 @@
+/*
+ * Andes ATCDMAC300 (Andes Technology DMA Controller)
+ *
+ * Copyright (c) 2022 Andes Tech. Corp.
+ *
+ * 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 ATCDMAC300_H
+#define ATCDMAC300_H
+
+#include "hw/sysbus.h"
+#include "qom/object.h"
+#include "qemu/coroutine.h"
+#include "block/aio.h"
+#include "sysemu/iothread.h"
+#include "sysemu/dma.h"
+
+#define TYPE_ATCDMAC300 "atcdmac300"
+OBJECT_DECLARE_SIMPLE_TYPE(ATCDMAC300State, ATCDMAC300)
+
+#define ATCDMAC300_IOPMP_SID            0
+
+#define ATCDMAC300_PRODUCT_ID           0x010230
+#define ATCDMAC300_REV_MAJOR            0x0
+#define ATCDMAC300_REV_MINOR            0x1
+
+/* DMAC Configuration Register (Offset 0x10)  */
+#define ATCDMAC300_DMA_CFG              0x10
+#define DMA_CFG_CHAIN_XFR               31
+#define DMA_CFG_REQ_SYNC                30
+#define DMA_CFG_DATA_WITDTH             24
+#define DMA_CFG_ADDR_WIDTH              17
+#define DMA_CFG_CORE_NUM                16
+#define DMA_CFG_BUS_NUM                 15
+#define DMA_CFG_REQ_NUM                 10
+#define DMA_CFG_FIFO_DEPTH              4
+#define DMA_CFG_CHAN_NUM                0
+
+/* Interrupt Status Register (Offset 0x20) */
+#define ATCDMAC300_DMAC_CTRL            0x20
+
+/* Channel Abort Register (Offset 0x24) */
+#define ATCDMAC300_CHN_ABT              0x24
+
+/* Interrupt Status Register (Offset 0x30) */
+#define ATCDMAC300_INT_STATUS           0x30
+#define INT_STATUS_TC                   16
+#define INT_STATUS_ABT                  8
+#define INT_STATUS_ERR                  0
+
+/* Interrupt Status Register (Offset 0x34) */
+#define ATCDMAC300_CHAN_ENABLE          0x34
+
+/* Channel n Control Register (Offset 0x40 + n*0x20) */
+#define CHAN_CTL_SRC_BUS_IDX            31
+#define CHAN_CTL_DST_BUS_IDX            30
+#define CHAN_CTL_PRIORITY               29
+#define CHAN_CTL_SRC_BURST_SZ           24
+#define CHAN_CTL_SRC_WIDTH              21
+#define CHAN_CTL_DST_WIDTH              18
+#define CHAN_CTL_SRC_MODE               17
+#define CHAN_CTL_DST_MODE               16
+#define CHAN_CTL_SRC_ADDR_CTL           14
+#define CHAN_CTL_DST_ADDR_CTL           12
+#define CHAN_CTL_SRC_REQ_SEL            8
+#define CHAN_CTL_DST_REQ_SEL            4
+#define CHAN_CTL_INT_ABT_MASK_POS       3
+#define CHAN_CTL_INT_ERR_MASK_POS       2
+#define CHAN_CTL_INT_TC_MASK_POS        1
+#define CHAN_CTL_ENABLE                 0
+
+#define CHAN_CTL_SRC_WIDTH_MASK         0x7
+#define CHAN_CTL_DST_WIDTH_MASK         0x7
+#define CHAN_CTL_SRC_BURST_SZ_MASK      0xf
+#define CHAN_CTL_SRC_ADDR_CTL_MASK      0x3
+#define CHAN_CTL_DST_ADDR_CTL_MASK      0x3
+
+#define ATCDMAC300_CHAN_CTL             0x40
+#define ATCDMAC300_CHAN_TRAN_SZ         0x44
+#define ATCDMAC300_CHAN_SRC_ADDR        0x48
+#define ATCDMAC300_CHAN_SRC_ADDR_H      0x4C
+#define ATCDMAC300_CHAN_DST_ADDR        0x50
+#define ATCDMAC300_CHAN_DST_ADDR_H      0x54
+#define ATCDMAC300_CHAN_LL_POINTER      0x58
+#define ATCDMAC300_CHAN_LL_POINTER_H    0x5C
+
+#define ATCDMAC300_IRQ_START            0x40
+#define ATCDMAC300_IRQ_END              (ATCDMAC300_IRQ_START + \
+                                         ATCDMAC300_MAX_CHAN)
+
+#define ATCDMAC300_MAX_BURST_SIZE       1024
+#define ATCDMAC300_MAX_CHAN             0x8
+
+#define PER_CHAN_OFFSET                 0x20
+#define ATCDMAC300_FIRST_CHAN_BASE      ATCDMAC300_CHAN_CTL
+#define ATCDMAC300_GET_CHAN(reg)        (((reg - ATCDMAC300_FIRST_CHAN_BASE) / \
+                                            PER_CHAN_OFFSET))
+#define ATCDMAC300_GET_OFF(reg, ch)     (reg - (ch * PER_CHAN_OFFSET))
+
+#define DMA_ABT_RESULT (1 << 3)
+
+typedef struct {
+    qemu_irq irq;
+
+    /* Channel control registers (n=0~7) */
+    uint32_t ChnCtrl;
+    uint32_t ChnTranSize;
+    uint32_t ChnSrcAddr;
+    uint64_t ChnSrcAddrH;
+    uint32_t ChnDstAddr;
+    uint64_t ChnDstAddrH;
+    uint32_t ChnLLPointer;
+    uint32_t ChnLLPointerH;
+} ATCDMAC300Chan;
+
+struct ATCDMAC300State {
+    /*< private >*/
+    SysBusDevice busdev;
+    /*< public >*/
+
+    qemu_irq irq;
+    MemoryRegion mmio;
+    uint32_t mmio_size;
+
+    /* ID and revision register */
+    uint32_t IdRev;
+
+    /* Configuration register */
+    uint32_t DMACfg;
+
+    /* Global control registers */
+    uint32_t DMACtrl;
+    uint32_t ChAbort;
+
+    /* Channel status registers */
+    uint32_t IntStatus;
+    uint32_t ChEN;
+
+    ATCDMAC300Chan chan[ATCDMAC300_MAX_CHAN];
+
+    /* To support iopmp */
+    AddressSpace *iopmp_as;
+    uint32_t sid;
+
+    Coroutine *co;
+    QEMUBH *bh;
+    bool running;
+    bool dma_bh_scheduled;
+    AioContext *ctx;
+    IOThread *iothread;
+};
+
+DeviceState *atcdmac300_create(const char *name, hwaddr addr, hwaddr mmio_size,
+                               qemu_irq irq);
+
+void atcdmac300_connect_iopmp_as(DeviceState *dev, AddressSpace *iopmp_as,
+                                 uint32_t sid);
+
+#endif /* ATCDMAC300_H */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 6/6] hw/riscv/virt: Add IOPMP support
  2023-10-25  5:14 ` Ethan Chen
  (?)
@ 2023-10-25  5:14   ` Ethan Chen via
  -1 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel
  Cc: Ethan Chen, Paolo Bonzini, Palmer Dabbelt, Alistair Francis,
	Bin Meng, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	open list:RISC-V TCG CPUs

- Add 'iopmp=on' option to enable iopmp
- Add 'iopmp_cascade=on' option to enable iopmp cascading.

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 hw/riscv/Kconfig        |  2 ++
 hw/riscv/virt.c         | 72 +++++++++++++++++++++++++++++++++++++++--
 include/hw/riscv/virt.h | 10 +++++-
 3 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index b6a5eb4452..c30a104aa4 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -45,6 +45,8 @@ config RISCV_VIRT
     select FW_CFG_DMA
     select PLATFORM_BUS
     select ACPI
+    select ATCDMAC300
+    select RISCV_IOPMP
 
 config SHAKTI_C
     bool
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 9de578c756..d3d4320c73 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -53,6 +53,9 @@
 #include "hw/display/ramfb.h"
 #include "hw/acpi/aml-build.h"
 #include "qapi/qapi-visit-common.h"
+#include "hw/misc/riscv_iopmp.h"
+#include "hw/dma/atcdmac300.h"
+
 
 /*
  * The virt machine physical address space used by some of the devices
@@ -97,6 +100,9 @@ static const MemMapEntry virt_memmap[] = {
     [VIRT_UART0] =        { 0x10000000,         0x100 },
     [VIRT_VIRTIO] =       { 0x10001000,        0x1000 },
     [VIRT_FW_CFG] =       { 0x10100000,          0x18 },
+    [VIRT_IOPMP] =        { 0x10200000,      0x100000 },
+    [VIRT_IOPMP2] =       { 0x10300000,      0x100000 },
+    [VIRT_DMAC] =         { 0x10400000,      0x100000 },
     [VIRT_FLASH] =        { 0x20000000,     0x4000000 },
     [VIRT_IMSIC_M] =      { 0x24000000, VIRT_IMSIC_MAX_SIZE },
     [VIRT_IMSIC_S] =      { 0x28000000, VIRT_IMSIC_MAX_SIZE },
@@ -1527,13 +1533,32 @@ static void virt_machine_init(MachineState *machine)
 
     create_platform_bus(s, mmio_irqchip);
 
-    serial_mm_init(system_memory, memmap[VIRT_UART0].base,
-        0, qdev_get_gpio_in(mmio_irqchip, UART0_IRQ), 399193,
+    serial_mm_init(system_memory, memmap[VIRT_UART0].base + 0x20,
+        0x2, qdev_get_gpio_in(DEVICE(mmio_irqchip), UART0_IRQ), 38400,
         serial_hd(0), DEVICE_LITTLE_ENDIAN);
 
     sysbus_create_simple("goldfish_rtc", memmap[VIRT_RTC].base,
         qdev_get_gpio_in(mmio_irqchip, RTC_IRQ));
 
+    /* DMAC */
+    DeviceState *dmac_dev = atcdmac300_create("atcdmac300",
+        memmap[VIRT_DMAC].base, memmap[VIRT_DMAC].size,
+        qdev_get_gpio_in(DEVICE(mmio_irqchip), DMAC_IRQ));
+
+    if (s->have_iopmp) {
+        /* IOPMP */
+        DeviceState *iopmp_dev = iopmp_create(memmap[VIRT_IOPMP].base,
+            qdev_get_gpio_in(DEVICE(mmio_irqchip), IOPMP_IRQ));
+        /* DMA with IOPMP */
+        atcdmac300_connect_iopmp_as(dmac_dev, &(IOPMP(iopmp_dev)->iopmp_as), 0);
+        if (s->have_iopmp_cascade) {
+            DeviceState *iopmp_dev2 = iopmp_create(memmap[VIRT_IOPMP2].base,
+                qdev_get_gpio_in(DEVICE(mmio_irqchip), IOPMP2_IRQ));
+            cascade_iopmp(iopmp_dev, iopmp_dev2);
+        }
+    }
+
+
     for (i = 0; i < ARRAY_SIZE(s->flash); i++) {
         /* Map legacy -drive if=pflash to machine properties */
         pflash_cfi01_legacy_drive(s->flash[i],
@@ -1628,6 +1653,35 @@ static void virt_set_aclint(Object *obj, bool value, Error **errp)
     s->have_aclint = value;
 }
 
+static bool virt_get_iopmp(Object *obj, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    return s->have_iopmp;
+}
+
+static void virt_set_iopmp(Object *obj, bool value, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    s->have_iopmp = value;
+}
+
+static bool virt_get_iopmp_cascade(Object *obj, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    return s->have_iopmp_cascade;
+}
+
+static void virt_set_iopmp_cascade(Object *obj, bool value, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    s->have_iopmp_cascade = value;
+}
+
+
 bool virt_is_acpi_enabled(RISCVVirtState *s)
 {
     return s->acpi != ON_OFF_AUTO_OFF;
@@ -1730,6 +1784,20 @@ static void virt_machine_class_init(ObjectClass *oc, void *data)
                               NULL, NULL);
     object_class_property_set_description(oc, "acpi",
                                           "Enable ACPI");
+
+    object_class_property_add_bool(oc, "iopmp", virt_get_iopmp,
+                                   virt_set_iopmp);
+    object_class_property_set_description(oc, "iopmp",
+                                          "Set on/off to enable/disable "
+                                          "iopmp device");
+
+    object_class_property_add_bool(oc, "iopmp-cascade",
+                                   virt_get_iopmp_cascade,
+                                   virt_set_iopmp_cascade);
+    object_class_property_set_description(oc, "iopmp-cascade",
+                                          "Set on/off to enable/disable "
+                                          "iopmp2 device which is cascaded by "
+                                          "iopmp1 device");
 }
 
 static const TypeInfo virt_machine_typeinfo = {
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index e5c474b26e..5fa2944d29 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -54,6 +54,8 @@ struct RISCVVirtState {
 
     int fdt_size;
     bool have_aclint;
+    bool have_iopmp;
+    bool have_iopmp_cascade;
     RISCVVirtAIAType aia_type;
     int aia_guests;
     char *oem_id;
@@ -82,12 +84,18 @@ enum {
     VIRT_PCIE_MMIO,
     VIRT_PCIE_PIO,
     VIRT_PLATFORM_BUS,
-    VIRT_PCIE_ECAM
+    VIRT_PCIE_ECAM,
+    VIRT_IOPMP,
+    VIRT_IOPMP2,
+    VIRT_DMAC,
 };
 
 enum {
     UART0_IRQ = 10,
     RTC_IRQ = 11,
+    DMAC_IRQ = 12,
+    IOPMP_IRQ = 13,
+    IOPMP2_IRQ = 14,
     VIRTIO_IRQ = 1, /* 1 to 8 */
     VIRTIO_COUNT = 8,
     PCIE_IRQ = 0x20, /* 32 to 35 */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 6/6] hw/riscv/virt: Add IOPMP support
@ 2023-10-25  5:14   ` Ethan Chen via
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel
  Cc: Ethan Chen, Paolo Bonzini, Palmer Dabbelt, Alistair Francis,
	Bin Meng, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	open list:RISC-V TCG CPUs

- Add 'iopmp=on' option to enable iopmp
- Add 'iopmp_cascade=on' option to enable iopmp cascading.

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 hw/riscv/Kconfig        |  2 ++
 hw/riscv/virt.c         | 72 +++++++++++++++++++++++++++++++++++++++--
 include/hw/riscv/virt.h | 10 +++++-
 3 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index b6a5eb4452..c30a104aa4 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -45,6 +45,8 @@ config RISCV_VIRT
     select FW_CFG_DMA
     select PLATFORM_BUS
     select ACPI
+    select ATCDMAC300
+    select RISCV_IOPMP
 
 config SHAKTI_C
     bool
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 9de578c756..d3d4320c73 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -53,6 +53,9 @@
 #include "hw/display/ramfb.h"
 #include "hw/acpi/aml-build.h"
 #include "qapi/qapi-visit-common.h"
+#include "hw/misc/riscv_iopmp.h"
+#include "hw/dma/atcdmac300.h"
+
 
 /*
  * The virt machine physical address space used by some of the devices
@@ -97,6 +100,9 @@ static const MemMapEntry virt_memmap[] = {
     [VIRT_UART0] =        { 0x10000000,         0x100 },
     [VIRT_VIRTIO] =       { 0x10001000,        0x1000 },
     [VIRT_FW_CFG] =       { 0x10100000,          0x18 },
+    [VIRT_IOPMP] =        { 0x10200000,      0x100000 },
+    [VIRT_IOPMP2] =       { 0x10300000,      0x100000 },
+    [VIRT_DMAC] =         { 0x10400000,      0x100000 },
     [VIRT_FLASH] =        { 0x20000000,     0x4000000 },
     [VIRT_IMSIC_M] =      { 0x24000000, VIRT_IMSIC_MAX_SIZE },
     [VIRT_IMSIC_S] =      { 0x28000000, VIRT_IMSIC_MAX_SIZE },
@@ -1527,13 +1533,32 @@ static void virt_machine_init(MachineState *machine)
 
     create_platform_bus(s, mmio_irqchip);
 
-    serial_mm_init(system_memory, memmap[VIRT_UART0].base,
-        0, qdev_get_gpio_in(mmio_irqchip, UART0_IRQ), 399193,
+    serial_mm_init(system_memory, memmap[VIRT_UART0].base + 0x20,
+        0x2, qdev_get_gpio_in(DEVICE(mmio_irqchip), UART0_IRQ), 38400,
         serial_hd(0), DEVICE_LITTLE_ENDIAN);
 
     sysbus_create_simple("goldfish_rtc", memmap[VIRT_RTC].base,
         qdev_get_gpio_in(mmio_irqchip, RTC_IRQ));
 
+    /* DMAC */
+    DeviceState *dmac_dev = atcdmac300_create("atcdmac300",
+        memmap[VIRT_DMAC].base, memmap[VIRT_DMAC].size,
+        qdev_get_gpio_in(DEVICE(mmio_irqchip), DMAC_IRQ));
+
+    if (s->have_iopmp) {
+        /* IOPMP */
+        DeviceState *iopmp_dev = iopmp_create(memmap[VIRT_IOPMP].base,
+            qdev_get_gpio_in(DEVICE(mmio_irqchip), IOPMP_IRQ));
+        /* DMA with IOPMP */
+        atcdmac300_connect_iopmp_as(dmac_dev, &(IOPMP(iopmp_dev)->iopmp_as), 0);
+        if (s->have_iopmp_cascade) {
+            DeviceState *iopmp_dev2 = iopmp_create(memmap[VIRT_IOPMP2].base,
+                qdev_get_gpio_in(DEVICE(mmio_irqchip), IOPMP2_IRQ));
+            cascade_iopmp(iopmp_dev, iopmp_dev2);
+        }
+    }
+
+
     for (i = 0; i < ARRAY_SIZE(s->flash); i++) {
         /* Map legacy -drive if=pflash to machine properties */
         pflash_cfi01_legacy_drive(s->flash[i],
@@ -1628,6 +1653,35 @@ static void virt_set_aclint(Object *obj, bool value, Error **errp)
     s->have_aclint = value;
 }
 
+static bool virt_get_iopmp(Object *obj, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    return s->have_iopmp;
+}
+
+static void virt_set_iopmp(Object *obj, bool value, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    s->have_iopmp = value;
+}
+
+static bool virt_get_iopmp_cascade(Object *obj, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    return s->have_iopmp_cascade;
+}
+
+static void virt_set_iopmp_cascade(Object *obj, bool value, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    s->have_iopmp_cascade = value;
+}
+
+
 bool virt_is_acpi_enabled(RISCVVirtState *s)
 {
     return s->acpi != ON_OFF_AUTO_OFF;
@@ -1730,6 +1784,20 @@ static void virt_machine_class_init(ObjectClass *oc, void *data)
                               NULL, NULL);
     object_class_property_set_description(oc, "acpi",
                                           "Enable ACPI");
+
+    object_class_property_add_bool(oc, "iopmp", virt_get_iopmp,
+                                   virt_set_iopmp);
+    object_class_property_set_description(oc, "iopmp",
+                                          "Set on/off to enable/disable "
+                                          "iopmp device");
+
+    object_class_property_add_bool(oc, "iopmp-cascade",
+                                   virt_get_iopmp_cascade,
+                                   virt_set_iopmp_cascade);
+    object_class_property_set_description(oc, "iopmp-cascade",
+                                          "Set on/off to enable/disable "
+                                          "iopmp2 device which is cascaded by "
+                                          "iopmp1 device");
 }
 
 static const TypeInfo virt_machine_typeinfo = {
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index e5c474b26e..5fa2944d29 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -54,6 +54,8 @@ struct RISCVVirtState {
 
     int fdt_size;
     bool have_aclint;
+    bool have_iopmp;
+    bool have_iopmp_cascade;
     RISCVVirtAIAType aia_type;
     int aia_guests;
     char *oem_id;
@@ -82,12 +84,18 @@ enum {
     VIRT_PCIE_MMIO,
     VIRT_PCIE_PIO,
     VIRT_PLATFORM_BUS,
-    VIRT_PCIE_ECAM
+    VIRT_PCIE_ECAM,
+    VIRT_IOPMP,
+    VIRT_IOPMP2,
+    VIRT_DMAC,
 };
 
 enum {
     UART0_IRQ = 10,
     RTC_IRQ = 11,
+    DMAC_IRQ = 12,
+    IOPMP_IRQ = 13,
+    IOPMP2_IRQ = 14,
     VIRTIO_IRQ = 1, /* 1 to 8 */
     VIRTIO_COUNT = 8,
     PCIE_IRQ = 0x20, /* 32 to 35 */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* [PATCH 6/6] hw/riscv/virt: Add IOPMP support
@ 2023-10-25  5:14   ` Ethan Chen via
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-25  5:14 UTC (permalink / raw)
  To: qemu-devel
  Cc: Ethan Chen, Paolo Bonzini, Palmer Dabbelt, Alistair Francis,
	Bin Meng, Weiwei Li, Daniel Henrique Barboza, Liu Zhiwei,
	open list:RISC-V TCG CPUs

- Add 'iopmp=on' option to enable iopmp
- Add 'iopmp_cascade=on' option to enable iopmp cascading.

Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 hw/riscv/Kconfig        |  2 ++
 hw/riscv/virt.c         | 72 +++++++++++++++++++++++++++++++++++++++--
 include/hw/riscv/virt.h | 10 +++++-
 3 files changed, 81 insertions(+), 3 deletions(-)

diff --git a/hw/riscv/Kconfig b/hw/riscv/Kconfig
index b6a5eb4452..c30a104aa4 100644
--- a/hw/riscv/Kconfig
+++ b/hw/riscv/Kconfig
@@ -45,6 +45,8 @@ config RISCV_VIRT
     select FW_CFG_DMA
     select PLATFORM_BUS
     select ACPI
+    select ATCDMAC300
+    select RISCV_IOPMP
 
 config SHAKTI_C
     bool
diff --git a/hw/riscv/virt.c b/hw/riscv/virt.c
index 9de578c756..d3d4320c73 100644
--- a/hw/riscv/virt.c
+++ b/hw/riscv/virt.c
@@ -53,6 +53,9 @@
 #include "hw/display/ramfb.h"
 #include "hw/acpi/aml-build.h"
 #include "qapi/qapi-visit-common.h"
+#include "hw/misc/riscv_iopmp.h"
+#include "hw/dma/atcdmac300.h"
+
 
 /*
  * The virt machine physical address space used by some of the devices
@@ -97,6 +100,9 @@ static const MemMapEntry virt_memmap[] = {
     [VIRT_UART0] =        { 0x10000000,         0x100 },
     [VIRT_VIRTIO] =       { 0x10001000,        0x1000 },
     [VIRT_FW_CFG] =       { 0x10100000,          0x18 },
+    [VIRT_IOPMP] =        { 0x10200000,      0x100000 },
+    [VIRT_IOPMP2] =       { 0x10300000,      0x100000 },
+    [VIRT_DMAC] =         { 0x10400000,      0x100000 },
     [VIRT_FLASH] =        { 0x20000000,     0x4000000 },
     [VIRT_IMSIC_M] =      { 0x24000000, VIRT_IMSIC_MAX_SIZE },
     [VIRT_IMSIC_S] =      { 0x28000000, VIRT_IMSIC_MAX_SIZE },
@@ -1527,13 +1533,32 @@ static void virt_machine_init(MachineState *machine)
 
     create_platform_bus(s, mmio_irqchip);
 
-    serial_mm_init(system_memory, memmap[VIRT_UART0].base,
-        0, qdev_get_gpio_in(mmio_irqchip, UART0_IRQ), 399193,
+    serial_mm_init(system_memory, memmap[VIRT_UART0].base + 0x20,
+        0x2, qdev_get_gpio_in(DEVICE(mmio_irqchip), UART0_IRQ), 38400,
         serial_hd(0), DEVICE_LITTLE_ENDIAN);
 
     sysbus_create_simple("goldfish_rtc", memmap[VIRT_RTC].base,
         qdev_get_gpio_in(mmio_irqchip, RTC_IRQ));
 
+    /* DMAC */
+    DeviceState *dmac_dev = atcdmac300_create("atcdmac300",
+        memmap[VIRT_DMAC].base, memmap[VIRT_DMAC].size,
+        qdev_get_gpio_in(DEVICE(mmio_irqchip), DMAC_IRQ));
+
+    if (s->have_iopmp) {
+        /* IOPMP */
+        DeviceState *iopmp_dev = iopmp_create(memmap[VIRT_IOPMP].base,
+            qdev_get_gpio_in(DEVICE(mmio_irqchip), IOPMP_IRQ));
+        /* DMA with IOPMP */
+        atcdmac300_connect_iopmp_as(dmac_dev, &(IOPMP(iopmp_dev)->iopmp_as), 0);
+        if (s->have_iopmp_cascade) {
+            DeviceState *iopmp_dev2 = iopmp_create(memmap[VIRT_IOPMP2].base,
+                qdev_get_gpio_in(DEVICE(mmio_irqchip), IOPMP2_IRQ));
+            cascade_iopmp(iopmp_dev, iopmp_dev2);
+        }
+    }
+
+
     for (i = 0; i < ARRAY_SIZE(s->flash); i++) {
         /* Map legacy -drive if=pflash to machine properties */
         pflash_cfi01_legacy_drive(s->flash[i],
@@ -1628,6 +1653,35 @@ static void virt_set_aclint(Object *obj, bool value, Error **errp)
     s->have_aclint = value;
 }
 
+static bool virt_get_iopmp(Object *obj, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    return s->have_iopmp;
+}
+
+static void virt_set_iopmp(Object *obj, bool value, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    s->have_iopmp = value;
+}
+
+static bool virt_get_iopmp_cascade(Object *obj, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    return s->have_iopmp_cascade;
+}
+
+static void virt_set_iopmp_cascade(Object *obj, bool value, Error **errp)
+{
+    RISCVVirtState *s = RISCV_VIRT_MACHINE(obj);
+
+    s->have_iopmp_cascade = value;
+}
+
+
 bool virt_is_acpi_enabled(RISCVVirtState *s)
 {
     return s->acpi != ON_OFF_AUTO_OFF;
@@ -1730,6 +1784,20 @@ static void virt_machine_class_init(ObjectClass *oc, void *data)
                               NULL, NULL);
     object_class_property_set_description(oc, "acpi",
                                           "Enable ACPI");
+
+    object_class_property_add_bool(oc, "iopmp", virt_get_iopmp,
+                                   virt_set_iopmp);
+    object_class_property_set_description(oc, "iopmp",
+                                          "Set on/off to enable/disable "
+                                          "iopmp device");
+
+    object_class_property_add_bool(oc, "iopmp-cascade",
+                                   virt_get_iopmp_cascade,
+                                   virt_set_iopmp_cascade);
+    object_class_property_set_description(oc, "iopmp-cascade",
+                                          "Set on/off to enable/disable "
+                                          "iopmp2 device which is cascaded by "
+                                          "iopmp1 device");
 }
 
 static const TypeInfo virt_machine_typeinfo = {
diff --git a/include/hw/riscv/virt.h b/include/hw/riscv/virt.h
index e5c474b26e..5fa2944d29 100644
--- a/include/hw/riscv/virt.h
+++ b/include/hw/riscv/virt.h
@@ -54,6 +54,8 @@ struct RISCVVirtState {
 
     int fdt_size;
     bool have_aclint;
+    bool have_iopmp;
+    bool have_iopmp_cascade;
     RISCVVirtAIAType aia_type;
     int aia_guests;
     char *oem_id;
@@ -82,12 +84,18 @@ enum {
     VIRT_PCIE_MMIO,
     VIRT_PCIE_PIO,
     VIRT_PLATFORM_BUS,
-    VIRT_PCIE_ECAM
+    VIRT_PCIE_ECAM,
+    VIRT_IOPMP,
+    VIRT_IOPMP2,
+    VIRT_DMAC,
 };
 
 enum {
     UART0_IRQ = 10,
     RTC_IRQ = 11,
+    DMAC_IRQ = 12,
+    IOPMP_IRQ = 13,
+    IOPMP2_IRQ = 14,
     VIRTIO_IRQ = 1, /* 1 to 8 */
     VIRTIO_COUNT = 8,
     PCIE_IRQ = 0x20, /* 32 to 35 */
-- 
2.34.1



^ permalink raw reply related	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/6] exec/memory: Introduce the translate_size function within the IOMMU class
  2023-10-25  5:14   ` Ethan Chen
  (?)
@ 2023-10-25 14:56   ` David Hildenbrand
  2023-10-26  7:14       ` Ethan Chen
  -1 siblings, 1 reply; 34+ messages in thread
From: David Hildenbrand @ 2023-10-25 14:56 UTC (permalink / raw)
  To: Ethan Chen, qemu-devel
  Cc: Paolo Bonzini, Peter Xu, Philippe Mathieu-Daudé

On 25.10.23 07:14, Ethan Chen wrote:
> IOMMU have size information during translation.
> 

Can you add some more information why we would want this and how the 
backend can do "better" things with the size at hand?

Note that I was not CCed on the cover letter.

> Signed-off-by: Ethan Chen <ethan84@andestech.com>
> ---
>   include/exec/memory.h | 19 +++++++++++++++++++
>   1 file changed, 19 insertions(+)
> 
> diff --git a/include/exec/memory.h b/include/exec/memory.h
> index 9087d02769..5520b7c8c0 100644
> --- a/include/exec/memory.h
> +++ b/include/exec/memory.h
> @@ -396,6 +396,25 @@ struct IOMMUMemoryRegionClass {
>        */
>       IOMMUTLBEntry (*translate)(IOMMUMemoryRegion *iommu, hwaddr addr,
>                                  IOMMUAccessFlags flag, int iommu_idx);
> +    /**
> +     * @translate_size:
> +     *
> +     * Return a TLB entry that contains a given address and size.
> +     *
> +     * @iommu: the IOMMUMemoryRegion
> +     *
> +     * @hwaddr: address to be translated within the memory region
> +     *
> +     * @size: size to indicate the scope of the entire transaction
> +     *
> +     * @flag: requested access permission
> +     *
> +     * @iommu_idx: IOMMU index for the translation
> +     */
> +    IOMMUTLBEntry (*translate_size)(IOMMUMemoryRegion *iommu, hwaddr addr,
> +                                    hwaddr size, IOMMUAccessFlags flag,
> +                                    int iommu_idx);
> +
>       /**
>        * @get_min_page_size:
>        *

-- 
Cheers,

David / dhildenb



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
  2023-10-25  5:14   ` Ethan Chen
  (?)
@ 2023-10-25 15:14   ` Peter Xu
  2023-10-26  6:48       ` Ethan Chen
  2023-10-27  3:28       ` Ethan Chen
  -1 siblings, 2 replies; 34+ messages in thread
From: Peter Xu @ 2023-10-25 15:14 UTC (permalink / raw)
  To: Ethan Chen
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé

On Wed, Oct 25, 2023 at 01:14:26PM +0800, Ethan Chen wrote:
> Signed-off-by: Ethan Chen <ethan84@andestech.com>
> ---
>  system/physmem.c | 9 +++++++--
>  1 file changed, 7 insertions(+), 2 deletions(-)
> 
> diff --git a/system/physmem.c b/system/physmem.c
> index fc2b0fee01..53b6ab735c 100644
> --- a/system/physmem.c
> +++ b/system/physmem.c
> @@ -432,8 +432,13 @@ static MemoryRegionSection address_space_translate_iommu(IOMMUMemoryRegion *iomm
>              iommu_idx = imrc->attrs_to_index(iommu_mr, attrs);
>          }
>  
> -        iotlb = imrc->translate(iommu_mr, addr, is_write ?
> -                                IOMMU_WO : IOMMU_RO, iommu_idx);
> +        if (imrc->translate_size) {
> +            iotlb = imrc->translate_size(iommu_mr, addr, *plen_out, is_write ?
> +                                         IOMMU_WO : IOMMU_RO, iommu_idx);
> +        } else {
> +            iotlb = imrc->translate(iommu_mr, addr, is_write ?
> +                                    IOMMU_WO : IOMMU_RO, iommu_idx);
> +        }

Currently the translation size is encoded in iotlb.addr_mask.  Can riscv do
the same?

For example, lookup addr in match_entry_md() ranges, report size back into
iotlb.addr_mask, rather than enforcing *plen_out range always resides in
one translation only.

IMHO it's actually legal if *plen_out covers more than one IOMMU
translations.  QEMU memory core should have taken care of that by
separately translate the ranges and apply RW on top.  With current proposal
of translate_size() I think it'll fail instead, which is not wanted.

Thanks,

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-26  6:48       ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-26  6:48 UTC (permalink / raw)
  To: Peter Xu
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé

On Wed, Oct 25, 2023 at 11:14:42AM -0400, Peter Xu wrote:
> On Wed, Oct 25, 2023 at 01:14:26PM +0800, Ethan Chen wrote:
> > Signed-off-by: Ethan Chen <ethan84@andestech.com>
> > ---
> >  system/physmem.c | 9 +++++++--
> >  1 file changed, 7 insertions(+), 2 deletions(-)
> > 
> > diff --git a/system/physmem.c b/system/physmem.c
> > index fc2b0fee01..53b6ab735c 100644
> > --- a/system/physmem.c
> > +++ b/system/physmem.c
> > @@ -432,8 +432,13 @@ static MemoryRegionSection address_space_translate_iommu(IOMMUMemoryRegion *iomm
> >              iommu_idx = imrc->attrs_to_index(iommu_mr, attrs);
> >          }
> >  
> > -        iotlb = imrc->translate(iommu_mr, addr, is_write ?
> > -                                IOMMU_WO : IOMMU_RO, iommu_idx);
> > +        if (imrc->translate_size) {
> > +            iotlb = imrc->translate_size(iommu_mr, addr, *plen_out, is_write ?
> > +                                         IOMMU_WO : IOMMU_RO, iommu_idx);
> > +        } else {
> > +            iotlb = imrc->translate(iommu_mr, addr, is_write ?
> > +                                    IOMMU_WO : IOMMU_RO, iommu_idx);
> > +        }
> 
> Currently the translation size is encoded in iotlb.addr_mask.  Can riscv do
> the same?
Riscv do the same, so translation size may be reduced by iotlb.addr_mask.
>
> For example, lookup addr in match_entry_md() ranges, report size back into
> iotlb.addr_mask, rather than enforcing *plen_out range always resides in
> one translation only.
>
> IMHO it's actually legal if *plen_out covers more than one IOMMU
> translations.  QEMU memory core should have taken care of that by
> separately translate the ranges and apply RW on top.  With current proposal
> of translate_size() I think it'll fail instead, which is not wanted.
>
My target is to support IOPMP partially hit error. IOPMP checks whole memory 
access region is in the same entry. If not, reject the access instead of modify
the access size.

Because most of IOPMP permisson checking features can be implemented by 
current IOMMU class, so I add this function in IOMMU class. There may be 
other more suitable ways to support partially hit error.
> Thanks,
> 
> -- 
> Peter Xu
> 
Thanks,
Ethan Chen


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-26  6:48       ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-26  6:48 UTC (permalink / raw)
  To: Peter Xu
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé

On Wed, Oct 25, 2023 at 11:14:42AM -0400, Peter Xu wrote:
> On Wed, Oct 25, 2023 at 01:14:26PM +0800, Ethan Chen wrote:
> > Signed-off-by: Ethan Chen <ethan84@andestech.com>
> > ---
> >  system/physmem.c | 9 +++++++--
> >  1 file changed, 7 insertions(+), 2 deletions(-)
> > 
> > diff --git a/system/physmem.c b/system/physmem.c
> > index fc2b0fee01..53b6ab735c 100644
> > --- a/system/physmem.c
> > +++ b/system/physmem.c
> > @@ -432,8 +432,13 @@ static MemoryRegionSection address_space_translate_iommu(IOMMUMemoryRegion *iomm
> >              iommu_idx = imrc->attrs_to_index(iommu_mr, attrs);
> >          }
> >  
> > -        iotlb = imrc->translate(iommu_mr, addr, is_write ?
> > -                                IOMMU_WO : IOMMU_RO, iommu_idx);
> > +        if (imrc->translate_size) {
> > +            iotlb = imrc->translate_size(iommu_mr, addr, *plen_out, is_write ?
> > +                                         IOMMU_WO : IOMMU_RO, iommu_idx);
> > +        } else {
> > +            iotlb = imrc->translate(iommu_mr, addr, is_write ?
> > +                                    IOMMU_WO : IOMMU_RO, iommu_idx);
> > +        }
> 
> Currently the translation size is encoded in iotlb.addr_mask.  Can riscv do
> the same?
Riscv do the same, so translation size may be reduced by iotlb.addr_mask.
>
> For example, lookup addr in match_entry_md() ranges, report size back into
> iotlb.addr_mask, rather than enforcing *plen_out range always resides in
> one translation only.
>
> IMHO it's actually legal if *plen_out covers more than one IOMMU
> translations.  QEMU memory core should have taken care of that by
> separately translate the ranges and apply RW on top.  With current proposal
> of translate_size() I think it'll fail instead, which is not wanted.
>
My target is to support IOPMP partially hit error. IOPMP checks whole memory 
access region is in the same entry. If not, reject the access instead of modify
the access size.

Because most of IOPMP permisson checking features can be implemented by 
current IOMMU class, so I add this function in IOMMU class. There may be 
other more suitable ways to support partially hit error.
> Thanks,
> 
> -- 
> Peter Xu
> 
Thanks,
Ethan Chen


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/6] exec/memory: Introduce the translate_size function within the IOMMU class
@ 2023-10-26  7:14       ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-26  7:14 UTC (permalink / raw)
  To: David Hildenbrand
  Cc: qemu-devel, Paolo Bonzini, Peter Xu, Philippe Mathieu-Daudé

On Wed, Oct 25, 2023 at 04:56:22PM +0200, David Hildenbrand wrote:
> On 25.10.23 07:14, Ethan Chen wrote:
> > IOMMU have size information during translation.
> > 
> 
> Can you add some more information why we would want this and how the backend
> can do "better" things with the size at hand?
>
With size information, IOMMU can reject a memory access which is patially in 
valid region.

Currently translation function limit memory access size with a mask, so the 
valid part of access will success. My target is to detect partially hit and 
reject whole access. Translation function cannot detect partially hit because 
it lacks size information.
> Note that I was not CCed on the cover letter.
> 
> > Signed-off-by: Ethan Chen <ethan84@andestech.com>
> > ---
> >   include/exec/memory.h | 19 +++++++++++++++++++
> >   1 file changed, 19 insertions(+)
> > 
> > diff --git a/include/exec/memory.h b/include/exec/memory.h
> > index 9087d02769..5520b7c8c0 100644
> > --- a/include/exec/memory.h
> > +++ b/include/exec/memory.h
> > @@ -396,6 +396,25 @@ struct IOMMUMemoryRegionClass {
> >        */
> >       IOMMUTLBEntry (*translate)(IOMMUMemoryRegion *iommu, hwaddr addr,
> >                                  IOMMUAccessFlags flag, int iommu_idx);
> > +    /**
> > +     * @translate_size:
> > +     *
> > +     * Return a TLB entry that contains a given address and size.
> > +     *
> > +     * @iommu: the IOMMUMemoryRegion
> > +     *
> > +     * @hwaddr: address to be translated within the memory region
> > +     *
> > +     * @size: size to indicate the scope of the entire transaction
> > +     *
> > +     * @flag: requested access permission
> > +     *
> > +     * @iommu_idx: IOMMU index for the translation
> > +     */
> > +    IOMMUTLBEntry (*translate_size)(IOMMUMemoryRegion *iommu, hwaddr addr,
> > +                                    hwaddr size, IOMMUAccessFlags flag,
> > +                                    int iommu_idx);
> > +
> >       /**
> >        * @get_min_page_size:
> >        *
> 
> -- 
> Cheers,
> 
> David / dhildenb
> 
Thanks,
Ethan Chen


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/6] exec/memory: Introduce the translate_size function within the IOMMU class
@ 2023-10-26  7:14       ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-26  7:14 UTC (permalink / raw)
  To: David Hildenbrand
  Cc: qemu-devel, Paolo Bonzini, Peter Xu, Philippe Mathieu-Daudé

On Wed, Oct 25, 2023 at 04:56:22PM +0200, David Hildenbrand wrote:
> On 25.10.23 07:14, Ethan Chen wrote:
> > IOMMU have size information during translation.
> > 
> 
> Can you add some more information why we would want this and how the backend
> can do "better" things with the size at hand?
>
With size information, IOMMU can reject a memory access which is patially in 
valid region.

Currently translation function limit memory access size with a mask, so the 
valid part of access will success. My target is to detect partially hit and 
reject whole access. Translation function cannot detect partially hit because 
it lacks size information.
> Note that I was not CCed on the cover letter.
> 
> > Signed-off-by: Ethan Chen <ethan84@andestech.com>
> > ---
> >   include/exec/memory.h | 19 +++++++++++++++++++
> >   1 file changed, 19 insertions(+)
> > 
> > diff --git a/include/exec/memory.h b/include/exec/memory.h
> > index 9087d02769..5520b7c8c0 100644
> > --- a/include/exec/memory.h
> > +++ b/include/exec/memory.h
> > @@ -396,6 +396,25 @@ struct IOMMUMemoryRegionClass {
> >        */
> >       IOMMUTLBEntry (*translate)(IOMMUMemoryRegion *iommu, hwaddr addr,
> >                                  IOMMUAccessFlags flag, int iommu_idx);
> > +    /**
> > +     * @translate_size:
> > +     *
> > +     * Return a TLB entry that contains a given address and size.
> > +     *
> > +     * @iommu: the IOMMUMemoryRegion
> > +     *
> > +     * @hwaddr: address to be translated within the memory region
> > +     *
> > +     * @size: size to indicate the scope of the entire transaction
> > +     *
> > +     * @flag: requested access permission
> > +     *
> > +     * @iommu_idx: IOMMU index for the translation
> > +     */
> > +    IOMMUTLBEntry (*translate_size)(IOMMUMemoryRegion *iommu, hwaddr addr,
> > +                                    hwaddr size, IOMMUAccessFlags flag,
> > +                                    int iommu_idx);
> > +
> >       /**
> >        * @get_min_page_size:
> >        *
> 
> -- 
> Cheers,
> 
> David / dhildenb
> 
Thanks,
Ethan Chen


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 1/6] exec/memory: Introduce the translate_size function within the IOMMU class
  2023-10-26  7:14       ` Ethan Chen
  (?)
@ 2023-10-26  7:26       ` David Hildenbrand
  -1 siblings, 0 replies; 34+ messages in thread
From: David Hildenbrand @ 2023-10-26  7:26 UTC (permalink / raw)
  To: Ethan Chen
  Cc: qemu-devel, Paolo Bonzini, Peter Xu, Philippe Mathieu-Daudé

On 26.10.23 09:14, Ethan Chen wrote:
> On Wed, Oct 25, 2023 at 04:56:22PM +0200, David Hildenbrand wrote:
>> On 25.10.23 07:14, Ethan Chen wrote:
>>> IOMMU have size information during translation.
>>>
>>
>> Can you add some more information why we would want this and how the backend
>> can do "better" things with the size at hand?
>>
> With size information, IOMMU can reject a memory access which is patially in
> valid region.
> 
> Currently translation function limit memory access size with a mask, so the
> valid part of access will success. My target is to detect partially hit and
> reject whole access. Translation function cannot detect partially hit because
> it lacks size information.

Thanks; please add some of that to the patch description.

-- 
Cheers,

David / dhildenb



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 0/6] Support RISC-V IOPMP
@ 2023-10-26 12:02   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-26 12:02 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, David Hildenbrand


I found that after add size information it is still not enough for IOPMP to 
reject partially hit error. Access is separated in flatview_read_continue and
lost the start address information. I will fix it in next version. 

Address start, address end will be added to MemTxAttr, translate_size will 
beremoved and translate_attr which is have full MemTxAttr information will be 
added in next version patch.


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 0/6] Support RISC-V IOPMP
@ 2023-10-26 12:02   ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-26 12:02 UTC (permalink / raw)
  To: qemu-devel; +Cc: Peter Xu, David Hildenbrand


I found that after add size information it is still not enough for IOPMP to 
reject partially hit error. Access is separated in flatview_read_continue and
lost the start address information. I will fix it in next version. 

Address start, address end will be added to MemTxAttr, translate_size will 
beremoved and translate_attr which is have full MemTxAttr information will be 
added in next version patch.


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
  2023-10-26  6:48       ` Ethan Chen
  (?)
@ 2023-10-26 14:20       ` Peter Xu
  -1 siblings, 0 replies; 34+ messages in thread
From: Peter Xu @ 2023-10-26 14:20 UTC (permalink / raw)
  To: Ethan Chen
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé

On Thu, Oct 26, 2023 at 02:48:14PM +0800, Ethan Chen wrote:
> My target is to support IOPMP partially hit error. IOPMP checks whole memory 
> access region is in the same entry. If not, reject the access instead of modify
> the access size.

Could you elaborate why is that important?  In what use case?

Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
DMA request with range=[0, 8K].  Now my understanding is what you want to
achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
request.

Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
chunk can succeed then if it falls in 0-4K.  Some further explanation of
the failure use case could be helpful.

Thanks,

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-27  3:28       ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-27  3:28 UTC (permalink / raw)
  To: Peter Xu
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé

On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> Could you elaborate why is that important?  In what use case?
I was not involved in the formulation of the IOPMP specification, but I'll try
to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
PMP entry must match all bytes of an access, or the access fails."

> 
> Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> DMA request with range=[0, 8K].  Now my understanding is what you want to
> achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> request.
> 
> Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> chunk can succeed then if it falls in 0-4K.  Some further explanation of
> the failure use case could be helpful.

IOPMP can only detect partially hit in an access. DMA device will split a 
large DMA transfer to small DMA transfers base on target and DMA transfer 
width, so partially hit error only happens when an access cross the boundary.
But to ensure that an access is only within one entry is still important. 
For example, an entry may mean permission of a device memory region. We do 
not want to see one DMA transfer can access mutilple devices, although DMA 
have permissions from multiple entries.

Thanks,
Ethan Chen


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-27  3:28       ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-27  3:28 UTC (permalink / raw)
  To: Peter Xu
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé

On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> Could you elaborate why is that important?  In what use case?
I was not involved in the formulation of the IOPMP specification, but I'll try
to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
PMP entry must match all bytes of an access, or the access fails."

> 
> Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> DMA request with range=[0, 8K].  Now my understanding is what you want to
> achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> request.
> 
> Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> chunk can succeed then if it falls in 0-4K.  Some further explanation of
> the failure use case could be helpful.

IOPMP can only detect partially hit in an access. DMA device will split a 
large DMA transfer to small DMA transfers base on target and DMA transfer 
width, so partially hit error only happens when an access cross the boundary.
But to ensure that an access is only within one entry is still important. 
For example, an entry may mean permission of a device memory region. We do 
not want to see one DMA transfer can access mutilple devices, although DMA 
have permissions from multiple entries.

Thanks,
Ethan Chen


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
  2023-10-27  3:28       ` Ethan Chen
  (?)
@ 2023-10-27 16:02       ` Peter Xu
  2023-10-27 16:13         ` Peter Xu
  2023-10-30  6:00           ` Ethan Chen
  -1 siblings, 2 replies; 34+ messages in thread
From: Peter Xu @ 2023-10-27 16:02 UTC (permalink / raw)
  To: Ethan Chen
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé

On Fri, Oct 27, 2023 at 11:28:36AM +0800, Ethan Chen wrote:
> On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> > Could you elaborate why is that important?  In what use case?
> I was not involved in the formulation of the IOPMP specification, but I'll try
> to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
> PMP entry must match all bytes of an access, or the access fails."
> 
> > 
> > Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> > DMA request with range=[0, 8K].  Now my understanding is what you want to
> > achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> > request.
> > 
> > Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> > IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> > chunk can succeed then if it falls in 0-4K.  Some further explanation of
> > the failure use case could be helpful.
> 
> IOPMP can only detect partially hit in an access. DMA device will split a 
> large DMA transfer to small DMA transfers base on target and DMA transfer 
> width, so partially hit error only happens when an access cross the boundary.
> But to ensure that an access is only within one entry is still important. 
> For example, an entry may mean permission of a device memory region. We do 
> not want to see one DMA transfer can access mutilple devices, although DMA 
> have permissions from multiple entries.

I was expecting a DMA request can be fulfilled successfully as long as the
DMA translations are valid for the whole range of the request, even if the
requested range may include two separate translated targets or more, each
point to different places (either RAM, or other devicie's MMIO regions).

AFAIK currently QEMU memory model will automatically split that large
request into two or more smaller requests, and fulfill them separately by
two/more IOMMU translations, with its memory access dispatched to the
specific memory regions.

The example you provided doesn't seem to be RISCV specific.  Do you mean it
is a generic requirement from PCI/PCIe POV, or is it only a restriction of
IOPMP?  If it's a valid PCI restriction, does it mean that all the rest
IOMMU implementations in QEMU currently are broken?

[copy Michael and Igor]

Thanks,

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
  2023-10-27 16:02       ` Peter Xu
@ 2023-10-27 16:13         ` Peter Xu
  2023-10-30  6:00           ` Ethan Chen
  1 sibling, 0 replies; 34+ messages in thread
From: Peter Xu @ 2023-10-27 16:13 UTC (permalink / raw)
  To: Ethan Chen
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé,
	Michael S. Tsirkin, Igor Mammedov

Add cc list.

On Fri, Oct 27, 2023 at 12:02:24PM -0400, Peter Xu wrote:
> On Fri, Oct 27, 2023 at 11:28:36AM +0800, Ethan Chen wrote:
> > On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> > > Could you elaborate why is that important?  In what use case?
> > I was not involved in the formulation of the IOPMP specification, but I'll try
> > to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
> > PMP entry must match all bytes of an access, or the access fails."
> > 
> > > 
> > > Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> > > DMA request with range=[0, 8K].  Now my understanding is what you want to
> > > achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> > > request.
> > > 
> > > Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> > > IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> > > chunk can succeed then if it falls in 0-4K.  Some further explanation of
> > > the failure use case could be helpful.
> > 
> > IOPMP can only detect partially hit in an access. DMA device will split a 
> > large DMA transfer to small DMA transfers base on target and DMA transfer 
> > width, so partially hit error only happens when an access cross the boundary.
> > But to ensure that an access is only within one entry is still important. 
> > For example, an entry may mean permission of a device memory region. We do 
> > not want to see one DMA transfer can access mutilple devices, although DMA 
> > have permissions from multiple entries.
> 
> I was expecting a DMA request can be fulfilled successfully as long as the
> DMA translations are valid for the whole range of the request, even if the
> requested range may include two separate translated targets or more, each
> point to different places (either RAM, or other devicie's MMIO regions).
> 
> AFAIK currently QEMU memory model will automatically split that large
> request into two or more smaller requests, and fulfill them separately by
> two/more IOMMU translations, with its memory access dispatched to the
> specific memory regions.
> 
> The example you provided doesn't seem to be RISCV specific.  Do you mean it
> is a generic requirement from PCI/PCIe POV, or is it only a restriction of
> IOPMP?  If it's a valid PCI restriction, does it mean that all the rest
> IOMMU implementations in QEMU currently are broken?
> 
> [copy Michael and Igor]
> 
> Thanks,
> 
> -- 
> Peter Xu

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-30  6:00           ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-30  6:00 UTC (permalink / raw)
  To: Peter Xu
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé,
	Michael S. Tsirkin, Igor Mammedov

On Fri, Oct 27, 2023 at 12:13:50PM -0400, Peter Xu wrote:
> Add cc list.
> 
> On Fri, Oct 27, 2023 at 12:02:24PM -0400, Peter Xu wrote:
> > On Fri, Oct 27, 2023 at 11:28:36AM +0800, Ethan Chen wrote:
> > > On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> > > > Could you elaborate why is that important?  In what use case?
> > > I was not involved in the formulation of the IOPMP specification, but I'll try
> > > to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
> > > PMP entry must match all bytes of an access, or the access fails."
> > > 
> > > > 
> > > > Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> > > > DMA request with range=[0, 8K].  Now my understanding is what you want to
> > > > achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> > > > request.
> > > > 
> > > > Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> > > > IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> > > > chunk can succeed then if it falls in 0-4K.  Some further explanation of
> > > > the failure use case could be helpful.
> > > 
> > > IOPMP can only detect partially hit in an access. DMA device will split a 
> > > large DMA transfer to small DMA transfers base on target and DMA transfer 
> > > width, so partially hit error only happens when an access cross the boundary.
> > > But to ensure that an access is only within one entry is still important. 
> > > For example, an entry may mean permission of a device memory region. We do 
> > > not want to see one DMA transfer can access mutilple devices, although DMA 
> > > have permissions from multiple entries.
> > 
> > I was expecting a DMA request can be fulfilled successfully as long as the
> > DMA translations are valid for the whole range of the request, even if the
> > requested range may include two separate translated targets or more, each
> > point to different places (either RAM, or other devicie's MMIO regions).

IOPMP is used to check DMA translation is vaild or not. In IOPMP specification
, a translation access more than one entry is not invalid.
Though it is not recommand, user can create an IOPMP entry contains mutiple
places to make this kind translations valid.

> > 
> > AFAIK currently QEMU memory model will automatically split that large
> > request into two or more smaller requests, and fulfill them separately by
> > two/more IOMMU translations, with its memory access dispatched to the
> > specific memory regions.

Because of requests may be split, I need a method to take the original request
information to IOPMP.

> > 
> > The example you provided doesn't seem to be RISCV specific.  Do you mean it
> > is a generic requirement from PCI/PCIe POV, or is it only a restriction of
> > IOPMP?  If it's a valid PCI restriction, does it mean that all the rest
> > IOMMU implementations in QEMU currently are broken?
> > 

It only a restriction of IOPMP.

Thanks,
Ethan Chen



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-30  6:00           ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-30  6:00 UTC (permalink / raw)
  To: Peter Xu
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé,
	Michael S. Tsirkin, Igor Mammedov

On Fri, Oct 27, 2023 at 12:13:50PM -0400, Peter Xu wrote:
> Add cc list.
> 
> On Fri, Oct 27, 2023 at 12:02:24PM -0400, Peter Xu wrote:
> > On Fri, Oct 27, 2023 at 11:28:36AM +0800, Ethan Chen wrote:
> > > On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> > > > Could you elaborate why is that important?  In what use case?
> > > I was not involved in the formulation of the IOPMP specification, but I'll try
> > > to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
> > > PMP entry must match all bytes of an access, or the access fails."
> > > 
> > > > 
> > > > Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> > > > DMA request with range=[0, 8K].  Now my understanding is what you want to
> > > > achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> > > > request.
> > > > 
> > > > Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> > > > IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> > > > chunk can succeed then if it falls in 0-4K.  Some further explanation of
> > > > the failure use case could be helpful.
> > > 
> > > IOPMP can only detect partially hit in an access. DMA device will split a 
> > > large DMA transfer to small DMA transfers base on target and DMA transfer 
> > > width, so partially hit error only happens when an access cross the boundary.
> > > But to ensure that an access is only within one entry is still important. 
> > > For example, an entry may mean permission of a device memory region. We do 
> > > not want to see one DMA transfer can access mutilple devices, although DMA 
> > > have permissions from multiple entries.
> > 
> > I was expecting a DMA request can be fulfilled successfully as long as the
> > DMA translations are valid for the whole range of the request, even if the
> > requested range may include two separate translated targets or more, each
> > point to different places (either RAM, or other devicie's MMIO regions).

IOPMP is used to check DMA translation is vaild or not. In IOPMP specification
, a translation access more than one entry is not invalid.
Though it is not recommand, user can create an IOPMP entry contains mutiple
places to make this kind translations valid.

> > 
> > AFAIK currently QEMU memory model will automatically split that large
> > request into two or more smaller requests, and fulfill them separately by
> > two/more IOMMU translations, with its memory access dispatched to the
> > specific memory regions.

Because of requests may be split, I need a method to take the original request
information to IOPMP.

> > 
> > The example you provided doesn't seem to be RISCV specific.  Do you mean it
> > is a generic requirement from PCI/PCIe POV, or is it only a restriction of
> > IOPMP?  If it's a valid PCI restriction, does it mean that all the rest
> > IOMMU implementations in QEMU currently are broken?
> > 

It only a restriction of IOPMP.

Thanks,
Ethan Chen



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
  2023-10-30  6:00           ` Ethan Chen
  (?)
@ 2023-10-30 15:02           ` Peter Xu
  2023-10-31  8:52               ` Ethan Chen
  -1 siblings, 1 reply; 34+ messages in thread
From: Peter Xu @ 2023-10-30 15:02 UTC (permalink / raw)
  To: Ethan Chen
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé,
	Michael S. Tsirkin, Igor Mammedov

On Mon, Oct 30, 2023 at 02:00:54PM +0800, Ethan Chen wrote:
> On Fri, Oct 27, 2023 at 12:13:50PM -0400, Peter Xu wrote:
> > Add cc list.
> > 
> > On Fri, Oct 27, 2023 at 12:02:24PM -0400, Peter Xu wrote:
> > > On Fri, Oct 27, 2023 at 11:28:36AM +0800, Ethan Chen wrote:
> > > > On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> > > > > Could you elaborate why is that important?  In what use case?
> > > > I was not involved in the formulation of the IOPMP specification, but I'll try
> > > > to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
> > > > PMP entry must match all bytes of an access, or the access fails."
> > > > 
> > > > > 
> > > > > Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> > > > > DMA request with range=[0, 8K].  Now my understanding is what you want to
> > > > > achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> > > > > request.
> > > > > 
> > > > > Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> > > > > IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> > > > > chunk can succeed then if it falls in 0-4K.  Some further explanation of
> > > > > the failure use case could be helpful.
> > > > 
> > > > IOPMP can only detect partially hit in an access. DMA device will split a 
> > > > large DMA transfer to small DMA transfers base on target and DMA transfer 
> > > > width, so partially hit error only happens when an access cross the boundary.
> > > > But to ensure that an access is only within one entry is still important. 
> > > > For example, an entry may mean permission of a device memory region. We do 
> > > > not want to see one DMA transfer can access mutilple devices, although DMA 
> > > > have permissions from multiple entries.
> > > 
> > > I was expecting a DMA request can be fulfilled successfully as long as the
> > > DMA translations are valid for the whole range of the request, even if the
> > > requested range may include two separate translated targets or more, each
> > > point to different places (either RAM, or other devicie's MMIO regions).
> 
> IOPMP is used to check DMA translation is vaild or not. In IOPMP specification
> , a translation access more than one entry is not invalid.
> Though it is not recommand, user can create an IOPMP entry contains mutiple
> places to make this kind translations valid.
> 
> > > 
> > > AFAIK currently QEMU memory model will automatically split that large
> > > request into two or more smaller requests, and fulfill them separately by
> > > two/more IOMMU translations, with its memory access dispatched to the
> > > specific memory regions.
> 
> Because of requests may be split, I need a method to take the original request
> information to IOPMP.

I'm not sure whether translate() is the "original request" either.  The
problem is QEMU can split the request for various reasons already, afaict.

For example, address_space_translate_internal() has this:

    if (memory_region_is_ram(mr)) {
        diff = int128_sub(section->size, int128_make64(addr));
        *plen = int128_get64(int128_min(diff, int128_make64(*plen)));
    }

Which can already shrink the request size from the caller before reaching
translate().  So the length passed into translate() can already be
modified.

Another thing is, we have two other common call sites for translate():

        memory_region_iommu_replay
        address_space_translate_for_iotlb

I'm not sure whether you've looked into them and think they don't need to
be trapped: at least memory_region_iommu_replay() looks all fine in this
regard because it always translate in min page size granule.  But I think
the restriction should apply to all translate()s.

translate_size() is weird on its own. If the only purpose is to pass the
length into translate(), another option is to add that parameter into
current translate(), allowing the implementation to ignore it.  I think
that'll be better, but even if so, I'm not 100% sure it'll always do what
you wanted as discussed above.

Thanks,

-- 
Peter Xu



^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-31  8:52               ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen via @ 2023-10-31  8:52 UTC (permalink / raw)
  To: Peter Xu
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé,
	Michael S. Tsirkin, Igor Mammedov

On Mon, Oct 30, 2023 at 11:02:30AM -0400, Peter Xu wrote:
> On Mon, Oct 30, 2023 at 02:00:54PM +0800, Ethan Chen wrote:
> > On Fri, Oct 27, 2023 at 12:13:50PM -0400, Peter Xu wrote:
> > > Add cc list.
> > > 
> > > On Fri, Oct 27, 2023 at 12:02:24PM -0400, Peter Xu wrote:
> > > > On Fri, Oct 27, 2023 at 11:28:36AM +0800, Ethan Chen wrote:
> > > > > On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> > > > > > Could you elaborate why is that important?  In what use case?
> > > > > I was not involved in the formulation of the IOPMP specification, but I'll try
> > > > > to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
> > > > > PMP entry must match all bytes of an access, or the access fails."
> > > > > 
> > > > > > 
> > > > > > Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> > > > > > DMA request with range=[0, 8K].  Now my understanding is what you want to
> > > > > > achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> > > > > > request.
> > > > > > 
> > > > > > Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> > > > > > IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> > > > > > chunk can succeed then if it falls in 0-4K.  Some further explanation of
> > > > > > the failure use case could be helpful.
> > > > > 
> > > > > IOPMP can only detect partially hit in an access. DMA device will split a 
> > > > > large DMA transfer to small DMA transfers base on target and DMA transfer 
> > > > > width, so partially hit error only happens when an access cross the boundary.
> > > > > But to ensure that an access is only within one entry is still important. 
> > > > > For example, an entry may mean permission of a device memory region. We do 
> > > > > not want to see one DMA transfer can access mutilple devices, although DMA 
> > > > > have permissions from multiple entries.
> > > > 
> > > > I was expecting a DMA request can be fulfilled successfully as long as the
> > > > DMA translations are valid for the whole range of the request, even if the
> > > > requested range may include two separate translated targets or more, each
> > > > point to different places (either RAM, or other devicie's MMIO regions).
> > 
> > IOPMP is used to check DMA translation is vaild or not. In IOPMP specification
> > , a translation access more than one entry is not invalid.
> > Though it is not recommand, user can create an IOPMP entry contains mutiple
> > places to make this kind translations valid.
> > 
> > > > 
> > > > AFAIK currently QEMU memory model will automatically split that large
> > > > request into two or more smaller requests, and fulfill them separately by
> > > > two/more IOMMU translations, with its memory access dispatched to the
> > > > specific memory regions.
> > 
> > Because of requests may be split, I need a method to take the original request
> > information to IOPMP.
> 
> I'm not sure whether translate() is the "original request" either.  The
> problem is QEMU can split the request for various reasons already, afaict.
> 
> For example, address_space_translate_internal() has this:
> 
>     if (memory_region_is_ram(mr)) {
>         diff = int128_sub(section->size, int128_make64(addr));
>         *plen = int128_get64(int128_min(diff, int128_make64(*plen)));
>     }
> 
> Which can already shrink the request size from the caller before reaching
> translate().  So the length passed into translate() can already be
> modified.
> 
> Another thing is, we have two other common call sites for translate():
> 
>         memory_region_iommu_replay
>         address_space_translate_for_iotlb
> 
> I'm not sure whether you've looked into them and think they don't need to
> be trapped: at least memory_region_iommu_replay() looks all fine in this
> regard because it always translate in min page size granule.  But I think
> the restriction should apply to all translate()s.
> 
> translate_size() is weird on its own. If the only purpose is to pass the
> length into translate(), another option is to add that parameter into
> current translate(), allowing the implementation to ignore it.  I think
> that'll be better, but even if so, I'm not 100% sure it'll always do what
> you wanted as discussed above.

It seems that there are too many things that have not been considered in my 
current method. I am doing the revision that no new translation function but 
adding start address and end address to MemTxAttrs. 

Since attrs_to_index() only return one interger, IOPMP attrs_to_index() will 
copy the address range to its device state and then handle the translate(). 

Thanks,
Ethan Chen


^ permalink raw reply	[flat|nested] 34+ messages in thread

* Re: [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented
@ 2023-10-31  8:52               ` Ethan Chen
  0 siblings, 0 replies; 34+ messages in thread
From: Ethan Chen @ 2023-10-31  8:52 UTC (permalink / raw)
  To: Peter Xu
  Cc: qemu-devel, Paolo Bonzini, David Hildenbrand,
	Philippe Mathieu-Daudé,
	Michael S. Tsirkin, Igor Mammedov

On Mon, Oct 30, 2023 at 11:02:30AM -0400, Peter Xu wrote:
> On Mon, Oct 30, 2023 at 02:00:54PM +0800, Ethan Chen wrote:
> > On Fri, Oct 27, 2023 at 12:13:50PM -0400, Peter Xu wrote:
> > > Add cc list.
> > > 
> > > On Fri, Oct 27, 2023 at 12:02:24PM -0400, Peter Xu wrote:
> > > > On Fri, Oct 27, 2023 at 11:28:36AM +0800, Ethan Chen wrote:
> > > > > On Thu, Oct 26, 2023 at 10:20:41AM -0400, Peter Xu wrote:
> > > > > > Could you elaborate why is that important?  In what use case?
> > > > > I was not involved in the formulation of the IOPMP specification, but I'll try
> > > > > to explain my perspective. IOPMP use the same the idea as PMP. "The matching 
> > > > > PMP entry must match all bytes of an access, or the access fails."
> > > > > 
> > > > > > 
> > > > > > Consider IOVA mapped for address range iova=[0, 4K] only, here we have a
> > > > > > DMA request with range=[0, 8K].  Now my understanding is what you want to
> > > > > > achieve is don't trigger the DMA to [0, 4K] and fail the whole [0, 8K]
> > > > > > request.
> > > > > > 
> > > > > > Can we just fail at the latter DMA [4K, 8K] when it happens?  After all,
> > > > > > IIUC a device can split the 0-8K DMA into two smaller DMAs, then the 1st
> > > > > > chunk can succeed then if it falls in 0-4K.  Some further explanation of
> > > > > > the failure use case could be helpful.
> > > > > 
> > > > > IOPMP can only detect partially hit in an access. DMA device will split a 
> > > > > large DMA transfer to small DMA transfers base on target and DMA transfer 
> > > > > width, so partially hit error only happens when an access cross the boundary.
> > > > > But to ensure that an access is only within one entry is still important. 
> > > > > For example, an entry may mean permission of a device memory region. We do 
> > > > > not want to see one DMA transfer can access mutilple devices, although DMA 
> > > > > have permissions from multiple entries.
> > > > 
> > > > I was expecting a DMA request can be fulfilled successfully as long as the
> > > > DMA translations are valid for the whole range of the request, even if the
> > > > requested range may include two separate translated targets or more, each
> > > > point to different places (either RAM, or other devicie's MMIO regions).
> > 
> > IOPMP is used to check DMA translation is vaild or not. In IOPMP specification
> > , a translation access more than one entry is not invalid.
> > Though it is not recommand, user can create an IOPMP entry contains mutiple
> > places to make this kind translations valid.
> > 
> > > > 
> > > > AFAIK currently QEMU memory model will automatically split that large
> > > > request into two or more smaller requests, and fulfill them separately by
> > > > two/more IOMMU translations, with its memory access dispatched to the
> > > > specific memory regions.
> > 
> > Because of requests may be split, I need a method to take the original request
> > information to IOPMP.
> 
> I'm not sure whether translate() is the "original request" either.  The
> problem is QEMU can split the request for various reasons already, afaict.
> 
> For example, address_space_translate_internal() has this:
> 
>     if (memory_region_is_ram(mr)) {
>         diff = int128_sub(section->size, int128_make64(addr));
>         *plen = int128_get64(int128_min(diff, int128_make64(*plen)));
>     }
> 
> Which can already shrink the request size from the caller before reaching
> translate().  So the length passed into translate() can already be
> modified.
> 
> Another thing is, we have two other common call sites for translate():
> 
>         memory_region_iommu_replay
>         address_space_translate_for_iotlb
> 
> I'm not sure whether you've looked into them and think they don't need to
> be trapped: at least memory_region_iommu_replay() looks all fine in this
> regard because it always translate in min page size granule.  But I think
> the restriction should apply to all translate()s.
> 
> translate_size() is weird on its own. If the only purpose is to pass the
> length into translate(), another option is to add that parameter into
> current translate(), allowing the implementation to ignore it.  I think
> that'll be better, but even if so, I'm not 100% sure it'll always do what
> you wanted as discussed above.

It seems that there are too many things that have not been considered in my 
current method. I am doing the revision that no new translation function but 
adding start address and end address to MemTxAttrs. 

Since attrs_to_index() only return one interger, IOPMP attrs_to_index() will 
copy the address range to its device state and then handle the translate(). 

Thanks,
Ethan Chen


^ permalink raw reply	[flat|nested] 34+ messages in thread

end of thread, other threads:[~2023-10-31  8:54 UTC | newest]

Thread overview: 34+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-10-25  5:14 [PATCH 0/6] Support RISC-V IOPMP Ethan Chen via
2023-10-25  5:14 ` Ethan Chen
2023-10-25  5:14 ` [PATCH 1/6] exec/memory: Introduce the translate_size function within the IOMMU class Ethan Chen via
2023-10-25  5:14   ` Ethan Chen
2023-10-25 14:56   ` David Hildenbrand
2023-10-26  7:14     ` Ethan Chen via
2023-10-26  7:14       ` Ethan Chen
2023-10-26  7:26       ` David Hildenbrand
2023-10-25  5:14 ` [PATCH 2/6] system/physmem: IOMMU: Invoke the translate_size function if it is implemented Ethan Chen via
2023-10-25  5:14   ` Ethan Chen
2023-10-25 15:14   ` Peter Xu
2023-10-26  6:48     ` Ethan Chen via
2023-10-26  6:48       ` Ethan Chen
2023-10-26 14:20       ` Peter Xu
2023-10-27  3:28     ` Ethan Chen via
2023-10-27  3:28       ` Ethan Chen
2023-10-27 16:02       ` Peter Xu
2023-10-27 16:13         ` Peter Xu
2023-10-30  6:00         ` Ethan Chen via
2023-10-30  6:00           ` Ethan Chen
2023-10-30 15:02           ` Peter Xu
2023-10-31  8:52             ` Ethan Chen via
2023-10-31  8:52               ` Ethan Chen
2023-10-25  5:14 ` [PATCH 3/6] exec/memattrs: Add iopmp source id to MemTxAttrs Ethan Chen via
2023-10-25  5:14   ` Ethan Chen
2023-10-25  5:14 ` [PATCH 4/6] Add RISC-V IOPMP support Ethan Chen via
2023-10-25  5:14   ` Ethan Chen
2023-10-25  5:14 ` [PATCH 5/6] hw/dma: Add Andes ATCDMAC300 support Ethan Chen via
2023-10-25  5:14   ` Ethan Chen
2023-10-25  5:14 ` [PATCH 6/6] hw/riscv/virt: Add IOPMP support Ethan Chen via
2023-10-25  5:14   ` Ethan Chen
2023-10-25  5:14   ` Ethan Chen via
2023-10-26 12:02 ` [PATCH 0/6] Support RISC-V IOPMP Ethan Chen via
2023-10-26 12:02   ` Ethan Chen

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.