This is an automated email from the ASF dual-hosted git repository. xiaoxiang781216 pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 736f369fd771d45434a51721bf3c71b6f241a8d0 Author: Bowen Wang <[email protected]> AuthorDate: Mon Jun 29 16:52:25 2026 +0800 drivers/virtio/virtio-blk: map the request buffers and split large transfers On a bouncing transport the data payload may exceed the shared memory mappable at once, and stack/user buffers are not reachable by the peer. Wrap req/data/resp with virtio_map_buf/virtio_unmap_buf and split the payload into chunks sized by virtio_free_largest. A failed chunk returns its error even after earlier chunks completed, since bchlib treats any non-negative result as the whole request done, and unmaps the data with length 0 so a bounce copies nothing into the caller's buffer. On MMIO/PCI without CONFIG_ARCH_ADDRENV free_largest is SIZE_MAX and map is a no-op, so this stays a single zero-copy pass. Signed-off-by: Bowen Wang <[email protected]> --- drivers/virtio/virtio-blk.c | 207 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 180 insertions(+), 27 deletions(-) diff --git a/drivers/virtio/virtio-blk.c b/drivers/virtio/virtio-blk.c index 52243531107..cbd44b966ad 100644 --- a/drivers/virtio/virtio-blk.c +++ b/drivers/virtio/virtio-blk.c @@ -27,6 +27,7 @@ #include <nuttx/debug.h> #include <errno.h> #include <stdio.h> +#include <sys/param.h> #include <nuttx/fs/fs.h> #include <nuttx/fs/ioctl.h> @@ -57,6 +58,9 @@ struct virtio_blk_priv_s /* BLK block_operations functions and they helper function */ +static ssize_t virtio_blk_rdwr_one(FAR struct virtio_blk_priv_s *priv, + FAR void *buf, blkcnt_t sector, + size_t nsectors, bool write); static ssize_t virtio_blk_rdwr(FAR struct virtio_blk_priv_s *priv, FAR void *buffer, blkcnt_t startsector, unsigned int nsectors, bool write); @@ -147,62 +151,121 @@ static void virtio_blk_wait_complete(FAR struct virtqueue *vq, } /**************************************************************************** - * Name: virtio_blk_rdwr + * Name: virtio_blk_rdwr_one * * Description: - * Common function for read and write + * Transfer a single chunk, sized to the shared memory a bounce transport + * can map at once. Map req/data/resp, submit, wait, then unmap. + * + * Returned Value: + * The number of sectors transferred (> 0) on success, or a negated errno + * on failure (-ENOMEM when the buffers cannot be mapped). * ****************************************************************************/ -static ssize_t virtio_blk_rdwr(FAR struct virtio_blk_priv_s *priv, - FAR void *buffer, blkcnt_t startsector, - unsigned int nsectors, bool write) +static ssize_t virtio_blk_rdwr_one(FAR struct virtio_blk_priv_s *priv, + FAR void *buf, blkcnt_t sector, + size_t nsectors, bool write) { FAR struct virtio_device *vdev = priv->vdev; FAR struct virtqueue *vq = vdev->vrings_info[0].vq; FAR struct virtqueue_buf vb[3]; struct virtio_blk_resp_s resp; struct virtio_blk_req_s req; + enum virtio_dma_map datadir; + FAR void *reqmap; + FAR void *respmap; + FAR void *datamap; + size_t maxsectors; + size_t datalen; + size_t chunk; irqstate_t flags; sem_t respsem; ssize_t ret; int readnum; - nxsem_init(&respsem, 0, 0); + datadir = write ? VIRTIO_DMA_TO_DEVICE : VIRTIO_DMA_FROM_DEVICE; - /* Build the block request */ + /* Build the block request for this chunk */ req.type = write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN; req.reserved = 0; - req.sector = startsector * priv->block_size >> VIRTIO_BLK_SECTOR_BITS; + req.sector = (sector * priv->block_size) >> VIRTIO_BLK_SECTOR_BITS; resp.status = VIRTIO_BLK_S_IOERR; + /* Map the data payload first, reserving one block for the tiny req/resp + * headers. The data buffer is the largest and the one likely to fail + * under carveout contention, so mapping it first means a failure costs no + * header bounce alloc/free churn. The reserved block (one sector, far + * larger than the two headers plus their alignment) keeps the later + * header maps from failing. A bounce transport returns -ENOMEM when it + * cannot satisfy a map. + */ + + maxsectors = virtio_free_largest(vdev) / priv->block_size; + if (maxsectors <= 1) + { + /* Need at least one sector for data plus one block for headers */ + + return -ENOMEM; + } + + maxsectors -= 1; + chunk = MIN(nsectors, maxsectors); + datalen = chunk * priv->block_size; + ret = virtio_map_buf(vdev, buf, datalen, datadir, &datamap); + if (ret < 0) + { + return ret; + } + + ret = virtio_map_buf(vdev, &req, sizeof(req), VIRTIO_DMA_TO_DEVICE, + &reqmap); + if (ret < 0) + { + goto out_data; + } + + ret = virtio_map_buf(vdev, &resp, sizeof(resp), VIRTIO_DMA_FROM_DEVICE, + &respmap); + if (ret < 0) + { + goto out_req; + } + + nxsem_init(&respsem, 0, 0); + /* Fill the virtqueue buffer: * Buffer 0: the block out header; * Buffer 1: the read/write buffer; * Buffer 2: the block in header, return the status. */ - vb[0].buf = &req; + vb[0].buf = reqmap; vb[0].len = VIRTIO_BLK_REQ_HEADER_SIZE; - vb[1].buf = buffer; - vb[1].len = nsectors * priv->block_size; - vb[2].buf = &resp; + vb[1].buf = datamap; + vb[1].len = datalen; + vb[2].buf = respmap; vb[2].len = VIRTIO_BLK_RESP_HEADER_SIZE; readnum = write ? 2 : 1; + flags = spin_lock_irqsave(&priv->lock); if (up_interrupt_context()) { - virtqueue_disable_cb_lock(vq, &priv->lock); + virtqueue_disable_cb(vq); } - flags = spin_lock_irqsave(&priv->lock); ret = virtqueue_add_buffer(vq, vb, readnum, 3 - readnum, &respsem); if (ret < 0) { + if (up_interrupt_context()) + { + virtqueue_enable_cb(vq); + } + spin_unlock_irqrestore(&priv->lock, flags); vrterr("virtqueue_add_buffer failed, ret=%zd\n", ret); - goto err; + goto out_resp; } virtqueue_kick(vq); @@ -212,19 +275,79 @@ static ssize_t virtio_blk_rdwr(FAR struct virtio_blk_priv_s *priv, virtio_blk_wait_complete(vq, &respsem); - if (resp.status != VIRTIO_BLK_S_OK) + if (up_interrupt_context()) + { + virtqueue_enable_cb_lock(vq, &priv->lock); + } + + ret = chunk; + +out_resp: + + /* unmap copies the device-written status back into resp; only after that + * is resp.status valid (on a bouncing transport resp is a bounce buffer). + */ + + virtio_unmap_buf(vdev, &resp, sizeof(resp), VIRTIO_DMA_FROM_DEVICE, + respmap); +out_req: + virtio_unmap_buf(vdev, &req, sizeof(req), VIRTIO_DMA_TO_DEVICE, reqmap); + + if (ret >= 0 && resp.status != VIRTIO_BLK_S_OK) { vrterr("%s Error\n", write ? "Write" : "Read"); ret = -EIO; } -err: - if (up_interrupt_context()) +out_data: + + /* On failure copy nothing back: a bounce holds no device data then, and + * the caller's buffer must stay as it was. + */ + + virtio_unmap_buf(vdev, buf, ret >= 0 ? datalen : 0, datadir, datamap); + return ret; +} + +/**************************************************************************** + * Name: virtio_blk_rdwr + * + * Description: + * Common function for read and write + * + ****************************************************************************/ + +static ssize_t virtio_blk_rdwr(FAR struct virtio_blk_priv_s *priv, + FAR void *buffer, blkcnt_t startsector, + unsigned int nsectors, bool write) +{ + blkcnt_t sector = startsector; + FAR uint8_t *buf = buffer; + ssize_t ret = 0; + + /* The data payload may exceed what the transport can map at once, so + * transfer it in chunks of virtio_free_largest(). Without a limit (MMIO/ + * PCI without CONFIG_ARCH_ADDRENV) this runs as a single pass. + */ + + while (nsectors > 0) { - virtqueue_enable_cb_lock(vq, &priv->lock); + /* Return the error even after good chunks: bchlib counts any + * non-negative result as all nsectors done. + */ + + ret = virtio_blk_rdwr_one(priv, buf, sector, nsectors, write); + if (ret < 0) + { + return ret; + } + + buf += ret * priv->block_size; + sector += ret; + nsectors -= ret; } - return ret >= 0 ? nsectors : ret; + return sector - startsector; } /**************************************************************************** @@ -329,7 +452,7 @@ static int virtio_blk_geometry(FAR struct inode *inode, } /**************************************************************************** - * Name: virtio_blk_ioctl + * Name: virtio_blk_flush ****************************************************************************/ static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv) @@ -339,12 +462,12 @@ static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv) FAR struct virtqueue_buf vb[2]; struct virtio_blk_resp_s resp; struct virtio_blk_req_s req; + FAR void *reqmap; + FAR void *respmap; irqstate_t flags; sem_t respsem; int ret; - nxsem_init(&respsem, 0, 0); - /* Build the block request */ req.type = VIRTIO_BLK_T_FLUSH; @@ -352,9 +475,27 @@ static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv) req.sector = 0; resp.status = VIRTIO_BLK_S_IOERR; - vb[0].buf = &req; + /* Map the headers so a bouncing transport can reach them */ + + ret = virtio_map_buf(vdev, &req, sizeof(req), VIRTIO_DMA_TO_DEVICE, + &reqmap); + if (ret < 0) + { + return ret; + } + + ret = virtio_map_buf(vdev, &resp, sizeof(resp), VIRTIO_DMA_FROM_DEVICE, + &respmap); + if (ret < 0) + { + goto out_req; + } + + nxsem_init(&respsem, 0, 0); + + vb[0].buf = reqmap; vb[0].len = VIRTIO_BLK_REQ_HEADER_SIZE; - vb[1].buf = &resp; + vb[1].buf = respmap; vb[1].len = VIRTIO_BLK_RESP_HEADER_SIZE; flags = spin_lock_irqsave(&priv->lock); @@ -362,7 +503,7 @@ static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv) if (ret < 0) { spin_unlock_irqrestore(&priv->lock, flags); - return ret; + goto out_resp; } virtqueue_kick(vq); @@ -371,7 +512,19 @@ static int virtio_blk_flush(FAR struct virtio_blk_priv_s *priv) /* Wait for the request completion */ virtio_blk_wait_complete(vq, &respsem); - if (resp.status != VIRTIO_BLK_S_OK) + +out_resp: + + /* unmap copies the device-written status back into resp; only after that + * is resp.status valid (on a bouncing transport resp is a bounce buffer). + */ + + virtio_unmap_buf(vdev, &resp, sizeof(resp), VIRTIO_DMA_FROM_DEVICE, + respmap); +out_req: + virtio_unmap_buf(vdev, &req, sizeof(req), VIRTIO_DMA_TO_DEVICE, reqmap); + + if (ret >= 0 && resp.status != VIRTIO_BLK_S_OK) { vrterr("Flush Error\n"); ret = -EIO;
