[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: [PATCH v4 3/4] hw/dma: Add Andes ATCDMAC300 support
From: |
Alistair Francis |
Subject: |
Re: [PATCH v4 3/4] hw/dma: Add Andes ATCDMAC300 support |
Date: |
Mon, 18 Dec 2023 14:14:05 +1000 |
On Wed, Nov 22, 2023 at 3:35 PM Ethan Chen via <qemu-devel@nongnu.org> wrote:
>
> ATCDMAC300 is a direct memory access controller (DMAC) which transfers data
> efficiently between devices on the AMBA AXI4 bus.
>
> ATCDMAC300 supports up to 8 DMA channels. Each DMA channel provides a set of
> registers to describe the intended data transfers
>
> To support RISC-V IOPMP, a memory access device needs to
> - Support setup the connection to IOPMP
> - Support asynchronous I/O to handle stall transactions
> - Support transaction information (optional)
>
> To setup the connection to IOPMP, function atcdmac300_connect_iopmp is called.
> The iopmp_as and sid are needed, and transaction_info_sink is optional (null
> if
> it is not supported).
>
> To handle IOPMP stall transaction, this device uses asynchronous I/O by doing
> memory access in bottom half coroutine. If it receives an IOPMP stall, the
> coroutine yields to let the cpu execute then will retry at the bottom half
> called next time. You can set the iothread property to make the device run on
> iothread.
>
> To send transaction information to IOPMP streamsink, function
> transaction_info_push is called before memory access.
>
> 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..7db408aa54
> --- /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
Same comment about using trace here
> +
> +#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 streamsink is not connected */
> + 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->transaction_info_sink) {
> + transaction_info_push(s->transaction_info_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->transaction_info_sink) {
> + transaction_info_push(s->transaction_info_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->transaction_info_sink) {
> + transaction_info_push(s->transaction_info_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->transaction_info_sink) {
> + transaction_info_push(s->transaction_info_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;
This could use some more newlines to make it easier to read. There are
a few other places in the patch that could as well.
> +
> + 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;
Couldn't these be combined into a single `src_addr_ctl <= 2` ?
This logic is difficult to follow so anything that can simplify things
would be great
> + }
> + 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;
> + }
Same here
> + 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 &= ~value;
> + 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 *transaction_info_sink, uint32_t
> sid)
> +{
> + ATCDMAC300State *s = ATCDMAC300(dev);
> + s->iopmp_as = iopmp_as;
> + s->transaction_info_sink = transaction_info_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..5b62039e33
> --- /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 *transaction_info_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 *transaction_info_sink, uint32_t
> sid);
> +
> +#endif /* ATCDMAC300_H */
> --
> 2.34.1
>
>
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- Re: [PATCH v4 3/4] hw/dma: Add Andes ATCDMAC300 support,
Alistair Francis <=