]>
Commit | Line | Data |
---|---|---|
1a72d9ae PD |
1 | /* |
2 | * Copyright (c) 2018 Citrix Systems Inc. | |
3 | * | |
4 | * This work is licensed under the terms of the GNU GPL, version 2 or later. | |
5 | * See the COPYING file in the top-level directory. | |
6 | */ | |
7 | ||
8 | #include "qemu/osdep.h" | |
9 | #include "qemu/cutils.h" | |
db725815 | 10 | #include "qemu/main-loop.h" |
0b8fa32f | 11 | #include "qemu/module.h" |
db9ff46e | 12 | #include "qemu/option.h" |
1a72d9ae | 13 | #include "qapi/error.h" |
db9ff46e | 14 | #include "qapi/qapi-commands-block-core.h" |
c577ff62 | 15 | #include "qapi/qapi-commands-qom.h" |
db9ff46e PD |
16 | #include "qapi/qapi-visit-block-core.h" |
17 | #include "qapi/qobject-input-visitor.h" | |
1a72d9ae | 18 | #include "qapi/visitor.h" |
db9ff46e PD |
19 | #include "qapi/qmp/qdict.h" |
20 | #include "qapi/qmp/qstring.h" | |
5f07c4d6 | 21 | #include "qom/object_interfaces.h" |
b6af8926 PD |
22 | #include "hw/xen/xen_common.h" |
23 | #include "hw/block/xen_blkif.h" | |
a27bd6c7 | 24 | #include "hw/qdev-properties.h" |
1a72d9ae | 25 | #include "hw/xen/xen-block.h" |
db9ff46e | 26 | #include "hw/xen/xen-backend.h" |
b6af8926 PD |
27 | #include "sysemu/blockdev.h" |
28 | #include "sysemu/block-backend.h" | |
29 | #include "sysemu/iothread.h" | |
30 | #include "dataplane/xen-block.h" | |
1a72d9ae PD |
31 | #include "trace.h" |
32 | ||
094a2239 PD |
33 | static char *xen_block_get_name(XenDevice *xendev, Error **errp) |
34 | { | |
35 | XenBlockDevice *blockdev = XEN_BLOCK_DEVICE(xendev); | |
36 | XenBlockVdev *vdev = &blockdev->props.vdev; | |
37 | ||
38 | return g_strdup_printf("%lu", vdev->number); | |
39 | } | |
40 | ||
82a29e30 PD |
41 | static void xen_block_disconnect(XenDevice *xendev, Error **errp) |
42 | { | |
43 | XenBlockDevice *blockdev = XEN_BLOCK_DEVICE(xendev); | |
44 | const char *type = object_get_typename(OBJECT(blockdev)); | |
45 | XenBlockVdev *vdev = &blockdev->props.vdev; | |
46 | ||
47 | trace_xen_block_disconnect(type, vdev->disk, vdev->partition); | |
b6af8926 PD |
48 | |
49 | xen_block_dataplane_stop(blockdev->dataplane); | |
82a29e30 PD |
50 | } |
51 | ||
52 | static void xen_block_connect(XenDevice *xendev, Error **errp) | |
53 | { | |
54 | XenBlockDevice *blockdev = XEN_BLOCK_DEVICE(xendev); | |
55 | const char *type = object_get_typename(OBJECT(blockdev)); | |
56 | XenBlockVdev *vdev = &blockdev->props.vdev; | |
5feeb718 PD |
57 | BlockConf *conf = &blockdev->props.conf; |
58 | unsigned int feature_large_sector_size; | |
b6af8926 PD |
59 | unsigned int order, nr_ring_ref, *ring_ref, event_channel, protocol; |
60 | char *str; | |
82a29e30 PD |
61 | |
62 | trace_xen_block_connect(type, vdev->disk, vdev->partition); | |
b6af8926 | 63 | |
5feeb718 PD |
64 | if (xen_device_frontend_scanf(xendev, "feature-large-sector-size", "%u", |
65 | &feature_large_sector_size) != 1) { | |
66 | feature_large_sector_size = 0; | |
67 | } | |
68 | ||
69 | if (feature_large_sector_size != 1 && | |
70 | conf->logical_block_size != XEN_BLKIF_SECTOR_SIZE) { | |
71 | error_setg(errp, "logical_block_size != %u not supported by frontend", | |
72 | XEN_BLKIF_SECTOR_SIZE); | |
73 | return; | |
74 | } | |
75 | ||
b6af8926 PD |
76 | if (xen_device_frontend_scanf(xendev, "ring-page-order", "%u", |
77 | &order) != 1) { | |
78 | nr_ring_ref = 1; | |
79 | ring_ref = g_new(unsigned int, nr_ring_ref); | |
80 | ||
81 | if (xen_device_frontend_scanf(xendev, "ring-ref", "%u", | |
82 | &ring_ref[0]) != 1) { | |
83 | error_setg(errp, "failed to read ring-ref"); | |
84 | g_free(ring_ref); | |
85 | return; | |
86 | } | |
87 | } else if (order <= blockdev->props.max_ring_page_order) { | |
88 | unsigned int i; | |
89 | ||
90 | nr_ring_ref = 1 << order; | |
91 | ring_ref = g_new(unsigned int, nr_ring_ref); | |
92 | ||
93 | for (i = 0; i < nr_ring_ref; i++) { | |
94 | const char *key = g_strdup_printf("ring-ref%u", i); | |
95 | ||
96 | if (xen_device_frontend_scanf(xendev, key, "%u", | |
97 | &ring_ref[i]) != 1) { | |
98 | error_setg(errp, "failed to read %s", key); | |
99 | g_free((gpointer)key); | |
100 | g_free(ring_ref); | |
101 | return; | |
102 | } | |
103 | ||
104 | g_free((gpointer)key); | |
105 | } | |
106 | } else { | |
107 | error_setg(errp, "invalid ring-page-order (%d)", order); | |
108 | return; | |
109 | } | |
110 | ||
111 | if (xen_device_frontend_scanf(xendev, "event-channel", "%u", | |
112 | &event_channel) != 1) { | |
113 | error_setg(errp, "failed to read event-channel"); | |
114 | g_free(ring_ref); | |
115 | return; | |
116 | } | |
117 | ||
118 | if (xen_device_frontend_scanf(xendev, "protocol", "%ms", | |
119 | &str) != 1) { | |
120 | protocol = BLKIF_PROTOCOL_NATIVE; | |
121 | } else { | |
122 | if (strcmp(str, XEN_IO_PROTO_ABI_X86_32) == 0) { | |
123 | protocol = BLKIF_PROTOCOL_X86_32; | |
124 | } else if (strcmp(str, XEN_IO_PROTO_ABI_X86_64) == 0) { | |
125 | protocol = BLKIF_PROTOCOL_X86_64; | |
126 | } else { | |
127 | protocol = BLKIF_PROTOCOL_NATIVE; | |
128 | } | |
129 | ||
130 | free(str); | |
131 | } | |
132 | ||
133 | xen_block_dataplane_start(blockdev->dataplane, ring_ref, nr_ring_ref, | |
134 | event_channel, protocol, errp); | |
135 | ||
136 | g_free(ring_ref); | |
82a29e30 PD |
137 | } |
138 | ||
b69c3c21 | 139 | static void xen_block_unrealize(XenDevice *xendev) |
1a72d9ae PD |
140 | { |
141 | XenBlockDevice *blockdev = XEN_BLOCK_DEVICE(xendev); | |
142 | XenBlockDeviceClass *blockdev_class = | |
143 | XEN_BLOCK_DEVICE_GET_CLASS(xendev); | |
144 | const char *type = object_get_typename(OBJECT(blockdev)); | |
145 | XenBlockVdev *vdev = &blockdev->props.vdev; | |
146 | ||
147 | if (vdev->type == XEN_BLOCK_VDEV_TYPE_INVALID) { | |
148 | return; | |
149 | } | |
150 | ||
151 | trace_xen_block_unrealize(type, vdev->disk, vdev->partition); | |
152 | ||
82a29e30 PD |
153 | /* Disconnect from the frontend in case this has not already happened */ |
154 | xen_block_disconnect(xendev, NULL); | |
155 | ||
b6af8926 PD |
156 | xen_block_dataplane_destroy(blockdev->dataplane); |
157 | blockdev->dataplane = NULL; | |
158 | ||
1a72d9ae | 159 | if (blockdev_class->unrealize) { |
b69c3c21 | 160 | blockdev_class->unrealize(blockdev); |
1a72d9ae PD |
161 | } |
162 | } | |
163 | ||
3149f183 PD |
164 | static void xen_block_set_size(XenBlockDevice *blockdev) |
165 | { | |
166 | const char *type = object_get_typename(OBJECT(blockdev)); | |
167 | XenBlockVdev *vdev = &blockdev->props.vdev; | |
168 | BlockConf *conf = &blockdev->props.conf; | |
5feeb718 | 169 | int64_t sectors = blk_getlength(conf->blk) / conf->logical_block_size; |
3149f183 PD |
170 | XenDevice *xendev = XEN_DEVICE(blockdev); |
171 | ||
172 | trace_xen_block_size(type, vdev->disk, vdev->partition, sectors); | |
173 | ||
174 | xen_device_backend_printf(xendev, "sectors", "%"PRIi64, sectors); | |
175 | } | |
176 | ||
177 | static void xen_block_resize_cb(void *opaque) | |
178 | { | |
179 | XenBlockDevice *blockdev = opaque; | |
180 | XenDevice *xendev = XEN_DEVICE(blockdev); | |
181 | enum xenbus_state state = xen_device_backend_get_state(xendev); | |
182 | ||
183 | xen_block_set_size(blockdev); | |
184 | ||
185 | /* | |
186 | * Mimic the behaviour of Linux xen-blkback and re-write the state | |
187 | * to trigger the frontend watch. | |
188 | */ | |
189 | xen_device_backend_printf(xendev, "state", "%u", state); | |
190 | } | |
191 | ||
192 | static const BlockDevOps xen_block_dev_ops = { | |
193 | .resize_cb = xen_block_resize_cb, | |
194 | }; | |
195 | ||
1a72d9ae PD |
196 | static void xen_block_realize(XenDevice *xendev, Error **errp) |
197 | { | |
1de7096d | 198 | ERRP_GUARD(); |
1a72d9ae PD |
199 | XenBlockDevice *blockdev = XEN_BLOCK_DEVICE(xendev); |
200 | XenBlockDeviceClass *blockdev_class = | |
201 | XEN_BLOCK_DEVICE_GET_CLASS(xendev); | |
202 | const char *type = object_get_typename(OBJECT(blockdev)); | |
203 | XenBlockVdev *vdev = &blockdev->props.vdev; | |
b6af8926 | 204 | BlockConf *conf = &blockdev->props.conf; |
5feeb718 | 205 | BlockBackend *blk = conf->blk; |
1a72d9ae PD |
206 | |
207 | if (vdev->type == XEN_BLOCK_VDEV_TYPE_INVALID) { | |
208 | error_setg(errp, "vdev property not set"); | |
209 | return; | |
210 | } | |
211 | ||
212 | trace_xen_block_realize(type, vdev->disk, vdev->partition); | |
213 | ||
214 | if (blockdev_class->realize) { | |
1de7096d VSO |
215 | blockdev_class->realize(blockdev, errp); |
216 | if (*errp) { | |
b6af8926 | 217 | return; |
1a72d9ae PD |
218 | } |
219 | } | |
b6af8926 PD |
220 | |
221 | /* | |
222 | * The blkif protocol does not deal with removable media, so it must | |
223 | * always be present, even for CDRom devices. | |
224 | */ | |
5feeb718 PD |
225 | assert(blk); |
226 | if (!blk_is_inserted(blk)) { | |
b6af8926 PD |
227 | error_setg(errp, "device needs media, but drive is empty"); |
228 | return; | |
229 | } | |
230 | ||
231 | if (!blkconf_apply_backend_options(conf, blockdev->info & VDISK_READONLY, | |
3149f183 | 232 | true, errp)) { |
b6af8926 PD |
233 | return; |
234 | } | |
235 | ||
236 | if (!(blockdev->info & VDISK_CDROM) && | |
237 | !blkconf_geometry(conf, NULL, 65535, 255, 255, errp)) { | |
238 | return; | |
239 | } | |
240 | ||
c56ee92f | 241 | if (!blkconf_blocksizes(conf, errp)) { |
b6af8926 PD |
242 | return; |
243 | } | |
244 | ||
5feeb718 PD |
245 | blk_set_dev_ops(blk, &xen_block_dev_ops, blockdev); |
246 | blk_set_guest_block_size(blk, conf->logical_block_size); | |
b6af8926 | 247 | |
15f08450 PD |
248 | if (conf->discard_granularity == -1) { |
249 | conf->discard_granularity = conf->physical_block_size; | |
250 | } | |
251 | ||
5feeb718 | 252 | if (blk_get_flags(blk) & BDRV_O_UNMAP) { |
b6af8926 | 253 | xen_device_backend_printf(xendev, "feature-discard", "%u", 1); |
15f08450 PD |
254 | xen_device_backend_printf(xendev, "discard-granularity", "%u", |
255 | conf->discard_granularity); | |
b6af8926 PD |
256 | } |
257 | ||
258 | xen_device_backend_printf(xendev, "feature-flush-cache", "%u", 1); | |
259 | xen_device_backend_printf(xendev, "max-ring-page-order", "%u", | |
260 | blockdev->props.max_ring_page_order); | |
261 | xen_device_backend_printf(xendev, "info", "%u", blockdev->info); | |
262 | ||
263 | xen_device_frontend_printf(xendev, "virtual-device", "%lu", | |
264 | vdev->number); | |
265 | xen_device_frontend_printf(xendev, "device-type", "%s", | |
266 | blockdev->device_type); | |
267 | ||
268 | xen_device_backend_printf(xendev, "sector-size", "%u", | |
5feeb718 | 269 | conf->logical_block_size); |
3149f183 PD |
270 | |
271 | xen_block_set_size(blockdev); | |
b6af8926 PD |
272 | |
273 | blockdev->dataplane = | |
5feeb718 PD |
274 | xen_block_dataplane_create(xendev, blk, conf->logical_block_size, |
275 | blockdev->props.iothread); | |
1a72d9ae PD |
276 | } |
277 | ||
82a29e30 PD |
278 | static void xen_block_frontend_changed(XenDevice *xendev, |
279 | enum xenbus_state frontend_state, | |
280 | Error **errp) | |
281 | { | |
1de7096d | 282 | ERRP_GUARD(); |
82a29e30 | 283 | enum xenbus_state backend_state = xen_device_backend_get_state(xendev); |
82a29e30 PD |
284 | |
285 | switch (frontend_state) { | |
286 | case XenbusStateInitialised: | |
287 | case XenbusStateConnected: | |
288 | if (backend_state == XenbusStateConnected) { | |
289 | break; | |
290 | } | |
291 | ||
1de7096d VSO |
292 | xen_block_disconnect(xendev, errp); |
293 | if (*errp) { | |
82a29e30 PD |
294 | break; |
295 | } | |
296 | ||
1de7096d VSO |
297 | xen_block_connect(xendev, errp); |
298 | if (*errp) { | |
82a29e30 PD |
299 | break; |
300 | } | |
301 | ||
302 | xen_device_backend_set_state(xendev, XenbusStateConnected); | |
303 | break; | |
304 | ||
305 | case XenbusStateClosing: | |
306 | xen_device_backend_set_state(xendev, XenbusStateClosing); | |
307 | break; | |
308 | ||
309 | case XenbusStateClosed: | |
ef916ab3 | 310 | case XenbusStateUnknown: |
1de7096d VSO |
311 | xen_block_disconnect(xendev, errp); |
312 | if (*errp) { | |
82a29e30 PD |
313 | break; |
314 | } | |
315 | ||
316 | xen_device_backend_set_state(xendev, XenbusStateClosed); | |
317 | break; | |
318 | ||
319 | default: | |
320 | break; | |
321 | } | |
322 | } | |
323 | ||
1a72d9ae PD |
324 | static char *disk_to_vbd_name(unsigned int disk) |
325 | { | |
326 | char *name, *prefix = (disk >= 26) ? | |
327 | disk_to_vbd_name((disk / 26) - 1) : g_strdup(""); | |
328 | ||
329 | name = g_strdup_printf("%s%c", prefix, 'a' + disk % 26); | |
330 | g_free(prefix); | |
331 | ||
332 | return name; | |
333 | } | |
334 | ||
335 | static void xen_block_get_vdev(Object *obj, Visitor *v, const char *name, | |
336 | void *opaque, Error **errp) | |
337 | { | |
1a72d9ae | 338 | Property *prop = opaque; |
828ade86 | 339 | XenBlockVdev *vdev = qdev_get_prop_ptr(obj, prop); |
1a72d9ae PD |
340 | char *str; |
341 | ||
342 | switch (vdev->type) { | |
343 | case XEN_BLOCK_VDEV_TYPE_DP: | |
344 | str = g_strdup_printf("d%lup%lu", vdev->disk, vdev->partition); | |
345 | break; | |
346 | ||
347 | case XEN_BLOCK_VDEV_TYPE_XVD: | |
348 | case XEN_BLOCK_VDEV_TYPE_HD: | |
349 | case XEN_BLOCK_VDEV_TYPE_SD: { | |
350 | char *name = disk_to_vbd_name(vdev->disk); | |
351 | ||
352 | str = g_strdup_printf("%s%s%lu", | |
353 | (vdev->type == XEN_BLOCK_VDEV_TYPE_XVD) ? | |
354 | "xvd" : | |
355 | (vdev->type == XEN_BLOCK_VDEV_TYPE_HD) ? | |
356 | "hd" : | |
357 | "sd", | |
358 | name, vdev->partition); | |
359 | g_free(name); | |
360 | break; | |
361 | } | |
362 | default: | |
363 | error_setg(errp, "invalid vdev type"); | |
364 | return; | |
365 | } | |
366 | ||
367 | visit_type_str(v, name, &str, errp); | |
368 | g_free(str); | |
369 | } | |
370 | ||
2ae23f0e PD |
371 | static int vbd_name_to_disk(const char *name, const char **endp, |
372 | unsigned long *disk) | |
1a72d9ae | 373 | { |
2ae23f0e | 374 | unsigned int n = 0; |
1a72d9ae PD |
375 | |
376 | while (*name != '\0') { | |
377 | if (!g_ascii_isalpha(*name) || !g_ascii_islower(*name)) { | |
378 | break; | |
379 | } | |
380 | ||
2ae23f0e PD |
381 | n *= 26; |
382 | n += *name++ - 'a' + 1; | |
1a72d9ae PD |
383 | } |
384 | *endp = name; | |
385 | ||
2ae23f0e PD |
386 | if (!n) { |
387 | return -1; | |
388 | } | |
389 | ||
390 | *disk = n - 1; | |
391 | ||
392 | return 0; | |
1a72d9ae PD |
393 | } |
394 | ||
395 | static void xen_block_set_vdev(Object *obj, Visitor *v, const char *name, | |
396 | void *opaque, Error **errp) | |
397 | { | |
1a72d9ae | 398 | Property *prop = opaque; |
828ade86 | 399 | XenBlockVdev *vdev = qdev_get_prop_ptr(obj, prop); |
1a72d9ae PD |
400 | char *str, *p; |
401 | const char *end; | |
402 | ||
668f62ec | 403 | if (!visit_type_str(v, name, &str, errp)) { |
1a72d9ae PD |
404 | return; |
405 | } | |
406 | ||
407 | p = strchr(str, 'd'); | |
408 | if (!p) { | |
409 | goto invalid; | |
410 | } | |
411 | ||
412 | *p++ = '\0'; | |
413 | if (*str == '\0') { | |
414 | vdev->type = XEN_BLOCK_VDEV_TYPE_DP; | |
415 | } else if (strcmp(str, "xv") == 0) { | |
416 | vdev->type = XEN_BLOCK_VDEV_TYPE_XVD; | |
417 | } else if (strcmp(str, "h") == 0) { | |
418 | vdev->type = XEN_BLOCK_VDEV_TYPE_HD; | |
419 | } else if (strcmp(str, "s") == 0) { | |
420 | vdev->type = XEN_BLOCK_VDEV_TYPE_SD; | |
421 | } else { | |
422 | goto invalid; | |
423 | } | |
424 | ||
425 | if (vdev->type == XEN_BLOCK_VDEV_TYPE_DP) { | |
426 | if (qemu_strtoul(p, &end, 10, &vdev->disk)) { | |
427 | goto invalid; | |
428 | } | |
429 | ||
430 | if (*end == 'p') { | |
210b9776 | 431 | if (*(++end) == '\0') { |
1a72d9ae PD |
432 | goto invalid; |
433 | } | |
434 | } | |
435 | } else { | |
2ae23f0e PD |
436 | if (vbd_name_to_disk(p, &end, &vdev->disk)) { |
437 | goto invalid; | |
438 | } | |
1a72d9ae PD |
439 | } |
440 | ||
441 | if (*end != '\0') { | |
442 | p = (char *)end; | |
443 | ||
444 | if (qemu_strtoul(p, &end, 10, &vdev->partition)) { | |
445 | goto invalid; | |
446 | } | |
447 | ||
448 | if (*end != '\0') { | |
449 | goto invalid; | |
450 | } | |
451 | } else { | |
452 | vdev->partition = 0; | |
453 | } | |
454 | ||
455 | switch (vdev->type) { | |
456 | case XEN_BLOCK_VDEV_TYPE_DP: | |
457 | case XEN_BLOCK_VDEV_TYPE_XVD: | |
458 | if (vdev->disk < (1 << 4) && vdev->partition < (1 << 4)) { | |
459 | vdev->number = (202 << 8) | (vdev->disk << 4) | | |
460 | vdev->partition; | |
461 | } else if (vdev->disk < (1 << 20) && vdev->partition < (1 << 8)) { | |
462 | vdev->number = (1 << 28) | (vdev->disk << 8) | | |
463 | vdev->partition; | |
464 | } else { | |
465 | goto invalid; | |
466 | } | |
467 | break; | |
468 | ||
469 | case XEN_BLOCK_VDEV_TYPE_HD: | |
470 | if ((vdev->disk == 0 || vdev->disk == 1) && | |
471 | vdev->partition < (1 << 6)) { | |
472 | vdev->number = (3 << 8) | (vdev->disk << 6) | vdev->partition; | |
473 | } else if ((vdev->disk == 2 || vdev->disk == 3) && | |
474 | vdev->partition < (1 << 6)) { | |
475 | vdev->number = (22 << 8) | ((vdev->disk - 2) << 6) | | |
476 | vdev->partition; | |
477 | } else { | |
478 | goto invalid; | |
479 | } | |
480 | break; | |
481 | ||
482 | case XEN_BLOCK_VDEV_TYPE_SD: | |
483 | if (vdev->disk < (1 << 4) && vdev->partition < (1 << 4)) { | |
484 | vdev->number = (8 << 8) | (vdev->disk << 4) | vdev->partition; | |
485 | } else { | |
486 | goto invalid; | |
487 | } | |
488 | break; | |
489 | ||
490 | default: | |
491 | goto invalid; | |
492 | } | |
493 | ||
494 | g_free(str); | |
495 | return; | |
496 | ||
497 | invalid: | |
498 | error_setg(errp, "invalid virtual disk specifier"); | |
499 | ||
500 | vdev->type = XEN_BLOCK_VDEV_TYPE_INVALID; | |
501 | g_free(str); | |
502 | } | |
503 | ||
504 | /* | |
505 | * This property deals with 'vdev' names adhering to the Xen VBD naming | |
506 | * scheme described in: | |
507 | * | |
508 | * https://xenbits.xen.org/docs/unstable/man/xen-vbd-interface.7.html | |
509 | */ | |
510 | const PropertyInfo xen_block_prop_vdev = { | |
511 | .name = "str", | |
512 | .description = "Virtual Disk specifier: d*p*/xvd*/hd*/sd*", | |
513 | .get = xen_block_get_vdev, | |
514 | .set = xen_block_set_vdev, | |
515 | }; | |
516 | ||
517 | static Property xen_block_props[] = { | |
518 | DEFINE_PROP("vdev", XenBlockDevice, props.vdev, | |
519 | xen_block_prop_vdev, XenBlockVdev), | |
b6af8926 PD |
520 | DEFINE_BLOCK_PROPERTIES(XenBlockDevice, props.conf), |
521 | DEFINE_PROP_UINT32("max-ring-page-order", XenBlockDevice, | |
522 | props.max_ring_page_order, 4), | |
523 | DEFINE_PROP_LINK("iothread", XenBlockDevice, props.iothread, | |
524 | TYPE_IOTHREAD, IOThread *), | |
1a72d9ae PD |
525 | DEFINE_PROP_END_OF_LIST() |
526 | }; | |
527 | ||
528 | static void xen_block_class_init(ObjectClass *class, void *data) | |
529 | { | |
530 | DeviceClass *dev_class = DEVICE_CLASS(class); | |
531 | XenDeviceClass *xendev_class = XEN_DEVICE_CLASS(class); | |
532 | ||
db9ff46e | 533 | xendev_class->backend = "qdisk"; |
b6af8926 | 534 | xendev_class->device = "vbd"; |
094a2239 | 535 | xendev_class->get_name = xen_block_get_name; |
1a72d9ae | 536 | xendev_class->realize = xen_block_realize; |
82a29e30 | 537 | xendev_class->frontend_changed = xen_block_frontend_changed; |
1a72d9ae PD |
538 | xendev_class->unrealize = xen_block_unrealize; |
539 | ||
4f67d30b | 540 | device_class_set_props(dev_class, xen_block_props); |
1a72d9ae PD |
541 | } |
542 | ||
543 | static const TypeInfo xen_block_type_info = { | |
544 | .name = TYPE_XEN_BLOCK_DEVICE, | |
545 | .parent = TYPE_XEN_DEVICE, | |
546 | .instance_size = sizeof(XenBlockDevice), | |
547 | .abstract = true, | |
548 | .class_size = sizeof(XenBlockDeviceClass), | |
549 | .class_init = xen_block_class_init, | |
550 | }; | |
551 | ||
b69c3c21 | 552 | static void xen_disk_unrealize(XenBlockDevice *blockdev) |
1a72d9ae PD |
553 | { |
554 | trace_xen_disk_unrealize(); | |
555 | } | |
556 | ||
557 | static void xen_disk_realize(XenBlockDevice *blockdev, Error **errp) | |
558 | { | |
b6af8926 PD |
559 | BlockConf *conf = &blockdev->props.conf; |
560 | ||
1a72d9ae | 561 | trace_xen_disk_realize(); |
b6af8926 PD |
562 | |
563 | blockdev->device_type = "disk"; | |
564 | ||
565 | if (!conf->blk) { | |
566 | error_setg(errp, "drive property not set"); | |
567 | return; | |
568 | } | |
569 | ||
570 | blockdev->info = blk_is_read_only(conf->blk) ? VDISK_READONLY : 0; | |
1a72d9ae PD |
571 | } |
572 | ||
573 | static void xen_disk_class_init(ObjectClass *class, void *data) | |
574 | { | |
575 | DeviceClass *dev_class = DEVICE_CLASS(class); | |
576 | XenBlockDeviceClass *blockdev_class = XEN_BLOCK_DEVICE_CLASS(class); | |
577 | ||
578 | blockdev_class->realize = xen_disk_realize; | |
579 | blockdev_class->unrealize = xen_disk_unrealize; | |
580 | ||
581 | dev_class->desc = "Xen Disk Device"; | |
582 | } | |
583 | ||
584 | static const TypeInfo xen_disk_type_info = { | |
585 | .name = TYPE_XEN_DISK_DEVICE, | |
586 | .parent = TYPE_XEN_BLOCK_DEVICE, | |
587 | .instance_size = sizeof(XenDiskDevice), | |
588 | .class_init = xen_disk_class_init, | |
589 | }; | |
590 | ||
b69c3c21 | 591 | static void xen_cdrom_unrealize(XenBlockDevice *blockdev) |
1a72d9ae PD |
592 | { |
593 | trace_xen_cdrom_unrealize(); | |
594 | } | |
595 | ||
596 | static void xen_cdrom_realize(XenBlockDevice *blockdev, Error **errp) | |
597 | { | |
b6af8926 PD |
598 | BlockConf *conf = &blockdev->props.conf; |
599 | ||
1a72d9ae | 600 | trace_xen_cdrom_realize(); |
b6af8926 PD |
601 | |
602 | blockdev->device_type = "cdrom"; | |
603 | ||
604 | if (!conf->blk) { | |
605 | int rc; | |
606 | ||
607 | /* Set up an empty drive */ | |
d861ab3a | 608 | conf->blk = blk_new(qemu_get_aio_context(), 0, BLK_PERM_ALL); |
b6af8926 PD |
609 | |
610 | rc = blk_attach_dev(conf->blk, DEVICE(blockdev)); | |
611 | if (!rc) { | |
612 | error_setg_errno(errp, -rc, "failed to create drive"); | |
613 | return; | |
614 | } | |
615 | } | |
616 | ||
617 | blockdev->info = VDISK_READONLY | VDISK_CDROM; | |
1a72d9ae PD |
618 | } |
619 | ||
620 | static void xen_cdrom_class_init(ObjectClass *class, void *data) | |
621 | { | |
622 | DeviceClass *dev_class = DEVICE_CLASS(class); | |
623 | XenBlockDeviceClass *blockdev_class = XEN_BLOCK_DEVICE_CLASS(class); | |
624 | ||
625 | blockdev_class->realize = xen_cdrom_realize; | |
626 | blockdev_class->unrealize = xen_cdrom_unrealize; | |
627 | ||
628 | dev_class->desc = "Xen CD-ROM Device"; | |
629 | } | |
630 | ||
631 | static const TypeInfo xen_cdrom_type_info = { | |
632 | .name = TYPE_XEN_CDROM_DEVICE, | |
633 | .parent = TYPE_XEN_BLOCK_DEVICE, | |
634 | .instance_size = sizeof(XenCDRomDevice), | |
635 | .class_init = xen_cdrom_class_init, | |
636 | }; | |
637 | ||
638 | static void xen_block_register_types(void) | |
639 | { | |
640 | type_register_static(&xen_block_type_info); | |
641 | type_register_static(&xen_disk_type_info); | |
642 | type_register_static(&xen_cdrom_type_info); | |
643 | } | |
644 | ||
645 | type_init(xen_block_register_types) | |
db9ff46e PD |
646 | |
647 | static void xen_block_blockdev_del(const char *node_name, Error **errp) | |
648 | { | |
649 | trace_xen_block_blockdev_del(node_name); | |
650 | ||
651 | qmp_blockdev_del(node_name, errp); | |
652 | } | |
653 | ||
654 | static char *xen_block_blockdev_add(const char *id, QDict *qdict, | |
655 | Error **errp) | |
656 | { | |
1de7096d | 657 | ERRP_GUARD(); |
db9ff46e PD |
658 | const char *driver = qdict_get_try_str(qdict, "driver"); |
659 | BlockdevOptions *options = NULL; | |
db9ff46e PD |
660 | char *node_name; |
661 | Visitor *v; | |
662 | ||
663 | if (!driver) { | |
664 | error_setg(errp, "no 'driver' parameter"); | |
665 | return NULL; | |
666 | } | |
667 | ||
668 | node_name = g_strdup_printf("%s-%s", id, driver); | |
669 | qdict_put_str(qdict, "node-name", node_name); | |
670 | ||
671 | trace_xen_block_blockdev_add(node_name); | |
672 | ||
673 | v = qobject_input_visitor_new(QOBJECT(qdict)); | |
b11a093c | 674 | visit_type_BlockdevOptions(v, NULL, &options, errp); |
db9ff46e | 675 | visit_free(v); |
b11a093c | 676 | if (!options) { |
db9ff46e PD |
677 | goto fail; |
678 | } | |
679 | ||
1de7096d | 680 | qmp_blockdev_add(options, errp); |
db9ff46e | 681 | |
1de7096d | 682 | if (*errp) { |
db9ff46e PD |
683 | goto fail; |
684 | } | |
685 | ||
686 | qapi_free_BlockdevOptions(options); | |
687 | ||
688 | return node_name; | |
689 | ||
690 | fail: | |
691 | if (options) { | |
692 | qapi_free_BlockdevOptions(options); | |
693 | } | |
694 | g_free(node_name); | |
695 | ||
696 | return NULL; | |
697 | } | |
698 | ||
699 | static void xen_block_drive_destroy(XenBlockDrive *drive, Error **errp) | |
700 | { | |
1de7096d | 701 | ERRP_GUARD(); |
db9ff46e PD |
702 | char *node_name = drive->node_name; |
703 | ||
704 | if (node_name) { | |
1de7096d VSO |
705 | xen_block_blockdev_del(node_name, errp); |
706 | if (*errp) { | |
db9ff46e PD |
707 | return; |
708 | } | |
709 | g_free(node_name); | |
710 | drive->node_name = NULL; | |
711 | } | |
712 | g_free(drive->id); | |
713 | g_free(drive); | |
714 | } | |
715 | ||
716 | static XenBlockDrive *xen_block_drive_create(const char *id, | |
717 | const char *device_type, | |
718 | QDict *opts, Error **errp) | |
719 | { | |
1de7096d | 720 | ERRP_GUARD(); |
db9ff46e PD |
721 | const char *params = qdict_get_try_str(opts, "params"); |
722 | const char *mode = qdict_get_try_str(opts, "mode"); | |
723 | const char *direct_io_safe = qdict_get_try_str(opts, "direct-io-safe"); | |
724 | const char *discard_enable = qdict_get_try_str(opts, "discard-enable"); | |
725 | char *driver = NULL; | |
726 | char *filename = NULL; | |
727 | XenBlockDrive *drive = NULL; | |
db9ff46e PD |
728 | QDict *file_layer; |
729 | QDict *driver_layer; | |
730 | ||
731 | if (params) { | |
732 | char **v = g_strsplit(params, ":", 2); | |
733 | ||
734 | if (v[1] == NULL) { | |
735 | filename = g_strdup(v[0]); | |
736 | driver = g_strdup("raw"); | |
737 | } else { | |
738 | if (strcmp(v[0], "aio") == 0) { | |
739 | driver = g_strdup("raw"); | |
740 | } else if (strcmp(v[0], "vhd") == 0) { | |
741 | driver = g_strdup("vpc"); | |
742 | } else { | |
743 | driver = g_strdup(v[0]); | |
744 | } | |
745 | filename = g_strdup(v[1]); | |
746 | } | |
747 | ||
748 | g_strfreev(v); | |
156ac944 PD |
749 | } else { |
750 | error_setg(errp, "no params"); | |
db9ff46e PD |
751 | goto done; |
752 | } | |
156ac944 PD |
753 | |
754 | assert(filename); | |
db9ff46e PD |
755 | assert(driver); |
756 | ||
757 | drive = g_new0(XenBlockDrive, 1); | |
758 | drive->id = g_strdup(id); | |
759 | ||
760 | file_layer = qdict_new(); | |
15f08450 | 761 | driver_layer = qdict_new(); |
db9ff46e PD |
762 | |
763 | qdict_put_str(file_layer, "driver", "file"); | |
764 | qdict_put_str(file_layer, "filename", filename); | |
156ac944 | 765 | g_free(filename); |
db9ff46e PD |
766 | |
767 | if (mode && *mode != 'w') { | |
768 | qdict_put_bool(file_layer, "read-only", true); | |
769 | } | |
770 | ||
771 | if (direct_io_safe) { | |
772 | unsigned long value; | |
773 | ||
774 | if (!qemu_strtoul(direct_io_safe, NULL, 2, &value) && !!value) { | |
775 | QDict *cache_qdict = qdict_new(); | |
776 | ||
777 | qdict_put_bool(cache_qdict, "direct", true); | |
ad85b0b4 | 778 | qdict_put(file_layer, "cache", cache_qdict); |
db9ff46e PD |
779 | |
780 | qdict_put_str(file_layer, "aio", "native"); | |
781 | } | |
782 | } | |
783 | ||
784 | if (discard_enable) { | |
785 | unsigned long value; | |
786 | ||
787 | if (!qemu_strtoul(discard_enable, NULL, 2, &value) && !!value) { | |
788 | qdict_put_str(file_layer, "discard", "unmap"); | |
15f08450 | 789 | qdict_put_str(driver_layer, "discard", "unmap"); |
db9ff46e PD |
790 | } |
791 | } | |
792 | ||
793 | /* | |
794 | * It is necessary to turn file locking off as an emulated device | |
795 | * may have already opened the same image file. | |
796 | */ | |
797 | qdict_put_str(file_layer, "locking", "off"); | |
798 | ||
db9ff46e | 799 | qdict_put_str(driver_layer, "driver", driver); |
156ac944 PD |
800 | g_free(driver); |
801 | ||
ad85b0b4 | 802 | qdict_put(driver_layer, "file", file_layer); |
db9ff46e PD |
803 | |
804 | g_assert(!drive->node_name); | |
805 | drive->node_name = xen_block_blockdev_add(drive->id, driver_layer, | |
1de7096d | 806 | errp); |
db9ff46e | 807 | |
156ac944 | 808 | qobject_unref(driver_layer); |
db9ff46e | 809 | |
156ac944 | 810 | done: |
1de7096d | 811 | if (*errp) { |
db9ff46e PD |
812 | xen_block_drive_destroy(drive, NULL); |
813 | return NULL; | |
814 | } | |
815 | ||
816 | return drive; | |
817 | } | |
818 | ||
819 | static const char *xen_block_drive_get_node_name(XenBlockDrive *drive) | |
820 | { | |
821 | return drive->node_name ? drive->node_name : ""; | |
822 | } | |
823 | ||
824 | static void xen_block_iothread_destroy(XenBlockIOThread *iothread, | |
825 | Error **errp) | |
826 | { | |
827 | qmp_object_del(iothread->id, errp); | |
828 | ||
829 | g_free(iothread->id); | |
830 | g_free(iothread); | |
831 | } | |
832 | ||
833 | static XenBlockIOThread *xen_block_iothread_create(const char *id, | |
834 | Error **errp) | |
835 | { | |
1de7096d | 836 | ERRP_GUARD(); |
db9ff46e | 837 | XenBlockIOThread *iothread = g_new(XenBlockIOThread, 1); |
5f07c4d6 | 838 | QDict *opts; |
0cd40042 | 839 | QObject *ret_data = NULL; |
db9ff46e PD |
840 | |
841 | iothread->id = g_strdup(id); | |
842 | ||
5f07c4d6 KW |
843 | opts = qdict_new(); |
844 | qdict_put_str(opts, "qom-type", TYPE_IOTHREAD); | |
845 | qdict_put_str(opts, "id", id); | |
1de7096d | 846 | qmp_object_add(opts, &ret_data, errp); |
5f07c4d6 KW |
847 | qobject_unref(opts); |
848 | qobject_unref(ret_data); | |
849 | ||
1de7096d | 850 | if (*errp) { |
db9ff46e PD |
851 | g_free(iothread->id); |
852 | g_free(iothread); | |
853 | return NULL; | |
854 | } | |
855 | ||
856 | return iothread; | |
857 | } | |
858 | ||
859 | static void xen_block_device_create(XenBackendInstance *backend, | |
860 | QDict *opts, Error **errp) | |
861 | { | |
1de7096d | 862 | ERRP_GUARD(); |
db9ff46e PD |
863 | XenBus *xenbus = xen_backend_get_bus(backend); |
864 | const char *name = xen_backend_get_name(backend); | |
865 | unsigned long number; | |
866 | const char *vdev, *device_type; | |
867 | XenBlockDrive *drive = NULL; | |
868 | XenBlockIOThread *iothread = NULL; | |
869 | XenDevice *xendev = NULL; | |
db9ff46e PD |
870 | const char *type; |
871 | XenBlockDevice *blockdev; | |
872 | ||
873 | if (qemu_strtoul(name, NULL, 10, &number)) { | |
874 | error_setg(errp, "failed to parse name '%s'", name); | |
875 | goto fail; | |
876 | } | |
877 | ||
878 | trace_xen_block_device_create(number); | |
879 | ||
880 | vdev = qdict_get_try_str(opts, "dev"); | |
881 | if (!vdev) { | |
882 | error_setg(errp, "no dev parameter"); | |
883 | goto fail; | |
884 | } | |
885 | ||
886 | device_type = qdict_get_try_str(opts, "device-type"); | |
887 | if (!device_type) { | |
888 | error_setg(errp, "no device-type parameter"); | |
889 | goto fail; | |
890 | } | |
891 | ||
892 | if (!strcmp(device_type, "disk")) { | |
893 | type = TYPE_XEN_DISK_DEVICE; | |
894 | } else if (!strcmp(device_type, "cdrom")) { | |
895 | type = TYPE_XEN_CDROM_DEVICE; | |
896 | } else { | |
897 | error_setg(errp, "invalid device-type parameter '%s'", device_type); | |
898 | goto fail; | |
899 | } | |
900 | ||
1de7096d | 901 | drive = xen_block_drive_create(vdev, device_type, opts, errp); |
db9ff46e | 902 | if (!drive) { |
1de7096d | 903 | error_prepend(errp, "failed to create drive: "); |
db9ff46e PD |
904 | goto fail; |
905 | } | |
906 | ||
1de7096d VSO |
907 | iothread = xen_block_iothread_create(vdev, errp); |
908 | if (*errp) { | |
909 | error_prepend(errp, "failed to create iothread: "); | |
db9ff46e PD |
910 | goto fail; |
911 | } | |
912 | ||
df707969 | 913 | xendev = XEN_DEVICE(qdev_new(type)); |
db9ff46e PD |
914 | blockdev = XEN_BLOCK_DEVICE(xendev); |
915 | ||
778a2dc5 | 916 | if (!object_property_set_str(OBJECT(xendev), "vdev", vdev, |
1de7096d VSO |
917 | errp)) { |
918 | error_prepend(errp, "failed to set 'vdev': "); | |
db9ff46e PD |
919 | goto fail; |
920 | } | |
921 | ||
778a2dc5 MA |
922 | if (!object_property_set_str(OBJECT(xendev), "drive", |
923 | xen_block_drive_get_node_name(drive), | |
1de7096d VSO |
924 | errp)) { |
925 | error_prepend(errp, "failed to set 'drive': "); | |
db9ff46e PD |
926 | goto fail; |
927 | } | |
928 | ||
778a2dc5 | 929 | if (!object_property_set_str(OBJECT(xendev), "iothread", iothread->id, |
1de7096d VSO |
930 | errp)) { |
931 | error_prepend(errp, "failed to set 'iothread': "); | |
db9ff46e PD |
932 | goto fail; |
933 | } | |
934 | ||
935 | blockdev->iothread = iothread; | |
936 | blockdev->drive = drive; | |
937 | ||
1de7096d VSO |
938 | if (!qdev_realize_and_unref(DEVICE(xendev), BUS(xenbus), errp)) { |
939 | error_prepend(errp, "realization of device %s failed: ", type); | |
db9ff46e PD |
940 | goto fail; |
941 | } | |
942 | ||
943 | xen_backend_set_device(backend, xendev); | |
944 | return; | |
945 | ||
946 | fail: | |
947 | if (xendev) { | |
948 | object_unparent(OBJECT(xendev)); | |
949 | } | |
950 | ||
951 | if (iothread) { | |
952 | xen_block_iothread_destroy(iothread, NULL); | |
953 | } | |
954 | ||
955 | if (drive) { | |
956 | xen_block_drive_destroy(drive, NULL); | |
957 | } | |
958 | } | |
959 | ||
960 | static void xen_block_device_destroy(XenBackendInstance *backend, | |
961 | Error **errp) | |
962 | { | |
1de7096d | 963 | ERRP_GUARD(); |
db9ff46e PD |
964 | XenDevice *xendev = xen_backend_get_device(backend); |
965 | XenBlockDevice *blockdev = XEN_BLOCK_DEVICE(xendev); | |
966 | XenBlockVdev *vdev = &blockdev->props.vdev; | |
967 | XenBlockDrive *drive = blockdev->drive; | |
968 | XenBlockIOThread *iothread = blockdev->iothread; | |
969 | ||
970 | trace_xen_block_device_destroy(vdev->number); | |
971 | ||
972 | object_unparent(OBJECT(xendev)); | |
973 | ||
974 | if (iothread) { | |
1de7096d VSO |
975 | xen_block_iothread_destroy(iothread, errp); |
976 | if (*errp) { | |
977 | error_prepend(errp, "failed to destroy iothread: "); | |
db9ff46e PD |
978 | return; |
979 | } | |
980 | } | |
981 | ||
982 | if (drive) { | |
1de7096d VSO |
983 | xen_block_drive_destroy(drive, errp); |
984 | if (*errp) { | |
985 | error_prepend(errp, "failed to destroy drive: "); | |
526ab8e8 | 986 | return; |
db9ff46e PD |
987 | } |
988 | } | |
989 | } | |
990 | ||
991 | static const XenBackendInfo xen_block_backend_info = { | |
992 | .type = "qdisk", | |
993 | .create = xen_block_device_create, | |
994 | .destroy = xen_block_device_destroy, | |
995 | }; | |
996 | ||
997 | static void xen_block_register_backend(void) | |
998 | { | |
999 | xen_backend_register(&xen_block_backend_info); | |
1000 | } | |
1001 | ||
1002 | xen_backend_init(xen_block_register_backend); |