nvdimm: virtio_pmem: drain requests in freeze

virtio_pmem_freeze() currently deletes virtqueues and resets the device
without waking threads waiting for a virtqueue descriptor or a host
completion.

Mark the request virtqueue broken before reset. This makes new submissions
fail fast and lets -ENOSPC waiters leave the wait list. Reset the device
before draining used and unused request tokens, then delete the virtqueues.
This wakes waiters with -EIO. It also keeps the detach call on a quiesced
device.

Clear req_vq after del_vqs(). Make drain tolerate a NULL queue so remove
after freeze does not dereference a stale virtqueue pointer. Also make
virtio_pmem_flush() stop checking req_vq once the broken state is visible.
A waiter woken by freeze/remove can resume after del_vqs() has cleared
req_vq.

Signed-off-by: Li Chen <me@linux.beauty>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Message-ID: <20260630092338.2094628-13-me@linux.beauty>
This commit is contained in:
Li Chen
2026-06-30 17:23:37 +08:00
committed by Michael S. Tsirkin
parent 36e33955ae
commit 74d7d137f4
2 changed files with 34 additions and 5 deletions

View File

@@ -99,6 +99,9 @@ void virtio_pmem_drain(struct virtio_pmem *vpmem)
struct virtio_pmem_request *req;
unsigned int len;
if (!vpmem->req_vq)
return;
while ((req = virtqueue_get_buf(vpmem->req_vq, &len)) != NULL) {
virtio_pmem_clear_inflight(vpmem, req);
virtio_pmem_complete_err(req);
@@ -218,6 +221,8 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
break;
}
if (READ_ONCE(vpmem->broken))
err = -EIO;
if (err == -EIO || virtqueue_is_broken(vpmem->req_vq))
virtio_pmem_mark_broken(vpmem);

View File

@@ -17,11 +17,16 @@ static struct virtio_device_id id_table[] = {
/* Initialize virt queue */
static int init_vq(struct virtio_pmem *vpmem)
{
int err;
/* single vq */
vpmem->req_vq = virtio_find_single_vq(vpmem->vdev,
virtio_pmem_host_ack, "flush_queue");
if (IS_ERR(vpmem->req_vq))
return PTR_ERR(vpmem->req_vq);
if (IS_ERR(vpmem->req_vq)) {
err = PTR_ERR(vpmem->req_vq);
vpmem->req_vq = NULL;
return err;
}
spin_lock_init(&vpmem->pmem_lock);
INIT_LIST_HEAD(&vpmem->req_list);
@@ -31,6 +36,15 @@ static int init_vq(struct virtio_pmem *vpmem)
return 0;
};
static void virtio_pmem_del_vqs(struct virtio_pmem *vpmem)
{
if (!vpmem->req_vq)
return;
vpmem->vdev->config->del_vqs(vpmem->vdev);
vpmem->req_vq = NULL;
}
static int virtio_pmem_validate(struct virtio_device *vdev)
{
struct virtio_shm_region shm_reg;
@@ -139,7 +153,7 @@ static int virtio_pmem_probe(struct virtio_device *vdev)
virtio_reset_device(vdev);
nvdimm_bus_unregister(vpmem->nvdimm_bus);
out_vq:
vdev->config->del_vqs(vdev);
virtio_pmem_del_vqs(vpmem);
out_wq:
destroy_workqueue(vpmem->flush_wq);
out_err:
@@ -164,18 +178,28 @@ static void virtio_pmem_remove(struct virtio_device *vdev)
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
nvdimm_bus_unregister(nvdimm_bus);
vdev->config->del_vqs(vdev);
virtio_pmem_del_vqs(vpmem);
destroy_workqueue(vpmem->flush_wq);
}
static int virtio_pmem_freeze(struct virtio_device *vdev)
{
struct virtio_pmem *vpmem = vdev->priv;
unsigned long flags;
spin_lock_irqsave(&vpmem->pmem_lock, flags);
virtio_pmem_mark_broken(vpmem);
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
drain_workqueue(vpmem->flush_wq);
vdev->config->del_vqs(vdev);
virtio_reset_device(vdev);
spin_lock_irqsave(&vpmem->pmem_lock, flags);
virtio_pmem_drain(vpmem);
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
virtio_pmem_del_vqs(vpmem);
return 0;
}