Message ID | 20220428154348.26380-1-frank.chang@sifive.com (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | hw/dma: Add Xilinx AXI CDMA | expand |
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. Do you plan to submit a machine that uses this DMA? 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. Best 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 = le64_to_cpu(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 = cpu_to_le64(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 = deposit64(s->src, 0, 32, value); > + break; > + case R_SA_MSB: > + s->src = deposit64(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 = value; > + 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 > >
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? > > 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 Engine to 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 written before the DMA transfer is started if we can't write both TAILDESC_PNTR and TAILDESC_PNTR_MSB at the same time? 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? Regards, Frank Chang > > Best 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 = le64_to_cpu(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 = cpu_to_le64(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 = deposit64(s->src, 0, 32, value); >> + break; >> + case R_SA_MSB: >> + s->src = deposit64(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 = value; >> + 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 >> >>
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 Engine > to 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 written > before the DMA transfer is started if we can't write both TAILDESC_PNTR > and TAILDESC_PNTR_MSB > at 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, Edgar > > Regards, > Frank Chang > > >> >> Best 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 = le64_to_cpu(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 = cpu_to_le64(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 = deposit64(s->src, 0, 32, value); >>> + break; >>> + case R_SA_MSB: >>> + s->src = deposit64(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 = value; >>> + 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 >>> >>>
On Tue, May 3, 2022 at 5:35 PM Edgar E. Iglesias <edgar.iglesias@gmail.com> wrote: > 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. > Thanks Edgar, Do you think it's a good idea to add CDMA in xlnx-zynqmp.c? (Though I found GDMA and ADMA already exist) I'm not familiar with Xilinx's FPGA family, and there are lots of variants. Not sure which machine is the best one for me to add CDMA. > It would be great if you guys had a public RISCV design with the CDMA that > we could model. > I would love to, but unfortunately, we don't have the spec for this model publicly yet. > >> >> >>> >>> 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 Engine >> to 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 written >> before the DMA transfer is started if we can't write both TAILDESC_PNTR >> and TAILDESC_PNTR_MSB >> at 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 guess I missed the description: "the address space is 32 bits" for TAILDESC_PNTR register in the spec. I will fix it in my next version patchset. Regards, Frank Chang > > > >> >> 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, > Edgar > > > >> >> Regards, >> Frank Chang >> >> >>> >>> Best 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 = le64_to_cpu(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 = cpu_to_le64(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 = deposit64(s->src, 0, 32, value); >>>> + break; >>>> + case R_SA_MSB: >>>> + s->src = deposit64(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 = value; >>>> + 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 >>>> >>>>
On Tue, May 3, 2022 at 5:06 PM Frank Chang <frank.chang@sifive.com> wrote: > On Tue, May 3, 2022 at 5:35 PM Edgar E. Iglesias <edgar.iglesias@gmail.com> > wrote: > >> 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. >> > > Thanks Edgar, > > Do you think it's a good idea to add CDMA in xlnx-zynqmp.c? > (Though I found GDMA and ADMA already exist) > > I'm not familiar with Xilinx's FPGA family, and there are lots of variants. > Not sure which machine is the best one for me to add CDMA. > xlnx-zynqmp.c models the hardened logic of the ZynqMP, the GDMA and ADMA are hard logic but this CDMA is not. xlnx-zcu102.c models a board with a ZynqMP and off-chip peripherals (reuses xlnx-zynqmp.c) but without anything programmed into the PL (FPGA) parts. If there's some kind of public design (Demo, product, reference design, whatever) that uses the CDMA as a soft IP on the PL and that is somewhat documented, perhaps we could add a xlnx-zcu102-name-of-design.c or a versal-xyz.c to enable this. I don't know of any such design though, but I'll let you know if I find something. > > >> It would be great if you guys had a public RISCV design with the CDMA >> that we could model. >> > > I would love to, > but unfortunately, we don't have the spec for this model publicly yet. > Or we wait for your machine to become public and make a model of that.... > > >> >>> >>> >>>> >>>> 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 Engine >>> to 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 written >>> before the DMA transfer is started if we can't write both TAILDESC_PNTR >>> and TAILDESC_PNTR_MSB >>> at 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 guess I missed the description: "the address space is 32 bits" for > TAILDESC_PNTR register in the spec. > I will fix it in my next version patchset. > > Regards, > Frank Chang > > >> >> >> >>> >>> 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, >> Edgar >> >> >> >>> >>> Regards, >>> Frank Chang >>> >>> >>>> >>>> Best 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 = le64_to_cpu(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 = cpu_to_le64(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 = deposit64(s->src, 0, 32, value); >>>>> + break; >>>>> + case R_SA_MSB: >>>>> + s->src = deposit64(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 = value; >>>>> + 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 >>>>> >>>>>
On Tue, May 3, 2022 at 7:12 PM Edgar E. Iglesias <edgar.iglesias@gmail.com> wrote: > On Tue, May 3, 2022 at 5:06 PM Frank Chang <frank.chang@sifive.com> wrote: > >> On Tue, May 3, 2022 at 5:35 PM Edgar E. Iglesias < >> edgar.iglesias@gmail.com> wrote: >> >>> 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. >>> >> >> Thanks Edgar, >> >> Do you think it's a good idea to add CDMA in xlnx-zynqmp.c? >> (Though I found GDMA and ADMA already exist) >> >> I'm not familiar with Xilinx's FPGA family, and there are lots of >> variants. >> Not sure which machine is the best one for me to add CDMA. >> > > xlnx-zynqmp.c models the hardened logic of the ZynqMP, the GDMA and ADMA > are hard logic but this CDMA is not. > xlnx-zcu102.c models a board with a ZynqMP and off-chip peripherals > (reuses xlnx-zynqmp.c) but without anything programmed into the PL (FPGA) > parts. > > If there's some kind of public design (Demo, product, reference design, > whatever) that uses the CDMA as a soft IP on the PL and that is somewhat > documented, perhaps we could add a xlnx-zcu102-name-of-design.c or a > versal-xyz.c to enable this. I don't know of any such design though, but > I'll let you know if I find something. > Hi Frank, This could be something: https://xilinx.github.io/Embedded-Design-Tutorials/docs/2020.2/docs/Introduction/Zynq7000-EDT/6-using-hp-port.html A machine model would be based on hw/arm/xilinx_zynq.c. Best regards, Edgar > > >> >> >>> It would be great if you guys had a public RISCV design with the CDMA >>> that we could model. >>> >> >> I would love to, >> but unfortunately, we don't have the spec for this model publicly yet. >> > > Or we wait for your machine to become public and make a model of that.... > > > >> >> >>> >>>> >>>> >>>>> >>>>> 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 Engine >>>> to 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 >>>> written >>>> before the DMA transfer is started if we can't write both TAILDESC_PNTR >>>> and TAILDESC_PNTR_MSB >>>> at 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 guess I missed the description: "the address space is 32 bits" for >> TAILDESC_PNTR register in the spec. >> I will fix it in my next version patchset. >> >> Regards, >> Frank Chang >> >> >>> >>> >>> >>>> >>>> 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, >>> Edgar >>> >>> >>> >>>> >>>> Regards, >>>> Frank Chang >>>> >>>> >>>>> >>>>> Best 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 = le64_to_cpu(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 = cpu_to_le64(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 = deposit64(s->src, 0, 32, value); >>>>>> + break; >>>>>> + case R_SA_MSB: >>>>>> + s->src = deposit64(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 = value; >>>>>> + 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 >>>>>> >>>>>>
On Thu, May 5, 2022 at 4:50 AM Edgar E. Iglesias <edgar.iglesias@gmail.com> wrote: > > On Tue, May 3, 2022 at 7:12 PM Edgar E. Iglesias <edgar.iglesias@gmail.com> > wrote: > >> On Tue, May 3, 2022 at 5:06 PM Frank Chang <frank.chang@sifive.com> >> wrote: >> >>> On Tue, May 3, 2022 at 5:35 PM Edgar E. Iglesias < >>> edgar.iglesias@gmail.com> wrote: >>> >>>> 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. >>>> >>> >>> Thanks Edgar, >>> >>> Do you think it's a good idea to add CDMA in xlnx-zynqmp.c? >>> (Though I found GDMA and ADMA already exist) >>> >>> I'm not familiar with Xilinx's FPGA family, and there are lots of >>> variants. >>> Not sure which machine is the best one for me to add CDMA. >>> >> >> xlnx-zynqmp.c models the hardened logic of the ZynqMP, the GDMA and ADMA >> are hard logic but this CDMA is not. >> xlnx-zcu102.c models a board with a ZynqMP and off-chip peripherals >> (reuses xlnx-zynqmp.c) but without anything programmed into the PL (FPGA) >> parts. >> >> If there's some kind of public design (Demo, product, reference design, >> whatever) that uses the CDMA as a soft IP on the PL and that is somewhat >> documented, perhaps we could add a xlnx-zcu102-name-of-design.c or a >> versal-xyz.c to enable this. I don't know of any such design though, but >> I'll let you know if I find something. >> > > Hi Frank, > > This could be something: > > https://xilinx.github.io/Embedded-Design-Tutorials/docs/2020.2/docs/Introduction/Zynq7000-EDT/6-using-hp-port.html > > A machine model would be based on hw/arm/xilinx_zynq.c. > Thanks Edgar, I was also aware of Zynq-7000 series SoC. But the datasheet: https://docs.xilinx.com/v/u/en-US/ds190-Zynq-7000-Overview doesn't mention that the DMA it uses is CDMA. It is something interchangeable for Zynq-7000 series SoC? And from the header comment of hw/arm/xilinx_zynq.c, it says it models Xilinx Zynq Baseboard. I'm not familiar with Xilinx's products family. Do you know what are the differences between "Xilinx Zynq Baseboard" and "Zynq-7000 SoC"? Regards, Frank Chang > > Best regards, > Edgar > > > >> >> >>> >>> >>>> It would be great if you guys had a public RISCV design with the CDMA >>>> that we could model. >>>> >>> >>> I would love to, >>> but unfortunately, we don't have the spec for this model publicly yet. >>> >> >> Or we wait for your machine to become public and make a model of that.... >> >> >> >>> >>> >>>> >>>>> >>>>> >>>>>> >>>>>> 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 Engine >>>>> to 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 >>>>> written >>>>> before the DMA transfer is started if we can't write both >>>>> TAILDESC_PNTR and TAILDESC_PNTR_MSB >>>>> at 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 guess I missed the description: "the address space is 32 bits" for >>> TAILDESC_PNTR register in the spec. >>> I will fix it in my next version patchset. >>> >>> Regards, >>> Frank Chang >>> >>> >>>> >>>> >>>> >>>>> >>>>> 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, >>>> Edgar >>>> >>>> >>>> >>>>> >>>>> Regards, >>>>> Frank Chang >>>>> >>>>> >>>>>> >>>>>> Best 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 = le64_to_cpu(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 = cpu_to_le64(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 = deposit64(s->src, 0, 32, value); >>>>>>> + break; >>>>>>> + case R_SA_MSB: >>>>>>> + s->src = deposit64(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 = value; >>>>>>> + 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 >>>>>>> >>>>>>>
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 = le64_to_cpu(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 = cpu_to_le64(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 = deposit64(s->src, 0, 32, value); + break; + case R_SA_MSB: + s->src = deposit64(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 = value; + 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]; +};