| /* |
| * QEMU model of the ZynqMP generic DMA |
| * |
| * Copyright (c) 2014 Xilinx Inc. |
| * Copyright (c) 2018 FEIMTECH AB |
| * |
| * Written by Edgar E. Iglesias <edgar.iglesias@xilinx.com>, |
| * Francisco Iglesias <francisco.iglesias@feimtech.se> |
| * |
| * 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/dma/xlnx-zdma.h" |
| #include "hw/irq.h" |
| #include "hw/qdev-properties.h" |
| #include "migration/vmstate.h" |
| #include "qemu/bitops.h" |
| #include "qemu/log.h" |
| #include "qemu/module.h" |
| #include "qapi/error.h" |
| |
| #ifndef XLNX_ZDMA_ERR_DEBUG |
| #define XLNX_ZDMA_ERR_DEBUG 0 |
| #endif |
| |
| REG32(ZDMA_ERR_CTRL, 0x0) |
| FIELD(ZDMA_ERR_CTRL, APB_ERR_RES, 0, 1) |
| REG32(ZDMA_CH_ISR, 0x100) |
| FIELD(ZDMA_CH_ISR, DMA_PAUSE, 11, 1) |
| FIELD(ZDMA_CH_ISR, DMA_DONE, 10, 1) |
| FIELD(ZDMA_CH_ISR, AXI_WR_DATA, 9, 1) |
| FIELD(ZDMA_CH_ISR, AXI_RD_DATA, 8, 1) |
| FIELD(ZDMA_CH_ISR, AXI_RD_DST_DSCR, 7, 1) |
| FIELD(ZDMA_CH_ISR, AXI_RD_SRC_DSCR, 6, 1) |
| FIELD(ZDMA_CH_ISR, IRQ_DST_ACCT_ERR, 5, 1) |
| FIELD(ZDMA_CH_ISR, IRQ_SRC_ACCT_ERR, 4, 1) |
| FIELD(ZDMA_CH_ISR, BYTE_CNT_OVRFL, 3, 1) |
| FIELD(ZDMA_CH_ISR, DST_DSCR_DONE, 2, 1) |
| FIELD(ZDMA_CH_ISR, SRC_DSCR_DONE, 1, 1) |
| FIELD(ZDMA_CH_ISR, INV_APB, 0, 1) |
| REG32(ZDMA_CH_IMR, 0x104) |
| FIELD(ZDMA_CH_IMR, DMA_PAUSE, 11, 1) |
| FIELD(ZDMA_CH_IMR, DMA_DONE, 10, 1) |
| FIELD(ZDMA_CH_IMR, AXI_WR_DATA, 9, 1) |
| FIELD(ZDMA_CH_IMR, AXI_RD_DATA, 8, 1) |
| FIELD(ZDMA_CH_IMR, AXI_RD_DST_DSCR, 7, 1) |
| FIELD(ZDMA_CH_IMR, AXI_RD_SRC_DSCR, 6, 1) |
| FIELD(ZDMA_CH_IMR, IRQ_DST_ACCT_ERR, 5, 1) |
| FIELD(ZDMA_CH_IMR, IRQ_SRC_ACCT_ERR, 4, 1) |
| FIELD(ZDMA_CH_IMR, BYTE_CNT_OVRFL, 3, 1) |
| FIELD(ZDMA_CH_IMR, DST_DSCR_DONE, 2, 1) |
| FIELD(ZDMA_CH_IMR, SRC_DSCR_DONE, 1, 1) |
| FIELD(ZDMA_CH_IMR, INV_APB, 0, 1) |
| REG32(ZDMA_CH_IEN, 0x108) |
| FIELD(ZDMA_CH_IEN, DMA_PAUSE, 11, 1) |
| FIELD(ZDMA_CH_IEN, DMA_DONE, 10, 1) |
| FIELD(ZDMA_CH_IEN, AXI_WR_DATA, 9, 1) |
| FIELD(ZDMA_CH_IEN, AXI_RD_DATA, 8, 1) |
| FIELD(ZDMA_CH_IEN, AXI_RD_DST_DSCR, 7, 1) |
| FIELD(ZDMA_CH_IEN, AXI_RD_SRC_DSCR, 6, 1) |
| FIELD(ZDMA_CH_IEN, IRQ_DST_ACCT_ERR, 5, 1) |
| FIELD(ZDMA_CH_IEN, IRQ_SRC_ACCT_ERR, 4, 1) |
| FIELD(ZDMA_CH_IEN, BYTE_CNT_OVRFL, 3, 1) |
| FIELD(ZDMA_CH_IEN, DST_DSCR_DONE, 2, 1) |
| FIELD(ZDMA_CH_IEN, SRC_DSCR_DONE, 1, 1) |
| FIELD(ZDMA_CH_IEN, INV_APB, 0, 1) |
| REG32(ZDMA_CH_IDS, 0x10c) |
| FIELD(ZDMA_CH_IDS, DMA_PAUSE, 11, 1) |
| FIELD(ZDMA_CH_IDS, DMA_DONE, 10, 1) |
| FIELD(ZDMA_CH_IDS, AXI_WR_DATA, 9, 1) |
| FIELD(ZDMA_CH_IDS, AXI_RD_DATA, 8, 1) |
| FIELD(ZDMA_CH_IDS, AXI_RD_DST_DSCR, 7, 1) |
| FIELD(ZDMA_CH_IDS, AXI_RD_SRC_DSCR, 6, 1) |
| FIELD(ZDMA_CH_IDS, IRQ_DST_ACCT_ERR, 5, 1) |
| FIELD(ZDMA_CH_IDS, IRQ_SRC_ACCT_ERR, 4, 1) |
| FIELD(ZDMA_CH_IDS, BYTE_CNT_OVRFL, 3, 1) |
| FIELD(ZDMA_CH_IDS, DST_DSCR_DONE, 2, 1) |
| FIELD(ZDMA_CH_IDS, SRC_DSCR_DONE, 1, 1) |
| FIELD(ZDMA_CH_IDS, INV_APB, 0, 1) |
| REG32(ZDMA_CH_CTRL0, 0x110) |
| FIELD(ZDMA_CH_CTRL0, OVR_FETCH, 7, 1) |
| FIELD(ZDMA_CH_CTRL0, POINT_TYPE, 6, 1) |
| FIELD(ZDMA_CH_CTRL0, MODE, 4, 2) |
| FIELD(ZDMA_CH_CTRL0, RATE_CTRL, 3, 1) |
| FIELD(ZDMA_CH_CTRL0, CONT_ADDR, 2, 1) |
| FIELD(ZDMA_CH_CTRL0, CONT, 1, 1) |
| REG32(ZDMA_CH_CTRL1, 0x114) |
| FIELD(ZDMA_CH_CTRL1, DST_ISSUE, 5, 5) |
| FIELD(ZDMA_CH_CTRL1, SRC_ISSUE, 0, 5) |
| REG32(ZDMA_CH_FCI, 0x118) |
| FIELD(ZDMA_CH_FCI, PROG_CELL_CNT, 2, 2) |
| FIELD(ZDMA_CH_FCI, SIDE, 1, 1) |
| FIELD(ZDMA_CH_FCI, EN, 0, 1) |
| REG32(ZDMA_CH_STATUS, 0x11c) |
| FIELD(ZDMA_CH_STATUS, STATE, 0, 2) |
| REG32(ZDMA_CH_DATA_ATTR, 0x120) |
| FIELD(ZDMA_CH_DATA_ATTR, ARBURST, 26, 2) |
| FIELD(ZDMA_CH_DATA_ATTR, ARCACHE, 22, 4) |
| FIELD(ZDMA_CH_DATA_ATTR, ARQOS, 18, 4) |
| FIELD(ZDMA_CH_DATA_ATTR, ARLEN, 14, 4) |
| FIELD(ZDMA_CH_DATA_ATTR, AWBURST, 12, 2) |
| FIELD(ZDMA_CH_DATA_ATTR, AWCACHE, 8, 4) |
| FIELD(ZDMA_CH_DATA_ATTR, AWQOS, 4, 4) |
| FIELD(ZDMA_CH_DATA_ATTR, AWLEN, 0, 4) |
| REG32(ZDMA_CH_DSCR_ATTR, 0x124) |
| FIELD(ZDMA_CH_DSCR_ATTR, AXCOHRNT, 8, 1) |
| FIELD(ZDMA_CH_DSCR_ATTR, AXCACHE, 4, 4) |
| FIELD(ZDMA_CH_DSCR_ATTR, AXQOS, 0, 4) |
| REG32(ZDMA_CH_SRC_DSCR_WORD0, 0x128) |
| REG32(ZDMA_CH_SRC_DSCR_WORD1, 0x12c) |
| FIELD(ZDMA_CH_SRC_DSCR_WORD1, MSB, 0, 17) |
| REG32(ZDMA_CH_SRC_DSCR_WORD2, 0x130) |
| FIELD(ZDMA_CH_SRC_DSCR_WORD2, SIZE, 0, 30) |
| REG32(ZDMA_CH_SRC_DSCR_WORD3, 0x134) |
| FIELD(ZDMA_CH_SRC_DSCR_WORD3, CMD, 3, 2) |
| FIELD(ZDMA_CH_SRC_DSCR_WORD3, INTR, 2, 1) |
| FIELD(ZDMA_CH_SRC_DSCR_WORD3, TYPE, 1, 1) |
| FIELD(ZDMA_CH_SRC_DSCR_WORD3, COHRNT, 0, 1) |
| REG32(ZDMA_CH_DST_DSCR_WORD0, 0x138) |
| REG32(ZDMA_CH_DST_DSCR_WORD1, 0x13c) |
| FIELD(ZDMA_CH_DST_DSCR_WORD1, MSB, 0, 17) |
| REG32(ZDMA_CH_DST_DSCR_WORD2, 0x140) |
| FIELD(ZDMA_CH_DST_DSCR_WORD2, SIZE, 0, 30) |
| REG32(ZDMA_CH_DST_DSCR_WORD3, 0x144) |
| FIELD(ZDMA_CH_DST_DSCR_WORD3, INTR, 2, 1) |
| FIELD(ZDMA_CH_DST_DSCR_WORD3, TYPE, 1, 1) |
| FIELD(ZDMA_CH_DST_DSCR_WORD3, COHRNT, 0, 1) |
| REG32(ZDMA_CH_WR_ONLY_WORD0, 0x148) |
| REG32(ZDMA_CH_WR_ONLY_WORD1, 0x14c) |
| REG32(ZDMA_CH_WR_ONLY_WORD2, 0x150) |
| REG32(ZDMA_CH_WR_ONLY_WORD3, 0x154) |
| REG32(ZDMA_CH_SRC_START_LSB, 0x158) |
| REG32(ZDMA_CH_SRC_START_MSB, 0x15c) |
| FIELD(ZDMA_CH_SRC_START_MSB, ADDR, 0, 17) |
| REG32(ZDMA_CH_DST_START_LSB, 0x160) |
| REG32(ZDMA_CH_DST_START_MSB, 0x164) |
| FIELD(ZDMA_CH_DST_START_MSB, ADDR, 0, 17) |
| REG32(ZDMA_CH_RATE_CTRL, 0x18c) |
| FIELD(ZDMA_CH_RATE_CTRL, CNT, 0, 12) |
| REG32(ZDMA_CH_SRC_CUR_PYLD_LSB, 0x168) |
| REG32(ZDMA_CH_SRC_CUR_PYLD_MSB, 0x16c) |
| FIELD(ZDMA_CH_SRC_CUR_PYLD_MSB, ADDR, 0, 17) |
| REG32(ZDMA_CH_DST_CUR_PYLD_LSB, 0x170) |
| REG32(ZDMA_CH_DST_CUR_PYLD_MSB, 0x174) |
| FIELD(ZDMA_CH_DST_CUR_PYLD_MSB, ADDR, 0, 17) |
| REG32(ZDMA_CH_SRC_CUR_DSCR_LSB, 0x178) |
| REG32(ZDMA_CH_SRC_CUR_DSCR_MSB, 0x17c) |
| FIELD(ZDMA_CH_SRC_CUR_DSCR_MSB, ADDR, 0, 17) |
| REG32(ZDMA_CH_DST_CUR_DSCR_LSB, 0x180) |
| REG32(ZDMA_CH_DST_CUR_DSCR_MSB, 0x184) |
| FIELD(ZDMA_CH_DST_CUR_DSCR_MSB, ADDR, 0, 17) |
| REG32(ZDMA_CH_TOTAL_BYTE, 0x188) |
| REG32(ZDMA_CH_RATE_CNTL, 0x18c) |
| FIELD(ZDMA_CH_RATE_CNTL, CNT, 0, 12) |
| REG32(ZDMA_CH_IRQ_SRC_ACCT, 0x190) |
| FIELD(ZDMA_CH_IRQ_SRC_ACCT, CNT, 0, 8) |
| REG32(ZDMA_CH_IRQ_DST_ACCT, 0x194) |
| FIELD(ZDMA_CH_IRQ_DST_ACCT, CNT, 0, 8) |
| REG32(ZDMA_CH_DBG0, 0x198) |
| FIELD(ZDMA_CH_DBG0, CMN_BUF_FREE, 0, 9) |
| REG32(ZDMA_CH_DBG1, 0x19c) |
| FIELD(ZDMA_CH_DBG1, CMN_BUF_OCC, 0, 9) |
| REG32(ZDMA_CH_CTRL2, 0x200) |
| FIELD(ZDMA_CH_CTRL2, EN, 0, 1) |
| |
| enum { |
| PT_REG = 0, |
| PT_MEM = 1, |
| }; |
| |
| enum { |
| CMD_HALT = 1, |
| CMD_STOP = 2, |
| }; |
| |
| enum { |
| RW_MODE_RW = 0, |
| RW_MODE_WO = 1, |
| RW_MODE_RO = 2, |
| }; |
| |
| enum { |
| DTYPE_LINEAR = 0, |
| DTYPE_LINKED = 1, |
| }; |
| |
| enum { |
| AXI_BURST_FIXED = 0, |
| AXI_BURST_INCR = 1, |
| }; |
| |
| static void zdma_ch_imr_update_irq(XlnxZDMA *s) |
| { |
| bool pending; |
| |
| pending = s->regs[R_ZDMA_CH_ISR] & ~s->regs[R_ZDMA_CH_IMR]; |
| |
| qemu_set_irq(s->irq_zdma_ch_imr, pending); |
| } |
| |
| static void zdma_ch_isr_postw(RegisterInfo *reg, uint64_t val64) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(reg->opaque); |
| zdma_ch_imr_update_irq(s); |
| } |
| |
| static uint64_t zdma_ch_ien_prew(RegisterInfo *reg, uint64_t val64) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(reg->opaque); |
| uint32_t val = val64; |
| |
| s->regs[R_ZDMA_CH_IMR] &= ~val; |
| zdma_ch_imr_update_irq(s); |
| return 0; |
| } |
| |
| static uint64_t zdma_ch_ids_prew(RegisterInfo *reg, uint64_t val64) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(reg->opaque); |
| uint32_t val = val64; |
| |
| s->regs[R_ZDMA_CH_IMR] |= val; |
| zdma_ch_imr_update_irq(s); |
| return 0; |
| } |
| |
| static void zdma_set_state(XlnxZDMA *s, XlnxZDMAState state) |
| { |
| s->state = state; |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_STATUS, STATE, state); |
| |
| /* Signal error if we have an error condition. */ |
| if (s->error) { |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_STATUS, STATE, 3); |
| } |
| } |
| |
| static void zdma_src_done(XlnxZDMA *s) |
| { |
| unsigned int cnt; |
| cnt = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_IRQ_SRC_ACCT, CNT); |
| cnt++; |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_IRQ_SRC_ACCT, CNT, cnt); |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, SRC_DSCR_DONE, true); |
| |
| /* Did we overflow? */ |
| if (cnt != ARRAY_FIELD_EX32(s->regs, ZDMA_CH_IRQ_SRC_ACCT, CNT)) { |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, IRQ_SRC_ACCT_ERR, true); |
| } |
| zdma_ch_imr_update_irq(s); |
| } |
| |
| static void zdma_dst_done(XlnxZDMA *s) |
| { |
| unsigned int cnt; |
| cnt = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_IRQ_DST_ACCT, CNT); |
| cnt++; |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_IRQ_DST_ACCT, CNT, cnt); |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, DST_DSCR_DONE, true); |
| |
| /* Did we overflow? */ |
| if (cnt != ARRAY_FIELD_EX32(s->regs, ZDMA_CH_IRQ_DST_ACCT, CNT)) { |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, IRQ_DST_ACCT_ERR, true); |
| } |
| zdma_ch_imr_update_irq(s); |
| } |
| |
| static uint64_t zdma_get_regaddr64(XlnxZDMA *s, unsigned int basereg) |
| { |
| uint64_t addr; |
| |
| addr = s->regs[basereg + 1]; |
| addr <<= 32; |
| addr |= s->regs[basereg]; |
| |
| return addr; |
| } |
| |
| static void zdma_put_regaddr64(XlnxZDMA *s, unsigned int basereg, uint64_t addr) |
| { |
| s->regs[basereg] = addr; |
| s->regs[basereg + 1] = addr >> 32; |
| } |
| |
| static void zdma_load_descriptor_reg(XlnxZDMA *s, unsigned int reg, |
| XlnxZDMADescr *descr) |
| { |
| descr->addr = zdma_get_regaddr64(s, reg); |
| descr->size = s->regs[reg + 2]; |
| descr->attr = s->regs[reg + 3]; |
| } |
| |
| static bool zdma_load_descriptor(XlnxZDMA *s, uint64_t addr, |
| XlnxZDMADescr *descr) |
| { |
| /* ZDMA descriptors must be aligned to their own size. */ |
| if (addr % sizeof(XlnxZDMADescr)) { |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "zdma: unaligned descriptor at %" PRIx64, |
| addr); |
| memset(descr, 0x0, sizeof(XlnxZDMADescr)); |
| s->error = true; |
| return false; |
| } |
| |
| descr->addr = address_space_ldq_le(&s->dma_as, addr, s->attr, NULL); |
| descr->size = address_space_ldl_le(&s->dma_as, addr + 8, s->attr, NULL); |
| descr->attr = address_space_ldl_le(&s->dma_as, addr + 12, s->attr, NULL); |
| return true; |
| } |
| |
| static void zdma_load_src_descriptor(XlnxZDMA *s) |
| { |
| uint64_t src_addr; |
| unsigned int ptype = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, POINT_TYPE); |
| |
| if (ptype == PT_REG) { |
| zdma_load_descriptor_reg(s, R_ZDMA_CH_SRC_DSCR_WORD0, &s->dsc_src); |
| return; |
| } |
| |
| src_addr = zdma_get_regaddr64(s, R_ZDMA_CH_SRC_CUR_DSCR_LSB); |
| |
| if (!zdma_load_descriptor(s, src_addr, &s->dsc_src)) { |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, AXI_RD_SRC_DSCR, true); |
| } |
| } |
| |
| static void zdma_update_descr_addr(XlnxZDMA *s, bool type, |
| unsigned int basereg) |
| { |
| uint64_t addr, next; |
| |
| if (type == DTYPE_LINEAR) { |
| addr = zdma_get_regaddr64(s, basereg); |
| next = addr + sizeof(s->dsc_dst); |
| } else { |
| addr = zdma_get_regaddr64(s, basereg); |
| addr += sizeof(s->dsc_dst); |
| next = address_space_ldq_le(&s->dma_as, addr, s->attr, NULL); |
| } |
| |
| zdma_put_regaddr64(s, basereg, next); |
| } |
| |
| static void zdma_load_dst_descriptor(XlnxZDMA *s) |
| { |
| uint64_t dst_addr; |
| unsigned int ptype = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, POINT_TYPE); |
| bool dst_type; |
| |
| if (ptype == PT_REG) { |
| zdma_load_descriptor_reg(s, R_ZDMA_CH_DST_DSCR_WORD0, &s->dsc_dst); |
| return; |
| } |
| |
| dst_addr = zdma_get_regaddr64(s, R_ZDMA_CH_DST_CUR_DSCR_LSB); |
| |
| if (!zdma_load_descriptor(s, dst_addr, &s->dsc_dst)) { |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, AXI_RD_DST_DSCR, true); |
| } |
| |
| /* Advance the descriptor pointer. */ |
| dst_type = FIELD_EX32(s->dsc_dst.words[3], ZDMA_CH_DST_DSCR_WORD3, TYPE); |
| zdma_update_descr_addr(s, dst_type, R_ZDMA_CH_DST_CUR_DSCR_LSB); |
| } |
| |
| static void zdma_write_dst(XlnxZDMA *s, uint8_t *buf, uint32_t len) |
| { |
| uint32_t dst_size, dlen; |
| bool dst_intr; |
| unsigned int ptype = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, POINT_TYPE); |
| unsigned int rw_mode = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, MODE); |
| unsigned int burst_type = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_DATA_ATTR, |
| AWBURST); |
| |
| /* FIXED burst types are only supported in simple dma mode. */ |
| if (ptype != PT_REG) { |
| burst_type = AXI_BURST_INCR; |
| } |
| |
| while (len) { |
| dst_size = FIELD_EX32(s->dsc_dst.words[2], ZDMA_CH_DST_DSCR_WORD2, |
| SIZE); |
| if (dst_size == 0 && ptype == PT_MEM) { |
| zdma_load_dst_descriptor(s); |
| dst_size = FIELD_EX32(s->dsc_dst.words[2], ZDMA_CH_DST_DSCR_WORD2, |
| SIZE); |
| } |
| |
| /* Match what hardware does by ignoring the dst_size and only using |
| * the src size for Simple register mode. */ |
| if (ptype == PT_REG && rw_mode != RW_MODE_WO) { |
| dst_size = len; |
| } |
| |
| dst_intr = FIELD_EX32(s->dsc_dst.words[3], ZDMA_CH_DST_DSCR_WORD3, |
| INTR); |
| |
| dlen = len > dst_size ? dst_size : len; |
| if (burst_type == AXI_BURST_FIXED) { |
| if (dlen > (s->cfg.bus_width / 8)) { |
| dlen = s->cfg.bus_width / 8; |
| } |
| } |
| |
| address_space_write(&s->dma_as, s->dsc_dst.addr, s->attr, buf, dlen); |
| if (burst_type == AXI_BURST_INCR) { |
| s->dsc_dst.addr += dlen; |
| } |
| dst_size -= dlen; |
| buf += dlen; |
| len -= dlen; |
| |
| if (dst_size == 0 && dst_intr) { |
| zdma_dst_done(s); |
| } |
| |
| /* Write back to buffered descriptor. */ |
| s->dsc_dst.words[2] = FIELD_DP32(s->dsc_dst.words[2], |
| ZDMA_CH_DST_DSCR_WORD2, |
| SIZE, |
| dst_size); |
| } |
| } |
| |
| static void zdma_process_descr(XlnxZDMA *s) |
| { |
| uint64_t src_addr; |
| uint32_t src_size, len; |
| unsigned int src_cmd; |
| bool src_intr, src_type; |
| unsigned int ptype = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, POINT_TYPE); |
| unsigned int rw_mode = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, MODE); |
| unsigned int burst_type = ARRAY_FIELD_EX32(s->regs, ZDMA_CH_DATA_ATTR, |
| ARBURST); |
| |
| src_addr = s->dsc_src.addr; |
| src_size = FIELD_EX32(s->dsc_src.words[2], ZDMA_CH_SRC_DSCR_WORD2, SIZE); |
| src_cmd = FIELD_EX32(s->dsc_src.words[3], ZDMA_CH_SRC_DSCR_WORD3, CMD); |
| src_type = FIELD_EX32(s->dsc_src.words[3], ZDMA_CH_SRC_DSCR_WORD3, TYPE); |
| src_intr = FIELD_EX32(s->dsc_src.words[3], ZDMA_CH_SRC_DSCR_WORD3, INTR); |
| |
| /* FIXED burst types and non-rw modes are only supported in |
| * simple dma mode. |
| */ |
| if (ptype != PT_REG) { |
| if (rw_mode != RW_MODE_RW) { |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "zDMA: rw-mode=%d but not simple DMA mode.\n", |
| rw_mode); |
| } |
| if (burst_type != AXI_BURST_INCR) { |
| qemu_log_mask(LOG_GUEST_ERROR, |
| "zDMA: burst_type=%d but not simple DMA mode.\n", |
| burst_type); |
| } |
| burst_type = AXI_BURST_INCR; |
| rw_mode = RW_MODE_RW; |
| } |
| |
| if (rw_mode == RW_MODE_WO) { |
| /* In Simple DMA Write-Only, we need to push DST size bytes |
| * regardless of what SRC size is set to. */ |
| src_size = FIELD_EX32(s->dsc_dst.words[2], ZDMA_CH_DST_DSCR_WORD2, |
| SIZE); |
| memcpy(s->buf, &s->regs[R_ZDMA_CH_WR_ONLY_WORD0], s->cfg.bus_width / 8); |
| } |
| |
| while (src_size) { |
| len = src_size > ARRAY_SIZE(s->buf) ? ARRAY_SIZE(s->buf) : src_size; |
| if (burst_type == AXI_BURST_FIXED) { |
| if (len > (s->cfg.bus_width / 8)) { |
| len = s->cfg.bus_width / 8; |
| } |
| } |
| |
| if (rw_mode == RW_MODE_WO) { |
| if (len > s->cfg.bus_width / 8) { |
| len = s->cfg.bus_width / 8; |
| } |
| } else { |
| address_space_read(&s->dma_as, src_addr, s->attr, s->buf, len); |
| if (burst_type == AXI_BURST_INCR) { |
| src_addr += len; |
| } |
| } |
| |
| if (rw_mode != RW_MODE_RO) { |
| zdma_write_dst(s, s->buf, len); |
| } |
| |
| s->regs[R_ZDMA_CH_TOTAL_BYTE] += len; |
| src_size -= len; |
| } |
| |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, DMA_DONE, true); |
| |
| if (src_intr) { |
| zdma_src_done(s); |
| } |
| |
| if (ptype == PT_REG || src_cmd == CMD_STOP) { |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_CTRL2, EN, 0); |
| zdma_set_state(s, DISABLED); |
| } |
| |
| if (src_cmd == CMD_HALT) { |
| zdma_set_state(s, PAUSED); |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, DMA_PAUSE, 1); |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, DMA_DONE, false); |
| zdma_ch_imr_update_irq(s); |
| return; |
| } |
| |
| zdma_update_descr_addr(s, src_type, R_ZDMA_CH_SRC_CUR_DSCR_LSB); |
| } |
| |
| static void zdma_run(XlnxZDMA *s) |
| { |
| while (s->state == ENABLED && !s->error) { |
| zdma_load_src_descriptor(s); |
| |
| if (s->error) { |
| zdma_set_state(s, DISABLED); |
| } else { |
| zdma_process_descr(s); |
| } |
| } |
| |
| zdma_ch_imr_update_irq(s); |
| } |
| |
| static void zdma_update_descr_addr_from_start(XlnxZDMA *s) |
| { |
| uint64_t src_addr, dst_addr; |
| |
| src_addr = zdma_get_regaddr64(s, R_ZDMA_CH_SRC_START_LSB); |
| zdma_put_regaddr64(s, R_ZDMA_CH_SRC_CUR_DSCR_LSB, src_addr); |
| dst_addr = zdma_get_regaddr64(s, R_ZDMA_CH_DST_START_LSB); |
| zdma_put_regaddr64(s, R_ZDMA_CH_DST_CUR_DSCR_LSB, dst_addr); |
| zdma_load_dst_descriptor(s); |
| } |
| |
| static void zdma_ch_ctrlx_postw(RegisterInfo *reg, uint64_t val64) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(reg->opaque); |
| |
| if (ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL2, EN)) { |
| s->error = false; |
| |
| if (s->state == PAUSED && |
| ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, CONT)) { |
| if (ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, CONT_ADDR) == 1) { |
| zdma_update_descr_addr_from_start(s); |
| } else { |
| bool src_type = FIELD_EX32(s->dsc_src.words[3], |
| ZDMA_CH_SRC_DSCR_WORD3, TYPE); |
| zdma_update_descr_addr(s, src_type, |
| R_ZDMA_CH_SRC_CUR_DSCR_LSB); |
| } |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_CTRL0, CONT, false); |
| zdma_set_state(s, ENABLED); |
| } else if (s->state == DISABLED) { |
| zdma_update_descr_addr_from_start(s); |
| zdma_set_state(s, ENABLED); |
| } |
| } else { |
| /* Leave Paused state? */ |
| if (s->state == PAUSED && |
| ARRAY_FIELD_EX32(s->regs, ZDMA_CH_CTRL0, CONT)) { |
| zdma_set_state(s, DISABLED); |
| } |
| } |
| |
| zdma_run(s); |
| } |
| |
| static RegisterAccessInfo zdma_regs_info[] = { |
| { .name = "ZDMA_ERR_CTRL", .addr = A_ZDMA_ERR_CTRL, |
| .rsvd = 0xfffffffe, |
| },{ .name = "ZDMA_CH_ISR", .addr = A_ZDMA_CH_ISR, |
| .rsvd = 0xfffff000, |
| .w1c = 0xfff, |
| .post_write = zdma_ch_isr_postw, |
| },{ .name = "ZDMA_CH_IMR", .addr = A_ZDMA_CH_IMR, |
| .reset = 0xfff, |
| .rsvd = 0xfffff000, |
| .ro = 0xfff, |
| },{ .name = "ZDMA_CH_IEN", .addr = A_ZDMA_CH_IEN, |
| .rsvd = 0xfffff000, |
| .pre_write = zdma_ch_ien_prew, |
| },{ .name = "ZDMA_CH_IDS", .addr = A_ZDMA_CH_IDS, |
| .rsvd = 0xfffff000, |
| .pre_write = zdma_ch_ids_prew, |
| },{ .name = "ZDMA_CH_CTRL0", .addr = A_ZDMA_CH_CTRL0, |
| .reset = 0x80, |
| .rsvd = 0xffffff01, |
| .post_write = zdma_ch_ctrlx_postw, |
| },{ .name = "ZDMA_CH_CTRL1", .addr = A_ZDMA_CH_CTRL1, |
| .reset = 0x3ff, |
| .rsvd = 0xfffffc00, |
| },{ .name = "ZDMA_CH_FCI", .addr = A_ZDMA_CH_FCI, |
| .rsvd = 0xffffffc0, |
| },{ .name = "ZDMA_CH_STATUS", .addr = A_ZDMA_CH_STATUS, |
| .rsvd = 0xfffffffc, |
| .ro = 0x3, |
| },{ .name = "ZDMA_CH_DATA_ATTR", .addr = A_ZDMA_CH_DATA_ATTR, |
| .reset = 0x483d20f, |
| .rsvd = 0xf0000000, |
| },{ .name = "ZDMA_CH_DSCR_ATTR", .addr = A_ZDMA_CH_DSCR_ATTR, |
| .rsvd = 0xfffffe00, |
| },{ .name = "ZDMA_CH_SRC_DSCR_WORD0", .addr = A_ZDMA_CH_SRC_DSCR_WORD0, |
| },{ .name = "ZDMA_CH_SRC_DSCR_WORD1", .addr = A_ZDMA_CH_SRC_DSCR_WORD1, |
| .rsvd = 0xfffe0000, |
| },{ .name = "ZDMA_CH_SRC_DSCR_WORD2", .addr = A_ZDMA_CH_SRC_DSCR_WORD2, |
| .rsvd = 0xc0000000, |
| },{ .name = "ZDMA_CH_SRC_DSCR_WORD3", .addr = A_ZDMA_CH_SRC_DSCR_WORD3, |
| .rsvd = 0xffffffe0, |
| },{ .name = "ZDMA_CH_DST_DSCR_WORD0", .addr = A_ZDMA_CH_DST_DSCR_WORD0, |
| },{ .name = "ZDMA_CH_DST_DSCR_WORD1", .addr = A_ZDMA_CH_DST_DSCR_WORD1, |
| .rsvd = 0xfffe0000, |
| },{ .name = "ZDMA_CH_DST_DSCR_WORD2", .addr = A_ZDMA_CH_DST_DSCR_WORD2, |
| .rsvd = 0xc0000000, |
| },{ .name = "ZDMA_CH_DST_DSCR_WORD3", .addr = A_ZDMA_CH_DST_DSCR_WORD3, |
| .rsvd = 0xfffffffa, |
| },{ .name = "ZDMA_CH_WR_ONLY_WORD0", .addr = A_ZDMA_CH_WR_ONLY_WORD0, |
| },{ .name = "ZDMA_CH_WR_ONLY_WORD1", .addr = A_ZDMA_CH_WR_ONLY_WORD1, |
| },{ .name = "ZDMA_CH_WR_ONLY_WORD2", .addr = A_ZDMA_CH_WR_ONLY_WORD2, |
| },{ .name = "ZDMA_CH_WR_ONLY_WORD3", .addr = A_ZDMA_CH_WR_ONLY_WORD3, |
| },{ .name = "ZDMA_CH_SRC_START_LSB", .addr = A_ZDMA_CH_SRC_START_LSB, |
| },{ .name = "ZDMA_CH_SRC_START_MSB", .addr = A_ZDMA_CH_SRC_START_MSB, |
| .rsvd = 0xfffe0000, |
| },{ .name = "ZDMA_CH_DST_START_LSB", .addr = A_ZDMA_CH_DST_START_LSB, |
| },{ .name = "ZDMA_CH_DST_START_MSB", .addr = A_ZDMA_CH_DST_START_MSB, |
| .rsvd = 0xfffe0000, |
| },{ .name = "ZDMA_CH_SRC_CUR_PYLD_LSB", .addr = A_ZDMA_CH_SRC_CUR_PYLD_LSB, |
| .ro = 0xffffffff, |
| },{ .name = "ZDMA_CH_SRC_CUR_PYLD_MSB", .addr = A_ZDMA_CH_SRC_CUR_PYLD_MSB, |
| .rsvd = 0xfffe0000, |
| .ro = 0x1ffff, |
| },{ .name = "ZDMA_CH_DST_CUR_PYLD_LSB", .addr = A_ZDMA_CH_DST_CUR_PYLD_LSB, |
| .ro = 0xffffffff, |
| },{ .name = "ZDMA_CH_DST_CUR_PYLD_MSB", .addr = A_ZDMA_CH_DST_CUR_PYLD_MSB, |
| .rsvd = 0xfffe0000, |
| .ro = 0x1ffff, |
| },{ .name = "ZDMA_CH_SRC_CUR_DSCR_LSB", .addr = A_ZDMA_CH_SRC_CUR_DSCR_LSB, |
| .ro = 0xffffffff, |
| },{ .name = "ZDMA_CH_SRC_CUR_DSCR_MSB", .addr = A_ZDMA_CH_SRC_CUR_DSCR_MSB, |
| .rsvd = 0xfffe0000, |
| .ro = 0x1ffff, |
| },{ .name = "ZDMA_CH_DST_CUR_DSCR_LSB", .addr = A_ZDMA_CH_DST_CUR_DSCR_LSB, |
| .ro = 0xffffffff, |
| },{ .name = "ZDMA_CH_DST_CUR_DSCR_MSB", .addr = A_ZDMA_CH_DST_CUR_DSCR_MSB, |
| .rsvd = 0xfffe0000, |
| .ro = 0x1ffff, |
| },{ .name = "ZDMA_CH_TOTAL_BYTE", .addr = A_ZDMA_CH_TOTAL_BYTE, |
| .w1c = 0xffffffff, |
| },{ .name = "ZDMA_CH_RATE_CNTL", .addr = A_ZDMA_CH_RATE_CNTL, |
| .rsvd = 0xfffff000, |
| },{ .name = "ZDMA_CH_IRQ_SRC_ACCT", .addr = A_ZDMA_CH_IRQ_SRC_ACCT, |
| .rsvd = 0xffffff00, |
| .ro = 0xff, |
| .cor = 0xff, |
| },{ .name = "ZDMA_CH_IRQ_DST_ACCT", .addr = A_ZDMA_CH_IRQ_DST_ACCT, |
| .rsvd = 0xffffff00, |
| .ro = 0xff, |
| .cor = 0xff, |
| },{ .name = "ZDMA_CH_DBG0", .addr = A_ZDMA_CH_DBG0, |
| .rsvd = 0xfffffe00, |
| .ro = 0x1ff, |
| |
| /* |
| * There's SW out there that will check the debug regs for free space. |
| * Claim that we always have 0x100 free. |
| */ |
| .reset = 0x100 |
| },{ .name = "ZDMA_CH_DBG1", .addr = A_ZDMA_CH_DBG1, |
| .rsvd = 0xfffffe00, |
| .ro = 0x1ff, |
| },{ .name = "ZDMA_CH_CTRL2", .addr = A_ZDMA_CH_CTRL2, |
| .rsvd = 0xfffffffe, |
| .post_write = zdma_ch_ctrlx_postw, |
| } |
| }; |
| |
| static void zdma_reset(DeviceState *dev) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(dev); |
| unsigned int i; |
| |
| for (i = 0; i < ARRAY_SIZE(s->regs_info); ++i) { |
| register_reset(&s->regs_info[i]); |
| } |
| |
| zdma_ch_imr_update_irq(s); |
| } |
| |
| static uint64_t zdma_read(void *opaque, hwaddr addr, unsigned size) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(opaque); |
| RegisterInfo *r = &s->regs_info[addr / 4]; |
| |
| if (!r->data) { |
| char *path = object_get_canonical_path(OBJECT(s)); |
| qemu_log("%s: Decode error: read from %" HWADDR_PRIx "\n", |
| path, |
| addr); |
| g_free(path); |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, INV_APB, true); |
| zdma_ch_imr_update_irq(s); |
| return 0; |
| } |
| return register_read(r, ~0, NULL, false); |
| } |
| |
| static void zdma_write(void *opaque, hwaddr addr, uint64_t value, |
| unsigned size) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(opaque); |
| RegisterInfo *r = &s->regs_info[addr / 4]; |
| |
| if (!r->data) { |
| char *path = object_get_canonical_path(OBJECT(s)); |
| qemu_log("%s: Decode error: write to %" HWADDR_PRIx "=%" PRIx64 "\n", |
| path, |
| addr, value); |
| g_free(path); |
| ARRAY_FIELD_DP32(s->regs, ZDMA_CH_ISR, INV_APB, true); |
| zdma_ch_imr_update_irq(s); |
| return; |
| } |
| register_write(r, value, ~0, NULL, false); |
| } |
| |
| static const MemoryRegionOps zdma_ops = { |
| .read = zdma_read, |
| .write = zdma_write, |
| .endianness = DEVICE_LITTLE_ENDIAN, |
| .valid = { |
| .min_access_size = 4, |
| .max_access_size = 4, |
| }, |
| }; |
| |
| static void zdma_realize(DeviceState *dev, Error **errp) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(dev); |
| unsigned int i; |
| |
| if (!s->dma_mr) { |
| error_setg(errp, TYPE_XLNX_ZDMA " 'dma' link not set"); |
| return; |
| } |
| address_space_init(&s->dma_as, s->dma_mr, "zdma-dma"); |
| |
| for (i = 0; i < ARRAY_SIZE(zdma_regs_info); ++i) { |
| RegisterInfo *r = &s->regs_info[zdma_regs_info[i].addr / 4]; |
| |
| *r = (RegisterInfo) { |
| .data = (uint8_t *)&s->regs[ |
| zdma_regs_info[i].addr / 4], |
| .data_size = sizeof(uint32_t), |
| .access = &zdma_regs_info[i], |
| .opaque = s, |
| }; |
| } |
| |
| s->attr = MEMTXATTRS_UNSPECIFIED; |
| } |
| |
| static void zdma_init(Object *obj) |
| { |
| XlnxZDMA *s = XLNX_ZDMA(obj); |
| SysBusDevice *sbd = SYS_BUS_DEVICE(obj); |
| |
| memory_region_init_io(&s->iomem, obj, &zdma_ops, s, |
| TYPE_XLNX_ZDMA, ZDMA_R_MAX * 4); |
| sysbus_init_mmio(sbd, &s->iomem); |
| sysbus_init_irq(sbd, &s->irq_zdma_ch_imr); |
| |
| 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 VMStateDescription vmstate_zdma = { |
| .name = TYPE_XLNX_ZDMA, |
| .version_id = 1, |
| .minimum_version_id = 1, |
| .fields = (VMStateField[]) { |
| VMSTATE_UINT32_ARRAY(regs, XlnxZDMA, ZDMA_R_MAX), |
| VMSTATE_UINT32(state, XlnxZDMA), |
| VMSTATE_UINT32_ARRAY(dsc_src.words, XlnxZDMA, 4), |
| VMSTATE_UINT32_ARRAY(dsc_dst.words, XlnxZDMA, 4), |
| VMSTATE_END_OF_LIST(), |
| } |
| }; |
| |
| static Property zdma_props[] = { |
| DEFINE_PROP_UINT32("bus-width", XlnxZDMA, cfg.bus_width, 64), |
| DEFINE_PROP_END_OF_LIST(), |
| }; |
| |
| static void zdma_class_init(ObjectClass *klass, void *data) |
| { |
| DeviceClass *dc = DEVICE_CLASS(klass); |
| |
| dc->reset = zdma_reset; |
| dc->realize = zdma_realize; |
| device_class_set_props(dc, zdma_props); |
| dc->vmsd = &vmstate_zdma; |
| } |
| |
| static const TypeInfo zdma_info = { |
| .name = TYPE_XLNX_ZDMA, |
| .parent = TYPE_SYS_BUS_DEVICE, |
| .instance_size = sizeof(XlnxZDMA), |
| .class_init = zdma_class_init, |
| .instance_init = zdma_init, |
| }; |
| |
| static void zdma_register_types(void) |
| { |
| type_register_static(&zdma_info); |
| } |
| |
| type_init(zdma_register_types) |