From: Peter Maydell <peter.maydell@linaro.org>
To: qemu-devel@nongnu.org
Subject: [PULL 48/54] hw/dma: Implement a Xilinx CSU DMA model
Date: Mon, 8 Mar 2021 17:32:38 +0000 [thread overview]
Message-ID: <20210308173244.20710-49-peter.maydell@linaro.org> (raw)
In-Reply-To: <20210308173244.20710-1-peter.maydell@linaro.org>
From: Xuzhou Cheng <xuzhou.cheng@windriver.com>
ZynqMP QSPI supports SPI transfer using DMA mode, but currently this
is unimplemented. When QSPI is programmed to use DMA mode, QEMU will
crash. This is observed when testing VxWorks 7.
This adds a Xilinx CSU DMA model and the implementation is based on
https://github.com/Xilinx/qemu/blob/master/hw/dma/csu_stream_dma.c.
The DST part of the model is verified along with ZynqMP GQSPI model.
Signed-off-by: Xuzhou Cheng <xuzhou.cheng@windriver.com>
Signed-off-by: Bin Meng <bin.meng@windriver.com>
Tested-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Reviewed-by: Edgar E. Iglesias <edgar.iglesias@xilinx.com>
Message-id: 20210303135254.3970-2-bmeng.cn@gmail.com
Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
---
include/hw/dma/xlnx_csu_dma.h | 52 +++
hw/dma/xlnx_csu_dma.c | 745 ++++++++++++++++++++++++++++++++++
hw/dma/Kconfig | 4 +
hw/dma/meson.build | 1 +
4 files changed, 802 insertions(+)
create mode 100644 include/hw/dma/xlnx_csu_dma.h
create mode 100644 hw/dma/xlnx_csu_dma.c
diff --git a/include/hw/dma/xlnx_csu_dma.h b/include/hw/dma/xlnx_csu_dma.h
new file mode 100644
index 00000000000..204d94c6737
--- /dev/null
+++ b/include/hw/dma/xlnx_csu_dma.h
@@ -0,0 +1,52 @@
+/*
+ * Xilinx Platform CSU Stream DMA emulation
+ *
+ * This implementation is based on
+ * https://github.com/Xilinx/qemu/blob/master/hw/dma/csu_stream_dma.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef XLNX_CSU_DMA_H
+#define XLNX_CSU_DMA_H
+
+#define TYPE_XLNX_CSU_DMA "xlnx.csu_dma"
+
+#define XLNX_CSU_DMA_R_MAX (0x2c / 4)
+
+typedef struct XlnxCSUDMA {
+ SysBusDevice busdev;
+ MemoryRegion iomem;
+ MemTxAttrs attr;
+ MemoryRegion *dma_mr;
+ AddressSpace *dma_as;
+ qemu_irq irq;
+ StreamSink *tx_dev; /* Used as generic StreamSink */
+ ptimer_state *src_timer;
+
+ uint16_t width;
+ bool is_dst;
+ bool r_size_last_word;
+
+ StreamCanPushNotifyFn notify;
+ void *notify_opaque;
+
+ uint32_t regs[XLNX_CSU_DMA_R_MAX];
+ RegisterInfo regs_info[XLNX_CSU_DMA_R_MAX];
+} XlnxCSUDMA;
+
+#define XLNX_CSU_DMA(obj) \
+ OBJECT_CHECK(XlnxCSUDMA, (obj), TYPE_XLNX_CSU_DMA)
+
+#endif
diff --git a/hw/dma/xlnx_csu_dma.c b/hw/dma/xlnx_csu_dma.c
new file mode 100644
index 00000000000..50709a3909b
--- /dev/null
+++ b/hw/dma/xlnx_csu_dma.c
@@ -0,0 +1,745 @@
+/*
+ * Xilinx Platform CSU Stream DMA emulation
+ *
+ * This implementation is based on
+ * https://github.com/Xilinx/qemu/blob/master/hw/dma/csu_stream_dma.c
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 or
+ * (at your option) version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/log.h"
+#include "qapi/error.h"
+#include "hw/hw.h"
+#include "hw/irq.h"
+#include "hw/qdev-properties.h"
+#include "hw/sysbus.h"
+#include "migration/vmstate.h"
+#include "sysemu/dma.h"
+#include "hw/ptimer.h"
+#include "hw/stream.h"
+#include "hw/register.h"
+#include "hw/dma/xlnx_csu_dma.h"
+
+/*
+ * Ref: UG1087 (v1.7) February 8, 2019
+ * https://www.xilinx.com/html_docs/registers/ug1087/ug1087-zynq-ultrascale-registers.html
+ * CSUDMA Module section
+ */
+REG32(ADDR, 0x0)
+ FIELD(ADDR, ADDR, 2, 30) /* wo */
+REG32(SIZE, 0x4)
+ FIELD(SIZE, SIZE, 2, 27) /* wo */
+ FIELD(SIZE, LAST_WORD, 0, 1) /* rw, only exists in SRC */
+REG32(STATUS, 0x8)
+ FIELD(STATUS, DONE_CNT, 13, 3) /* wtc */
+ FIELD(STATUS, FIFO_LEVEL, 5, 8) /* ro */
+ FIELD(STATUS, OUTSTANDING, 1, 4) /* ro */
+ FIELD(STATUS, BUSY, 0, 1) /* ro */
+REG32(CTRL, 0xc)
+ FIELD(CTRL, FIFOTHRESH, 25, 7) /* rw, only exists in DST, reset 0x40 */
+ FIELD(CTRL, APB_ERR_RESP, 24, 1) /* rw */
+ FIELD(CTRL, ENDIANNESS, 23, 1) /* rw */
+ FIELD(CTRL, AXI_BRST_TYPE, 22, 1) /* rw */
+ FIELD(CTRL, TIMEOUT_VAL, 10, 12) /* rw, reset: 0xFFE */
+ FIELD(CTRL, FIFO_THRESH, 2, 8) /* rw, reset: 0x80 */
+ FIELD(CTRL, PAUSE_STRM, 1, 1) /* rw */
+ FIELD(CTRL, PAUSE_MEM, 0, 1) /* rw */
+REG32(CRC, 0x10)
+REG32(INT_STATUS, 0x14)
+ FIELD(INT_STATUS, FIFO_OVERFLOW, 7, 1) /* wtc */
+ FIELD(INT_STATUS, INVALID_APB, 6, 1) /* wtc */
+ FIELD(INT_STATUS, THRESH_HIT, 5, 1) /* wtc */
+ FIELD(INT_STATUS, TIMEOUT_MEM, 4, 1) /* wtc */
+ FIELD(INT_STATUS, TIMEOUT_STRM, 3, 1) /* wtc */
+ FIELD(INT_STATUS, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */
+ FIELD(INT_STATUS, DONE, 1, 1) /* wtc */
+ FIELD(INT_STATUS, MEM_DONE, 0, 1) /* wtc */
+REG32(INT_ENABLE, 0x18)
+ FIELD(INT_ENABLE, FIFO_OVERFLOW, 7, 1) /* wtc */
+ FIELD(INT_ENABLE, INVALID_APB, 6, 1) /* wtc */
+ FIELD(INT_ENABLE, THRESH_HIT, 5, 1) /* wtc */
+ FIELD(INT_ENABLE, TIMEOUT_MEM, 4, 1) /* wtc */
+ FIELD(INT_ENABLE, TIMEOUT_STRM, 3, 1) /* wtc */
+ FIELD(INT_ENABLE, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */
+ FIELD(INT_ENABLE, DONE, 1, 1) /* wtc */
+ FIELD(INT_ENABLE, MEM_DONE, 0, 1) /* wtc */
+REG32(INT_DISABLE, 0x1c)
+ FIELD(INT_DISABLE, FIFO_OVERFLOW, 7, 1) /* wtc */
+ FIELD(INT_DISABLE, INVALID_APB, 6, 1) /* wtc */
+ FIELD(INT_DISABLE, THRESH_HIT, 5, 1) /* wtc */
+ FIELD(INT_DISABLE, TIMEOUT_MEM, 4, 1) /* wtc */
+ FIELD(INT_DISABLE, TIMEOUT_STRM, 3, 1) /* wtc */
+ FIELD(INT_DISABLE, AXI_BRESP_ERR, 2, 1) /* wtc, SRC: AXI_RDERR */
+ FIELD(INT_DISABLE, DONE, 1, 1) /* wtc */
+ FIELD(INT_DISABLE, MEM_DONE, 0, 1) /* wtc */
+REG32(INT_MASK, 0x20)
+ FIELD(INT_MASK, FIFO_OVERFLOW, 7, 1) /* ro, reset: 0x1 */
+ FIELD(INT_MASK, INVALID_APB, 6, 1) /* ro, reset: 0x1 */
+ FIELD(INT_MASK, THRESH_HIT, 5, 1) /* ro, reset: 0x1 */
+ FIELD(INT_MASK, TIMEOUT_MEM, 4, 1) /* ro, reset: 0x1 */
+ FIELD(INT_MASK, TIMEOUT_STRM, 3, 1) /* ro, reset: 0x1 */
+ FIELD(INT_MASK, AXI_BRESP_ERR, 2, 1) /* ro, reset: 0x1, SRC: AXI_RDERR */
+ FIELD(INT_MASK, DONE, 1, 1) /* ro, reset: 0x1 */
+ FIELD(INT_MASK, MEM_DONE, 0, 1) /* ro, reset: 0x1 */
+REG32(CTRL2, 0x24)
+ FIELD(CTRL2, ARCACHE, 24, 3) /* rw */
+ FIELD(CTRL2, ROUTE_BIT, 23, 1) /* rw */
+ FIELD(CTRL2, TIMEOUT_EN, 22, 1) /* rw */
+ FIELD(CTRL2, TIMEOUT_PRE, 4, 12) /* rw, reset: 0xFFF */
+ FIELD(CTRL2, MAX_OUTS_CMDS, 0, 4) /* rw, reset: 0x8 */
+REG32(ADDR_MSB, 0x28)
+ FIELD(ADDR_MSB, ADDR_MSB, 0, 17) /* wo */
+
+#define R_CTRL_TIMEOUT_VAL_RESET (0xFFE)
+#define R_CTRL_FIFO_THRESH_RESET (0x80)
+#define R_CTRL_FIFOTHRESH_RESET (0x40)
+
+#define R_CTRL2_TIMEOUT_PRE_RESET (0xFFF)
+#define R_CTRL2_MAX_OUTS_CMDS_RESET (0x8)
+
+#define XLNX_CSU_DMA_ERR_DEBUG (0)
+#define XLNX_CSU_DMA_INT_R_MASK (0xff)
+
+/* UG1807: Set the prescaler value for the timeout in clk (~2.5ns) cycles */
+#define XLNX_CSU_DMA_TIMER_FREQ (400 * 1000 * 1000)
+
+static bool xlnx_csu_dma_is_paused(XlnxCSUDMA *s)
+{
+ bool paused;
+
+ paused = !!(s->regs[R_CTRL] & R_CTRL_PAUSE_STRM_MASK);
+ paused |= !!(s->regs[R_CTRL] & R_CTRL_PAUSE_MEM_MASK);
+
+ return paused;
+}
+
+static bool xlnx_csu_dma_get_eop(XlnxCSUDMA *s)
+{
+ return s->r_size_last_word;
+}
+
+static bool xlnx_csu_dma_burst_is_fixed(XlnxCSUDMA *s)
+{
+ return !!(s->regs[R_CTRL] & R_CTRL_AXI_BRST_TYPE_MASK);
+}
+
+static bool xlnx_csu_dma_timeout_enabled(XlnxCSUDMA *s)
+{
+ return !!(s->regs[R_CTRL2] & R_CTRL2_TIMEOUT_EN_MASK);
+}
+
+static void xlnx_csu_dma_update_done_cnt(XlnxCSUDMA *s, int a)
+{
+ int cnt;
+
+ /* Increase DONE_CNT */
+ cnt = ARRAY_FIELD_EX32(s->regs, STATUS, DONE_CNT) + a;
+ ARRAY_FIELD_DP32(s->regs, STATUS, DONE_CNT, cnt);
+}
+
+static void xlnx_csu_dma_data_process(XlnxCSUDMA *s, uint8_t *buf, uint32_t len)
+{
+ uint32_t bswap;
+ uint32_t i;
+
+ bswap = s->regs[R_CTRL] & R_CTRL_ENDIANNESS_MASK;
+ if (s->is_dst && !bswap) {
+ /* Fast when ENDIANNESS cleared */
+ return;
+ }
+
+ for (i = 0; i < len; i += 4) {
+ uint8_t *b = &buf[i];
+ union {
+ uint8_t u8[4];
+ uint32_t u32;
+ } v = {
+ .u8 = { b[0], b[1], b[2], b[3] }
+ };
+
+ if (!s->is_dst) {
+ s->regs[R_CRC] += v.u32;
+ }
+ if (bswap) {
+ /*
+ * No point using bswap, we need to writeback
+ * into a potentially unaligned pointer.
+ */
+ b[0] = v.u8[3];
+ b[1] = v.u8[2];
+ b[2] = v.u8[1];
+ b[3] = v.u8[0];
+ }
+ }
+}
+
+static void xlnx_csu_dma_update_irq(XlnxCSUDMA *s)
+{
+ qemu_set_irq(s->irq, !!(s->regs[R_INT_STATUS] & ~s->regs[R_INT_MASK]));
+}
+
+/* len is in bytes */
+static uint32_t xlnx_csu_dma_read(XlnxCSUDMA *s, uint8_t *buf, uint32_t len)
+{
+ hwaddr addr = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR];
+ MemTxResult result = MEMTX_OK;
+
+ if (xlnx_csu_dma_burst_is_fixed(s)) {
+ uint32_t i;
+
+ for (i = 0; i < len && (result == MEMTX_OK); i += s->width) {
+ uint32_t mlen = MIN(len - i, s->width);
+
+ result = address_space_rw(s->dma_as, addr, s->attr,
+ buf + i, mlen, false);
+ }
+ } else {
+ result = address_space_rw(s->dma_as, addr, s->attr, buf, len, false);
+ }
+
+ if (result == MEMTX_OK) {
+ xlnx_csu_dma_data_process(s, buf, len);
+ } else {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address 0x%lx for mem read",
+ __func__, addr);
+ s->regs[R_INT_STATUS] |= R_INT_STATUS_AXI_BRESP_ERR_MASK;
+ xlnx_csu_dma_update_irq(s);
+ }
+ return len;
+}
+
+/* len is in bytes */
+static uint32_t xlnx_csu_dma_write(XlnxCSUDMA *s, uint8_t *buf, uint32_t len)
+{
+ hwaddr addr = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR];
+ MemTxResult result = MEMTX_OK;
+
+ xlnx_csu_dma_data_process(s, buf, len);
+ if (xlnx_csu_dma_burst_is_fixed(s)) {
+ uint32_t i;
+
+ for (i = 0; i < len && (result == MEMTX_OK); i += s->width) {
+ uint32_t mlen = MIN(len - i, s->width);
+
+ result = address_space_rw(s->dma_as, addr, s->attr,
+ buf, mlen, true);
+ buf += mlen;
+ }
+ } else {
+ result = address_space_rw(s->dma_as, addr, s->attr, buf, len, true);
+ }
+
+ if (result != MEMTX_OK) {
+ qemu_log_mask(LOG_GUEST_ERROR, "%s: Bad address 0x%lx for mem write",
+ __func__, addr);
+ s->regs[R_INT_STATUS] |= R_INT_STATUS_AXI_BRESP_ERR_MASK;
+ xlnx_csu_dma_update_irq(s);
+ }
+ return len;
+}
+
+static void xlnx_csu_dma_done(XlnxCSUDMA *s)
+{
+ s->regs[R_STATUS] &= ~R_STATUS_BUSY_MASK;
+ s->regs[R_INT_STATUS] |= R_INT_STATUS_DONE_MASK;
+
+ if (!s->is_dst) {
+ s->regs[R_INT_STATUS] |= R_INT_STATUS_MEM_DONE_MASK;
+ }
+
+ xlnx_csu_dma_update_done_cnt(s, 1);
+}
+
+static uint32_t xlnx_csu_dma_advance(XlnxCSUDMA *s, uint32_t len)
+{
+ uint32_t size = s->regs[R_SIZE];
+ hwaddr dst = (hwaddr)s->regs[R_ADDR_MSB] << 32 | s->regs[R_ADDR];
+
+ assert(len <= size);
+
+ size -= len;
+ s->regs[R_SIZE] = size;
+
+ if (!xlnx_csu_dma_burst_is_fixed(s)) {
+ dst += len;
+ s->regs[R_ADDR] = (uint32_t) dst;
+ s->regs[R_ADDR_MSB] = dst >> 32;
+ }
+
+ if (size == 0) {
+ xlnx_csu_dma_done(s);
+ }
+
+ return size;
+}
+
+static void xlnx_csu_dma_src_notify(void *opaque)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(opaque);
+ unsigned char buf[4 * 1024];
+ size_t rlen = 0;
+
+ ptimer_transaction_begin(s->src_timer);
+ /* Stop the backpreassure timer */
+ ptimer_stop(s->src_timer);
+
+ while (s->regs[R_SIZE] && !xlnx_csu_dma_is_paused(s) &&
+ stream_can_push(s->tx_dev, xlnx_csu_dma_src_notify, s)) {
+ uint32_t plen = MIN(s->regs[R_SIZE], sizeof buf);
+ bool eop = false;
+
+ /* Did we fit it all? */
+ if (s->regs[R_SIZE] == plen && xlnx_csu_dma_get_eop(s)) {
+ eop = true;
+ }
+
+ /* DMA transfer */
+ xlnx_csu_dma_read(s, buf, plen);
+ rlen = stream_push(s->tx_dev, buf, plen, eop);
+ xlnx_csu_dma_advance(s, rlen);
+ }
+
+ if (xlnx_csu_dma_timeout_enabled(s) && s->regs[R_SIZE] &&
+ !stream_can_push(s->tx_dev, xlnx_csu_dma_src_notify, s)) {
+ uint32_t timeout = ARRAY_FIELD_EX32(s->regs, CTRL, TIMEOUT_VAL);
+ uint32_t div = ARRAY_FIELD_EX32(s->regs, CTRL2, TIMEOUT_PRE) + 1;
+ uint32_t freq = XLNX_CSU_DMA_TIMER_FREQ;
+
+ freq /= div;
+ ptimer_set_freq(s->src_timer, freq);
+ ptimer_set_count(s->src_timer, timeout);
+ ptimer_run(s->src_timer, 1);
+ }
+
+ ptimer_transaction_commit(s->src_timer);
+ xlnx_csu_dma_update_irq(s);
+}
+
+static uint64_t addr_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ /* Address is word aligned */
+ return val & R_ADDR_ADDR_MASK;
+}
+
+static uint64_t size_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+
+ if (s->regs[R_SIZE] != 0) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: Starting DMA while already running.\n", __func__);
+ }
+
+ if (!s->is_dst) {
+ s->r_size_last_word = !!(val & R_SIZE_LAST_WORD_MASK);
+ }
+
+ /* Size is word aligned */
+ return val & R_SIZE_SIZE_MASK;
+}
+
+static uint64_t size_post_read(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+
+ return val | s->r_size_last_word;
+}
+
+static void size_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+
+ s->regs[R_STATUS] |= R_STATUS_BUSY_MASK;
+
+ /*
+ * Note that if SIZE is programmed to 0, and the DMA is started,
+ * the interrupts DONE and MEM_DONE will be asserted.
+ */
+ if (s->regs[R_SIZE] == 0) {
+ xlnx_csu_dma_done(s);
+ xlnx_csu_dma_update_irq(s);
+ return;
+ }
+
+ /* Set SIZE is considered the last step in transfer configuration */
+ if (!s->is_dst) {
+ xlnx_csu_dma_src_notify(s);
+ } else {
+ if (s->notify) {
+ s->notify(s->notify_opaque);
+ }
+ }
+}
+
+static uint64_t status_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ return val & (R_STATUS_DONE_CNT_MASK | R_STATUS_BUSY_MASK);
+}
+
+static void ctrl_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+
+ if (!s->is_dst) {
+ if (!xlnx_csu_dma_is_paused(s)) {
+ xlnx_csu_dma_src_notify(s);
+ }
+ } else {
+ if (!xlnx_csu_dma_is_paused(s) && s->notify) {
+ s->notify(s->notify_opaque);
+ }
+ }
+}
+
+static uint64_t int_status_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+
+ /* DMA counter decrements when flag 'DONE' is cleared */
+ if ((val & s->regs[R_INT_STATUS] & R_INT_STATUS_DONE_MASK)) {
+ xlnx_csu_dma_update_done_cnt(s, -1);
+ }
+
+ return s->regs[R_INT_STATUS] & ~val;
+}
+
+static void int_status_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+
+ xlnx_csu_dma_update_irq(s);
+}
+
+static uint64_t int_enable_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+ uint32_t v32 = val;
+
+ /*
+ * R_INT_ENABLE doesn't have its own state.
+ * It is used to indirectly modify R_INT_MASK.
+ *
+ * 1: Enable this interrupt field (the mask bit will be cleared to 0)
+ * 0: No effect
+ */
+ s->regs[R_INT_MASK] &= ~v32;
+ return 0;
+}
+
+static void int_enable_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+
+ xlnx_csu_dma_update_irq(s);
+}
+
+static uint64_t int_disable_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+ uint32_t v32 = val;
+
+ /*
+ * R_INT_DISABLE doesn't have its own state.
+ * It is used to indirectly modify R_INT_MASK.
+ *
+ * 1: Disable this interrupt field (the mask bit will be set to 1)
+ * 0: No effect
+ */
+ s->regs[R_INT_MASK] |= v32;
+ return 0;
+}
+
+static void int_disable_post_write(RegisterInfo *reg, uint64_t val)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(reg->opaque);
+
+ xlnx_csu_dma_update_irq(s);
+}
+
+static uint64_t addr_msb_pre_write(RegisterInfo *reg, uint64_t val)
+{
+ return val & R_ADDR_MSB_ADDR_MSB_MASK;
+}
+
+static const RegisterAccessInfo *xlnx_csu_dma_regs_info[] = {
+#define DMACH_REGINFO(NAME, snd) \
+ (const RegisterAccessInfo []) { \
+ { \
+ .name = #NAME "_ADDR", \
+ .addr = A_ADDR, \
+ .pre_write = addr_pre_write \
+ }, { \
+ .name = #NAME "_SIZE", \
+ .addr = A_SIZE, \
+ .pre_write = size_pre_write, \
+ .post_write = size_post_write, \
+ .post_read = size_post_read \
+ }, { \
+ .name = #NAME "_STATUS", \
+ .addr = A_STATUS, \
+ .pre_write = status_pre_write, \
+ .w1c = R_STATUS_DONE_CNT_MASK, \
+ .ro = (R_STATUS_BUSY_MASK \
+ | R_STATUS_FIFO_LEVEL_MASK \
+ | R_STATUS_OUTSTANDING_MASK) \
+ }, { \
+ .name = #NAME "_CTRL", \
+ .addr = A_CTRL, \
+ .post_write = ctrl_post_write, \
+ .reset = ((R_CTRL_TIMEOUT_VAL_RESET << R_CTRL_TIMEOUT_VAL_SHIFT) \
+ | (R_CTRL_FIFO_THRESH_RESET << R_CTRL_FIFO_THRESH_SHIFT)\
+ | (snd ? 0 : R_CTRL_FIFOTHRESH_RESET \
+ << R_CTRL_FIFOTHRESH_SHIFT)) \
+ }, { \
+ .name = #NAME "_CRC", \
+ .addr = A_CRC, \
+ }, { \
+ .name = #NAME "_INT_STATUS", \
+ .addr = A_INT_STATUS, \
+ .pre_write = int_status_pre_write, \
+ .post_write = int_status_post_write \
+ }, { \
+ .name = #NAME "_INT_ENABLE", \
+ .addr = A_INT_ENABLE, \
+ .pre_write = int_enable_pre_write, \
+ .post_write = int_enable_post_write \
+ }, { \
+ .name = #NAME "_INT_DISABLE", \
+ .addr = A_INT_DISABLE, \
+ .pre_write = int_disable_pre_write, \
+ .post_write = int_disable_post_write \
+ }, { \
+ .name = #NAME "_INT_MASK", \
+ .addr = A_INT_MASK, \
+ .ro = ~0, \
+ .reset = XLNX_CSU_DMA_INT_R_MASK \
+ }, { \
+ .name = #NAME "_CTRL2", \
+ .addr = A_CTRL2, \
+ .reset = ((R_CTRL2_TIMEOUT_PRE_RESET \
+ << R_CTRL2_TIMEOUT_PRE_SHIFT) \
+ | (R_CTRL2_MAX_OUTS_CMDS_RESET \
+ << R_CTRL2_MAX_OUTS_CMDS_SHIFT)) \
+ }, { \
+ .name = #NAME "_ADDR_MSB", \
+ .addr = A_ADDR_MSB, \
+ .pre_write = addr_msb_pre_write \
+ } \
+ }
+
+ DMACH_REGINFO(DMA_SRC, true),
+ DMACH_REGINFO(DMA_DST, false)
+};
+
+static const MemoryRegionOps xlnx_csu_dma_ops = {
+ .read = register_read_memory,
+ .write = register_write_memory,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 4,
+ .max_access_size = 4,
+ }
+};
+
+static void xlnx_csu_dma_src_timeout_hit(void *opaque)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(opaque);
+
+ /* Ignore if the timeout is masked */
+ if (!xlnx_csu_dma_timeout_enabled(s)) {
+ return;
+ }
+
+ s->regs[R_INT_STATUS] |= R_INT_STATUS_TIMEOUT_STRM_MASK;
+ xlnx_csu_dma_update_irq(s);
+}
+
+static size_t xlnx_csu_dma_stream_push(StreamSink *obj, uint8_t *buf,
+ size_t len, bool eop)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(obj);
+ uint32_t size = s->regs[R_SIZE];
+ uint32_t mlen = MIN(size, len) & (~3); /* Size is word aligned */
+
+ /* Be called when it's DST */
+ assert(s->is_dst);
+
+ if (size == 0 || len <= 0) {
+ return 0;
+ }
+
+ if (len && (xlnx_csu_dma_is_paused(s) || mlen == 0)) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "csu-dma: DST channel dropping %zd b of data.\n", len);
+ s->regs[R_INT_STATUS] |= R_INT_STATUS_FIFO_OVERFLOW_MASK;
+ return len;
+ }
+
+ if (xlnx_csu_dma_write(s, buf, mlen) != mlen) {
+ return 0;
+ }
+
+ xlnx_csu_dma_advance(s, mlen);
+ xlnx_csu_dma_update_irq(s);
+
+ return mlen;
+}
+
+static bool xlnx_csu_dma_stream_can_push(StreamSink *obj,
+ StreamCanPushNotifyFn notify,
+ void *notify_opaque)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(obj);
+
+ if (s->regs[R_SIZE] != 0) {
+ return true;
+ } else {
+ s->notify = notify;
+ s->notify_opaque = notify_opaque;
+ return false;
+ }
+}
+
+static void xlnx_csu_dma_reset(DeviceState *dev)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(dev);
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) {
+ register_reset(&s->regs_info[i]);
+ }
+}
+
+static void xlnx_csu_dma_realize(DeviceState *dev, Error **errp)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(dev);
+ RegisterInfoArray *reg_array;
+
+ reg_array =
+ register_init_block32(dev, xlnx_csu_dma_regs_info[!!s->is_dst],
+ XLNX_CSU_DMA_R_MAX,
+ s->regs_info, s->regs,
+ &xlnx_csu_dma_ops,
+ XLNX_CSU_DMA_ERR_DEBUG,
+ XLNX_CSU_DMA_R_MAX * 4);
+ memory_region_add_subregion(&s->iomem,
+ 0x0,
+ ®_array->mem);
+
+ sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->iomem);
+ sysbus_init_irq(SYS_BUS_DEVICE(dev), &s->irq);
+
+ if (!s->is_dst && !s->tx_dev) {
+ error_setg(errp, "zynqmp.csu-dma: Stream not connected");
+ return;
+ }
+
+ s->src_timer = ptimer_init(xlnx_csu_dma_src_timeout_hit,
+ s, PTIMER_POLICY_DEFAULT);
+
+ if (s->dma_mr) {
+ s->dma_as = g_malloc0(sizeof(AddressSpace));
+ address_space_init(s->dma_as, s->dma_mr, NULL);
+ } else {
+ s->dma_as = &address_space_memory;
+ }
+
+ s->attr = MEMTXATTRS_UNSPECIFIED;
+
+ s->r_size_last_word = 0;
+}
+
+static const VMStateDescription vmstate_xlnx_csu_dma = {
+ .name = TYPE_XLNX_CSU_DMA,
+ .version_id = 0,
+ .minimum_version_id = 0,
+ .minimum_version_id_old = 0,
+ .fields = (VMStateField[]) {
+ VMSTATE_PTIMER(src_timer, XlnxCSUDMA),
+ VMSTATE_UINT16(width, XlnxCSUDMA),
+ VMSTATE_BOOL(is_dst, XlnxCSUDMA),
+ VMSTATE_BOOL(r_size_last_word, XlnxCSUDMA),
+ VMSTATE_UINT32_ARRAY(regs, XlnxCSUDMA, XLNX_CSU_DMA_R_MAX),
+ VMSTATE_END_OF_LIST(),
+ }
+};
+
+static Property xlnx_csu_dma_properties[] = {
+ /*
+ * Ref PG021, Stream Data Width:
+ * Data width in bits of the AXI S2MM AXI4-Stream Data bus.
+ * This value must be equal or less than the Memory Map Data Width.
+ * Valid values are 8, 16, 32, 64, 128, 512 and 1024.
+ * "dma-width" is the byte value of the "Stream Data Width".
+ */
+ DEFINE_PROP_UINT16("dma-width", XlnxCSUDMA, width, 4),
+ /*
+ * The CSU DMA is a two-channel, simple DMA, allowing separate control of
+ * the SRC (read) channel and DST (write) channel. "is-dst" is used to mark
+ * which channel the device is connected to.
+ */
+ DEFINE_PROP_BOOL("is-dst", XlnxCSUDMA, is_dst, true),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void xlnx_csu_dma_class_init(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ StreamSinkClass *ssc = STREAM_SINK_CLASS(klass);
+
+ dc->reset = xlnx_csu_dma_reset;
+ dc->realize = xlnx_csu_dma_realize;
+ dc->vmsd = &vmstate_xlnx_csu_dma;
+ device_class_set_props(dc, xlnx_csu_dma_properties);
+
+ ssc->push = xlnx_csu_dma_stream_push;
+ ssc->can_push = xlnx_csu_dma_stream_can_push;
+}
+
+static void xlnx_csu_dma_init(Object *obj)
+{
+ XlnxCSUDMA *s = XLNX_CSU_DMA(obj);
+
+ memory_region_init(&s->iomem, obj, TYPE_XLNX_CSU_DMA,
+ XLNX_CSU_DMA_R_MAX * 4);
+
+ object_property_add_link(obj, "stream-connected-dma", TYPE_STREAM_SINK,
+ (Object **)&s->tx_dev,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_STRONG);
+ object_property_add_link(obj, "dma", TYPE_MEMORY_REGION,
+ (Object **)&s->dma_mr,
+ qdev_prop_allow_set_link_before_realize,
+ OBJ_PROP_LINK_STRONG);
+}
+
+static const TypeInfo xlnx_csu_dma_info = {
+ .name = TYPE_XLNX_CSU_DMA,
+ .parent = TYPE_SYS_BUS_DEVICE,
+ .instance_size = sizeof(XlnxCSUDMA),
+ .class_init = xlnx_csu_dma_class_init,
+ .instance_init = xlnx_csu_dma_init,
+ .interfaces = (InterfaceInfo[]) {
+ { TYPE_STREAM_SINK },
+ { }
+ }
+};
+
+static void xlnx_csu_dma_register_types(void)
+{
+ type_register_static(&xlnx_csu_dma_info);
+}
+
+type_init(xlnx_csu_dma_register_types)
diff --git a/hw/dma/Kconfig b/hw/dma/Kconfig
index 5d6be1a7a7a..98fbb1bb049 100644
--- a/hw/dma/Kconfig
+++ b/hw/dma/Kconfig
@@ -26,3 +26,7 @@ config STP2000
config SIFIVE_PDMA
bool
+
+config XLNX_CSU_DMA
+ bool
+ select REGISTER
diff --git a/hw/dma/meson.build b/hw/dma/meson.build
index 47b4a7cb47b..5c78a4e05ff 100644
--- a/hw/dma/meson.build
+++ b/hw/dma/meson.build
@@ -14,3 +14,4 @@ softmmu_ss.add(when: 'CONFIG_OMAP', if_true: files('omap_dma.c', 'soc_dma.c'))
softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_dma.c'))
softmmu_ss.add(when: 'CONFIG_RASPI', if_true: files('bcm2835_dma.c'))
softmmu_ss.add(when: 'CONFIG_SIFIVE_PDMA', if_true: files('sifive_pdma.c'))
+softmmu_ss.add(when: 'CONFIG_XLNX_CSU_DMA', if_true: files('xlnx_csu_dma.c'))
--
2.20.1
next prev parent reply other threads:[~2021-03-08 18:35 UTC|newest]
Thread overview: 56+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-03-08 17:31 [PULL 00/54] target-arm queue Peter Maydell
2021-03-08 17:31 ` [PULL 01/54] clock: Add ClockEvent parameter to callbacks Peter Maydell
2021-03-08 17:31 ` [PULL 02/54] clock: Add ClockPreUpdate callback event type Peter Maydell
2021-03-08 17:31 ` [PULL 03/54] clock: Add clock_ns_to_ticks() function Peter Maydell
2021-03-08 17:31 ` [PULL 04/54] hw/timer/npcm7xx_timer: Use new clock_ns_to_ticks() Peter Maydell
2021-03-08 17:31 ` [PULL 05/54] hw/arm/armsse: Introduce SSE subsystem version property Peter Maydell
2021-03-08 17:31 ` [PULL 06/54] hw/misc/iotkit-sysctl: Remove is_sse200 flag Peter Maydell
2021-03-08 17:31 ` [PULL 07/54] hw/misc/iotkit-secctl.c: Implement SSE-300 PID register values Peter Maydell
2021-03-08 17:31 ` [PULL 08/54] hw/misc/iotkit-sysinfo.c: " Peter Maydell
2021-03-08 17:31 ` [PULL 09/54] hw/arm/armsse.c: Use correct SYS_CONFIG0 register value for SSE-300 Peter Maydell
2021-03-08 17:32 ` [PULL 10/54] hw/misc/iotkit-sysinfo.c: Implement SYS_CONFIG1 and IIDR Peter Maydell
2021-03-08 17:32 ` [PULL 11/54] hw/timer/sse-counter: Model the SSE Subsystem System Counter Peter Maydell
2021-03-08 17:32 ` [PULL 12/54] hw/timer/sse-timer: Model the SSE Subsystem System Timer Peter Maydell
2021-03-08 17:32 ` [PULL 13/54] hw/misc/iotkit-sysctl: Add SSE-300 cases which match SSE-200 behaviour Peter Maydell
2021-03-08 17:32 ` [PULL 14/54] hw/misc/iotkit-sysctl: Handle CPU_WAIT, NMI_ENABLE for SSE-300 Peter Maydell
2021-03-08 17:32 ` [PULL 15/54] hw/misc/iotkit-sysctl: Handle INITSVTOR* " Peter Maydell
2021-03-08 17:32 ` [PULL 16/54] hw/misc/iotkit-sysctl: Implement dummy version of SSE-300 PWRCTRL register Peter Maydell
2021-03-08 17:32 ` [PULL 17/54] hw/misc/iotkit-sysctl: Handle SSE-300 changes to PDCM_PD_*_SENSE registers Peter Maydell
2021-03-08 17:32 ` [PULL 18/54] hw/misc/iotkit-sysctl: Implement SSE-200 and SSE-300 PID register values Peter Maydell
2021-03-08 17:32 ` [PULL 19/54] hw/arm/Kconfig: Move ARMSSE_CPUID and ARMSSE_MHU stanzas to hw/misc Peter Maydell
2021-03-08 17:32 ` [PULL 20/54] hw/misc/sse-cpu-pwrctrl: Implement SSE-300 CPU<N>_PWRCTRL register block Peter Maydell
2021-03-08 17:32 ` [PULL 21/54] hw/arm/armsse: Use an array for apb_ppc fields in the state structure Peter Maydell
2021-03-08 17:32 ` [PULL 22/54] hw/arm/armsse: Add a define for number of IRQs used by the SSE itself Peter Maydell
2021-03-08 17:32 ` [PULL 23/54] hw/arm/armsse: Add framework for data-driven device placement Peter Maydell
2021-03-08 17:32 ` [PULL 24/54] hw/arm/armsse: Move dual-timer device into data-driven framework Peter Maydell
2021-03-08 17:32 ` [PULL 25/54] hw/arm/armsse: Move watchdogs " Peter Maydell
2021-03-08 17:32 ` [PULL 26/54] hw/arm/armsse: Move s32ktimer " Peter Maydell
2021-03-08 17:32 ` [PULL 27/54] hw/arm/armsse: Move sysinfo register block " Peter Maydell
2021-03-08 17:32 ` [PULL 28/54] hw/arm/armsse: Move sysctl " Peter Maydell
2021-03-08 17:32 ` [PULL 29/54] hw/arm/armsse: Move PPUs " Peter Maydell
2021-03-08 17:32 ` [PULL 30/54] hw/arm/armsse: Add missing SSE-200 SYS_PPU Peter Maydell
2021-03-08 17:32 ` [PULL 31/54] hw/arm/armsse: Indirect irq_is_common[] through ARMSSEInfo Peter Maydell
2021-03-08 17:32 ` [PULL 32/54] hw/arm/armsse: Add support for SSE variants with a system counter Peter Maydell
2021-03-08 17:32 ` [PULL 33/54] hw/arm/armsse: Add support for TYPE_SSE_TIMER in ARMSSEDeviceInfo Peter Maydell
2021-03-08 17:32 ` [PULL 34/54] hw/arm/armsse: Support variants with ARMSSE_CPU_PWRCTRL block Peter Maydell
2021-03-08 17:32 ` [PULL 35/54] hw/arm/armsse: Add SSE-300 support Peter Maydell
2021-03-08 17:32 ` [PULL 36/54] hw/arm/mps2-tz: Make UART overflow IRQ board-specific Peter Maydell
2021-03-08 17:32 ` [PULL 37/54] hw/misc/mps2-fpgaio: Fold counters subsection into main vmstate Peter Maydell
2021-03-08 17:32 ` [PULL 38/54] hw/misc/mps2-fpgaio: Support AN547 DBGCTRL register Peter Maydell
2021-03-08 17:32 ` [PULL 39/54] hw/misc/mps2-scc: Implement changes for AN547 Peter Maydell
2021-03-08 17:32 ` [PULL 40/54] hw/arm/mps2-tz: Support running APB peripherals on different clock Peter Maydell
2021-03-08 17:32 ` [PULL 41/54] hw/arm/mps2-tz: Make initsvtor0 setting board-specific Peter Maydell
2021-03-08 17:32 ` [PULL 42/54] hw/arm/mps2-tz: Add new mps3-an547 board Peter Maydell
2021-03-08 17:32 ` [PULL 43/54] docs/system/arm/mps2.rst: Document the " Peter Maydell
2021-03-08 17:32 ` [PULL 44/54] tests/qtest/sse-timer-test: Add simple test of the SSE counter Peter Maydell
2021-03-08 17:32 ` [PULL 45/54] tests/qtest/sse-timer-test: Test the system timer Peter Maydell
2021-03-08 17:32 ` [PULL 46/54] tests/qtest/sse-timer-test: Test counter scaling changes Peter Maydell
2021-03-08 17:32 ` [PULL 47/54] target/arm: Restrict v7A TCG cpus to TCG accel Peter Maydell
2021-03-08 17:32 ` Peter Maydell [this message]
2021-03-08 17:32 ` [PULL 49/54] hw/arm: xlnx-zynqmp: Clean up coding convention issues Peter Maydell
2021-03-08 17:32 ` [PULL 50/54] hw/arm: xlnx-zynqmp: Connect a Xilinx CSU DMA module for QSPI Peter Maydell
2021-03-08 17:32 ` [PULL 51/54] hw/ssi: xilinx_spips: Clean up coding convention issues Peter Maydell
2021-03-08 17:32 ` [PULL 52/54] hw/ssi: xilinx_spips: Remove DMA related dead codes from zynqmp_spips Peter Maydell
2021-03-08 17:32 ` [PULL 53/54] hw/timer/renesas_tmr: Prefix constants for CSS values with CSS_ Peter Maydell
2021-03-08 17:32 ` [PULL 54/54] hw/timer/renesas_tmr: Fix use of uninitialized data in read_tcnt() Peter Maydell
2021-03-08 18:49 ` [PULL 00/54] target-arm queue no-reply
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210308173244.20710-49-peter.maydell@linaro.org \
--to=peter.maydell@linaro.org \
--cc=qemu-devel@nongnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).