forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
125 lines
3.6 KiB
125 lines
3.6 KiB
// 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");
|
|
|