]>
Commit | Line | Data |
---|---|---|
98fc1ada DDAG |
1 | /* |
2 | * Vhost-user filesystem virtio device | |
3 | * | |
4 | * Copyright 2018-2019 Red Hat, Inc. | |
5 | * | |
6 | * Authors: | |
7 | * Stefan Hajnoczi <stefanha@redhat.com> | |
8 | * | |
9 | * This work is licensed under the terms of the GNU GPL, version 2 or | |
10 | * (at your option) any later version. See the COPYING file in the | |
11 | * top-level directory. | |
12 | */ | |
13 | ||
14 | #include "qemu/osdep.h" | |
15 | #include <sys/ioctl.h> | |
16 | #include "standard-headers/linux/virtio_fs.h" | |
17 | #include "qapi/error.h" | |
18 | #include "hw/qdev-properties.h" | |
ce35e229 | 19 | #include "hw/qdev-properties-system.h" |
98fc1ada DDAG |
20 | #include "hw/virtio/virtio-bus.h" |
21 | #include "hw/virtio/virtio-access.h" | |
22 | #include "qemu/error-report.h" | |
b8f3e6a1 | 23 | #include "hw/virtio/vhost.h" |
98fc1ada DDAG |
24 | #include "hw/virtio/vhost-user-fs.h" |
25 | #include "monitor/monitor.h" | |
6da32fe5 | 26 | #include "sysemu/sysemu.h" |
98fc1ada | 27 | |
ace66791 AK |
28 | static const int user_feature_bits[] = { |
29 | VIRTIO_F_VERSION_1, | |
30 | VIRTIO_RING_F_INDIRECT_DESC, | |
31 | VIRTIO_RING_F_EVENT_IDX, | |
32 | VIRTIO_F_NOTIFY_ON_EMPTY, | |
33 | VIRTIO_F_RING_PACKED, | |
34 | VIRTIO_F_IOMMU_PLATFORM, | |
35 | ||
36 | VHOST_INVALID_FEATURE_BIT | |
37 | }; | |
38 | ||
98fc1ada DDAG |
39 | static void vuf_get_config(VirtIODevice *vdev, uint8_t *config) |
40 | { | |
41 | VHostUserFS *fs = VHOST_USER_FS(vdev); | |
42 | struct virtio_fs_config fscfg = {}; | |
43 | ||
44 | memcpy((char *)fscfg.tag, fs->conf.tag, | |
45 | MIN(strlen(fs->conf.tag) + 1, sizeof(fscfg.tag))); | |
46 | ||
47 | virtio_stl_p(vdev, &fscfg.num_request_queues, fs->conf.num_request_queues); | |
48 | ||
49 | memcpy(config, &fscfg, sizeof(fscfg)); | |
50 | } | |
51 | ||
52 | static void vuf_start(VirtIODevice *vdev) | |
53 | { | |
54 | VHostUserFS *fs = VHOST_USER_FS(vdev); | |
55 | BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); | |
56 | VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); | |
57 | int ret; | |
58 | int i; | |
59 | ||
60 | if (!k->set_guest_notifiers) { | |
61 | error_report("binding does not support guest notifiers"); | |
62 | return; | |
63 | } | |
64 | ||
65 | ret = vhost_dev_enable_notifiers(&fs->vhost_dev, vdev); | |
66 | if (ret < 0) { | |
67 | error_report("Error enabling host notifiers: %d", -ret); | |
68 | return; | |
69 | } | |
70 | ||
71 | ret = k->set_guest_notifiers(qbus->parent, fs->vhost_dev.nvqs, true); | |
72 | if (ret < 0) { | |
73 | error_report("Error binding guest notifier: %d", -ret); | |
74 | goto err_host_notifiers; | |
75 | } | |
76 | ||
77 | fs->vhost_dev.acked_features = vdev->guest_features; | |
78 | ret = vhost_dev_start(&fs->vhost_dev, vdev); | |
79 | if (ret < 0) { | |
80 | error_report("Error starting vhost: %d", -ret); | |
81 | goto err_guest_notifiers; | |
82 | } | |
83 | ||
84 | /* | |
85 | * guest_notifier_mask/pending not used yet, so just unmask | |
86 | * everything here. virtio-pci will do the right thing by | |
87 | * enabling/disabling irqfd. | |
88 | */ | |
89 | for (i = 0; i < fs->vhost_dev.nvqs; i++) { | |
90 | vhost_virtqueue_mask(&fs->vhost_dev, vdev, i, false); | |
91 | } | |
92 | ||
93 | return; | |
94 | ||
95 | err_guest_notifiers: | |
96 | k->set_guest_notifiers(qbus->parent, fs->vhost_dev.nvqs, false); | |
97 | err_host_notifiers: | |
98 | vhost_dev_disable_notifiers(&fs->vhost_dev, vdev); | |
99 | } | |
100 | ||
101 | static void vuf_stop(VirtIODevice *vdev) | |
102 | { | |
103 | VHostUserFS *fs = VHOST_USER_FS(vdev); | |
104 | BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(vdev))); | |
105 | VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); | |
106 | int ret; | |
107 | ||
108 | if (!k->set_guest_notifiers) { | |
109 | return; | |
110 | } | |
111 | ||
112 | vhost_dev_stop(&fs->vhost_dev, vdev); | |
113 | ||
114 | ret = k->set_guest_notifiers(qbus->parent, fs->vhost_dev.nvqs, false); | |
115 | if (ret < 0) { | |
116 | error_report("vhost guest notifier cleanup failed: %d", ret); | |
117 | return; | |
118 | } | |
119 | ||
120 | vhost_dev_disable_notifiers(&fs->vhost_dev, vdev); | |
121 | } | |
122 | ||
123 | static void vuf_set_status(VirtIODevice *vdev, uint8_t status) | |
124 | { | |
125 | VHostUserFS *fs = VHOST_USER_FS(vdev); | |
9f6bcfd9 | 126 | bool should_start = virtio_device_started(vdev, status); |
98fc1ada | 127 | |
b8f3e6a1 | 128 | if (vhost_dev_is_started(&fs->vhost_dev) == should_start) { |
98fc1ada DDAG |
129 | return; |
130 | } | |
131 | ||
132 | if (should_start) { | |
133 | vuf_start(vdev); | |
134 | } else { | |
135 | vuf_stop(vdev); | |
136 | } | |
137 | } | |
138 | ||
139 | static uint64_t vuf_get_features(VirtIODevice *vdev, | |
ace66791 AK |
140 | uint64_t features, |
141 | Error **errp) | |
98fc1ada | 142 | { |
ace66791 AK |
143 | VHostUserFS *fs = VHOST_USER_FS(vdev); |
144 | ||
145 | return vhost_get_features(&fs->vhost_dev, user_feature_bits, features); | |
98fc1ada DDAG |
146 | } |
147 | ||
148 | static void vuf_handle_output(VirtIODevice *vdev, VirtQueue *vq) | |
149 | { | |
150 | /* | |
151 | * Not normally called; it's the daemon that handles the queue; | |
152 | * however virtio's cleanup path can call this. | |
153 | */ | |
154 | } | |
155 | ||
156 | static void vuf_guest_notifier_mask(VirtIODevice *vdev, int idx, | |
157 | bool mask) | |
158 | { | |
159 | VHostUserFS *fs = VHOST_USER_FS(vdev); | |
160 | ||
161 | vhost_virtqueue_mask(&fs->vhost_dev, vdev, idx, mask); | |
162 | } | |
163 | ||
164 | static bool vuf_guest_notifier_pending(VirtIODevice *vdev, int idx) | |
165 | { | |
166 | VHostUserFS *fs = VHOST_USER_FS(vdev); | |
167 | ||
168 | return vhost_virtqueue_pending(&fs->vhost_dev, idx); | |
169 | } | |
170 | ||
171 | static void vuf_device_realize(DeviceState *dev, Error **errp) | |
172 | { | |
173 | VirtIODevice *vdev = VIRTIO_DEVICE(dev); | |
174 | VHostUserFS *fs = VHOST_USER_FS(dev); | |
175 | unsigned int i; | |
176 | size_t len; | |
177 | int ret; | |
178 | ||
179 | if (!fs->conf.chardev.chr) { | |
180 | error_setg(errp, "missing chardev"); | |
181 | return; | |
182 | } | |
183 | ||
184 | if (!fs->conf.tag) { | |
185 | error_setg(errp, "missing tag property"); | |
186 | return; | |
187 | } | |
188 | len = strlen(fs->conf.tag); | |
189 | if (len == 0) { | |
190 | error_setg(errp, "tag property cannot be empty"); | |
191 | return; | |
192 | } | |
193 | if (len > sizeof_field(struct virtio_fs_config, tag)) { | |
194 | error_setg(errp, "tag property must be %zu bytes or less", | |
195 | sizeof_field(struct virtio_fs_config, tag)); | |
196 | return; | |
197 | } | |
198 | ||
199 | if (fs->conf.num_request_queues == 0) { | |
200 | error_setg(errp, "num-request-queues property must be larger than 0"); | |
201 | return; | |
202 | } | |
203 | ||
204 | if (!is_power_of_2(fs->conf.queue_size)) { | |
205 | error_setg(errp, "queue-size property must be a power of 2"); | |
206 | return; | |
207 | } | |
208 | ||
209 | if (fs->conf.queue_size > VIRTQUEUE_MAX_SIZE) { | |
210 | error_setg(errp, "queue-size property must be %u or smaller", | |
211 | VIRTQUEUE_MAX_SIZE); | |
212 | return; | |
213 | } | |
214 | ||
215 | if (!vhost_user_init(&fs->vhost_user, &fs->conf.chardev, errp)) { | |
216 | return; | |
217 | } | |
218 | ||
3857cd5c | 219 | virtio_init(vdev, VIRTIO_ID_FS, sizeof(struct virtio_fs_config)); |
98fc1ada DDAG |
220 | |
221 | /* Hiprio queue */ | |
2e5bc659 | 222 | fs->hiprio_vq = virtio_add_queue(vdev, fs->conf.queue_size, vuf_handle_output); |
98fc1ada DDAG |
223 | |
224 | /* Request queues */ | |
2e5bc659 | 225 | fs->req_vqs = g_new(VirtQueue *, fs->conf.num_request_queues); |
98fc1ada | 226 | for (i = 0; i < fs->conf.num_request_queues; i++) { |
2e5bc659 | 227 | fs->req_vqs[i] = virtio_add_queue(vdev, fs->conf.queue_size, vuf_handle_output); |
98fc1ada DDAG |
228 | } |
229 | ||
230 | /* 1 high prio queue, plus the number configured */ | |
231 | fs->vhost_dev.nvqs = 1 + fs->conf.num_request_queues; | |
232 | fs->vhost_dev.vqs = g_new0(struct vhost_virtqueue, fs->vhost_dev.nvqs); | |
233 | ret = vhost_dev_init(&fs->vhost_dev, &fs->vhost_user, | |
a6945f22 | 234 | VHOST_BACKEND_TYPE_USER, 0, errp); |
98fc1ada | 235 | if (ret < 0) { |
98fc1ada DDAG |
236 | goto err_virtio; |
237 | } | |
238 | ||
239 | return; | |
240 | ||
241 | err_virtio: | |
242 | vhost_user_cleanup(&fs->vhost_user); | |
2e5bc659 | 243 | virtio_delete_queue(fs->hiprio_vq); |
ba07cf5d | 244 | for (i = 0; i < fs->conf.num_request_queues; i++) { |
2e5bc659 | 245 | virtio_delete_queue(fs->req_vqs[i]); |
ba07cf5d | 246 | } |
2e5bc659 | 247 | g_free(fs->req_vqs); |
98fc1ada DDAG |
248 | virtio_cleanup(vdev); |
249 | g_free(fs->vhost_dev.vqs); | |
250 | return; | |
251 | } | |
252 | ||
b69c3c21 | 253 | static void vuf_device_unrealize(DeviceState *dev) |
98fc1ada DDAG |
254 | { |
255 | VirtIODevice *vdev = VIRTIO_DEVICE(dev); | |
256 | VHostUserFS *fs = VHOST_USER_FS(dev); | |
ba07cf5d | 257 | int i; |
98fc1ada DDAG |
258 | |
259 | /* This will stop vhost backend if appropriate. */ | |
260 | vuf_set_status(vdev, 0); | |
261 | ||
262 | vhost_dev_cleanup(&fs->vhost_dev); | |
263 | ||
264 | vhost_user_cleanup(&fs->vhost_user); | |
265 | ||
2e5bc659 | 266 | virtio_delete_queue(fs->hiprio_vq); |
ba07cf5d | 267 | for (i = 0; i < fs->conf.num_request_queues; i++) { |
2e5bc659 | 268 | virtio_delete_queue(fs->req_vqs[i]); |
ba07cf5d | 269 | } |
2e5bc659 | 270 | g_free(fs->req_vqs); |
98fc1ada DDAG |
271 | virtio_cleanup(vdev); |
272 | g_free(fs->vhost_dev.vqs); | |
273 | fs->vhost_dev.vqs = NULL; | |
274 | } | |
275 | ||
c255488d JP |
276 | static struct vhost_dev *vuf_get_vhost(VirtIODevice *vdev) |
277 | { | |
278 | VHostUserFS *fs = VHOST_USER_FS(vdev); | |
279 | return &fs->vhost_dev; | |
280 | } | |
281 | ||
98fc1ada DDAG |
282 | static const VMStateDescription vuf_vmstate = { |
283 | .name = "vhost-user-fs", | |
284 | .unmigratable = 1, | |
285 | }; | |
286 | ||
287 | static Property vuf_properties[] = { | |
288 | DEFINE_PROP_CHR("chardev", VHostUserFS, conf.chardev), | |
289 | DEFINE_PROP_STRING("tag", VHostUserFS, conf.tag), | |
290 | DEFINE_PROP_UINT16("num-request-queues", VHostUserFS, | |
291 | conf.num_request_queues, 1), | |
292 | DEFINE_PROP_UINT16("queue-size", VHostUserFS, conf.queue_size, 128), | |
98fc1ada DDAG |
293 | DEFINE_PROP_END_OF_LIST(), |
294 | }; | |
295 | ||
6da32fe5 LE |
296 | static void vuf_instance_init(Object *obj) |
297 | { | |
298 | VHostUserFS *fs = VHOST_USER_FS(obj); | |
299 | ||
300 | device_add_bootindex_property(obj, &fs->bootindex, "bootindex", | |
301 | "/filesystem@0", DEVICE(obj)); | |
302 | } | |
303 | ||
98fc1ada DDAG |
304 | static void vuf_class_init(ObjectClass *klass, void *data) |
305 | { | |
306 | DeviceClass *dc = DEVICE_CLASS(klass); | |
307 | VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); | |
308 | ||
4f67d30b | 309 | device_class_set_props(dc, vuf_properties); |
98fc1ada DDAG |
310 | dc->vmsd = &vuf_vmstate; |
311 | set_bit(DEVICE_CATEGORY_STORAGE, dc->categories); | |
312 | vdc->realize = vuf_device_realize; | |
313 | vdc->unrealize = vuf_device_unrealize; | |
314 | vdc->get_features = vuf_get_features; | |
315 | vdc->get_config = vuf_get_config; | |
316 | vdc->set_status = vuf_set_status; | |
317 | vdc->guest_notifier_mask = vuf_guest_notifier_mask; | |
318 | vdc->guest_notifier_pending = vuf_guest_notifier_pending; | |
c255488d | 319 | vdc->get_vhost = vuf_get_vhost; |
98fc1ada DDAG |
320 | } |
321 | ||
322 | static const TypeInfo vuf_info = { | |
323 | .name = TYPE_VHOST_USER_FS, | |
324 | .parent = TYPE_VIRTIO_DEVICE, | |
325 | .instance_size = sizeof(VHostUserFS), | |
6da32fe5 | 326 | .instance_init = vuf_instance_init, |
98fc1ada DDAG |
327 | .class_init = vuf_class_init, |
328 | }; | |
329 | ||
330 | static void vuf_register_types(void) | |
331 | { | |
332 | type_register_static(&vuf_info); | |
333 | } | |
334 | ||
335 | type_init(vuf_register_types) |