1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* |
3 | * virtio_pmem.c: Virtio pmem Driver |
4 | * |
5 | * Discovers persistent memory range information |
6 | * from host and registers the virtual pmem device |
7 | * with libnvdimm core. |
8 | */ |
9 | #include "virtio_pmem.h" |
10 | #include "nd.h" |
11 | |
12 | static struct virtio_device_id id_table[] = { |
13 | { VIRTIO_ID_PMEM, VIRTIO_DEV_ANY_ID }, |
14 | { 0 }, |
15 | }; |
16 | |
17 | /* Initialize virt queue */ |
18 | static int init_vq(struct virtio_pmem *vpmem) |
19 | { |
20 | /* single vq */ |
21 | vpmem->req_vq = virtio_find_single_vq(vdev: vpmem->vdev, |
22 | c: virtio_pmem_host_ack, n: "flush_queue" ); |
23 | if (IS_ERR(ptr: vpmem->req_vq)) |
24 | return PTR_ERR(ptr: vpmem->req_vq); |
25 | |
26 | spin_lock_init(&vpmem->pmem_lock); |
27 | INIT_LIST_HEAD(list: &vpmem->req_list); |
28 | |
29 | return 0; |
30 | }; |
31 | |
32 | static int virtio_pmem_validate(struct virtio_device *vdev) |
33 | { |
34 | struct virtio_shm_region shm_reg; |
35 | |
36 | if (virtio_has_feature(vdev, VIRTIO_PMEM_F_SHMEM_REGION) && |
37 | !virtio_get_shm_region(vdev, region: &shm_reg, id: (u8)VIRTIO_PMEM_SHMEM_REGION_ID) |
38 | ) { |
39 | dev_notice(&vdev->dev, "failed to get shared memory region %d\n" , |
40 | VIRTIO_PMEM_SHMEM_REGION_ID); |
41 | __virtio_clear_bit(vdev, VIRTIO_PMEM_F_SHMEM_REGION); |
42 | } |
43 | return 0; |
44 | } |
45 | |
46 | static int virtio_pmem_probe(struct virtio_device *vdev) |
47 | { |
48 | struct nd_region_desc ndr_desc = {}; |
49 | struct nd_region *nd_region; |
50 | struct virtio_pmem *vpmem; |
51 | struct resource res; |
52 | struct virtio_shm_region shm_reg; |
53 | int err = 0; |
54 | |
55 | if (!vdev->config->get) { |
56 | dev_err(&vdev->dev, "%s failure: config access disabled\n" , |
57 | __func__); |
58 | return -EINVAL; |
59 | } |
60 | |
61 | vpmem = devm_kzalloc(dev: &vdev->dev, size: sizeof(*vpmem), GFP_KERNEL); |
62 | if (!vpmem) { |
63 | err = -ENOMEM; |
64 | goto out_err; |
65 | } |
66 | |
67 | vpmem->vdev = vdev; |
68 | vdev->priv = vpmem; |
69 | err = init_vq(vpmem); |
70 | if (err) { |
71 | dev_err(&vdev->dev, "failed to initialize virtio pmem vq's\n" ); |
72 | goto out_err; |
73 | } |
74 | |
75 | if (virtio_has_feature(vdev, VIRTIO_PMEM_F_SHMEM_REGION)) { |
76 | virtio_get_shm_region(vdev, region: &shm_reg, id: (u8)VIRTIO_PMEM_SHMEM_REGION_ID); |
77 | vpmem->start = shm_reg.addr; |
78 | vpmem->size = shm_reg.len; |
79 | } else { |
80 | virtio_cread_le(vpmem->vdev, struct virtio_pmem_config, |
81 | start, &vpmem->start); |
82 | virtio_cread_le(vpmem->vdev, struct virtio_pmem_config, |
83 | size, &vpmem->size); |
84 | } |
85 | |
86 | res.start = vpmem->start; |
87 | res.end = vpmem->start + vpmem->size - 1; |
88 | vpmem->nd_desc.provider_name = "virtio-pmem" ; |
89 | vpmem->nd_desc.module = THIS_MODULE; |
90 | |
91 | vpmem->nvdimm_bus = nvdimm_bus_register(parent: &vdev->dev, |
92 | nfit_desc: &vpmem->nd_desc); |
93 | if (!vpmem->nvdimm_bus) { |
94 | dev_err(&vdev->dev, "failed to register device with nvdimm_bus\n" ); |
95 | err = -ENXIO; |
96 | goto out_vq; |
97 | } |
98 | |
99 | dev_set_drvdata(dev: &vdev->dev, data: vpmem->nvdimm_bus); |
100 | |
101 | ndr_desc.res = &res; |
102 | |
103 | ndr_desc.numa_node = memory_add_physaddr_to_nid(start: res.start); |
104 | ndr_desc.target_node = phys_to_target_node(start: res.start); |
105 | if (ndr_desc.target_node == NUMA_NO_NODE) { |
106 | ndr_desc.target_node = ndr_desc.numa_node; |
107 | dev_dbg(&vdev->dev, "changing target node from %d to %d" , |
108 | NUMA_NO_NODE, ndr_desc.target_node); |
109 | } |
110 | |
111 | ndr_desc.flush = async_pmem_flush; |
112 | ndr_desc.provider_data = vdev; |
113 | set_bit(nr: ND_REGION_PAGEMAP, addr: &ndr_desc.flags); |
114 | set_bit(nr: ND_REGION_ASYNC, addr: &ndr_desc.flags); |
115 | /* |
116 | * The NVDIMM region could be available before the |
117 | * virtio_device_ready() that is called by |
118 | * virtio_dev_probe(), so we set device ready here. |
119 | */ |
120 | virtio_device_ready(dev: vdev); |
121 | nd_region = nvdimm_pmem_region_create(nvdimm_bus: vpmem->nvdimm_bus, ndr_desc: &ndr_desc); |
122 | if (!nd_region) { |
123 | dev_err(&vdev->dev, "failed to create nvdimm region\n" ); |
124 | err = -ENXIO; |
125 | goto out_nd; |
126 | } |
127 | return 0; |
128 | out_nd: |
129 | virtio_reset_device(dev: vdev); |
130 | nvdimm_bus_unregister(nvdimm_bus: vpmem->nvdimm_bus); |
131 | out_vq: |
132 | vdev->config->del_vqs(vdev); |
133 | out_err: |
134 | return err; |
135 | } |
136 | |
137 | static void virtio_pmem_remove(struct virtio_device *vdev) |
138 | { |
139 | struct nvdimm_bus *nvdimm_bus = dev_get_drvdata(dev: &vdev->dev); |
140 | |
141 | nvdimm_bus_unregister(nvdimm_bus); |
142 | vdev->config->del_vqs(vdev); |
143 | virtio_reset_device(dev: vdev); |
144 | } |
145 | |
146 | static unsigned int features[] = { |
147 | VIRTIO_PMEM_F_SHMEM_REGION, |
148 | }; |
149 | |
150 | static struct virtio_driver virtio_pmem_driver = { |
151 | .feature_table = features, |
152 | .feature_table_size = ARRAY_SIZE(features), |
153 | .driver.name = KBUILD_MODNAME, |
154 | .driver.owner = THIS_MODULE, |
155 | .id_table = id_table, |
156 | .validate = virtio_pmem_validate, |
157 | .probe = virtio_pmem_probe, |
158 | .remove = virtio_pmem_remove, |
159 | }; |
160 | |
161 | module_virtio_driver(virtio_pmem_driver); |
162 | MODULE_DEVICE_TABLE(virtio, id_table); |
163 | MODULE_DESCRIPTION("Virtio pmem driver" ); |
164 | MODULE_LICENSE("GPL" ); |
165 | |