On Mon, May 2, 2022 at 6:29 PM Edgar E. Iglesias <edgar.igles...@gmail.com> wrote:
> 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. > 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.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 >> >>