// SPDX-License-Identifier: GPL-2.0 /* * virtio_pmem.c: Virtio pmem Driver * * Discovers persistent memory range information * from host and registers the virtual pmem device * with libnvdimm core. */ #include "virtio_pmem.h" #include "nd.h" static struct virtio_device_id id_table[] = { { VIRTIO_ID_PMEM, VIRTIO_DEV_ANY_ID }, { 0 }, }; /* Initialize virt queue */ static int init_vq(struct virtio_pmem *vpmem) { /* single vq */ vpmem->req_vq = virtio_find_single_vq(vpmem->vdev, virtio_pmem_host_ack, "flush_queue"); if (IS_ERR(vpmem->req_vq)) return PTR_ERR(vpmem->req_vq); spin_lock_init(&vpmem->pmem_lock); INIT_LIST_HEAD(&vpmem->req_list); return 0; }; static int virtio_pmem_validate(struct virtio_device *vdev) { struct virtio_shm_region shm_reg; if (virtio_has_feature(vdev, VIRTIO_PMEM_F_SHMEM_REGION) && !virtio_get_shm_region(vdev, &shm_reg, (u8)VIRTIO_PMEM_SHMEM_REGION_ID) ) { dev_notice(&vdev->dev, "failed to get shared memory region %d\n", VIRTIO_PMEM_SHMEM_REGION_ID); __virtio_clear_bit(vdev, VIRTIO_PMEM_F_SHMEM_REGION); } return 0; } static int virtio_pmem_probe(struct virtio_device *vdev) { struct nd_region_desc ndr_desc = {}; struct nd_region *nd_region; struct virtio_pmem *vpmem; struct resource res; struct virtio_shm_region shm_reg; int err = 0; if (!vdev->config->get) { dev_err(&vdev->dev, "%s failure: config access disabled\n", __func__); return -EINVAL; } vpmem = devm_kzalloc(&vdev->dev, sizeof(*vpmem), GFP_KERNEL); if (!vpmem) { err = -ENOMEM; goto out_err; } vpmem->vdev = vdev; vdev->priv = vpmem; err = init_vq(vpmem); if (err) { dev_err(&vdev->dev, "failed to initialize virtio pmem vq's\n"); goto out_err; } if (virtio_has_feature(vdev, VIRTIO_PMEM_F_SHMEM_REGION)) { virtio_get_shm_region(vdev, &shm_reg, (u8)VIRTIO_PMEM_SHMEM_REGION_ID); vpmem->start = shm_reg.addr; vpmem->size = shm_reg.len; } else { virtio_cread_le(vpmem->vdev, struct virtio_pmem_config, start, &vpmem->start); virtio_cread_le(vpmem->vdev, struct virtio_pmem_config, size, &vpmem->size); } res.start = vpmem->start; res.end = vpmem->start + vpmem->size - 1; vpmem->nd_desc.provider_name = "virtio-pmem"; vpmem->nd_desc.module = THIS_MODULE; vpmem->nvdimm_bus = nvdimm_bus_register(&vdev->dev, &vpmem->nd_desc); if (!vpmem->nvdimm_bus) { dev_err(&vdev->dev, "failed to register device with nvdimm_bus\n"); err = -ENXIO; goto out_vq; } dev_set_drvdata(&vdev->dev, vpmem->nvdimm_bus); ndr_desc.res = &res; ndr_desc.numa_node = memory_add_physaddr_to_nid(res.start); ndr_desc.target_node = phys_to_target_node(res.start); if (ndr_desc.target_node == NUMA_NO_NODE) { ndr_desc.target_node = ndr_desc.numa_node; dev_dbg(&vdev->dev, "changing target node from %d to %d", NUMA_NO_NODE, ndr_desc.target_node); } ndr_desc.flush = async_pmem_flush; ndr_desc.provider_data = vdev; set_bit(ND_REGION_PAGEMAP, &ndr_desc.flags); set_bit(ND_REGION_ASYNC, &ndr_desc.flags); /* * The NVDIMM region could be available before the * virtio_device_ready() that is called by * virtio_dev_probe(), so we set device ready here. */ virtio_device_ready(vdev); nd_region = nvdimm_pmem_region_create(vpmem->nvdimm_bus, &ndr_desc); if (!nd_region) { dev_err(&vdev->dev, "failed to create nvdimm region\n"); err = -ENXIO; goto out_nd; } return 0; out_nd: virtio_reset_device(vdev); nvdimm_bus_unregister(vpmem->nvdimm_bus); out_vq: vdev->config->del_vqs(vdev); out_err: return err; } static void virtio_pmem_remove(struct virtio_device *vdev) { struct nvdimm_bus *nvdimm_bus = dev_get_drvdata(&vdev->dev); nvdimm_bus_unregister(nvdimm_bus); vdev->config->del_vqs(vdev); virtio_reset_device(vdev); } static unsigned int features[] = { VIRTIO_PMEM_F_SHMEM_REGION, }; static struct virtio_driver virtio_pmem_driver = { .feature_table = features, .feature_table_size = ARRAY_SIZE(features), .driver.name = KBUILD_MODNAME, .driver.owner = THIS_MODULE, .id_table = id_table, .validate = virtio_pmem_validate, .probe = virtio_pmem_probe, .remove = virtio_pmem_remove, }; module_virtio_driver(virtio_pmem_driver); MODULE_DEVICE_TABLE(virtio, id_table); MODULE_DESCRIPTION("Virtio pmem driver"); MODULE_LICENSE("GPL");