1 | /* |
2 | * Virtio SCSI dataplane |
3 | * |
4 | * Copyright Red Hat, Inc. 2014 |
5 | * |
6 | * Authors: |
7 | * Fam Zheng <famz@redhat.com> |
8 | * |
9 | * This work is licensed under the terms of the GNU GPL, version 2 or later. |
10 | * See the COPYING file in the top-level directory. |
11 | * |
12 | */ |
13 | |
14 | #include "qemu/osdep.h" |
15 | #include "qapi/error.h" |
16 | #include "hw/virtio/virtio-scsi.h" |
17 | #include "qemu/error-report.h" |
18 | #include "sysemu/block-backend.h" |
19 | #include "hw/scsi/scsi.h" |
20 | #include "scsi/constants.h" |
21 | #include "hw/virtio/virtio-bus.h" |
22 | #include "hw/virtio/virtio-access.h" |
23 | |
24 | /* Context: QEMU global mutex held */ |
25 | void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp) |
26 | { |
27 | VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s); |
28 | VirtIODevice *vdev = VIRTIO_DEVICE(s); |
29 | BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); |
30 | VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); |
31 | |
32 | if (vs->conf.iothread) { |
33 | if (!k->set_guest_notifiers || !k->ioeventfd_assign) { |
34 | error_setg(errp, |
35 | "device is incompatible with iothread " |
36 | "(transport does not support notifiers)" ); |
37 | return; |
38 | } |
39 | if (!virtio_device_ioeventfd_enabled(vdev)) { |
40 | error_setg(errp, "ioeventfd is required for iothread" ); |
41 | return; |
42 | } |
43 | s->ctx = iothread_get_aio_context(vs->conf.iothread); |
44 | } else { |
45 | if (!virtio_device_ioeventfd_enabled(vdev)) { |
46 | return; |
47 | } |
48 | s->ctx = qemu_get_aio_context(); |
49 | } |
50 | } |
51 | |
52 | static bool virtio_scsi_data_plane_handle_cmd(VirtIODevice *vdev, |
53 | VirtQueue *vq) |
54 | { |
55 | bool progress; |
56 | VirtIOSCSI *s = VIRTIO_SCSI(vdev); |
57 | |
58 | virtio_scsi_acquire(s); |
59 | assert(s->ctx && s->dataplane_started); |
60 | progress = virtio_scsi_handle_cmd_vq(s, vq); |
61 | virtio_scsi_release(s); |
62 | return progress; |
63 | } |
64 | |
65 | static bool virtio_scsi_data_plane_handle_ctrl(VirtIODevice *vdev, |
66 | VirtQueue *vq) |
67 | { |
68 | bool progress; |
69 | VirtIOSCSI *s = VIRTIO_SCSI(vdev); |
70 | |
71 | virtio_scsi_acquire(s); |
72 | assert(s->ctx && s->dataplane_started); |
73 | progress = virtio_scsi_handle_ctrl_vq(s, vq); |
74 | virtio_scsi_release(s); |
75 | return progress; |
76 | } |
77 | |
78 | static bool virtio_scsi_data_plane_handle_event(VirtIODevice *vdev, |
79 | VirtQueue *vq) |
80 | { |
81 | bool progress; |
82 | VirtIOSCSI *s = VIRTIO_SCSI(vdev); |
83 | |
84 | virtio_scsi_acquire(s); |
85 | assert(s->ctx && s->dataplane_started); |
86 | progress = virtio_scsi_handle_event_vq(s, vq); |
87 | virtio_scsi_release(s); |
88 | return progress; |
89 | } |
90 | |
91 | static int virtio_scsi_vring_init(VirtIOSCSI *s, VirtQueue *vq, int n, |
92 | VirtIOHandleAIOOutput fn) |
93 | { |
94 | BusState *qbus = BUS(qdev_get_parent_bus(DEVICE(s))); |
95 | int rc; |
96 | |
97 | /* Set up virtqueue notify */ |
98 | rc = virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), n, true); |
99 | if (rc != 0) { |
100 | fprintf(stderr, "virtio-scsi: Failed to set host notifier (%d)\n" , |
101 | rc); |
102 | s->dataplane_fenced = true; |
103 | return rc; |
104 | } |
105 | |
106 | virtio_queue_aio_set_host_notifier_handler(vq, s->ctx, fn); |
107 | return 0; |
108 | } |
109 | |
110 | /* Context: BH in IOThread */ |
111 | static void virtio_scsi_dataplane_stop_bh(void *opaque) |
112 | { |
113 | VirtIOSCSI *s = opaque; |
114 | VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s); |
115 | int i; |
116 | |
117 | virtio_queue_aio_set_host_notifier_handler(vs->ctrl_vq, s->ctx, NULL); |
118 | virtio_queue_aio_set_host_notifier_handler(vs->event_vq, s->ctx, NULL); |
119 | for (i = 0; i < vs->conf.num_queues; i++) { |
120 | virtio_queue_aio_set_host_notifier_handler(vs->cmd_vqs[i], s->ctx, NULL); |
121 | } |
122 | } |
123 | |
124 | /* Context: QEMU global mutex held */ |
125 | int virtio_scsi_dataplane_start(VirtIODevice *vdev) |
126 | { |
127 | int i; |
128 | int rc; |
129 | BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); |
130 | VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); |
131 | VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev); |
132 | VirtIOSCSI *s = VIRTIO_SCSI(vdev); |
133 | |
134 | if (s->dataplane_started || |
135 | s->dataplane_starting || |
136 | s->dataplane_fenced) { |
137 | return 0; |
138 | } |
139 | |
140 | s->dataplane_starting = true; |
141 | |
142 | /* Set up guest notifier (irq) */ |
143 | rc = k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, true); |
144 | if (rc != 0) { |
145 | error_report("virtio-scsi: Failed to set guest notifiers (%d), " |
146 | "ensure -accel kvm is set." , rc); |
147 | goto fail_guest_notifiers; |
148 | } |
149 | |
150 | aio_context_acquire(s->ctx); |
151 | rc = virtio_scsi_vring_init(s, vs->ctrl_vq, 0, |
152 | virtio_scsi_data_plane_handle_ctrl); |
153 | if (rc) { |
154 | goto fail_vrings; |
155 | } |
156 | rc = virtio_scsi_vring_init(s, vs->event_vq, 1, |
157 | virtio_scsi_data_plane_handle_event); |
158 | if (rc) { |
159 | goto fail_vrings; |
160 | } |
161 | for (i = 0; i < vs->conf.num_queues; i++) { |
162 | rc = virtio_scsi_vring_init(s, vs->cmd_vqs[i], i + 2, |
163 | virtio_scsi_data_plane_handle_cmd); |
164 | if (rc) { |
165 | goto fail_vrings; |
166 | } |
167 | } |
168 | |
169 | s->dataplane_starting = false; |
170 | s->dataplane_started = true; |
171 | aio_context_release(s->ctx); |
172 | return 0; |
173 | |
174 | fail_vrings: |
175 | aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s); |
176 | aio_context_release(s->ctx); |
177 | for (i = 0; i < vs->conf.num_queues + 2; i++) { |
178 | virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false); |
179 | virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i); |
180 | } |
181 | k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false); |
182 | fail_guest_notifiers: |
183 | s->dataplane_fenced = true; |
184 | s->dataplane_starting = false; |
185 | s->dataplane_started = true; |
186 | return -ENOSYS; |
187 | } |
188 | |
189 | /* Context: QEMU global mutex held */ |
190 | void virtio_scsi_dataplane_stop(VirtIODevice *vdev) |
191 | { |
192 | BusState *qbus = qdev_get_parent_bus(DEVICE(vdev)); |
193 | VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus); |
194 | VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(vdev); |
195 | VirtIOSCSI *s = VIRTIO_SCSI(vdev); |
196 | int i; |
197 | |
198 | if (!s->dataplane_started || s->dataplane_stopping) { |
199 | return; |
200 | } |
201 | |
202 | /* Better luck next time. */ |
203 | if (s->dataplane_fenced) { |
204 | s->dataplane_fenced = false; |
205 | s->dataplane_started = false; |
206 | return; |
207 | } |
208 | s->dataplane_stopping = true; |
209 | |
210 | aio_context_acquire(s->ctx); |
211 | aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s); |
212 | aio_context_release(s->ctx); |
213 | |
214 | blk_drain_all(); /* ensure there are no in-flight requests */ |
215 | |
216 | for (i = 0; i < vs->conf.num_queues + 2; i++) { |
217 | virtio_bus_set_host_notifier(VIRTIO_BUS(qbus), i, false); |
218 | virtio_bus_cleanup_host_notifier(VIRTIO_BUS(qbus), i); |
219 | } |
220 | |
221 | /* Clean up guest notifier (irq) */ |
222 | k->set_guest_notifiers(qbus->parent, vs->conf.num_queues + 2, false); |
223 | s->dataplane_stopping = false; |
224 | s->dataplane_started = false; |
225 | } |
226 | |