mirror of
https://github.com/torvalds/linux.git
synced 2026-09-22 04:34:03 +02:00
nvdimm: virtio_pmem: stop allocating child flush bio
pmem_submit_bio() passes the parent bio to nvdimm_flush() for REQ_FUA. For virtio-pmem this makes async_pmem_flush() allocate and submit a child PREFLUSH bio chained to the parent. That child allocation is in the block submit path. Making it blocking with GFP_NOIO can consume the same global bio mempool that submit_bio() uses, while making it GFP_ATOMIC can fail under pressure. A forced failure of the child allocation produced: virtio_pmem: forcing child bio allocation failure for test Buffer I/O error on dev pmem0, logical block 0, lost sync page write EXT4-fs (pmem0): I/O error while writing superblock EXT4-fs (pmem0): mount failed Avoid the child bio without turning REQ_FUA into a synchronous submit-path wait. Let provider flush callbacks return NVDIMM_FLUSH_ASYNC after taking ownership of parent bio completion. pmem_submit_bio() returns in that case, and virtio-pmem queues an ordered WQ_MEM_RECLAIM work item that runs the existing host flush path and completes the parent bio. This keeps the asynchronous completion model of the child-bio path while removing the child bio allocation from the submit path. Signed-off-by: Li Chen <me@linux.beauty> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-ID: <20260630092338.2094628-5-me@linux.beauty>
This commit is contained in:
parent
009e18ca5e
commit
40f356e610
|
|
@ -9,6 +9,12 @@
|
|||
#include "virtio_pmem.h"
|
||||
#include "nd.h"
|
||||
|
||||
struct virtio_pmem_flush_work {
|
||||
struct work_struct work;
|
||||
struct nd_region *nd_region;
|
||||
struct bio *bio;
|
||||
};
|
||||
|
||||
/* The interrupt handler */
|
||||
void virtio_pmem_host_ack(struct virtqueue *vq)
|
||||
{
|
||||
|
|
@ -107,30 +113,46 @@ static int virtio_pmem_flush(struct nd_region *nd_region)
|
|||
return err;
|
||||
};
|
||||
|
||||
static void virtio_pmem_flush_work(struct work_struct *work)
|
||||
{
|
||||
struct virtio_pmem_flush_work *flush;
|
||||
int err;
|
||||
|
||||
flush = container_of(work, struct virtio_pmem_flush_work, work);
|
||||
err = virtio_pmem_flush(flush->nd_region);
|
||||
if (err > 0)
|
||||
err = -EIO;
|
||||
if (err)
|
||||
flush->bio->bi_status = errno_to_blk_status(err);
|
||||
bio_endio(flush->bio);
|
||||
kfree(flush);
|
||||
}
|
||||
|
||||
/* The asynchronous flush callback function */
|
||||
int async_pmem_flush(struct nd_region *nd_region, struct bio *bio)
|
||||
{
|
||||
/*
|
||||
* Create child bio for asynchronous flush and chain with
|
||||
* parent bio. Otherwise directly call nd_region flush.
|
||||
*/
|
||||
if (bio && bio->bi_iter.bi_sector != -1) {
|
||||
struct bio *child = bio_alloc(bio->bi_bdev, 0,
|
||||
REQ_OP_WRITE | REQ_PREFLUSH,
|
||||
GFP_ATOMIC);
|
||||
struct virtio_device *vdev = nd_region->provider_data;
|
||||
struct virtio_pmem *vpmem = vdev->priv;
|
||||
struct virtio_pmem_flush_work *flush;
|
||||
int err;
|
||||
|
||||
if (!child)
|
||||
if (bio && bio->bi_iter.bi_sector != -1) {
|
||||
flush = kmalloc_obj(*flush, GFP_NOIO);
|
||||
if (!flush)
|
||||
return -ENOMEM;
|
||||
bio_clone_blkg_association(child, bio);
|
||||
child->bi_iter.bi_sector = -1;
|
||||
bio_chain(child, bio);
|
||||
submit_bio(child);
|
||||
return 0;
|
||||
|
||||
INIT_WORK(&flush->work, virtio_pmem_flush_work);
|
||||
flush->nd_region = nd_region;
|
||||
flush->bio = bio;
|
||||
queue_work(vpmem->flush_wq, &flush->work);
|
||||
return NVDIMM_FLUSH_ASYNC;
|
||||
}
|
||||
if (virtio_pmem_flush(nd_region))
|
||||
|
||||
err = virtio_pmem_flush(nd_region);
|
||||
if (err > 0)
|
||||
return -EIO;
|
||||
|
||||
return 0;
|
||||
return err;
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(async_pmem_flush);
|
||||
MODULE_DESCRIPTION("Virtio Persistent Memory Driver");
|
||||
|
|
|
|||
|
|
@ -241,8 +241,11 @@ static void pmem_submit_bio(struct bio *bio)
|
|||
bio_end_io_acct(bio, start);
|
||||
}
|
||||
|
||||
if ((bio->bi_opf & REQ_FUA) && !bio->bi_status)
|
||||
if ((bio->bi_opf & REQ_FUA) && !bio->bi_status) {
|
||||
ret = nvdimm_flush(nd_region, bio);
|
||||
if (ret == NVDIMM_FLUSH_ASYNC)
|
||||
return;
|
||||
}
|
||||
|
||||
if (ret)
|
||||
bio->bi_status = errno_to_blk_status(ret);
|
||||
|
|
|
|||
|
|
@ -1094,6 +1094,8 @@ int nvdimm_flush(struct nd_region *nd_region, struct bio *bio)
|
|||
rc = generic_nvdimm_flush(nd_region);
|
||||
else {
|
||||
rc = nd_region->flush(nd_region, bio);
|
||||
if (rc > 0)
|
||||
return rc;
|
||||
if (rc && rc != -ENOMEM)
|
||||
rc = -EIO;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,10 +67,17 @@ static int virtio_pmem_probe(struct virtio_device *vdev)
|
|||
mutex_init(&vpmem->flush_lock);
|
||||
vpmem->vdev = vdev;
|
||||
vdev->priv = vpmem;
|
||||
vpmem->flush_wq = alloc_ordered_workqueue("virtio-pmem-flush",
|
||||
WQ_MEM_RECLAIM);
|
||||
if (!vpmem->flush_wq) {
|
||||
err = -ENOMEM;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
err = init_vq(vpmem);
|
||||
if (err) {
|
||||
dev_err(&vdev->dev, "failed to initialize virtio pmem vq's\n");
|
||||
goto out_err;
|
||||
goto out_wq;
|
||||
}
|
||||
|
||||
if (virtio_has_feature(vdev, VIRTIO_PMEM_F_SHMEM_REGION)) {
|
||||
|
|
@ -131,6 +138,8 @@ static int virtio_pmem_probe(struct virtio_device *vdev)
|
|||
nvdimm_bus_unregister(vpmem->nvdimm_bus);
|
||||
out_vq:
|
||||
vdev->config->del_vqs(vdev);
|
||||
out_wq:
|
||||
destroy_workqueue(vpmem->flush_wq);
|
||||
out_err:
|
||||
return err;
|
||||
}
|
||||
|
|
@ -138,14 +147,20 @@ static int virtio_pmem_probe(struct virtio_device *vdev)
|
|||
static void virtio_pmem_remove(struct virtio_device *vdev)
|
||||
{
|
||||
struct nvdimm_bus *nvdimm_bus = dev_get_drvdata(&vdev->dev);
|
||||
struct virtio_pmem *vpmem = vdev->priv;
|
||||
|
||||
nvdimm_bus_unregister(nvdimm_bus);
|
||||
drain_workqueue(vpmem->flush_wq);
|
||||
vdev->config->del_vqs(vdev);
|
||||
virtio_reset_device(vdev);
|
||||
destroy_workqueue(vpmem->flush_wq);
|
||||
}
|
||||
|
||||
static int virtio_pmem_freeze(struct virtio_device *vdev)
|
||||
{
|
||||
struct virtio_pmem *vpmem = vdev->priv;
|
||||
|
||||
drain_workqueue(vpmem->flush_wq);
|
||||
vdev->config->del_vqs(vdev);
|
||||
virtio_reset_device(vdev);
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include <linux/libnvdimm.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/spinlock.h>
|
||||
#include <linux/workqueue.h>
|
||||
|
||||
struct virtio_pmem_request {
|
||||
struct virtio_pmem_req req;
|
||||
|
|
@ -39,6 +40,9 @@ struct virtio_pmem {
|
|||
/* Serialize flush requests to the device. */
|
||||
struct mutex flush_lock;
|
||||
|
||||
/* Complete asynchronous FUA flushes outside the submit path. */
|
||||
struct workqueue_struct *flush_wq;
|
||||
|
||||
/* nvdimm bus registers virtio pmem device */
|
||||
struct nvdimm_bus *nvdimm_bus;
|
||||
struct nvdimm_bus_descriptor nd_desc;
|
||||
|
|
|
|||
|
|
@ -126,6 +126,15 @@ struct nd_mapping_desc {
|
|||
struct bio;
|
||||
struct resource;
|
||||
struct nd_region;
|
||||
|
||||
/*
|
||||
* Provider flush callback return values:
|
||||
* 0: flush completed synchronously
|
||||
* <0: flush failed
|
||||
* >0: flush completion was queued and @bio will be completed later
|
||||
*/
|
||||
#define NVDIMM_FLUSH_ASYNC 1
|
||||
|
||||
struct nd_region_desc {
|
||||
struct resource *res;
|
||||
struct nd_mapping_desc *mapping;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user