On Thu, Apr 28, 2022 at 5:43 PM <frank.ch...@sifive.com> wrote: > From: Frank Chang <frank.ch...@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.ch...@sifive.com> > Reviewed-by: Jim Shu <jim....@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.ch...@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.ch...@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 > >