On Tue, May 3, 2022 at 3:16 AM Frank Chang <frank.ch...@sifive.com> wrote:

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

If there's a reference design somewhere we could use we could potentially
create a new zynqmp or versal based machine.
It would be great if you guys had a public RISCV design with the CDMA that
we could model.


>
>
>>
>> The CDMA has a 32-bit AXI4-Lite port for register accesses (see page 6
>> and 8 in the spec you referenced), so axicdma_ops.impl.max should be 4 and
>> you shouldn't need the read/write q versions.
>>
>
> Okay, that's something I was not aware of.
>
> However, I have a question regarding the 64-bit address space.
>
> For 64-bit address space, i.e. xlnx,addrwidth = 64.
> The CDMA spec says that:
> "TAILDESC_PNTR[_MSB] register causes the AXI CDMA SG Engine
> to start fetching descriptors starting from the CURDESC_PNTR register
> value."
>
> It seems that DMA will start the transfer if either TAILDESC_PNTR or
> TAILDESC_PNTR_MSB is written.
> Then how can we guarantee that the full 64-bit address pointer is written
> before the DMA transfer is started if we can't write both TAILDESC_PNTR
> and TAILDESC_PNTR_MSB
> at the same time?
>

This is described on pages 25 and 26:
"When the AXI CDMA is in SG Mode and the address space is 32 bits
(CDMACR.SGMode = 1), a write by the software application to the
TAILDESC_PNTR register causes the AXI CDMA SG Engine to start fetching
descriptors"

I.e TAILDESC_PNTR only starts the DMA if 32bit addresses have been selected.
If 64bit addresses are selected, TAILDESC_PNTR_MSB starts the DMA:

"When the AXI CDMA is in SG Mode, and the address space is more than 32
bits, (CDMACR.SGMode = 1), a write by the software application to the
TAILDESC_PNTR_MSB register causes the AXI CDMA SG Engine to start fetching
descriptors"



>
> I'm also awarded that Xilinx CDMA Linux driver also has separate 32-bit
> writes for a 64-bit address.
> But wouldn't that cause, e.g. dmatest to be failed?
>

The driver probably relies on the interconnect to down-size the 64bit
access to 2x32bit ones. QEMU will do the same so this shouldn't be a
problem.

Best regards,
Edgar



>
> Regards,
> Frank Chang
>
>
>>
>> Best regards,
>> Edgar
>>
>>
>>
>>>
>>> Signed-off-by: Frank Chang <frank.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