// 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");