18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * virtio_pmem.c: Virtio pmem Driver
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci * Discovers persistent memory range information
68c2ecf20Sopenharmony_ci * from host and provides a virtio based flushing
78c2ecf20Sopenharmony_ci * interface.
88c2ecf20Sopenharmony_ci */
98c2ecf20Sopenharmony_ci#include "virtio_pmem.h"
108c2ecf20Sopenharmony_ci#include "nd.h"
118c2ecf20Sopenharmony_ci
128c2ecf20Sopenharmony_ci /* The interrupt handler */
138c2ecf20Sopenharmony_civoid virtio_pmem_host_ack(struct virtqueue *vq)
148c2ecf20Sopenharmony_ci{
158c2ecf20Sopenharmony_ci	struct virtio_pmem *vpmem = vq->vdev->priv;
168c2ecf20Sopenharmony_ci	struct virtio_pmem_request *req_data, *req_buf;
178c2ecf20Sopenharmony_ci	unsigned long flags;
188c2ecf20Sopenharmony_ci	unsigned int len;
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vpmem->pmem_lock, flags);
218c2ecf20Sopenharmony_ci	while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) {
228c2ecf20Sopenharmony_ci		req_data->done = true;
238c2ecf20Sopenharmony_ci		wake_up(&req_data->host_acked);
248c2ecf20Sopenharmony_ci
258c2ecf20Sopenharmony_ci		if (!list_empty(&vpmem->req_list)) {
268c2ecf20Sopenharmony_ci			req_buf = list_first_entry(&vpmem->req_list,
278c2ecf20Sopenharmony_ci					struct virtio_pmem_request, list);
288c2ecf20Sopenharmony_ci			req_buf->wq_buf_avail = true;
298c2ecf20Sopenharmony_ci			wake_up(&req_buf->wq_buf);
308c2ecf20Sopenharmony_ci			list_del(&req_buf->list);
318c2ecf20Sopenharmony_ci		}
328c2ecf20Sopenharmony_ci	}
338c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
348c2ecf20Sopenharmony_ci}
358c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(virtio_pmem_host_ack);
368c2ecf20Sopenharmony_ci
378c2ecf20Sopenharmony_ci /* The request submission function */
388c2ecf20Sopenharmony_cistatic int virtio_pmem_flush(struct nd_region *nd_region)
398c2ecf20Sopenharmony_ci{
408c2ecf20Sopenharmony_ci	struct virtio_device *vdev = nd_region->provider_data;
418c2ecf20Sopenharmony_ci	struct virtio_pmem *vpmem  = vdev->priv;
428c2ecf20Sopenharmony_ci	struct virtio_pmem_request *req_data;
438c2ecf20Sopenharmony_ci	struct scatterlist *sgs[2], sg, ret;
448c2ecf20Sopenharmony_ci	unsigned long flags;
458c2ecf20Sopenharmony_ci	int err, err1;
468c2ecf20Sopenharmony_ci
478c2ecf20Sopenharmony_ci	might_sleep();
488c2ecf20Sopenharmony_ci	req_data = kmalloc(sizeof(*req_data), GFP_KERNEL);
498c2ecf20Sopenharmony_ci	if (!req_data)
508c2ecf20Sopenharmony_ci		return -ENOMEM;
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci	req_data->done = false;
538c2ecf20Sopenharmony_ci	init_waitqueue_head(&req_data->host_acked);
548c2ecf20Sopenharmony_ci	init_waitqueue_head(&req_data->wq_buf);
558c2ecf20Sopenharmony_ci	INIT_LIST_HEAD(&req_data->list);
568c2ecf20Sopenharmony_ci	req_data->req.type = cpu_to_le32(VIRTIO_PMEM_REQ_TYPE_FLUSH);
578c2ecf20Sopenharmony_ci	sg_init_one(&sg, &req_data->req, sizeof(req_data->req));
588c2ecf20Sopenharmony_ci	sgs[0] = &sg;
598c2ecf20Sopenharmony_ci	sg_init_one(&ret, &req_data->resp.ret, sizeof(req_data->resp));
608c2ecf20Sopenharmony_ci	sgs[1] = &ret;
618c2ecf20Sopenharmony_ci
628c2ecf20Sopenharmony_ci	spin_lock_irqsave(&vpmem->pmem_lock, flags);
638c2ecf20Sopenharmony_ci	 /*
648c2ecf20Sopenharmony_ci	  * If virtqueue_add_sgs returns -ENOSPC then req_vq virtual
658c2ecf20Sopenharmony_ci	  * queue does not have free descriptor. We add the request
668c2ecf20Sopenharmony_ci	  * to req_list and wait for host_ack to wake us up when free
678c2ecf20Sopenharmony_ci	  * slots are available.
688c2ecf20Sopenharmony_ci	  */
698c2ecf20Sopenharmony_ci	while ((err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data,
708c2ecf20Sopenharmony_ci					GFP_ATOMIC)) == -ENOSPC) {
718c2ecf20Sopenharmony_ci
728c2ecf20Sopenharmony_ci		dev_info(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n");
738c2ecf20Sopenharmony_ci		req_data->wq_buf_avail = false;
748c2ecf20Sopenharmony_ci		list_add_tail(&req_data->list, &vpmem->req_list);
758c2ecf20Sopenharmony_ci		spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
768c2ecf20Sopenharmony_ci
778c2ecf20Sopenharmony_ci		/* A host response results in "host_ack" getting called */
788c2ecf20Sopenharmony_ci		wait_event(req_data->wq_buf, req_data->wq_buf_avail);
798c2ecf20Sopenharmony_ci		spin_lock_irqsave(&vpmem->pmem_lock, flags);
808c2ecf20Sopenharmony_ci	}
818c2ecf20Sopenharmony_ci	err1 = virtqueue_kick(vpmem->req_vq);
828c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&vpmem->pmem_lock, flags);
838c2ecf20Sopenharmony_ci	/*
848c2ecf20Sopenharmony_ci	 * virtqueue_add_sgs failed with error different than -ENOSPC, we can't
858c2ecf20Sopenharmony_ci	 * do anything about that.
868c2ecf20Sopenharmony_ci	 */
878c2ecf20Sopenharmony_ci	if (err || !err1) {
888c2ecf20Sopenharmony_ci		dev_info(&vdev->dev, "failed to send command to virtio pmem device\n");
898c2ecf20Sopenharmony_ci		err = -EIO;
908c2ecf20Sopenharmony_ci	} else {
918c2ecf20Sopenharmony_ci		/* A host repsonse results in "host_ack" getting called */
928c2ecf20Sopenharmony_ci		wait_event(req_data->host_acked, req_data->done);
938c2ecf20Sopenharmony_ci		err = le32_to_cpu(req_data->resp.ret);
948c2ecf20Sopenharmony_ci	}
958c2ecf20Sopenharmony_ci
968c2ecf20Sopenharmony_ci	kfree(req_data);
978c2ecf20Sopenharmony_ci	return err;
988c2ecf20Sopenharmony_ci};
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci/* The asynchronous flush callback function */
1018c2ecf20Sopenharmony_ciint async_pmem_flush(struct nd_region *nd_region, struct bio *bio)
1028c2ecf20Sopenharmony_ci{
1038c2ecf20Sopenharmony_ci	/*
1048c2ecf20Sopenharmony_ci	 * Create child bio for asynchronous flush and chain with
1058c2ecf20Sopenharmony_ci	 * parent bio. Otherwise directly call nd_region flush.
1068c2ecf20Sopenharmony_ci	 */
1078c2ecf20Sopenharmony_ci	if (bio && bio->bi_iter.bi_sector != -1) {
1088c2ecf20Sopenharmony_ci		struct bio *child = bio_alloc(GFP_ATOMIC, 0);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci		if (!child)
1118c2ecf20Sopenharmony_ci			return -ENOMEM;
1128c2ecf20Sopenharmony_ci		bio_copy_dev(child, bio);
1138c2ecf20Sopenharmony_ci		child->bi_opf = REQ_PREFLUSH;
1148c2ecf20Sopenharmony_ci		child->bi_iter.bi_sector = -1;
1158c2ecf20Sopenharmony_ci		bio_chain(child, bio);
1168c2ecf20Sopenharmony_ci		submit_bio(child);
1178c2ecf20Sopenharmony_ci		return 0;
1188c2ecf20Sopenharmony_ci	}
1198c2ecf20Sopenharmony_ci	if (virtio_pmem_flush(nd_region))
1208c2ecf20Sopenharmony_ci		return -EIO;
1218c2ecf20Sopenharmony_ci
1228c2ecf20Sopenharmony_ci	return 0;
1238c2ecf20Sopenharmony_ci};
1248c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(async_pmem_flush);
1258c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
126