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. 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