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

Reply via email to