// 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" struct virtio_pmem_flush_work { struct work_struct work; struct nd_region *nd_region; struct bio *bio; }; static void virtio_pmem_req_release(struct kref *kref) { struct virtio_pmem_request *req; req = container_of(kref, struct virtio_pmem_request, kref); kfree(req); } static void virtio_pmem_signal_done(struct virtio_pmem_request *req) { /* Pairs with smp_load_acquire() in virtio_pmem_req_done(). */ smp_store_release(&req->done, true); wake_up(&req->host_acked); } static bool virtio_pmem_req_done(struct virtio_pmem_request *req) { /* Pairs with smp_store_release() in virtio_pmem_signal_done(). */ return smp_load_acquire(&req->done); } static void virtio_pmem_complete_err(struct virtio_pmem_request *req) { req->resp.ret = cpu_to_le32(1); virtio_pmem_signal_done(req); } static void virtio_pmem_wake_one_waiter(struct virtio_pmem *vpmem) { struct virtio_pmem_request *req_buf; if (list_empty(&vpmem->req_list)) return; req_buf = list_first_entry(&vpmem->req_list, struct virtio_pmem_request, list); list_del_init(&req_buf->list); WRITE_ONCE(req_buf->wq_buf_avail, true); wake_up(&req_buf->wq_buf); } static void virtio_pmem_wake_all_waiters(struct virtio_pmem *vpmem) { struct virtio_pmem_request *req, *tmp; list_for_each_entry_safe(req, tmp, &vpmem->req_list, list) { list_del_init(&req->list); WRITE_ONCE(req->wq_buf_avail, true); wake_up(&req->wq_buf); } } static void virtio_pmem_clear_inflight(struct virtio_pmem *vpmem, struct virtio_pmem_request *req) { if (vpmem->req_inflight == req) vpmem->req_inflight = NULL; } static void virtio_pmem_wake_inflight(struct virtio_pmem *vpmem) { struct virtio_pmem_request *req = vpmem->req_inflight; if (req) wake_up(&req->host_acked); } void virtio_pmem_mark_broken(struct virtio_pmem *vpmem) { if (!READ_ONCE(vpmem->broken)) { WRITE_ONCE(vpmem->broken, true); dev_err_once(&vpmem->vdev->dev, "virtqueue is broken\n"); } virtio_pmem_wake_inflight(vpmem); virtio_pmem_wake_all_waiters(vpmem); } EXPORT_SYMBOL_GPL(virtio_pmem_mark_broken); void virtio_pmem_drain(struct virtio_pmem *vpmem) { struct virtio_pmem_request *req; unsigned int len; if (!vpmem->req_vq) return; while ((req = virtqueue_get_buf(vpmem->req_vq, &len)) != NULL) { virtio_pmem_clear_inflight(vpmem, req); virtio_pmem_complete_err(req); kref_put(&req->kref, virtio_pmem_req_release); } while ((req = virtqueue_detach_unused_buf(vpmem->req_vq)) != NULL) { virtio_pmem_clear_inflight(vpmem, req); virtio_pmem_complete_err(req); kref_put(&req->kref, virtio_pmem_req_release); } } EXPORT_SYMBOL_GPL(virtio_pmem_drain); /* The interrupt handler */ void virtio_pmem_host_ack(struct virtqueue *vq) { struct virtio_pmem *vpmem = vq->vdev->priv; struct virtio_pmem_request *req_data; unsigned long flags; unsigned int len; spin_lock_irqsave(&vpmem->pmem_lock, flags); while ((req_data = virtqueue_get_buf(vq, &len)) != NULL) { virtio_pmem_clear_inflight(vpmem, req_data); virtio_pmem_wake_one_waiter(vpmem); if (READ_ONCE(vpmem->broken)) virtio_pmem_complete_err(req_data); else virtio_pmem_signal_done(req_data); kref_put(&req_data->kref, virtio_pmem_req_release); } 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; guard(mutex)(&vpmem->flush_lock); /* * Don't bother to submit the request to the device if the device is * not activated. */ if (vdev->config->get_status(vdev) & VIRTIO_CONFIG_S_NEEDS_RESET) { dev_info(&vdev->dev, "virtio pmem device needs a reset\n"); return -EIO; } if (READ_ONCE(vpmem->broken)) return -EIO; req_data = kmalloc_obj(*req_data, GFP_NOIO); if (!req_data) return -ENOMEM; kref_init(&req_data->kref); WRITE_ONCE(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. */ for (;;) { if (READ_ONCE(vpmem->broken)) { err = -EIO; break; } err = virtqueue_add_sgs(vpmem->req_vq, sgs, 1, 1, req_data, GFP_ATOMIC); if (!err) { /* * Take the virtqueue reference while @pmem_lock is * held so completion cannot run concurrently. */ kref_get(&req_data->kref); vpmem->req_inflight = req_data; break; } if (err != -ENOSPC) break; dev_info_ratelimited(&vdev->dev, "failed to send command to virtio pmem device, no free slots in the virtqueue\n"); WRITE_ONCE(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, READ_ONCE(req_data->wq_buf_avail) || READ_ONCE(vpmem->broken)); spin_lock_irqsave(&vpmem->pmem_lock, flags); if (READ_ONCE(vpmem->broken)) break; } if (READ_ONCE(vpmem->broken)) err = -EIO; if (err == -EIO || virtqueue_is_broken(vpmem->req_vq)) virtio_pmem_mark_broken(vpmem); err1 = true; if (!err && !READ_ONCE(vpmem->broken)) { err1 = virtqueue_kick(vpmem->req_vq); if (!err1) virtio_pmem_mark_broken(vpmem); } spin_unlock_irqrestore(&vpmem->pmem_lock, flags); /* * virtqueue_add_sgs failed with error different than -ENOSPC, we can't * do anything about that. */ if (READ_ONCE(vpmem->broken) || err || !err1) { dev_info(&vdev->dev, "failed to send command to virtio pmem device\n"); err = -EIO; } else { /* A host response results in "host_ack" getting called */ wait_event(req_data->host_acked, virtio_pmem_req_done(req_data) || READ_ONCE(vpmem->broken)); if (virtio_pmem_req_done(req_data)) err = le32_to_cpu(req_data->resp.ret); else err = -EIO; } kref_put(&req_data->kref, virtio_pmem_req_release); 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) { struct virtio_device *vdev = nd_region->provider_data; struct virtio_pmem *vpmem = vdev->priv; struct virtio_pmem_flush_work *flush; unsigned long flags; int err; if (bio && bio->bi_iter.bi_sector != -1) { flush = kmalloc_obj(*flush, GFP_NOIO); if (!flush) return -ENOMEM; INIT_WORK(&flush->work, virtio_pmem_flush_work); flush->nd_region = nd_region; flush->bio = bio; spin_lock_irqsave(&vpmem->pmem_lock, flags); if (READ_ONCE(vpmem->broken)) { spin_unlock_irqrestore(&vpmem->pmem_lock, flags); kfree(flush); return -EIO; } queue_work(vpmem->flush_wq, &flush->work); spin_unlock_irqrestore(&vpmem->pmem_lock, flags); return NVDIMM_FLUSH_ASYNC; } err = virtio_pmem_flush(nd_region); if (err > 0) return -EIO; return err; }; EXPORT_SYMBOL_GPL(async_pmem_flush); MODULE_DESCRIPTION("Virtio Persistent Memory Driver"); MODULE_LICENSE("GPL");