* [Devel] [PATCH hci-8.1] drivers/vhost/blk: support VIRTIO_BLK_F_WRITE_ZEROES
@ 2026-11-02 11:30 Alexander Ivanov
2026-11-02 11:38 ` no-reply
0 siblings, 1 reply; 2+ messages in thread
From: Alexander Ivanov @ 2026-11-02 11:30 UTC (permalink / raw)
To: andrey.zhadchenko; +Cc: devel
QEMU virtio-blk advertises WRITE_ZEROES, so a guest switched to
vhost-blk may issue such requests and gets VIRTIO_BLK_S_UNSUPP.
Handle VIRTIO_BLK_T_WRITE_ZEROES with __blkdev_issue_zeroout(), which
splits the range by the device limit and falls back to writing zero
pages when the backend has no WRITE ZEROES support. The UNMAP flag
maps to the absence of BLKDEV_ZERO_NOUNMAP. Only a single segment per
request is accepted, as in QEMU, so the device config must report
max_write_zeroes_seg = 1. The segment is taken from the device-readable
part of the chain, where the virtio spec places it.
Complete a range not aligned to the logical block size and a request
to a read-only backend with VIRTIO_BLK_S_IOERR, as QEMU does. The
backend is read-only as for VIRTIO_BLK_T_OUT: the fd lacks FMODE_WRITE
or the bdev is read-only. BLKROSET or set_disk_ro() may switch the bdev
read-only after the check, so -EPERM of __blkdev_issue_zeroout() is
completed with VIRTIO_BLK_S_IOERR too. An error returned from the
submit path is reported as a vq error while the request never
completes, so the guest would hang on it.
Complete a range beyond the device capacity with VIRTIO_BLK_S_IOERR.
The range is zeroed in the vhost worker with vq->mutex held, so issue it
in 1 GiB chunks, each one when the previous completes. The size is not
left to the VMM: a guest reads a zero max_write_zeroes_sectors as no
limit. The request stays in flight until its last chunk, holding
vhost_blk_flush() with dev.mutex taken, so a range larger than a bio
can carry is completed with VIRTIO_BLK_S_IOERR, as QEMU does above
max_write_zeroes_sectors.
A device may drop WRITE ZEROES support only after a request fails,
e.g. sd clears the limit on a failed WRITE SAME. Like
blkdev_issue_write_zeroes(), retry such a request with zero pages when
the limit has become zero instead of reporting VIRTIO_BLK_S_IOERR.
https://virtuozzo.atlassian.net/browse/VSTOR-141684
Feature: vhost-blk: in-kernel accelerator for virtio-blk guests
Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
---
drivers/vhost/blk.c | 160 +++++++++++++++++++++++++++++++++++++++++++-
1 file changed, 158 insertions(+), 2 deletions(-)
diff --git a/drivers/vhost/blk.c b/drivers/vhost/blk.c
index 731dbf58fb69..4393fe0d84eb 100644
--- a/drivers/vhost/blk.c
+++ b/drivers/vhost/blk.c
@@ -29,7 +29,8 @@ enum {
(1ULL << VIRTIO_RING_F_INDIRECT_DESC) |
(1ULL << VIRTIO_RING_F_EVENT_IDX) |
(1ULL << VIRTIO_BLK_F_MQ) |
- (1ULL << VIRTIO_BLK_F_FLUSH),
+ (1ULL << VIRTIO_BLK_F_FLUSH) |
+ (1ULL << VIRTIO_BLK_F_WRITE_ZEROES),
};
/*
@@ -75,6 +76,9 @@ struct vhost_blk_req {
struct iovec status[VHOST_MAX_METADATA_IOV];
sector_t sector;
+ sector_t nr_sects;
+ struct block_device *bdev;
+ unsigned int zeroout_flags;
int bi_opf;
u16 head;
size_t len;
@@ -264,6 +268,47 @@ static int vhost_blk_move_bb_to_req(struct vhost_blk_req *req)
return 0;
}
+/*
+ * The range is zeroed in the vhost worker with vq->mutex held, so it is
+ * split into chunks bounded like a data request. The chunk size is a
+ * multiple of any logical block size.
+ */
+#define VHOST_BLK_MAX_WRITE_ZEROES_SECTORS (SZ_1G >> SECTOR_SHIFT)
+
+/*
+ * A request holds vhost_blk_flush() until its last chunk completes, so the
+ * whole range is bounded too. No Linux guest sends more, a bio cannot carry
+ * it.
+ */
+#define VHOST_BLK_MAX_WRITE_ZEROES_REQ_SECTORS (UINT_MAX >> SECTOR_SHIFT)
+
+static int vhost_blk_bio_make_zeroes(struct vhost_blk_req *req,
+ struct block_device *bdev)
+{
+ sector_t nr_sects = min_t(sector_t, req->nr_sects,
+ VHOST_BLK_MAX_WRITE_ZEROES_SECTORS);
+ struct bio *bio = NULL;
+ int ret;
+
+ ret = __blkdev_issue_zeroout(bdev, req->sector, nr_sects,
+ GFP_KERNEL, &bio, req->zeroout_flags);
+ if (ret)
+ return ret;
+
+ bio->bi_private = req;
+ bio->bi_end_io = vhost_blk_req_done;
+ req->bio = req->inline_bio;
+ req->bio[0] = bio;
+
+ /* Zero pages are plain writes, a failure of them is not retried */
+ if (bio_op(bio) != REQ_OP_WRITE_ZEROES)
+ req->bi_opf = REQ_OP_WRITE;
+
+ atomic_set(&req->bio_nr, 1);
+
+ return 0;
+}
+
static int vhost_blk_bio_make(struct vhost_blk_req *req,
struct block_device *bdev)
{
@@ -277,6 +322,9 @@ static int vhost_blk_bio_make(struct vhost_blk_req *req,
if (unlikely(req->bi_opf & REQ_PREFLUSH))
return vhost_blk_bio_make_simple(req, bdev);
+ if (req->bi_opf == REQ_OP_WRITE_ZEROES)
+ return vhost_blk_bio_make_zeroes(req, bdev);
+
/*
* Requests with unaligned io vectors are not supported by underlined
* block device, let's use an aligned intermediary "bounce buffer" to
@@ -443,6 +491,49 @@ static bool vhost_blk_backend_ro(struct file *f)
bdev_read_only(I_BDEV(f->f_mapping->host));
}
+static int vhost_blk_parse_write_zeroes(struct vhost_blk_req *req, u8 *status)
+{
+ struct file *f = vhost_vq_get_backend(&req->blk_vq->vq);
+ struct block_device *bdev = I_BDEV(f->f_mapping->host);
+ sector_t bs_mask = (bdev_logical_block_size(bdev) >> SECTOR_SHIFT) - 1;
+ struct virtio_blk_discard_write_zeroes seg;
+ struct iov_iter iter;
+ u32 flags;
+
+ if (req->len != sizeof(seg)) {
+ *status = VIRTIO_BLK_S_UNSUPP;
+ return 0;
+ }
+
+ iov_iter_init(&iter, ITER_SOURCE, req->iov, req->iov_nr, req->len);
+ if (copy_from_iter(&seg, sizeof(seg), &iter) != sizeof(seg))
+ return -EFAULT;
+
+ flags = le32_to_cpu(seg.flags);
+ if (flags & ~VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP) {
+ *status = VIRTIO_BLK_S_UNSUPP;
+ return 0;
+ }
+
+ req->sector = le64_to_cpu(seg.sector);
+ req->nr_sects = le32_to_cpu(seg.num_sectors);
+ req->bdev = bdev;
+ req->zeroout_flags = flags & VIRTIO_BLK_WRITE_ZEROES_FLAG_UNMAP ?
+ 0 : BLKDEV_ZERO_NOUNMAP;
+
+ if (vhost_blk_backend_ro(f) ||
+ (req->sector | req->nr_sects) & bs_mask ||
+ req->nr_sects > VHOST_BLK_MAX_WRITE_ZEROES_REQ_SECTORS ||
+ req->sector > bdev_nr_sectors(bdev) ||
+ req->nr_sects > bdev_nr_sectors(bdev) - req->sector) {
+ *status = VIRTIO_BLK_S_IOERR;
+ return 0;
+ }
+
+ *status = VIRTIO_BLK_S_OK;
+ return 0;
+}
+
static int vhost_blk_req_handle(struct vhost_virtqueue *vq,
struct virtio_blk_outhdr *hdr,
u16 head, u16 out, u16 in)
@@ -462,6 +553,7 @@ static int vhost_blk_req_handle(struct vhost_virtqueue *vq,
req->head = head;
req->blk = blk;
req->sector = vhost64_to_cpu(vq, hdr->sector);
+ req->nr_sects = 0;
req->iov = blk_vq->iov;
req->bio_err = 0;
in_len = iov_length(vq->iov + out, in);
@@ -477,7 +569,7 @@ static int vhost_blk_req_handle(struct vhost_virtqueue *vq,
req->status[0].iov_base = st->iov_base + st->iov_len;
req->status[0].iov_len = sizeof(status);
- if (type == VIRTIO_BLK_T_OUT) {
+ if (type == VIRTIO_BLK_T_OUT || type == VIRTIO_BLK_T_WRITE_ZEROES) {
data_iov = vq->iov;
data_nr = out;
} else {
@@ -525,6 +617,22 @@ static int vhost_blk_req_handle(struct vhost_virtqueue *vq,
req->bi_opf = REQ_OP_WRITE | REQ_PREFLUSH;
ret = vhost_blk_req_submit(req);
break;
+ case VIRTIO_BLK_T_WRITE_ZEROES:
+ ret = vhost_blk_parse_write_zeroes(req, &status);
+ if (ret)
+ break;
+ if (status == VIRTIO_BLK_S_OK && req->nr_sects) {
+ req->bi_opf = REQ_OP_WRITE_ZEROES;
+ ret = vhost_blk_req_submit(req);
+ if (ret != -EPERM)
+ break;
+ status = VIRTIO_BLK_S_IOERR;
+ }
+ ret = vhost_blk_set_status(req, status);
+ if (ret)
+ break;
+ vhost_add_used_and_signal(&blk->dev, vq, head, sizeof(status));
+ break;
case VIRTIO_BLK_T_GET_ID:
len = min_t(size_t,
strnlen(blk->serial, VIRTIO_BLK_ID_BYTES) + 1,
@@ -548,6 +656,51 @@ static int vhost_blk_req_handle(struct vhost_virtqueue *vq,
return ret;
}
+/*
+ * Some devices clear their WRITE ZEROES limit only after a failed request,
+ * as blkdev_issue_write_zeroes() expects. Retry such a request with zero
+ * pages instead of failing it.
+ */
+static bool vhost_blk_retry_zeroes(struct vhost_blk_req *req)
+{
+ if (!req->bio_err || req->bi_opf != REQ_OP_WRITE_ZEROES)
+ return false;
+
+ if (bdev_write_zeroes_sectors(req->bdev))
+ return false;
+
+ /* The limit may come back before the bio is built, retry only once */
+ req->bi_opf = REQ_OP_WRITE;
+ if (vhost_blk_bio_make_zeroes(req, req->bdev))
+ return false;
+
+ req->bio_err = 0;
+ vhost_blk_bio_send(req);
+ return true;
+}
+
+/* Issue the next chunk of a write-zeroes range, if any is left */
+static bool vhost_blk_next_zeroes(struct vhost_blk_req *req)
+{
+ int ret;
+
+ if (req->bio_err || req->nr_sects <= VHOST_BLK_MAX_WRITE_ZEROES_SECTORS)
+ return false;
+
+ req->sector += VHOST_BLK_MAX_WRITE_ZEROES_SECTORS;
+ req->nr_sects -= VHOST_BLK_MAX_WRITE_ZEROES_SECTORS;
+
+ req->bi_opf = REQ_OP_WRITE_ZEROES;
+ ret = vhost_blk_bio_make_zeroes(req, req->bdev);
+ if (ret) {
+ req->bio_err = ret;
+ return false;
+ }
+
+ vhost_blk_bio_send(req);
+ return true;
+}
+
static void vhost_blk_handle_guest_kick(struct vhost_work *work)
{
struct virtio_blk_outhdr hdr;
@@ -648,6 +801,9 @@ static void vhost_blk_handle_host_kick(struct vhost_work *work)
if (!blk)
blk = req->blk;
+ if (vhost_blk_retry_zeroes(req) || vhost_blk_next_zeroes(req))
+ continue;
+
if (!req->bio_err && req->bb && req->bi_opf == REQ_OP_READ) {
if (vhost_blk_move_bb_to_req(req))
req->bio_err = EINVAL;
--
2.53.0
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 2+ messages in thread* Re: [Devel] [PATCH hci-8.1] drivers/vhost/blk: support VIRTIO_BLK_F_WRITE_ZEROES
2026-11-02 11:30 [Devel] [PATCH hci-8.1] drivers/vhost/blk: support VIRTIO_BLK_F_WRITE_ZEROES Alexander Ivanov
@ 2026-11-02 11:38 ` no-reply
0 siblings, 0 replies; 2+ messages in thread
From: no-reply @ 2026-11-02 11:38 UTC (permalink / raw)
To: Alexander Ivanov, andrey.zhadchenko; +Cc: devel
> QEMU virtio-blk advertises WRITE_ZEROES, so a guest switched to
> vhost-blk may issue such requests and gets VIRTIO_BLK_S_UNSUPP.
>
> Handle VIRTIO_BLK_T_WRITE_ZEROES with __blkdev_issue_zeroout(), which
> splits the range by the device limit and falls back to writing zero
> pages when the backend has no WRITE ZEROES support. The UNMAP flag
> maps to the absence of BLKDEV_ZERO_NOUNMAP. Only a single segment per
> request is accepted, as in QEMU, so the device config must report
> max_write_zeroes_seg = 1. The segment is taken from the device-readable
> part of the chain, where the virtio spec places it.
>
> Complete a range not aligned to the logical block size and a request
> to a read-only backend with VIRTIO_BLK_S_IOERR, as QEMU does. The
> backend is read-only as for VIRTIO_BLK_T_OUT: the fd lacks FMODE_WRITE
> or the bdev is read-only. BLKROSET or set_disk_ro() may switch the bdev
> [ ... ]
> the limit has become zero instead of reporting VIRTIO_BLK_S_IOERR.
>
> https://virtuozzo.atlassian.net/browse/VSTOR-141684
> Feature: vhost-blk: in-kernel accelerator for virtio-blk guests
> Signed-off-by: Alexander Ivanov <alexander.ivanov@virtuozzo.com>
Sashiko has reviewed this patch and found no issues. It looks great!
--
Sashiko AI review · https://sashiko.vzint.dev/#/patchset/20261102113051.4190522-1-alexander.ivanov@virtuozzo.com?part=1
_______________________________________________
Devel mailing list
Devel@openvz.org
https://lists.openvz.org/mailman/listinfo/devel
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-11-02 11:39 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-02 11:30 [Devel] [PATCH hci-8.1] drivers/vhost/blk: support VIRTIO_BLK_F_WRITE_ZEROES Alexander Ivanov
2026-11-02 11:38 ` no-reply
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.