On Mon, Oct 26, 2026 at 11:11:10AM +0100, Daniel Gomez wrote:
> From: Daniel Gomez <[email protected]>
>
> When submitting large IO beyond IOV_MAX, ensure the last dma mapped
> chunk ends on an aligned boundary and unmap the remainder so the next
> chunk submission takes it. Otherwise the discard path at the end of
> dma_blk_cb() drops the unaligned tail and those bytes are lost.
>
> Reproducer: Write 8 MiB through unaligned path and read it back using
> the aligned path:
>
> fio --name=demo --filename=/dev/nvme0n1 --rw=write --bs=8M --size=8M \
> --direct=1 --iomem_align=4 --verify=crc32c
>
> fio --name=demo --filename=/dev/nvme0n1 --rw=write --bs=8M --size=8M \
> --direct=1 --verify=crc32c --verify_only
>
> Reviewed-by: Keith Busch <[email protected]>
> Signed-off-by: Daniel Gomez <[email protected]>
> ---
> system/dma-helpers.c | 11 +++++++++++
> 1 file changed, 11 insertions(+)
>
> diff --git a/system/dma-helpers.c b/system/dma-helpers.c
> index 8ee83ce9e75..9004ad50ae3 100644
> --- a/system/dma-helpers.c
> +++ b/system/dma-helpers.c
> @@ -158,6 +158,17 @@ static void dma_blk_cb(void *opaque, int ret)
> }
> if (!mem)
> break;
> +
> + /* Ensure the last slot in the chunk ends on an aligned boundary */
> + if (dbs->iov.niov == IOV_MAX - 1) {
The maximum number of iovec elements depends on the BlockDrivers that
make up this BlockBackend and it might not be IOV_MAX. blk_get_max_iov()
should be used instead of IOV_MAX.
> + dma_addr_t aligned = QEMU_ALIGN_DOWN(dbs->iov.size + cur_len,
> + dbs->align);
> + if (aligned <= dbs->iov.size) {
> + dma_memory_unmap(dbs->sg->as, mem, cur_len, dbs->dir, 0);
> + break;
> + }
> + cur_len = aligned - dbs->iov.size;
indentation is off here.
> + }
> qemu_iovec_add(&dbs->iov, mem, cur_len);
This can add an aligned-down number of bytes that is less than the
number of bytes that was mapped with dma_memory_map(). The next
dma_blk_unmap() invokes dma_memory_unmap() with this aligned-down
number and never unmaps the full original mapping we got from
dma_memory_map().
> dbs->sg_cur_byte += cur_len;
> if (dbs->sg_cur_byte == dbs->sg->sg[dbs->sg_cur_index].len) {
>
> --
> 2.56.0
>
>