diff --git a/drivers/block/virtio_blk.c b/drivers/block/virtio_blk.c
index 32bf3ba07a9d..252ec9366153 100644
--- a/drivers/block/virtio_blk.c
+++ b/drivers/block/virtio_blk.c
@@ -6,6 +6,7 @@
#include <linux/hdreg.h>
#include <linux/module.h>
#include <linux/mutex.h>
+#include <linux/completion.h>
#include <linux/interrupt.h>
#include <linux/virtio.h>
#include <linux/virtio_blk.h>
@@ -52,6 +53,15 @@ struct virtio_blk_vq {
char name[VQ_NAME_LEN];
} ____cacheline_aligned_in_smp;
+struct virtio_blk_ctrl_vq {
+ struct virtqueue *vq;
+ struct mutex mutex;
+ spinlock_t lock;
+ unsigned int inflight;
+ bool dead;
+ struct completion drained;
+};
+
struct virtio_blk {
/*
* This mutex must be held by anything that may run after@@ -83,6 +93,9 @@ struct virtio_blk {
/* For zoned device */
unsigned int zone_sectors;
+
+ /* Control virtqueue state. */
+ struct virtio_blk_ctrl_vq ctrl_vq;
};
struct virtblk_req {@@ -110,6 +123,12 @@ struct virtblk_req {
struct scatterlist sg[];
};
+struct virtblk_ctrl_request {
+ __virtio32 type;
+ u8 status;
+ struct completion compl;
+};
+
static inline blk_status_t virtblk_result(u8 status)
{
switch (status) {@@ -863,11 +882,131 @@ static int virtblk_getgeo(struct gendisk *disk, struct hd_geometry *geo)
return ret;
}
+#define VIRTBLK_CTRL_VQ_DRAIN_TIMEOUT (10 * HZ)
+
+/* Prevent new submissions and wait for in-flight requests to complete. */
+static void virtblk_ctrl_vq_quiesce(struct virtio_blk *vblk)
+{
+ unsigned long flags;
+ bool need_wait;
+
+ if (!vblk->ctrl_vq.vq)
+ return;
+
+ init_completion(&vblk->ctrl_vq.drained);
+
+ spin_lock_irqsave(&vblk->ctrl_vq.lock, flags);
+ vblk->ctrl_vq.dead = true;
+ need_wait = vblk->ctrl_vq.inflight != 0;
+ spin_unlock_irqrestore(&vblk->ctrl_vq.lock, flags);
+
+ if (need_wait &&
+ !wait_for_completion_timeout(&vblk->ctrl_vq.drained,
+ VIRTBLK_CTRL_VQ_DRAIN_TIMEOUT))
+ dev_warn(&vblk->vdev->dev,
+ "timed out waiting for control queue requests to complete\n");
+}
+
+/* Fail requests left in the control queue after reset. */
+static void virtblk_ctrl_vq_drain(struct virtio_blk *vblk)
+{
+ struct virtblk_ctrl_request *creq;
+ unsigned long flags;
+
+ if (!vblk->ctrl_vq.vq)
+ return;
+
+ spin_lock_irqsave(&vblk->ctrl_vq.lock, flags);
+ while ((creq = virtqueue_detach_unused_buf(vblk->ctrl_vq.vq)) != NULL) {
+ if (WARN_ON_ONCE(!vblk->ctrl_vq.inflight))
+ ;
+ else
+ vblk->ctrl_vq.inflight--;
+ creq->status = VIRTIO_BLK_S_IOERR;
+ spin_unlock_irqrestore(&vblk->ctrl_vq.lock, flags);
+ complete(&creq->compl);
+ spin_lock_irqsave(&vblk->ctrl_vq.lock, flags);
+ }
+ spin_unlock_irqrestore(&vblk->ctrl_vq.lock, flags);
+}
+
+static void virtblk_ctrlq_callback(struct virtqueue *vq)
+{
+ struct virtio_blk *vblk = vq->vdev->priv;
+ struct virtblk_ctrl_request *creq;
+ unsigned long flags;
+ unsigned int len;
+
+ spin_lock_irqsave(&vblk->ctrl_vq.lock, flags);
+ do {
+ virtqueue_disable_cb(vq);
+ while ((creq = virtqueue_get_buf(vq, &len)) != NULL) {
+ bool drained = false;
+
+ if (WARN_ON_ONCE(!vblk->ctrl_vq.inflight)) {
+ /*
+ * Still complete the request. Never leave a
+ * synchronous caller blocked because the accounting
+ * state was already inconsistent.
+ */
+ spin_unlock_irqrestore(&vblk->ctrl_vq.lock, flags);
+ complete(&creq->compl);
+ spin_lock_irqsave(&vblk->ctrl_vq.lock, flags);
+ continue;
+ }
+
+ if (--vblk->ctrl_vq.inflight == 0 && vblk->ctrl_vq.dead)
+ drained = true;
+ spin_unlock_irqrestore(&vblk->ctrl_vq.lock, flags);
+ if (drained)
+ complete(&vblk->ctrl_vq.drained);
+ complete(&creq->compl);
+ spin_lock_irqsave(&vblk->ctrl_vq.lock, flags);
+ }
+ } while (!virtqueue_enable_cb(vq));
+ spin_unlock_irqrestore(&vblk->ctrl_vq.lock, flags);
+}
+
+/* Submit a control-queue request and wait for completion. */
+static int virtblk_ctrl_vq_request(struct virtio_blk *vblk,
+ struct virtblk_ctrl_request *creq,
+ struct scatterlist *sgs[],
+ unsigned int out_sgs, unsigned int in_sgs)
+{
+ unsigned long flags;
+ int err;
+
+ mutex_lock(&vblk->ctrl_vq.mutex);
+ init_completion(&creq->compl);
+
+ spin_lock_irqsave(&vblk->ctrl_vq.lock, flags);
+ if (vblk->ctrl_vq.dead) {
+ spin_unlock_irqrestore(&vblk->ctrl_vq.lock, flags);
+ mutex_unlock(&vblk->ctrl_vq.mutex);
+ return -ENODEV;
+ }
+ err = virtqueue_add_sgs(vblk->ctrl_vq.vq, sgs, out_sgs, in_sgs, creq, GFP_ATOMIC);
+ if (!err) {
+ vblk->ctrl_vq.inflight++;
+ virtqueue_kick(vblk->ctrl_vq.vq);
+ }
+ spin_unlock_irqrestore(&vblk->ctrl_vq.lock, flags);
+ if (err) {
+ mutex_unlock(&vblk->ctrl_vq.mutex);
+ return err;
+ }
+
+ wait_for_completion(&creq->compl);
+ mutex_unlock(&vblk->ctrl_vq.mutex);
+ return 0;
+}
+
static void virtblk_free_disk(struct gendisk *disk)
{
struct virtio_blk *vblk = disk->private_data;
ida_free(&vd_index_ida, vblk->index);
+ mutex_destroy(&vblk->ctrl_vq.mutex);
mutex_destroy(&vblk->vdev_mutex);
kfree(vblk);
}@@ -965,6 +1104,8 @@ static int init_vq(struct virtio_blk *vblk)
struct virtqueue **vqs;
unsigned short num_vqs;
unsigned short num_poll_vqs;
+ unsigned short total_vqs;
+ bool has_ctrl_vq;
struct virtio_device *vdev = vblk->vdev;
struct irq_affinity desc = { 0, };
@@ -993,12 +1134,19 @@ static int init_vq(struct virtio_blk *vblk)
vblk->io_queues[HCTX_TYPE_READ],
vblk->io_queues[HCTX_TYPE_POLL]);
+ /*
+ * The control vq is appended after the data vqs whenever
+ * F_CTRL_VQ is negotiated.
+ */
+ has_ctrl_vq = virtio_has_feature(vdev, VIRTIO_BLK_F_CTRL_VQ);
+ total_vqs = num_vqs + (has_ctrl_vq ? 1 : 0);
+
vblk->vqs = kmalloc_objs(*vblk->vqs, num_vqs);
if (!vblk->vqs)
return -ENOMEM;
- vqs_info = kzalloc_objs(*vqs_info, num_vqs);
- vqs = kmalloc_objs(*vqs, num_vqs);
+ vqs_info = kzalloc_objs(*vqs_info, total_vqs);
+ vqs = kmalloc_objs(*vqs, total_vqs);
if (!vqs_info || !vqs) {
err = -ENOMEM;
goto out;@@ -1015,8 +1163,13 @@ static int init_vq(struct virtio_blk *vblk)
vqs_info[i].name = vblk->vqs[i].name;
}
+ if (has_ctrl_vq) {
+ vqs_info[num_vqs].callback = virtblk_ctrlq_callback;
+ vqs_info[num_vqs].name = "control";
+ }
+
/* Discover virtqueues and write information to configuration. */
- err = virtio_find_vqs(vdev, num_vqs, vqs, vqs_info, &desc);
+ err = virtio_find_vqs(vdev, total_vqs, vqs, vqs_info, &desc);
if (err)
goto out;
@@ -1025,6 +1178,9 @@ static int init_vq(struct virtio_blk *vblk)
vblk->vqs[i].vq = vqs[i];
}
vblk->num_vqs = num_vqs;
+ vblk->ctrl_vq.vq = has_ctrl_vq ? vqs[num_vqs] : NULL;
+ vblk->ctrl_vq.dead = false;
+ vblk->ctrl_vq.inflight = 0;
out:
kfree(vqs);
@@ -1464,14 +1620,18 @@ static int virtblk_probe(struct virtio_device *vdev)
}
mutex_init(&vblk->vdev_mutex);
+ mutex_init(&vblk->ctrl_vq.mutex);
+ spin_lock_init(&vblk->ctrl_vq.lock);
vblk->vdev = vdev;
INIT_WORK(&vblk->config_work, virtblk_config_changed_work);
err = init_vq(vblk);
- if (err)
+ if (err) {
+ dev_err(&vdev->dev, "init virt queue failed: err = %d\n", err);
goto out_free_vblk;
+ }
/* Default queue sizing is to fill the ring. */
if (!virtblk_queue_depth) {@@ -1553,6 +1713,7 @@ static int virtblk_probe(struct virtio_device *vdev)
out_free_vq:
vdev->config->del_vqs(vdev);
kfree(vblk->vqs);
+ vblk->ctrl_vq.vq = NULL;
out_free_vblk:
kfree(vblk);
out_free_index:
@@ -1571,16 +1732,21 @@ static void virtblk_remove(struct virtio_device *vdev)
del_gendisk(vblk->disk);
blk_mq_free_tag_set(&vblk->tag_set);
+ virtblk_ctrl_vq_quiesce(vblk);
+
mutex_lock(&vblk->vdev_mutex);
/* Stop all the virtqueues. */
virtio_reset_device(vdev);
+ virtblk_ctrl_vq_drain(vblk);
/* Virtqueues are stopped, nothing can use vblk->vdev anymore. */
vblk->vdev = NULL;
vdev->config->del_vqs(vdev);
kfree(vblk->vqs);
+ vblk->vqs = NULL;
+ vblk->ctrl_vq.vq = NULL;
mutex_unlock(&vblk->vdev_mutex);
@@ -1593,6 +1759,8 @@ static int virtblk_freeze_priv(struct virtio_device *vdev)
struct request_queue *q = vblk->disk->queue;
unsigned int memflags;
+ virtblk_ctrl_vq_quiesce(vblk);
+
/* Ensure no requests in virtqueues before deleting vqs. */
memflags = blk_mq_freeze_queue(q);
blk_mq_quiesce_queue_nowait(q);
@@ -1600,6 +1768,7 @@ static int virtblk_freeze_priv(struct virtio_device *vdev)
/* Ensure we don't receive any more interrupts */
virtio_reset_device(vdev);
+ virtblk_ctrl_vq_drain(vblk);
/* Make sure no work handler is accessing the device. */
flush_work(&vblk->config_work);
@@ -1612,6 +1781,7 @@ static int virtblk_freeze_priv(struct virtio_device *vdev)
* pointers safely.
*/
vblk->vqs = NULL;
+ vblk->ctrl_vq.vq = NULL;
return 0;
}
@@ -1672,6 +1842,7 @@ static unsigned int features[] = {
VIRTIO_BLK_F_FLUSH, VIRTIO_BLK_F_TOPOLOGY, VIRTIO_BLK_F_CONFIG_WCE,
VIRTIO_BLK_F_MQ, VIRTIO_BLK_F_DISCARD, VIRTIO_BLK_F_WRITE_ZEROES,
VIRTIO_BLK_F_SECURE_ERASE, VIRTIO_BLK_F_ZONED,
+ VIRTIO_BLK_F_CTRL_VQ,
};
static struct virtio_driver virtio_blk = {diff --git a/include/uapi/linux/virtio_blk.h b/include/uapi/linux/virtio_blk.h
index 3744e4da1b2a..0a16972a1535 100644
--- a/include/uapi/linux/virtio_blk.h
+++ b/include/uapi/linux/virtio_blk.h
@@ -42,6 +42,7 @@
#define VIRTIO_BLK_F_WRITE_ZEROES 14 /* WRITE ZEROES is supported */
#define VIRTIO_BLK_F_SECURE_ERASE 16 /* Secure Erase is supported */
#define VIRTIO_BLK_F_ZONED 17 /* Zoned block device */
+#define VIRTIO_BLK_F_CTRL_VQ 22 /* Control queue */
/* Legacy feature bits */
#ifndef VIRTIO_BLK_NO_LEGACY
--
2.34.1