X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=dma-helpers.c;h=433d8b21b344d7eaa8ffee840d2bc62eefb269fc;hb=5f893b4e9c207c5a38cf7b141d2c54fa30046afd;hp=ba7f897d4266f137f4060a9c5157fa04d7831f3d;hpb=f897235e0a0840c44645b609dc52a4c47ddfc3d5;p=qemu.git diff --git a/dma-helpers.c b/dma-helpers.c index ba7f897d4..433d8b21b 100644 --- a/dma-helpers.c +++ b/dma-helpers.c @@ -8,22 +8,53 @@ */ #include "dma.h" -#include "block_int.h" +#include "trace.h" +#include "range.h" +#include "qemu-thread.h" -void qemu_sglist_init(QEMUSGList *qsg, int alloc_hint) +/* #define DEBUG_IOMMU */ + +static void do_dma_memory_set(dma_addr_t addr, uint8_t c, dma_addr_t len) +{ +#define FILLBUF_SIZE 512 + uint8_t fillbuf[FILLBUF_SIZE]; + int l; + + memset(fillbuf, c, FILLBUF_SIZE); + while (len > 0) { + l = len < FILLBUF_SIZE ? len : FILLBUF_SIZE; + cpu_physical_memory_rw(addr, fillbuf, l, true); + len -= l; + addr += l; + } +} + +int dma_memory_set(DMAContext *dma, dma_addr_t addr, uint8_t c, dma_addr_t len) +{ + dma_barrier(dma, DMA_DIRECTION_FROM_DEVICE); + + if (dma_has_iommu(dma)) { + return iommu_dma_memory_set(dma, addr, c, len); + } + do_dma_memory_set(addr, c, len); + + return 0; +} + +void qemu_sglist_init(QEMUSGList *qsg, int alloc_hint, DMAContext *dma) { - qsg->sg = qemu_malloc(alloc_hint * sizeof(ScatterGatherEntry)); + qsg->sg = g_malloc(alloc_hint * sizeof(ScatterGatherEntry)); qsg->nsg = 0; qsg->nalloc = alloc_hint; qsg->size = 0; + qsg->dma = dma; } -void qemu_sglist_add(QEMUSGList *qsg, target_phys_addr_t base, - target_phys_addr_t len) +void qemu_sglist_add(QEMUSGList *qsg, dma_addr_t base, dma_addr_t len) { if (qsg->nsg == qsg->nalloc) { qsg->nalloc = 2 * qsg->nalloc + 1; - qsg->sg = qemu_realloc(qsg->sg, qsg->nalloc * sizeof(ScatterGatherEntry)); + qsg->sg = g_realloc(qsg->sg, qsg->nalloc * sizeof(ScatterGatherEntry)); } qsg->sg[qsg->nsg].base = base; qsg->sg[qsg->nsg].len = len; @@ -33,7 +64,8 @@ void qemu_sglist_add(QEMUSGList *qsg, target_phys_addr_t base, void qemu_sglist_destroy(QEMUSGList *qsg) { - qemu_free(qsg->sg); + g_free(qsg->sg); + memset(qsg, 0, sizeof(*qsg)); } typedef struct { @@ -42,9 +74,10 @@ typedef struct { BlockDriverAIOCB *acb; QEMUSGList *sg; uint64_t sector_num; - int is_write; + DMADirection dir; + bool in_cancel; int sg_cur_index; - target_phys_addr_t sg_cur_byte; + dma_addr_t sg_cur_byte; QEMUIOVector iov; QEMUBH *bh; DMAIOFunc *io_func; @@ -58,7 +91,7 @@ static void reschedule_dma(void *opaque) qemu_bh_delete(dbs->bh); dbs->bh = NULL; - dma_bdrv_cb(opaque, 0); + dma_bdrv_cb(dbs, 0); } static void continue_after_map_failure(void *opaque) @@ -74,34 +107,55 @@ static void dma_bdrv_unmap(DMAAIOCB *dbs) int i; for (i = 0; i < dbs->iov.niov; ++i) { - cpu_physical_memory_unmap(dbs->iov.iov[i].iov_base, - dbs->iov.iov[i].iov_len, !dbs->is_write, - dbs->iov.iov[i].iov_len); + dma_memory_unmap(dbs->sg->dma, dbs->iov.iov[i].iov_base, + dbs->iov.iov[i].iov_len, dbs->dir, + dbs->iov.iov[i].iov_len); + } + qemu_iovec_reset(&dbs->iov); +} + +static void dma_complete(DMAAIOCB *dbs, int ret) +{ + trace_dma_complete(dbs, ret, dbs->common.cb); + + dma_bdrv_unmap(dbs); + if (dbs->common.cb) { + dbs->common.cb(dbs->common.opaque, ret); + } + qemu_iovec_destroy(&dbs->iov); + if (dbs->bh) { + qemu_bh_delete(dbs->bh); + dbs->bh = NULL; + } + if (!dbs->in_cancel) { + /* Requests may complete while dma_aio_cancel is in progress. In + * this case, the AIOCB should not be released because it is still + * referenced by dma_aio_cancel. */ + qemu_aio_release(dbs); } } static void dma_bdrv_cb(void *opaque, int ret) { DMAAIOCB *dbs = (DMAAIOCB *)opaque; - target_phys_addr_t cur_addr, cur_len; + dma_addr_t cur_addr, cur_len; void *mem; + trace_dma_bdrv_cb(dbs, ret); + dbs->acb = NULL; dbs->sector_num += dbs->iov.size / 512; dma_bdrv_unmap(dbs); - qemu_iovec_reset(&dbs->iov); if (dbs->sg_cur_index == dbs->sg->nsg || ret < 0) { - dbs->common.cb(dbs->common.opaque, ret); - qemu_iovec_destroy(&dbs->iov); - qemu_aio_release(dbs); + dma_complete(dbs, ret); return; } while (dbs->sg_cur_index < dbs->sg->nsg) { cur_addr = dbs->sg->sg[dbs->sg_cur_index].base + dbs->sg_cur_byte; cur_len = dbs->sg->sg[dbs->sg_cur_index].len - dbs->sg_cur_byte; - mem = cpu_physical_memory_map(cur_addr, &cur_len, !dbs->is_write); + mem = dma_memory_map(dbs->sg->dma, cur_addr, &cur_len, dbs->dir); if (!mem) break; qemu_iovec_add(&dbs->iov, mem, cur_len); @@ -113,26 +167,31 @@ static void dma_bdrv_cb(void *opaque, int ret) } if (dbs->iov.size == 0) { + trace_dma_map_wait(dbs); cpu_register_map_client(dbs, continue_after_map_failure); return; } dbs->acb = dbs->io_func(dbs->bs, dbs->sector_num, &dbs->iov, dbs->iov.size / 512, dma_bdrv_cb, dbs); - if (!dbs->acb) { - dma_bdrv_unmap(dbs); - qemu_iovec_destroy(&dbs->iov); - return; - } + assert(dbs->acb); } static void dma_aio_cancel(BlockDriverAIOCB *acb) { DMAAIOCB *dbs = container_of(acb, DMAAIOCB, common); + trace_dma_aio_cancel(dbs); + if (dbs->acb) { - bdrv_aio_cancel(dbs->acb); + BlockDriverAIOCB *acb = dbs->acb; + dbs->acb = NULL; + dbs->in_cancel = true; + bdrv_aio_cancel(acb); + dbs->in_cancel = false; } + dbs->common.cb = NULL; + dma_complete(dbs, 0); } static AIOPool dma_aio_pool = { @@ -143,25 +202,23 @@ static AIOPool dma_aio_pool = { BlockDriverAIOCB *dma_bdrv_io( BlockDriverState *bs, QEMUSGList *sg, uint64_t sector_num, DMAIOFunc *io_func, BlockDriverCompletionFunc *cb, - void *opaque, int is_write) + void *opaque, DMADirection dir) { DMAAIOCB *dbs = qemu_aio_get(&dma_aio_pool, bs, cb, opaque); + trace_dma_bdrv_io(dbs, bs, sector_num, (dir == DMA_DIRECTION_TO_DEVICE)); + dbs->acb = NULL; dbs->bs = bs; dbs->sg = sg; dbs->sector_num = sector_num; dbs->sg_cur_index = 0; dbs->sg_cur_byte = 0; - dbs->is_write = is_write; + dbs->dir = dir; dbs->io_func = io_func; dbs->bh = NULL; qemu_iovec_init(&dbs->iov, sg->nsg); dma_bdrv_cb(dbs, 0); - if (!dbs->acb) { - qemu_aio_release(dbs); - return NULL; - } return &dbs->common; } @@ -170,12 +227,209 @@ BlockDriverAIOCB *dma_bdrv_read(BlockDriverState *bs, QEMUSGList *sg, uint64_t sector, void (*cb)(void *opaque, int ret), void *opaque) { - return dma_bdrv_io(bs, sg, sector, bdrv_aio_readv, cb, opaque, 0); + return dma_bdrv_io(bs, sg, sector, bdrv_aio_readv, cb, opaque, + DMA_DIRECTION_FROM_DEVICE); } BlockDriverAIOCB *dma_bdrv_write(BlockDriverState *bs, QEMUSGList *sg, uint64_t sector, void (*cb)(void *opaque, int ret), void *opaque) { - return dma_bdrv_io(bs, sg, sector, bdrv_aio_writev, cb, opaque, 1); + return dma_bdrv_io(bs, sg, sector, bdrv_aio_writev, cb, opaque, + DMA_DIRECTION_TO_DEVICE); +} + + +static uint64_t dma_buf_rw(uint8_t *ptr, int32_t len, QEMUSGList *sg, + DMADirection dir) +{ + uint64_t resid; + int sg_cur_index; + + resid = sg->size; + sg_cur_index = 0; + len = MIN(len, resid); + while (len > 0) { + ScatterGatherEntry entry = sg->sg[sg_cur_index++]; + int32_t xfer = MIN(len, entry.len); + dma_memory_rw(sg->dma, entry.base, ptr, xfer, dir); + ptr += xfer; + len -= xfer; + resid -= xfer; + } + + return resid; +} + +uint64_t dma_buf_read(uint8_t *ptr, int32_t len, QEMUSGList *sg) +{ + return dma_buf_rw(ptr, len, sg, DMA_DIRECTION_FROM_DEVICE); +} + +uint64_t dma_buf_write(uint8_t *ptr, int32_t len, QEMUSGList *sg) +{ + return dma_buf_rw(ptr, len, sg, DMA_DIRECTION_TO_DEVICE); +} + +void dma_acct_start(BlockDriverState *bs, BlockAcctCookie *cookie, + QEMUSGList *sg, enum BlockAcctType type) +{ + bdrv_acct_start(bs, cookie, sg->size, type); +} + +bool iommu_dma_memory_valid(DMAContext *dma, dma_addr_t addr, dma_addr_t len, + DMADirection dir) +{ + target_phys_addr_t paddr, plen; + +#ifdef DEBUG_IOMMU + fprintf(stderr, "dma_memory_check context=%p addr=0x" DMA_ADDR_FMT + " len=0x" DMA_ADDR_FMT " dir=%d\n", dma, addr, len, dir); +#endif + + while (len) { + if (dma->translate(dma, addr, &paddr, &plen, dir) != 0) { + return false; + } + + /* The translation might be valid for larger regions. */ + if (plen > len) { + plen = len; + } + + len -= plen; + addr += plen; + } + + return true; +} + +int iommu_dma_memory_rw(DMAContext *dma, dma_addr_t addr, + void *buf, dma_addr_t len, DMADirection dir) +{ + target_phys_addr_t paddr, plen; + int err; + +#ifdef DEBUG_IOMMU + fprintf(stderr, "dma_memory_rw context=%p addr=0x" DMA_ADDR_FMT " len=0x" + DMA_ADDR_FMT " dir=%d\n", dma, addr, len, dir); +#endif + + while (len) { + err = dma->translate(dma, addr, &paddr, &plen, dir); + if (err) { + /* + * In case of failure on reads from the guest, we clean the + * destination buffer so that a device that doesn't test + * for errors will not expose qemu internal memory. + */ + memset(buf, 0, len); + return -1; + } + + /* The translation might be valid for larger regions. */ + if (plen > len) { + plen = len; + } + + cpu_physical_memory_rw(paddr, buf, plen, + dir == DMA_DIRECTION_FROM_DEVICE); + + len -= plen; + addr += plen; + buf += plen; + } + + return 0; +} + +int iommu_dma_memory_set(DMAContext *dma, dma_addr_t addr, uint8_t c, + dma_addr_t len) +{ + target_phys_addr_t paddr, plen; + int err; + +#ifdef DEBUG_IOMMU + fprintf(stderr, "dma_memory_set context=%p addr=0x" DMA_ADDR_FMT + " len=0x" DMA_ADDR_FMT "\n", dma, addr, len); +#endif + + while (len) { + err = dma->translate(dma, addr, &paddr, &plen, + DMA_DIRECTION_FROM_DEVICE); + if (err) { + return err; + } + + /* The translation might be valid for larger regions. */ + if (plen > len) { + plen = len; + } + + do_dma_memory_set(paddr, c, plen); + + len -= plen; + addr += plen; + } + + return 0; +} + +void dma_context_init(DMAContext *dma, DMATranslateFunc translate, + DMAMapFunc map, DMAUnmapFunc unmap) +{ +#ifdef DEBUG_IOMMU + fprintf(stderr, "dma_context_init(%p, %p, %p, %p)\n", + dma, translate, map, unmap); +#endif + dma->translate = translate; + dma->map = map; + dma->unmap = unmap; +} + +void *iommu_dma_memory_map(DMAContext *dma, dma_addr_t addr, dma_addr_t *len, + DMADirection dir) +{ + int err; + target_phys_addr_t paddr, plen; + void *buf; + + if (dma->map) { + return dma->map(dma, addr, len, dir); + } + + plen = *len; + err = dma->translate(dma, addr, &paddr, &plen, dir); + if (err) { + return NULL; + } + + /* + * If this is true, the virtual region is contiguous, + * but the translated physical region isn't. We just + * clamp *len, much like cpu_physical_memory_map() does. + */ + if (plen < *len) { + *len = plen; + } + + buf = cpu_physical_memory_map(paddr, &plen, + dir == DMA_DIRECTION_FROM_DEVICE); + *len = plen; + + return buf; +} + +void iommu_dma_memory_unmap(DMAContext *dma, void *buffer, dma_addr_t len, + DMADirection dir, dma_addr_t access_len) +{ + if (dma->unmap) { + dma->unmap(dma, buffer, len, dir, access_len); + return; + } + + cpu_physical_memory_unmap(buffer, len, + dir == DMA_DIRECTION_FROM_DEVICE, + access_len); + }