[PATCH v3 3/4] hw/dma: Add Andes ATCDMAC300 support

Ethan Chen via posted 4 patches 1 year ago
Maintainers: Paolo Bonzini <pbonzini@redhat.com>, Palmer Dabbelt <palmer@dabbelt.com>, Alistair Francis <alistair.francis@wdc.com>, Bin Meng <bin.meng@windriver.com>, Weiwei Li <liwei1518@gmail.com>, Daniel Henrique Barboza <dbarboza@ventanamicro.com>, Liu Zhiwei <zhiwei_liu@linux.alibaba.com>
There is a newer version of this series
[PATCH v3 3/4] hw/dma: Add Andes ATCDMAC300 support
Posted by Ethan Chen via 1 year ago
Signed-off-by: Ethan Chen <ethan84@andestech.com>
---
 hw/dma/Kconfig              |   4 +
 hw/dma/atcdmac300.c         | 566 ++++++++++++++++++++++++++++++++++++
 hw/dma/meson.build          |   1 +
 include/hw/dma/atcdmac300.h | 180 ++++++++++++
 4 files changed, 751 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..a1d335b52f 100644
--- a/hw/dma/Kconfig
+++ b/hw/dma/Kconfig
@@ -30,3 +30,7 @@ config SIFIVE_PDMA
 config XLNX_CSU_DMA
     bool
     select REGISTER
+
+config ATCDMAC300
+    bool
+    select STREAM
diff --git a/hw/dma/atcdmac300.c b/hw/dma/atcdmac300.c
new file mode 100644
index 0000000000..bc6012f44e
--- /dev/null
+++ b/hw/dma/atcdmac300.c
@@ -0,0 +1,566 @@
+/*
+ * 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"
+#include "hw/stream.h"
+#include "hw/misc/riscv_iopmp_transaction_info.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 void transaction_info_push(StreamSink *sink, uint8_t *buf,
+                                bool eop)
+{
+    if (sink == NULL) {
+        /* Do nothing if address stream is not support*/
+        return;
+    }
+    if (eop) {
+        while (stream_push(sink, buf, sizeof(iopmp_transaction_info), true)
+               == 0) {
+            ;
+        }
+    } else {
+        while (stream_push(sink, buf, sizeof(iopmp_transaction_info), false)
+               == 0) {
+            ;
+        }
+    }
+}
+
+static MemTxResult dma_iopmp_read(ATCDMAC300State *s, hwaddr addr, void *buf,
+                                  hwaddr len,
+                                  iopmp_transaction_info *transaction)
+{
+    MemTxResult result;
+    if (s->iopmp_as) {
+        if (s->iopmp_address_sink) {
+            transaction_info_push(s->iopmp_address_sink,
+                                (uint8_t *)transaction, false);
+        }
+        MemTxAttrs dma_attrs = {.requester_id = s->sid};
+        result = address_space_rw(s->iopmp_as, addr, dma_attrs,
+                                buf, len, false);
+        if (s->iopmp_address_sink) {
+            transaction_info_push(s->iopmp_address_sink,
+                                (uint8_t *)transaction, true);
+            return result;
+        }
+    }
+    cpu_physical_memory_read(addr, buf, len);
+    return MEMTX_OK;
+}
+
+static MemTxResult dma_iopmp_write(ATCDMAC300State *s, hwaddr addr, void *buf,
+                                   hwaddr len,
+                                   iopmp_transaction_info *transaction)
+{
+    MemTxResult result = 0;
+    if (s->iopmp_as) {
+        if (s->iopmp_address_sink) {
+            transaction_info_push(s->iopmp_address_sink,
+                                (uint8_t *)transaction, false);
+        }
+        MemTxAttrs dma_attrs = {.requester_id = s->sid};
+        result = address_space_rw(s->iopmp_as, addr, dma_attrs,
+                                  buf, len, true);
+        if (s->iopmp_address_sink) {
+            transaction_info_push(s->iopmp_address_sink,
+                                (uint8_t *)transaction, true);
+            return result;
+        }
+    }
+    cpu_physical_memory_write(addr, buf, len);
+    return MEMTX_OK;
+}
+
+static void atcdmac300_co_run_channel(void *opaque, int ch)
+{
+    ATCDMAC300State *s = opaque;
+    int result;
+    uint64_t src_addr, dst_addr;
+    /* End address for AXI_BOUNDARY check */
+    uint64_t src_end_addr, dst_end_addr;
+    /* DMA register bit field */
+    uint32_t src_addr_ctl, dst_addr_ctl, int_tc_mask, int_err_mask,
+             int_abort_mask, burst_size, src_width, dst_width;
+    /* Internal computation */
+    uint32_t remain_size_byte, dst_remain_byte, burst_size_transfer,
+             src_burst_remain, src_width_byte, dst_width_byte,
+             burst_size_byte, dma_remain_transfer_size, buf_index;
+    uint32_t axi_src_len, axi_dst_len;
+    uint8_t buf[ATCDMAC300_MAX_BURST_SIZE * 32];
+    iopmp_transaction_info src_transaction, dst_transaction;
+    src_transaction.sid = s->sid;
+    dst_transaction.sid = s->sid;
+    if (((s->chan[ch].ChnCtrl >> CHAN_CTL_ENABLE) & 0x1) != 0x1) {
+        return;
+    }
+    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_byte = 1 << src_width;
+    dst_width_byte = 1 << dst_width;
+    dma_remain_transfer_size = s->chan[ch].ChnTranSize;
+    remain_size_byte = dma_remain_transfer_size * src_width_byte;
+    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;
+    burst_size_transfer = (1 << burst_size);
+    burst_size_byte = burst_size_transfer * src_width_byte;
+    if (remain_size_byte && burst_size < 11 &&
+        src_width < 6 && dst_width < 6 &&
+        (src_addr & (src_width_byte - 1)) == 0 &&
+        (dst_addr & (dst_width_byte - 1)) == 0 &&
+        (remain_size_byte & (dst_width_byte - 1)) == 0 &&
+        (burst_size_byte & (dst_width_byte - 1)) == 0) {
+        while (remain_size_byte > 0) {
+            if (s->ChAbort & (1 << ch)) {
+                /* check abort status before a dma brust start*/
+                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);
+                }
+                return;
+            }
+            int i;
+            src_burst_remain = MIN(burst_size_transfer,
+                                   dma_remain_transfer_size);
+            dst_remain_byte = src_burst_remain * src_width_byte;
+            buf_index = 0;
+            /* One DMA burst may need mutiple AXI bursts */
+            while (src_burst_remain) {
+                if (src_addr_ctl == 0) {
+                    axi_src_len = MIN(src_burst_remain,
+                                      AXI_BURST_INC_LEN_MAX + 1);
+                    src_end_addr = src_width_byte * axi_src_len + src_addr;
+                    if ((src_addr & AXI_BOUNDARY) !=
+                         (src_end_addr & AXI_BOUNDARY)) {
+                            src_end_addr &= AXI_BOUNDARY;
+                            axi_src_len = (src_end_addr - src_addr) /
+                                          src_width_byte;
+                        }
+                    /* Convert AXI signal to general IOPMP transaction */
+                    src_transaction.start_addr = src_addr;
+                    src_transaction.end_addr = src_end_addr - 1;
+                }
+                if (src_addr_ctl == 1) {
+                    /* AXI does not support decrement type, use fixed type */
+                    src_transaction.start_addr = src_addr;
+                    src_transaction.end_addr = src_addr + src_width_byte - 1;
+                }
+                if (src_addr_ctl == 2) {
+                    src_transaction.start_addr = src_addr;
+                    src_transaction.end_addr = src_addr + src_width_byte - 1;
+                }
+                memset(buf, 0, sizeof(buf));
+                /* src_burst */
+                for (i = 0; i < axi_src_len; i++) {
+                    if (src_addr_ctl == 1) {
+                        /* Change AXI addr for decrement address mode */
+                        src_transaction.start_addr = src_addr;
+                        src_transaction.end_addr = src_addr + src_width_byte
+                                                   - 1;
+                    }
+                    buf_index += src_width_byte;
+                    result = dma_iopmp_read(s, src_addr, &buf[buf_index],
+                                            src_width_byte, &src_transaction);
+                    while (result == MEMTX_IOPMP_STALL) {
+                        qemu_coroutine_yield();
+                        result = dma_iopmp_read(s, src_addr, &buf[buf_index],
+                                                src_width_byte,
+                                                &src_transaction);
+                    }
+                    if (result != MEMTX_OK) {
+                        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);
+                        }
+                        return;
+                    }
+                    if (src_addr_ctl == 0) {
+                        src_addr += src_width_byte;
+                    }
+                    if (src_addr_ctl == 1) {
+                        src_addr -= src_width_byte;
+                    }
+                }
+                src_burst_remain -= axi_src_len;
+                dma_remain_transfer_size -= axi_src_len;
+                remain_size_byte -= axi_src_len * src_width_byte;
+            }
+            buf_index = 0;
+            /* One src burst may need mutiple dst bursts*/
+            while (dst_remain_byte > 0) {
+                if (dst_addr_ctl == 0) {
+                    axi_dst_len =
+                        (dst_remain_byte / dst_width_byte);
+                    axi_dst_len = MIN(axi_dst_len,
+                                        AXI_BURST_INC_LEN_MAX + 1);
+                    dst_end_addr = dst_width_byte * axi_dst_len
+                                    + dst_addr;
+                        if ((dst_addr & AXI_BOUNDARY) !=
+                            (dst_end_addr & AXI_BOUNDARY)) {
+                            dst_end_addr &= AXI_BOUNDARY;
+                            axi_dst_len = (dst_end_addr - dst_addr) /
+                                          dst_width_byte;
+                        }
+                    dst_transaction.start_addr = dst_addr;
+                    dst_transaction.end_addr = dst_end_addr - 1;
+                }
+                if (dst_addr_ctl == 1) {
+                    dst_transaction.start_addr = dst_addr;
+                    dst_transaction.end_addr = dst_addr + dst_width_byte
+                                            - 1;
+                }
+                if (dst_addr_ctl == 2) {
+                    dst_transaction.start_addr = dst_addr;
+                    dst_transaction.end_addr = dst_addr + dst_width_byte
+                                            - 1;
+                }
+                for (i = 0; i < axi_dst_len; i++) {
+                    if (dst_addr_ctl == 1) {
+                        /* Change AXI addr for decrement address mode */
+                        dst_transaction.start_addr = dst_addr;
+                        dst_transaction.end_addr = dst_addr + dst_width_byte
+                                                   - 1;
+                    }
+                    buf_index += dst_width_byte;
+                    result = dma_iopmp_write(s, dst_addr, &buf[buf_index],
+                                             dst_width_byte, &dst_transaction);
+                    while (result == MEMTX_IOPMP_STALL) {
+                        qemu_coroutine_yield();
+                        result = dma_iopmp_write(s, dst_addr, &buf[buf_index],
+                                                 dst_width_byte,
+                                                 &dst_transaction);
+                    }
+                    if (result != MEMTX_OK) {
+                        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);
+                        }
+                        return;
+                    }
+                    if (dst_addr_ctl == 0) {
+                        dst_addr += dst_width_byte;
+                    }
+                    if (dst_addr_ctl == 1) {
+                        dst_addr -= dst_width_byte;
+                    }
+                }
+                dst_remain_byte -= dst_width_byte * axi_dst_len;
+            }
+        }
+        /* DMA transfer complete */
+        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);
+        }
+        return;
+    } 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);
+        }
+    }
+}
+
+static void atcdmac300_co_run(void *opaque)
+{
+
+    while (1) {
+        for (int ch = 0; ch < ATCDMAC300_MAX_CHAN; ch++) {
+            atcdmac300_co_run_channel(opaque, ch);
+            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(DeviceState *dev, AddressSpace *iopmp_as,
+                                 StreamSink *iopmp_address_sink, uint32_t sid)
+{
+    ATCDMAC300State *s = ATCDMAC300(dev);
+    s->iopmp_as = iopmp_as;
+    s->iopmp_address_sink = iopmp_address_sink;
+    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..4f798e2a68
--- /dev/null
+++ b/include/hw/dma/atcdmac300.h
@@ -0,0 +1,180 @@
+/*
+ * 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"
+#include "hw/stream.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 AXI_BURST_TYPE_FIX 0
+#define AXI_BURST_TYPE_INC 1
+#define AXI_BURST_INC_LEN_MAX 255
+#define AXI_BURST_FIX_LEN_MAX 15
+#define AXI_BOUNDARY 0x1000
+
+#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;
+    StreamSink *iopmp_address_sink;
+    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(DeviceState *dev, AddressSpace *iopmp_as,
+                              StreamSink *iopmp_address_sink, uint32_t sid);
+
+#endif /* ATCDMAC300_H */
-- 
2.34.1
Re: [PATCH v3 3/4] hw/dma: Add Andes ATCDMAC300 support
Posted by Alistair Francis 1 year ago
On Tue, Nov 14, 2023 at 7:49 PM Ethan Chen via <qemu-devel@nongnu.org> wrote:
>

What is an "ATCDMAC300"? Can you provide more context?

Alistair


> Signed-off-by: Ethan Chen <ethan84@andestech.com>
> ---
>  hw/dma/Kconfig              |   4 +
>  hw/dma/atcdmac300.c         | 566 ++++++++++++++++++++++++++++++++++++
>  hw/dma/meson.build          |   1 +
>  include/hw/dma/atcdmac300.h | 180 ++++++++++++
>  4 files changed, 751 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..a1d335b52f 100644
> --- a/hw/dma/Kconfig
> +++ b/hw/dma/Kconfig
> @@ -30,3 +30,7 @@ config SIFIVE_PDMA
>  config XLNX_CSU_DMA
>      bool
>      select REGISTER
> +
> +config ATCDMAC300
> +    bool
> +    select STREAM
> diff --git a/hw/dma/atcdmac300.c b/hw/dma/atcdmac300.c
> new file mode 100644
> index 0000000000..bc6012f44e
> --- /dev/null
> +++ b/hw/dma/atcdmac300.c
> @@ -0,0 +1,566 @@
> +/*
> + * 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"
> +#include "hw/stream.h"
> +#include "hw/misc/riscv_iopmp_transaction_info.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 void transaction_info_push(StreamSink *sink, uint8_t *buf,
> +                                bool eop)
> +{
> +    if (sink == NULL) {
> +        /* Do nothing if address stream is not support*/
> +        return;
> +    }
> +    if (eop) {
> +        while (stream_push(sink, buf, sizeof(iopmp_transaction_info), true)
> +               == 0) {
> +            ;
> +        }
> +    } else {
> +        while (stream_push(sink, buf, sizeof(iopmp_transaction_info), false)
> +               == 0) {
> +            ;
> +        }
> +    }
> +}
> +
> +static MemTxResult dma_iopmp_read(ATCDMAC300State *s, hwaddr addr, void *buf,
> +                                  hwaddr len,
> +                                  iopmp_transaction_info *transaction)
> +{
> +    MemTxResult result;
> +    if (s->iopmp_as) {
> +        if (s->iopmp_address_sink) {
> +            transaction_info_push(s->iopmp_address_sink,
> +                                (uint8_t *)transaction, false);
> +        }
> +        MemTxAttrs dma_attrs = {.requester_id = s->sid};
> +        result = address_space_rw(s->iopmp_as, addr, dma_attrs,
> +                                buf, len, false);
> +        if (s->iopmp_address_sink) {
> +            transaction_info_push(s->iopmp_address_sink,
> +                                (uint8_t *)transaction, true);
> +            return result;
> +        }
> +    }
> +    cpu_physical_memory_read(addr, buf, len);
> +    return MEMTX_OK;
> +}
> +
> +static MemTxResult dma_iopmp_write(ATCDMAC300State *s, hwaddr addr, void *buf,
> +                                   hwaddr len,
> +                                   iopmp_transaction_info *transaction)
> +{
> +    MemTxResult result = 0;
> +    if (s->iopmp_as) {
> +        if (s->iopmp_address_sink) {
> +            transaction_info_push(s->iopmp_address_sink,
> +                                (uint8_t *)transaction, false);
> +        }
> +        MemTxAttrs dma_attrs = {.requester_id = s->sid};
> +        result = address_space_rw(s->iopmp_as, addr, dma_attrs,
> +                                  buf, len, true);
> +        if (s->iopmp_address_sink) {
> +            transaction_info_push(s->iopmp_address_sink,
> +                                (uint8_t *)transaction, true);
> +            return result;
> +        }
> +    }
> +    cpu_physical_memory_write(addr, buf, len);
> +    return MEMTX_OK;
> +}
> +
> +static void atcdmac300_co_run_channel(void *opaque, int ch)
> +{
> +    ATCDMAC300State *s = opaque;
> +    int result;
> +    uint64_t src_addr, dst_addr;
> +    /* End address for AXI_BOUNDARY check */
> +    uint64_t src_end_addr, dst_end_addr;
> +    /* DMA register bit field */
> +    uint32_t src_addr_ctl, dst_addr_ctl, int_tc_mask, int_err_mask,
> +             int_abort_mask, burst_size, src_width, dst_width;
> +    /* Internal computation */
> +    uint32_t remain_size_byte, dst_remain_byte, burst_size_transfer,
> +             src_burst_remain, src_width_byte, dst_width_byte,
> +             burst_size_byte, dma_remain_transfer_size, buf_index;
> +    uint32_t axi_src_len, axi_dst_len;
> +    uint8_t buf[ATCDMAC300_MAX_BURST_SIZE * 32];
> +    iopmp_transaction_info src_transaction, dst_transaction;
> +    src_transaction.sid = s->sid;
> +    dst_transaction.sid = s->sid;
> +    if (((s->chan[ch].ChnCtrl >> CHAN_CTL_ENABLE) & 0x1) != 0x1) {
> +        return;
> +    }
> +    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_byte = 1 << src_width;
> +    dst_width_byte = 1 << dst_width;
> +    dma_remain_transfer_size = s->chan[ch].ChnTranSize;
> +    remain_size_byte = dma_remain_transfer_size * src_width_byte;
> +    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;
> +    burst_size_transfer = (1 << burst_size);
> +    burst_size_byte = burst_size_transfer * src_width_byte;
> +    if (remain_size_byte && burst_size < 11 &&
> +        src_width < 6 && dst_width < 6 &&
> +        (src_addr & (src_width_byte - 1)) == 0 &&
> +        (dst_addr & (dst_width_byte - 1)) == 0 &&
> +        (remain_size_byte & (dst_width_byte - 1)) == 0 &&
> +        (burst_size_byte & (dst_width_byte - 1)) == 0) {
> +        while (remain_size_byte > 0) {
> +            if (s->ChAbort & (1 << ch)) {
> +                /* check abort status before a dma brust start*/
> +                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);
> +                }
> +                return;
> +            }
> +            int i;
> +            src_burst_remain = MIN(burst_size_transfer,
> +                                   dma_remain_transfer_size);
> +            dst_remain_byte = src_burst_remain * src_width_byte;
> +            buf_index = 0;
> +            /* One DMA burst may need mutiple AXI bursts */
> +            while (src_burst_remain) {
> +                if (src_addr_ctl == 0) {
> +                    axi_src_len = MIN(src_burst_remain,
> +                                      AXI_BURST_INC_LEN_MAX + 1);
> +                    src_end_addr = src_width_byte * axi_src_len + src_addr;
> +                    if ((src_addr & AXI_BOUNDARY) !=
> +                         (src_end_addr & AXI_BOUNDARY)) {
> +                            src_end_addr &= AXI_BOUNDARY;
> +                            axi_src_len = (src_end_addr - src_addr) /
> +                                          src_width_byte;
> +                        }
> +                    /* Convert AXI signal to general IOPMP transaction */
> +                    src_transaction.start_addr = src_addr;
> +                    src_transaction.end_addr = src_end_addr - 1;
> +                }
> +                if (src_addr_ctl == 1) {
> +                    /* AXI does not support decrement type, use fixed type */
> +                    src_transaction.start_addr = src_addr;
> +                    src_transaction.end_addr = src_addr + src_width_byte - 1;
> +                }
> +                if (src_addr_ctl == 2) {
> +                    src_transaction.start_addr = src_addr;
> +                    src_transaction.end_addr = src_addr + src_width_byte - 1;
> +                }
> +                memset(buf, 0, sizeof(buf));
> +                /* src_burst */
> +                for (i = 0; i < axi_src_len; i++) {
> +                    if (src_addr_ctl == 1) {
> +                        /* Change AXI addr for decrement address mode */
> +                        src_transaction.start_addr = src_addr;
> +                        src_transaction.end_addr = src_addr + src_width_byte
> +                                                   - 1;
> +                    }
> +                    buf_index += src_width_byte;
> +                    result = dma_iopmp_read(s, src_addr, &buf[buf_index],
> +                                            src_width_byte, &src_transaction);
> +                    while (result == MEMTX_IOPMP_STALL) {
> +                        qemu_coroutine_yield();
> +                        result = dma_iopmp_read(s, src_addr, &buf[buf_index],
> +                                                src_width_byte,
> +                                                &src_transaction);
> +                    }
> +                    if (result != MEMTX_OK) {
> +                        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);
> +                        }
> +                        return;
> +                    }
> +                    if (src_addr_ctl == 0) {
> +                        src_addr += src_width_byte;
> +                    }
> +                    if (src_addr_ctl == 1) {
> +                        src_addr -= src_width_byte;
> +                    }
> +                }
> +                src_burst_remain -= axi_src_len;
> +                dma_remain_transfer_size -= axi_src_len;
> +                remain_size_byte -= axi_src_len * src_width_byte;
> +            }
> +            buf_index = 0;
> +            /* One src burst may need mutiple dst bursts*/
> +            while (dst_remain_byte > 0) {
> +                if (dst_addr_ctl == 0) {
> +                    axi_dst_len =
> +                        (dst_remain_byte / dst_width_byte);
> +                    axi_dst_len = MIN(axi_dst_len,
> +                                        AXI_BURST_INC_LEN_MAX + 1);
> +                    dst_end_addr = dst_width_byte * axi_dst_len
> +                                    + dst_addr;
> +                        if ((dst_addr & AXI_BOUNDARY) !=
> +                            (dst_end_addr & AXI_BOUNDARY)) {
> +                            dst_end_addr &= AXI_BOUNDARY;
> +                            axi_dst_len = (dst_end_addr - dst_addr) /
> +                                          dst_width_byte;
> +                        }
> +                    dst_transaction.start_addr = dst_addr;
> +                    dst_transaction.end_addr = dst_end_addr - 1;
> +                }
> +                if (dst_addr_ctl == 1) {
> +                    dst_transaction.start_addr = dst_addr;
> +                    dst_transaction.end_addr = dst_addr + dst_width_byte
> +                                            - 1;
> +                }
> +                if (dst_addr_ctl == 2) {
> +                    dst_transaction.start_addr = dst_addr;
> +                    dst_transaction.end_addr = dst_addr + dst_width_byte
> +                                            - 1;
> +                }
> +                for (i = 0; i < axi_dst_len; i++) {
> +                    if (dst_addr_ctl == 1) {
> +                        /* Change AXI addr for decrement address mode */
> +                        dst_transaction.start_addr = dst_addr;
> +                        dst_transaction.end_addr = dst_addr + dst_width_byte
> +                                                   - 1;
> +                    }
> +                    buf_index += dst_width_byte;
> +                    result = dma_iopmp_write(s, dst_addr, &buf[buf_index],
> +                                             dst_width_byte, &dst_transaction);
> +                    while (result == MEMTX_IOPMP_STALL) {
> +                        qemu_coroutine_yield();
> +                        result = dma_iopmp_write(s, dst_addr, &buf[buf_index],
> +                                                 dst_width_byte,
> +                                                 &dst_transaction);
> +                    }
> +                    if (result != MEMTX_OK) {
> +                        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);
> +                        }
> +                        return;
> +                    }
> +                    if (dst_addr_ctl == 0) {
> +                        dst_addr += dst_width_byte;
> +                    }
> +                    if (dst_addr_ctl == 1) {
> +                        dst_addr -= dst_width_byte;
> +                    }
> +                }
> +                dst_remain_byte -= dst_width_byte * axi_dst_len;
> +            }
> +        }
> +        /* DMA transfer complete */
> +        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);
> +        }
> +        return;
> +    } 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);
> +        }
> +    }
> +}
> +
> +static void atcdmac300_co_run(void *opaque)
> +{
> +
> +    while (1) {
> +        for (int ch = 0; ch < ATCDMAC300_MAX_CHAN; ch++) {
> +            atcdmac300_co_run_channel(opaque, ch);
> +            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(DeviceState *dev, AddressSpace *iopmp_as,
> +                                 StreamSink *iopmp_address_sink, uint32_t sid)
> +{
> +    ATCDMAC300State *s = ATCDMAC300(dev);
> +    s->iopmp_as = iopmp_as;
> +    s->iopmp_address_sink = iopmp_address_sink;
> +    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..4f798e2a68
> --- /dev/null
> +++ b/include/hw/dma/atcdmac300.h
> @@ -0,0 +1,180 @@
> +/*
> + * 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"
> +#include "hw/stream.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 AXI_BURST_TYPE_FIX 0
> +#define AXI_BURST_TYPE_INC 1
> +#define AXI_BURST_INC_LEN_MAX 255
> +#define AXI_BURST_FIX_LEN_MAX 15
> +#define AXI_BOUNDARY 0x1000
> +
> +#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;
> +    StreamSink *iopmp_address_sink;
> +    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(DeviceState *dev, AddressSpace *iopmp_as,
> +                              StreamSink *iopmp_address_sink, uint32_t sid);
> +
> +#endif /* ATCDMAC300_H */
> --
> 2.34.1
>
>