126 lines
3.6 KiB
C
126 lines
3.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* virtio_pmem.c: Virtio pmem Driver
|
|
*
|
|
* Discovers persistent memory range information
|
|
* from host and provides a virtio based flushing
|
|
* interface.
|
|
*/
|
|
#include "virtio_pmem.h"
|
|
#include "nd.h"
|
|
|
|
/* The interrupt handler */
|
|
void virtio_pmem_host_ack(struct virtqueue *vq)
|
|
{
|
|
struct virtio_pmem *vpmem = vq->vdev->priv;
|
|
struct virtio_pmem_request *req_data, *req_buf;
|
|
unsigned long flags;
|
|
unsigned int len;
|
|
|
|
spin_lock_irqsave(&vpmem->pmem_lock, flags);
|
|
while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) {
|
|
req_data->done = true;
|
|
wake_up(&req_data->host_acked);
|
|
|
|
if (!list_empty(&vpmem->req_list)) {
|
|
req_buf = list_first_entry(&vpmem->req_list,
|
|
struct virtio_pmem_request, list);
|
|
req_buf->wq_buf_avail = true;
|
|
wake_up(&req_buf->wq_buf);
|
|
list_del(&req_buf->list);
|
|
}
|
|
}
|
|
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
|
|
}
|
|
EXPORT_SYMBOL_GPL(virtio_pmem_host_ack);
|
|
|
|
/* The request submission function */
|
|
static int virtio_pmem_flush(struct nd_region *nd_region)
|
|
{
|
|
struct virtio_device *vdev = nd_region->provider_data;
|
|
struct virtio_pmem *vpmem = vdev->priv;
|
|
struct virtio_pmem_request *req_data;
|
|
struct scatterlist *sgs[2], sg, ret;
|
|
unsigned long flags;
|
|
int err, err1;
|
|
|
|
might_sleep();
|
|
req_data = kmalloc(sizeof(*req_data), GFP_KERNEL);
|
|
if (!req_data)
|
|
return -ENOMEM;
|
|
|
|
req_data->done = false;
|
|
init_waitqueue_head(&req_data->host_acked);
|
|
init_waitqueue_head(&req_data->wq_buf);
|
|
INIT_LIST_HEAD(&req_data->list);
|
|
req_data->req.type = cpu_to_le32(VIRTIO_PMEM_REQ_TYPE_FLUSH);
|
|
sg_init_one(&sg, &req_data->req, sizeof(req_data->req));
|
|
sgs[0] = &sg;
|
|
sg_init_one(&ret, &req_data->resp.ret, sizeof(req_data->resp));
|
|
sgs[1] = &ret;
|
|
|
|
spin_lock_irqsave(&vpmem->pmem_lock, flags);
|
|
/*
|
|
* If virtqueue_add_sgs returns -ENOSPC then req_vq virtual
|
|
* queue does not have free descriptor. We add the request
|
|
* to req_list and wait for host_ack to wake us up when free
|
|
* slots are available.
|
|
*/
|
|
while ((err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data,
|
|
GFP_ATOMIC)) == -ENOSPC) {
|
|
|
|
dev_info(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n");
|
|
req_data->wq_buf_avail = false;
|
|
list_add_tail(&req_data->list, &vpmem->req_list);
|
|
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
|
|
|
|
/* A host response results in "host_ack" getting called */
|
|
wait_event(req_data->wq_buf, req_data->wq_buf_avail);
|
|
spin_lock_irqsave(&vpmem->pmem_lock, flags);
|
|
}
|
|
err1 = virtqueue_kick(vpmem->req_vq);
|
|
spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
|
|
/*
|
|
* virtqueue_add_sgs failed with error different than -ENOSPC, we can't
|
|
* do anything about that.
|
|
*/
|
|
if (err || !err1) {
|
|
dev_info(&vdev->dev, "failed to send command to virtio pmem device\n");
|
|
err = -EIO;
|
|
} else {
|
|
/* A host repsonse results in "host_ack" getting called */
|
|
wait_event(req_data->host_acked, req_data->done);
|
|
err = le32_to_cpu(req_data->resp.ret);
|
|
}
|
|
|
|
kfree(req_data);
|
|
return err;
|
|
};
|
|
|
|
/* 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(GFP_ATOMIC, 0);
|
|
|
|
if (!child)
|
|
return -ENOMEM;
|
|
bio_copy_dev(child, bio);
|
|
child->bi_opf = REQ_PREFLUSH;
|
|
child->bi_iter.bi_sector = -1;
|
|
bio_chain(child, bio);
|
|
submit_bio(child);
|
|
return 0;
|
|
}
|
|
if (virtio_pmem_flush(nd_region))
|
|
return -EIO;
|
|
|
|
return 0;
|
|
};
|
|
EXPORT_SYMBOL_GPL(async_pmem_flush);
|
|
MODULE_LICENSE("GPL");
|