|
From: | Frank Chang |
Subject: | Re: [PATCH] hw/dma: Add Xilinx AXI CDMA |
Date: | Tue, 3 May 2022 23:06:24 +0800 |
On Tue, May 3, 2022 at 3:16 AM Frank Chang <frank.chang@sifive.com> wrote:On Mon, May 2, 2022 at 6:29 PM Edgar E. Iglesias <edgar.iglesias@gmail.com> wrote:On Thu, Apr 28, 2022 at 5:43 PM <frank.chang@sifive.com> wrote:From: Frank Chang <frank.chang@sifive.com>
Add Xilinx AXI CDMA model, which follows
AXI Central Direct Memory Access v4.1 spec:
https://docs.xilinx.com/v/u/en-US/pg034-axi-cdma
Supports both Simple DMA and Scatter Gather modes.Hi Frank,Thanks for modeling this! I have a couple of questions.Hi Edgar,Thanks for reviewing.Do you plan to submit a machine that uses this DMA?Currently, Xilinx CDMA is used in our internal platform only, which is not upstream.Do you have any suggestions for the existing machine that I can add Xilinx CDMA to?Or perhaps, ARM virt machine?If there's a reference design somewhere we could use we could potentially create a new zynqmp or versal based machine.
It would be great if you guys had a public RISCV design with the CDMA that we could model.
The CDMA has a 32-bit AXI4-Lite port for register accesses (see page 6 and 8 in the spec you referenced), so axicdma_ops.impl.max should be 4 and you shouldn't need the read/write q versions.Okay, that's something I was not aware of.However, I have a question regarding the 64-bit address space.For 64-bit address space, i.e. xlnx,addrwidth = 64.The CDMA spec says that:"TAILDESC_PNTR[_MSB] register causes the AXI CDMA SG Engineto start fetching descriptors starting from the CURDESC_PNTR register value."It seems that DMA will start the transfer if either TAILDESC_PNTR or TAILDESC_PNTR_MSB is written.Then how can we guarantee that the full 64-bit address pointer is writtenbefore the DMA transfer is started if we can't write both TAILDESC_PNTR and TAILDESC_PNTR_MSBat the same time?This is described on pages 25 and 26:"When the AXI CDMA is in SG Mode and the address space is 32 bits (CDMACR.SGMode = 1), a write by the software application to the TAILDESC_PNTR register causes the AXI CDMA SG Engine to start fetching descriptors"I.e TAILDESC_PNTR only starts the DMA if 32bit addresses have been selected.If 64bit addresses are selected, TAILDESC_PNTR_MSB starts the DMA:"When the AXI CDMA is in SG Mode, and the address space is more than 32 bits, (CDMACR.SGMode = 1), a write by the software application to the TAILDESC_PNTR_MSB register causes the AXI CDMA SG Engine to start fetching descriptors"
I'm also awarded that Xilinx CDMA Linux driver also has separate 32-bit writes for a 64-bit address.But wouldn't that cause, e.g. dmatest to be failed?The driver probably relies on the interconnect to down-size the 64bit access to 2x32bit ones. QEMU will do the same so this shouldn't be a problem.Best regards,EdgarRegards,Frank ChangBest regards,Edgar
Signed-off-by: Frank Chang <frank.chang@sifive.com>
Reviewed-by: Jim Shu <jim.shu@sifive.com>
---
hw/dma/meson.build | 2 +-
hw/dma/xilinx_axicdma.c | 792 ++++++++++++++++++++++++++++++++
include/hw/dma/xilinx_axicdma.h | 72 +++
3 files changed, 865 insertions(+), 1 deletion(-)
create mode 100644 hw/dma/xilinx_axicdma.c
create mode 100644 include/hw/dma/xilinx_axicdma.h
diff --git a/hw/dma/meson.build b/hw/dma/meson.build
index f3f0661bc3..85edf80b82 100644
--- a/hw/dma/meson.build
+++ b/hw/dma/meson.build
@@ -3,7 +3,7 @@ softmmu_ss.add(when: 'CONFIG_PL080', if_true: files('pl080.c'))
softmmu_ss.add(when: 'CONFIG_PL330', if_true: files('pl330.c'))
softmmu_ss.add(when: 'CONFIG_I82374', if_true: files('i82374.c'))
softmmu_ss.add(when: 'CONFIG_I8257', if_true: files('i8257.c'))
-softmmu_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('xilinx_axidma.c'))
+softmmu_ss.add(when: 'CONFIG_XILINX_AXI', if_true: files('xilinx_axidma.c', 'xilinx_axicdma.c'))
softmmu_ss.add(when: 'CONFIG_ZYNQ_DEVCFG', if_true: files('xlnx-zynq-devcfg.c'))
softmmu_ss.add(when: 'CONFIG_ETRAXFS', if_true: files('etraxfs_dma.c'))
softmmu_ss.add(when: 'CONFIG_STP2000', if_true: files('sparc32_dma.c'))
diff --git a/hw/dma/xilinx_axicdma.c b/hw/dma/xilinx_axicdma.c
new file mode 100644
index 0000000000..50aec3ec45
--- /dev/null
+++ b/hw/dma/xilinx_axicdma.c
@@ -0,0 +1,792 @@
+/*
+ * QEMU model of Xilinx AXI-CDMA block.
+ *
+ * Copyright (c) 2022 Frank Chang <frank.chang@sifive.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "qemu/osdep.h"
+#include "hw/hw.h"
+#include "hw/irq.h"
+#include "hw/ptimer.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "qapi/error.h"
+#include "qemu/log.h"
+#include "qemu/module.h"
+#include "qemu/timer.h"
+#include "qom/object.h"
+#include "sysemu/dma.h"
+#include "hw/dma/xilinx_axicdma.h"
+
+#define R_CDMACR 0x00
+#define R_CDMASR 0x04
+#define R_CURDESC 0x08
+#define R_CURDESC_MSB 0x0c
+#define R_TAILDESC 0x10
+#define R_TAILDESC_MSB 0x14
+#define R_SA 0x18
+#define R_SA_MSB 0x1c
+#define R_DA 0x20
+#define R_DA_MSB 0x24
+#define R_BTT 0x28
+
+#define R_MAX 0x30
+
+/* CDMACR */
+#define CDMACR_TAIL_PNTR_EN BIT(1)
+#define CDMACR_RESET BIT(2)
+#define CDMACR_SGMODE BIT(3)
+#define CDMACR_KEY_HOLE_READ BIT(4)
+#define CDMACR_KEY_HOLE_WRITE BIT(5)
+#define CDMACR_CYCLIC_BD_ENABLE BIT(6)
+#define CDMACR_IOC_IRQ_EN BIT(12)
+#define CDMACR_DLY_IRQ_EN BIT(13)
+#define CDMACR_ERR_IRQ_EN BIT(14)
+
+#define CDMACR_MASK 0xffff707c
+
+/* TailPntrEn = 1, IRQThreshold = 1. */
+#define CDMACR_DEFAULT 0x10002
+
+/* CDMASR */
+#define CDMASR_IDLE BIT(1)
+#define CDMASR_SG_INCLUD BIT(3)
+#define CDMASR_DMA_INT_ERR BIT(4)
+#define CDMASR_DMA_SLV_ERR BIT(5)
+#define CDMASR_DMA_DEC_ERR BIT(6)
+#define CDMASR_SG_INT_ERR BIT(8)
+#define CDMASR_SG_SLV_ERR BIT(9)
+#define CDMASR_SG_DEC_ERR BIT(10)
+#define CDMASR_IOC_IRQ BIT(12)
+#define CDMASR_DLY_IRQ BIT(13)
+#define CDMASR_ERR_IRQ BIT(14)
+
+#define CDMASR_IRQ_THRES_SHIFT 16
+#define CDMASR_IRQ_THRES_WIDTH 8
+#define CDMASR_IRQ_DELAY_SHIFT 24
+#define CDMASR_IRQ_DELAY_WIDTH 8
+
+#define CDMASR_IRQ_MASK (CDMASR_IOC_IRQ | \
+ CDMASR_DLY_IRQ | \
+ CDMASR_ERR_IRQ)
+
+/* Idle = 1, SGIncld = 1, IRQThresholdSts = 1. */
+#define CDMASR_DEFAULT 0x1000a
+
+#define CURDESC_MASK 0xffffffc0
+
+#define TAILDESC_MASK 0xffffffc0
+
+#define BTT_MAX_SIZE (1UL << 26)
+#define BTT_MASK (BTT_MAX_SIZE - 1)
+
+/* SDesc - Status */
+#define SDEC_STATUS_DMA_INT_ERR BIT(28)
+#define SDEC_STATUS_DMA_SLV_ERR BIT(29)
+#define SDEC_STATUS_DMA_DEC_ERR BIT(30)
+#define SDEC_STATUS_DMA_CMPLT BIT(31)
+
+
+static void axicdma_set_dma_err(XilinxAXICDMA *s, uint32_t err);
+static void axicdma_set_sg_dma_err(XilinxAXICDMA *s, uint32_t err, hwaddr addr);
+static void axicdma_set_sg_err(XilinxAXICDMA *s, uint32_t err);
+
+static void axicdma_update_irq(XilinxAXICDMA *s)
+{
+ uint32_t enable, pending;
+
+ enable = s->control & CDMASR_IRQ_MASK;
+ pending = s->status & CDMASR_IRQ_MASK;
+
+ qemu_set_irq(s->irq, !!(enable & pending));
+}
+
+static void axicdma_set_irq(XilinxAXICDMA *s, uint32_t irq, bool level)
+{
+ g_assert(irq == CDMASR_IOC_IRQ ||
+ irq == CDMASR_DLY_IRQ ||
+ irq == CDMASR_ERR_IRQ);
+
+ if (level) {
+ s->status |= irq;
+ } else {
+ s->status &= ~irq;
+ }
+
+ axicdma_update_irq(s);
+}
+
+static void axicdma_reload_complete_cnt(XilinxAXICDMA *s)
+{
+ s->complete_cnt = extract32(s->control, CDMASR_IRQ_THRES_SHIFT,
+ CDMASR_IRQ_THRES_WIDTH);
+}
+
+static void timer_hit(void *opaque)
+{
+ XilinxAXICDMA *s = XILINX_AXI_CDMA(opaque);
+
+ axicdma_set_irq(s, CDMASR_DLY_IRQ, true);
+ axicdma_reload_complete_cnt(s);
+}
+
+static bool sdesc_load(XilinxAXICDMA *s, hwaddr addr)
+{
+ SDesc *d = &s->sdesc;
+ MemTxResult ret;
+
+ ret = address_space_read(s->as, addr, MEMTXATTRS_UNSPECIFIED,
+ d, sizeof(SDesc));
+ if (ret != MEMTX_OK) {
+ axicdma_set_sg_err(s, CDMASR_SG_DEC_ERR);
+ return false;
+ }
+
+ /* Convert from LE into host endianness. */
+ d->nxtdesc = le64_to_cpu(d->nxtdesc);
+ d->src = ""> + d->dest = le64_to_cpu(d->dest);
+ d->control = le32_to_cpu(d->control);
+ d->status = le32_to_cpu(d->status);
+
+ return true;
+}
+
+static bool sdesc_store(XilinxAXICDMA *s, hwaddr addr)
+{
+ SDesc *d = &s->sdesc;
+ MemTxResult ret;
+
+ /* Convert from host endianness into LE. */
+ d->nxtdesc = cpu_to_le64(d->nxtdesc);
+ d->src = ""> + d->dest = cpu_to_le64(d->dest);
+ d->control = cpu_to_le32(d->control);
+ d->status = cpu_to_le32(d->status);
+
+ ret = address_space_write(s->as, addr, MEMTXATTRS_UNSPECIFIED,
+ d, sizeof(SDesc));
+ if (ret != MEMTX_OK) {
+ axicdma_set_sg_err(s, CDMASR_SG_DEC_ERR);
+ return false;
+ }
+
+ return true;
+}
+
+static void sdesc_complete(XilinxAXICDMA *s)
+{
+ uint32_t irq_delay = extract32(s->control, CDMASR_IRQ_DELAY_SHIFT,
+ CDMASR_IRQ_DELAY_WIDTH);
+
+ if (irq_delay) {
+ /* Restart the delayed timer. */
+ ptimer_transaction_begin(s->ptimer);
+ ptimer_stop(s->ptimer);
+ ptimer_set_count(s->ptimer, irq_delay);
+ ptimer_run(s->ptimer, 1);
+ ptimer_transaction_commit(s->ptimer);
+ }
+
+ s->complete_cnt--;
+
+ if (s->complete_cnt == 0) {
+ /* Raise the IOC irq. */
+ axicdma_set_irq(s, CDMASR_IOC_IRQ, true);
+ axicdma_reload_complete_cnt(s);
+ }
+}
+
+static inline bool axicdma_sgmode(XilinxAXICDMA *s)
+{
+ return !!(s->control & CDMACR_SGMODE);
+}
+
+static void axicdma_set_dma_err(XilinxAXICDMA *s, uint32_t err)
+{
+ g_assert(err == CDMASR_DMA_INT_ERR ||
+ err == CDMASR_DMA_SLV_ERR ||
+ err == CDMASR_DMA_DEC_ERR);
+
+ s->status |= err;
+ axicdma_set_irq(s, CDMASR_ERR_IRQ, true);
+}
+
+static void axicdma_set_sg_dma_err(XilinxAXICDMA *s, uint32_t err, hwaddr addr)
+{
+ g_assert(axicdma_sgmode(s));
+
+ axicdma_set_dma_err(s, err);
+
+ /*
+ * There are 24-bit shift between
+ * SDesc status bit and CDMACR.DMA_[INT|SLV|DEC]_ERR bit.
+ */
+ s->sdesc.status |= (err << 24);
+ sdesc_store(s, addr);
+}
+
+static void axicdma_set_sg_err(XilinxAXICDMA *s, uint32_t err)
+{
+ g_assert(err == CDMASR_SG_INT_ERR ||
+ err == CDMASR_SG_SLV_ERR ||
+ err == CDMASR_SG_DEC_ERR);
+
+ s->status |= err;
+ axicdma_set_irq(s, CDMASR_ERR_IRQ, true);
+}
+
+static bool axicdma_perform_dma(XilinxAXICDMA *s, hwaddr src, hwaddr dest,
+ uint32_t btt)
+{
+ uint32_t r_off = 0, w_off = 0;
+ uint32_t len;
+ MemTxResult ret;
+
+ while (btt > 0) {
+ len = MIN(btt, CDMA_BUF_SIZE);
+
+ ret = dma_memory_read(s->as, src + r_off, s->buf + r_off, len,
+ MEMTXATTRS_UNSPECIFIED);
+ if (ret != MEMTX_OK) {
+ return false;
+ }
+
+ ret = dma_memory_write(s->as, dest + w_off, s->buf + w_off, len,
+ MEMTXATTRS_UNSPECIFIED);
+ if (ret != MEMTX_OK) {
+ return false;
+ }
+
+ btt -= len;
+
+ if (!(s->control & CDMACR_KEY_HOLE_READ)) {
+ r_off += len;
+ }
+
+ if (!(s->control & CDMACR_KEY_HOLE_WRITE)) {
+ w_off += len;
+ }
+ }
+
+ return true;
+}
+
+static void axicdma_run_simple(XilinxAXICDMA *s)
+{
+ if (!s->btt) {
+ /* Value of zero BTT is not allowed. */
+ axicdma_set_dma_err(s, CDMASR_DMA_INT_ERR);
+ return;
+ }
+
+ if (!axicdma_perform_dma(s, s->src, s->dest, s->btt)) {
+ axicdma_set_dma_err(s, CDMASR_DMA_DEC_ERR);
+ return;
+ }
+
+ /* Generate IOC interrupt. */
+ axicdma_set_irq(s, CDMASR_IOC_IRQ, true);
+}
+
+static void axicdma_run_sgmode(XilinxAXICDMA *s)
+{
+ uint64_t pdesc;
+ uint32_t btt;
+
+ while (1) {
+ if (!sdesc_load(s, s->curdesc)) {
+ break;
+ }
+
+ if (s->sdesc.status & SDEC_STATUS_DMA_CMPLT) {
+ axicdma_set_sg_err(s, CDMASR_SG_INT_ERR);
+ break;
+ }
+
+ btt = s->sdesc.control & BTT_MASK;
+
+ if (btt == 0) {
+ /* Value of zero BTT is not allowed. */
+ axicdma_set_sg_err(s, CDMASR_SG_INT_ERR);
+ break;
+ }
+
+ if (!axicdma_perform_dma(s, s->sdesc.src, s->sdesc.dest, btt)) {
+ axicdma_set_sg_dma_err(s, CDMASR_DMA_DEC_ERR, s->curdesc);
+ break;
+ }
+
+ /* Update the descriptor. */
+ s->sdesc.status |= SDEC_STATUS_DMA_CMPLT;
+ sdesc_store(s, s->curdesc);
+ sdesc_complete(s);
+
+ /* Advance current descriptor pointer. */
+ pdesc = s->curdesc;
+ s->curdesc = s->sdesc.nxtdesc;
+
+ if (!(s->control & CDMACR_CYCLIC_BD_ENABLE) &&
+ pdesc == s->taildesc) {
+ /* Reach the tail descriptor. */
+ break;
+ }
+ }
+
+ /* Stop the delayed timer. */
+ ptimer_transaction_begin(s->ptimer);
+ ptimer_stop(s->ptimer);
+ ptimer_transaction_commit(s->ptimer);
+}
+
+static void axicdma_run(XilinxAXICDMA *s)
+{
+ bool sgmode = axicdma_sgmode(s);
+
+ /* Not idle. */
+ s->status &= ~CDMASR_IDLE;
+
+ if (!sgmode) {
+ axicdma_run_simple(s);
+ } else {
+ axicdma_run_sgmode(s);
+ }
+
+ /* Idle. */
+ s->status |= CDMASR_IDLE;
+}
+
+static void axicdma_reset(XilinxAXICDMA *s)
+{
+ s->control = CDMACR_DEFAULT;
+ s->status = CDMASR_DEFAULT;
+ s->complete_cnt = 1;
+ qemu_irq_lower(s->irq);
+}
+
+static void axicdma_write_control(XilinxAXICDMA *s, uint32_t value)
+{
+ if (value & CDMACR_RESET) {
+ axicdma_reset(s);
+ return;
+ }
+
+ /*
+ * The minimum setting for the threshold is 0x01.
+ * A write of 0x00 to CDMACR.IRQThreshold has no effect.
+ */
+ if (!extract32(value, CDMASR_IRQ_THRES_SHIFT, CDMASR_IRQ_THRES_WIDTH)) {
+ value = deposit32(value, CDMASR_IRQ_THRES_SHIFT, CDMASR_IRQ_THRES_WIDTH,
+ s->control);
+ }
+
+ /*
+ * AXI CDMA is built with SG enabled,
+ * tail pointer mode is always enabled.
+ */
+ s->control = (value & CDMACR_MASK) | CDMACR_TAIL_PNTR_EN;
+
+ if (!axicdma_sgmode(s)) {
+ /*
+ * CDMASR.Dly_Irq, CURDESC_PNTR, TAILDESC_PNTR are cleared
+ * when not in SGMode.
+ */
+ s->status &= ~CDMASR_DLY_IRQ;
+ s->curdesc = 0;
+ s->taildesc = 0;
+ }
+
+ axicdma_reload_complete_cnt(s);
+}
+
+static uint32_t axicdma_read_status(XilinxAXICDMA *s)
+{
+ uint32_t value = s->status;
+
+ value = deposit32(value, CDMASR_IRQ_THRES_SHIFT,
+ CDMASR_IRQ_THRES_WIDTH, s->complete_cnt);
+ value = deposit32(value, CDMASR_IRQ_DELAY_SHIFT,
+ CDMASR_IRQ_DELAY_WIDTH, ptimer_get_count(s->ptimer));
+
+ return value;
+}
+
+static void axicdma_write_status(XilinxAXICDMA *s, uint32_t value)
+{
+ /* Write 1s to clear interrupts. */
+ s->status &= ~(value & CDMASR_IRQ_MASK);
+ axicdma_update_irq(s);
+}
+
+static void axicdma_write_curdesc(XilinxAXICDMA *s, uint64_t value)
+{
+ /* Should be idle. */
+ g_assert(s->status & CDMASR_IDLE);
+
+ if (!axicdma_sgmode(s)) {
+ /* This register is cleared if SGMode = 0. */
+ return;
+ }
+
+ s->curdesc = value & CURDESC_MASK;
+}
+
+static void axicdma_write_taildesc(XilinxAXICDMA *s, uint64_t value)
+{
+ /* Should be idle. */
+ g_assert(s->status & CDMASR_IDLE);
+
+ if (!axicdma_sgmode(s)) {
+ /* This register is cleared if SGMode = 0. */
+ return;
+ }
+
+ s->taildesc = value & TAILDESC_MASK;
+
+ /* Kick-off CDMA. */
+ axicdma_run(s);
+}
+
+static void axicdma_write_btt(XilinxAXICDMA *s, uint64_t value)
+{
+ s->btt = value & BTT_MASK;
+
+ if (!axicdma_sgmode(s)) {
+ /* Kick-off CDMA. */
+ axicdma_run(s);
+ }
+}
+
+static uint32_t axicdma_readl(void *opaque, hwaddr addr, unsigned size)
+{
+ XilinxAXICDMA *s = opaque;
+ uint32_t value = 0;
+
+ if (s->addrwidth <= 32) {
+ switch (addr) {
+ case R_CURDESC_MSB:
+ case R_TAILDESC_MSB:
+ case R_SA_MSB:
+ case R_DA_MSB:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Invalid 32-bit read to 0x%" HWADDR_PRIX "\n",
+ __func__, addr);
+ return value;
+ }
+ }
+
+ switch (addr) {
+ case R_CDMACR:
+ value = s->control;
+ break;
+ case R_CDMASR:
+ value = axicdma_read_status(s);
+ break;
+ case R_CURDESC:
+ value = s->curdesc;
+ break;
+ case R_CURDESC_MSB:
+ value = extract64(s->curdesc, 32, 32);
+ break;
+ case R_TAILDESC:
+ value = s->taildesc;
+ break;
+ case R_TAILDESC_MSB:
+ value = extract64(s->taildesc, 32, 32);
+ break;
+ case R_SA:
+ value = s->src;
+ break;
+ case R_SA_MSB:
+ value = extract64(s->src, 32, 32);
+ break;
+ case R_DA:
+ value = s->dest;
+ break;
+ case R_DA_MSB:
+ value = extract64(s->dest, 32, 32);
+ break;
+ case R_BTT:
+ value = s->btt;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Invalid 32-bit read to 0x%" HWADDR_PRIX "\n",
+ __func__, addr);
+ }
+
+ return value;
+}
+
+static uint32_t axicdma_readq(void *opaque, hwaddr addr, unsigned size)
+{
+ XilinxAXICDMA *s = opaque;
+ uint64_t value = 0;
+
+ switch (addr) {
+ case R_CDMACR:
+ value = s->control;
+ break;
+ case R_CDMASR:
+ value = axicdma_read_status(s);
+ break;
+ case R_CURDESC:
+ value = s->curdesc;
+ break;
+ case R_TAILDESC:
+ value = s->taildesc;
+ break;
+ case R_SA:
+ value = s->src;
+ break;
+ case R_DA:
+ value = s->dest;
+ break;
+ case R_BTT:
+ value = s->btt;
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Invalid 64-bit read to 0x%" HWADDR_PRIX "\n",
+ __func__, addr);
+ }
+
+ return value;
+}
+
+static uint64_t axicdma_read(void *opaque, hwaddr addr, unsigned size)
+{
+ uint64_t value = 0;
+
+ switch (size) {
+ case 4:
+ value = axicdma_readl(opaque, addr, size);
+ break;
+ case 8:
+ value = axicdma_readq(opaque, addr, size);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Invalid read size %u to AXI-CDMA\n",
+ __func__, size);
+ }
+
+ return value;
+}
+
+static void axicdma_writel(void *opaque, hwaddr addr, uint32_t value,
+ unsigned size)
+{
+ XilinxAXICDMA *s = opaque;
+
+ if (s->addrwidth <= 32) {
+ switch (addr) {
+ case R_CURDESC_MSB:
+ case R_TAILDESC_MSB:
+ case R_SA_MSB:
+ case R_DA_MSB:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Invalid 32-bit write to 0x%" HWADDR_PRIX "\n",
+ __func__, addr);
+ return;
+ }
+ }
+
+ switch (addr) {
+ case R_CDMACR:
+ axicdma_write_control(s, value);
+ break;
+ case R_CDMASR:
+ axicdma_write_status(s, value);
+ break;
+ case R_CURDESC:
+ axicdma_write_curdesc(s, deposit64(s->curdesc, 0, 32, value));
+ break;
+ case R_CURDESC_MSB:
+ axicdma_write_curdesc(s, deposit64(s->curdesc, 32, 32, value));
+ break;
+ case R_TAILDESC:
+ axicdma_write_taildesc(s, deposit64(s->taildesc, 0, 32, value));
+ break;
+ case R_TAILDESC_MSB:
+ axicdma_write_taildesc(s, deposit64(s->taildesc, 32, 32, value));
+ break;
+ case R_SA:
+ s->src = "" 0, 32, value);
+ break;
+ case R_SA_MSB:
+ s->src = "" 32, 32, value);
+ break;
+ case R_DA:
+ s->dest = deposit64(s->dest, 0, 32, value);
+ break;
+ case R_DA_MSB:
+ s->dest = deposit64(s->dest, 32, 32, value);
+ break;
+ case R_BTT:
+ axicdma_write_btt(s, value);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Invalid 32-bit write to 0x%" HWADDR_PRIX "\n",
+ __func__, addr);
+ }
+}
+
+static void axicdma_writeq(void *opaque, hwaddr addr, uint64_t value,
+ unsigned size)
+{
+ XilinxAXICDMA *s = opaque;
+
+ switch (addr) {
+ case R_CDMACR:
+ axicdma_write_control(s, value);
+ break;
+ case R_CDMASR:
+ axicdma_write_status(s, value);
+ break;
+ case R_CURDESC:
+ axicdma_write_curdesc(s, value);
+ break;
+ case R_TAILDESC:
+ axicdma_write_taildesc(s, value);
+ break;
+ case R_SA:
+ s->src = ""> + break;
+ case R_DA:
+ s->dest = value;
+ break;
+ case R_BTT:
+ axicdma_write_btt(s, value);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Invalid 64-bit write to 0x%" HWADDR_PRIX "\n",
+ __func__, addr);
+ }
+}
+
+static void axicdma_write(void *opaque, hwaddr addr,
+ uint64_t value, unsigned size)
+{
+ switch (size) {
+ case 4:
+ axicdma_writel(opaque, addr, value, size);
+ break;
+ case 8:
+ axicdma_writeq(opaque, addr, value, size);
+ break;
+ default:
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Invalid write size %u to AXI-CDMA\n",
+ __func__, size);
+ }
+}
+
+static const MemoryRegionOps axicdma_ops = {
+ .read = axicdma_read,
+ .write = axicdma_write,
+ .endianness = DEVICE_NATIVE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 8
+ },
+ .impl = {
+ .min_access_size = 4,
+ .max_access_size = 8,
+ },
+};
+
+static void xilinx_axicdma_realize(DeviceState *dev, Error **errp)
+{
+ XilinxAXICDMA *s = XILINX_AXI_CDMA(dev);
+ SysBusDevice *sbd = SYS_BUS_DEVICE(dev);
+
+ memory_region_init_io(&s->mmio, OBJECT(dev), &axicdma_ops, s,
+ TYPE_XILINX_AXI_CDMA, R_MAX);
+ sysbus_init_mmio(sbd, &s->mmio);
+ sysbus_init_irq(sbd, &s->irq);
+
+ if (!s->dma_mr || s->dma_mr == get_system_memory()) {
+ /* Avoid creating new AddressSpace for system memory. */
+ s->as = &address_space_memory;
+ } else {
+ s->as = g_new0(AddressSpace, 1);
+ address_space_init(s->as, s->dma_mr, memory_region_name(s->dma_mr));
+ }
+
+ s->ptimer = ptimer_init(timer_hit, s, PTIMER_POLICY_DEFAULT);
+ ptimer_transaction_begin(s->ptimer);
+ ptimer_set_freq(s->ptimer, s->freqhz);
+ ptimer_transaction_commit(s->ptimer);
+}
+
+static void xilinx_axicdma_unrealize(DeviceState *dev)
+{
+ XilinxAXICDMA *s = XILINX_AXI_CDMA(dev);
+
+ if (s->ptimer) {
+ ptimer_free(s->ptimer);
+ }
+
+ if (s->as && s->dma_mr != get_system_memory()) {
+ g_free(s->as);
+ }
+}
+
+static Property axicdma_properties[] = {
+ DEFINE_PROP_UINT32("freqhz", XilinxAXICDMA, freqhz, 50000000),
+ DEFINE_PROP_INT32("addrwidth", XilinxAXICDMA, addrwidth, 64),
+ DEFINE_PROP_LINK("dma", XilinxAXICDMA, dma_mr,
+ TYPE_MEMORY_REGION, MemoryRegion *),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xilinx_axicdma_reset_enter(Object *obj, ResetType type)
+{
+ axicdma_reset(XILINX_AXI_CDMA(obj));
+}
+
+static void axicdma_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ResettableClass *rc = RESETTABLE_CLASS(klass);
+
+ dc->realize = xilinx_axicdma_realize;
+ dc->unrealize = xilinx_axicdma_unrealize;
+ device_class_set_props(dc, axicdma_properties);
+
+ rc->phases.enter = xilinx_axicdma_reset_enter;
+}
+
+static const TypeInfo axicdma_info = {
+ .name = TYPE_XILINX_AXI_CDMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XilinxAXICDMA),
+ .class_init = axicdma_class_init,
+};
+
+static void xilinx_axicdma_register_types(void)
+{
+ type_register_static(&axicdma_info);
+}
+
+type_init(xilinx_axicdma_register_types)
diff --git a/include/hw/dma/xilinx_axicdma.h b/include/hw/dma/xilinx_axicdma.h
new file mode 100644
index 0000000000..67b7cfce99
--- /dev/null
+++ b/include/hw/dma/xilinx_axicdma.h
@@ -0,0 +1,72 @@
+/*
+ * QEMU model of Xilinx AXI-CDMA block.
+ *
+ * Copyright (c) 2022 Frank Chang <frank.chang@sifive.com>.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "exec/hwaddr.h"
+#include "hw/ptimer.h"
+#include "hw/sysbus.h"
+#include "qom/object.h"
+#include "qemu/units.h"
+
+#define CDMA_BUF_SIZE (64 * KiB)
+
+typedef struct XilinxAXICDMA XilinxAXICDMA;
+
+#define TYPE_XILINX_AXI_CDMA "xlnx.axi-cdma"
+OBJECT_DECLARE_SIMPLE_TYPE(XilinxAXICDMA, XILINX_AXI_CDMA)
+
+/* Scatter Gather Transfer Descriptor */
+typedef struct SDesc {
+ uint64_t nxtdesc;
+ hwaddr src;
+ hwaddr dest;
+ uint32_t control;
+ uint32_t status;
+} SDesc;
+
+struct XilinxAXICDMA {
+ /*< private >*/
+ SysBusDevice parent_obj;
+
+ /*< public >*/
+ MemoryRegion mmio;
+ AddressSpace *as;
+ MemoryRegion *dma_mr;
+
+ /* Properties */
+ uint32_t control;
+ uint32_t status;
+ uint64_t curdesc;
+ uint64_t taildesc;
+ hwaddr src;
+ hwaddr dest;
+ uint32_t btt;
+
+ uint32_t freqhz;
+ int32_t addrwidth;
+ ptimer_state *ptimer;
+ SDesc sdesc;
+ qemu_irq irq;
+ uint16_t complete_cnt;
+ char buf[CDMA_BUF_SIZE];
+};
--
2.35.1
[Prev in Thread] | Current Thread | [Next in Thread] |