Skip to content

Commit 2e8e18c

Browse files
stefanhaRHkevmw
authored andcommitted
virtio-scsi: add iothread-vq-mapping parameter
Allow virtio-scsi virtqueues to be assigned to different IOThreads. This makes it possible to take advantage of host multi-queue block layer scalability by assigning virtqueues that have affinity with vCPUs to different IOThreads that have affinity with host CPUs. The same feature was introduced for virtio-blk in the past: https://developers.redhat.com/articles/2024/09/05/scaling-virtio-blk-disk-io-iothread-virtqueue-mapping Here are fio randread 4k iodepth=64 results from a 4 vCPU guest with an Intel P4800X SSD: iothreads IOPS ------------------------------ 1 189576 2 312698 4 346744 Signed-off-by: Stefan Hajnoczi <[email protected]> Message-ID: <[email protected]> Tested-by: Peter Krempa <[email protected]> [kwolf: Updated 051 output, virtio-scsi can now use any iothread] Reviewed-by: Kevin Wolf <[email protected]> Signed-off-by: Kevin Wolf <[email protected]>
1 parent b50629c commit 2e8e18c

File tree

4 files changed

+108
-52
lines changed

4 files changed

+108
-52
lines changed

hw/scsi/virtio-scsi-dataplane.c

Lines changed: 65 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "system/block-backend.h"
1919
#include "hw/scsi/scsi.h"
2020
#include "scsi/constants.h"
21+
#include "hw/virtio/iothread-vq-mapping.h"
2122
#include "hw/virtio/virtio-bus.h"
2223

2324
/* Context: BQL held */
@@ -27,8 +28,16 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
2728
VirtIODevice *vdev = VIRTIO_DEVICE(s);
2829
BusState *qbus = qdev_get_parent_bus(DEVICE(vdev));
2930
VirtioBusClass *k = VIRTIO_BUS_GET_CLASS(qbus);
31+
uint16_t num_vqs = vs->conf.num_queues + VIRTIO_SCSI_VQ_NUM_FIXED;
3032

31-
if (vs->conf.iothread) {
33+
if (vs->conf.iothread && vs->conf.iothread_vq_mapping_list) {
34+
error_setg(errp,
35+
"iothread and iothread-vq-mapping properties cannot be set "
36+
"at the same time");
37+
return;
38+
}
39+
40+
if (vs->conf.iothread || vs->conf.iothread_vq_mapping_list) {
3241
if (!k->set_guest_notifiers || !k->ioeventfd_assign) {
3342
error_setg(errp,
3443
"device is incompatible with iothread "
@@ -39,13 +48,48 @@ void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp)
3948
error_setg(errp, "ioeventfd is required for iothread");
4049
return;
4150
}
42-
s->ctx = iothread_get_aio_context(vs->conf.iothread);
43-
} else {
44-
if (!virtio_device_ioeventfd_enabled(vdev)) {
51+
}
52+
53+
s->vq_aio_context = g_new(AioContext *, num_vqs);
54+
55+
if (vs->conf.iothread_vq_mapping_list) {
56+
if (!iothread_vq_mapping_apply(vs->conf.iothread_vq_mapping_list,
57+
s->vq_aio_context, num_vqs, errp)) {
58+
g_free(s->vq_aio_context);
59+
s->vq_aio_context = NULL;
4560
return;
4661
}
47-
s->ctx = qemu_get_aio_context();
62+
} else if (vs->conf.iothread) {
63+
AioContext *ctx = iothread_get_aio_context(vs->conf.iothread);
64+
for (uint16_t i = 0; i < num_vqs; i++) {
65+
s->vq_aio_context[i] = ctx;
66+
}
67+
68+
/* Released in virtio_scsi_dataplane_cleanup() */
69+
object_ref(OBJECT(vs->conf.iothread));
70+
} else {
71+
AioContext *ctx = qemu_get_aio_context();
72+
for (unsigned i = 0; i < num_vqs; i++) {
73+
s->vq_aio_context[i] = ctx;
74+
}
75+
}
76+
}
77+
78+
/* Context: BQL held */
79+
void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s)
80+
{
81+
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
82+
83+
if (vs->conf.iothread_vq_mapping_list) {
84+
iothread_vq_mapping_cleanup(vs->conf.iothread_vq_mapping_list);
4885
}
86+
87+
if (vs->conf.iothread) {
88+
object_unref(OBJECT(vs->conf.iothread));
89+
}
90+
91+
g_free(s->vq_aio_context);
92+
s->vq_aio_context = NULL;
4993
}
5094

5195
static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n)
@@ -66,31 +110,20 @@ static int virtio_scsi_set_host_notifier(VirtIOSCSI *s, VirtQueue *vq, int n)
66110
}
67111

68112
/* Context: BH in IOThread */
69-
static void virtio_scsi_dataplane_stop_bh(void *opaque)
113+
static void virtio_scsi_dataplane_stop_vq_bh(void *opaque)
70114
{
71-
VirtIOSCSI *s = opaque;
72-
VirtIOSCSICommon *vs = VIRTIO_SCSI_COMMON(s);
115+
AioContext *ctx = qemu_get_current_aio_context();
116+
VirtQueue *vq = opaque;
73117
EventNotifier *host_notifier;
74-
int i;
75118

76-
virtio_queue_aio_detach_host_notifier(vs->ctrl_vq, s->ctx);
77-
host_notifier = virtio_queue_get_host_notifier(vs->ctrl_vq);
119+
virtio_queue_aio_detach_host_notifier(vq, ctx);
120+
host_notifier = virtio_queue_get_host_notifier(vq);
78121

79122
/*
80123
* Test and clear notifier after disabling event, in case poll callback
81124
* didn't have time to run.
82125
*/
83126
virtio_queue_host_notifier_read(host_notifier);
84-
85-
virtio_queue_aio_detach_host_notifier(vs->event_vq, s->ctx);
86-
host_notifier = virtio_queue_get_host_notifier(vs->event_vq);
87-
virtio_queue_host_notifier_read(host_notifier);
88-
89-
for (i = 0; i < vs->conf.num_queues; i++) {
90-
virtio_queue_aio_detach_host_notifier(vs->cmd_vqs[i], s->ctx);
91-
host_notifier = virtio_queue_get_host_notifier(vs->cmd_vqs[i]);
92-
virtio_queue_host_notifier_read(host_notifier);
93-
}
94127
}
95128

96129
/* Context: BQL held */
@@ -154,11 +187,14 @@ int virtio_scsi_dataplane_start(VirtIODevice *vdev)
154187
smp_wmb(); /* paired with aio_notify_accept() */
155188

156189
if (s->bus.drain_count == 0) {
157-
virtio_queue_aio_attach_host_notifier(vs->ctrl_vq, s->ctx);
158-
virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq, s->ctx);
190+
virtio_queue_aio_attach_host_notifier(vs->ctrl_vq,
191+
s->vq_aio_context[0]);
192+
virtio_queue_aio_attach_host_notifier_no_poll(vs->event_vq,
193+
s->vq_aio_context[1]);
159194

160195
for (i = 0; i < vs->conf.num_queues; i++) {
161-
virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], s->ctx);
196+
AioContext *ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i];
197+
virtio_queue_aio_attach_host_notifier(vs->cmd_vqs[i], ctx);
162198
}
163199
}
164200
return 0;
@@ -207,7 +243,11 @@ void virtio_scsi_dataplane_stop(VirtIODevice *vdev)
207243
s->dataplane_stopping = true;
208244

209245
if (s->bus.drain_count == 0) {
210-
aio_wait_bh_oneshot(s->ctx, virtio_scsi_dataplane_stop_bh, s);
246+
for (i = 0; i < vs->conf.num_queues + VIRTIO_SCSI_VQ_NUM_FIXED; i++) {
247+
VirtQueue *vq = virtio_get_queue(&vs->parent_obj, i);
248+
AioContext *ctx = s->vq_aio_context[i];
249+
aio_wait_bh_oneshot(ctx, virtio_scsi_dataplane_stop_vq_bh, vq);
250+
}
211251
}
212252

213253
blk_drain_all(); /* ensure there are no in-flight requests */

hw/scsi/virtio-scsi.c

Lines changed: 38 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "hw/qdev-properties.h"
2828
#include "hw/scsi/scsi.h"
2929
#include "scsi/constants.h"
30+
#include "hw/virtio/iothread-vq-mapping.h"
3031
#include "hw/virtio/virtio-bus.h"
3132
#include "hw/virtio/virtio-access.h"
3233
#include "trace.h"
@@ -318,13 +319,6 @@ static void virtio_scsi_cancel_notify(Notifier *notifier, void *data)
318319
g_free(n);
319320
}
320321

321-
static inline void virtio_scsi_ctx_check(VirtIOSCSI *s, SCSIDevice *d)
322-
{
323-
if (s->dataplane_started && d && blk_is_available(d->conf.blk)) {
324-
assert(blk_get_aio_context(d->conf.blk) == s->ctx);
325-
}
326-
}
327-
328322
static void virtio_scsi_do_one_tmf_bh(VirtIOSCSIReq *req)
329323
{
330324
VirtIOSCSI *s = req->dev;
@@ -517,9 +511,11 @@ static void virtio_scsi_flush_defer_tmf_to_aio_context(VirtIOSCSI *s)
517511

518512
assert(!s->dataplane_started);
519513

520-
if (s->ctx) {
514+
for (uint32_t i = 0; i < s->parent_obj.conf.num_queues; i++) {
515+
AioContext *ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i];
516+
521517
/* Our BH only runs after previously scheduled BHs */
522-
aio_wait_bh_oneshot(s->ctx, dummy_bh, NULL);
518+
aio_wait_bh_oneshot(ctx, dummy_bh, NULL);
523519
}
524520
}
525521

@@ -575,7 +571,6 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req)
575571
AioContext *ctx;
576572
int ret = 0;
577573

578-
virtio_scsi_ctx_check(s, d);
579574
/* Here VIRTIO_SCSI_S_OK means "FUNCTION COMPLETE". */
580575
req->resp.tmf.response = VIRTIO_SCSI_S_OK;
581576

@@ -639,6 +634,8 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req)
639634

640635
case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET:
641636
case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET: {
637+
g_autoptr(GHashTable) aio_contexts = g_hash_table_new(NULL, NULL);
638+
642639
if (!d) {
643640
goto fail;
644641
}
@@ -648,8 +645,15 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req)
648645

649646
qatomic_inc(&req->remaining);
650647

651-
ctx = s->ctx ?: qemu_get_aio_context();
652-
virtio_scsi_defer_tmf_to_aio_context(req, ctx);
648+
for (uint32_t i = 0; i < s->parent_obj.conf.num_queues; i++) {
649+
ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED + i];
650+
651+
if (!g_hash_table_add(aio_contexts, ctx)) {
652+
continue; /* skip previously added AioContext */
653+
}
654+
655+
virtio_scsi_defer_tmf_to_aio_context(req, ctx);
656+
}
653657

654658
virtio_scsi_tmf_dec_remaining(req);
655659
ret = -EINPROGRESS;
@@ -770,9 +774,12 @@ static void virtio_scsi_handle_ctrl_vq(VirtIOSCSI *s, VirtQueue *vq)
770774
*/
771775
static bool virtio_scsi_defer_to_dataplane(VirtIOSCSI *s)
772776
{
773-
if (!s->ctx || s->dataplane_started) {
777+
if (s->dataplane_started) {
774778
return false;
775779
}
780+
if (s->vq_aio_context[0] == qemu_get_aio_context()) {
781+
return false; /* not using IOThreads */
782+
}
776783

777784
virtio_device_start_ioeventfd(&s->parent_obj.parent_obj);
778785
return !s->dataplane_fenced;
@@ -946,7 +953,6 @@ static int virtio_scsi_handle_cmd_req_prepare(VirtIOSCSI *s, VirtIOSCSIReq *req)
946953
virtio_scsi_complete_cmd_req(req);
947954
return -ENOENT;
948955
}
949-
virtio_scsi_ctx_check(s, d);
950956
req->sreq = scsi_req_new(d, req->req.cmd.tag,
951957
virtio_scsi_get_lun(req->req.cmd.lun),
952958
req->req.cmd.cdb, vs->cdb_size, req);
@@ -1218,14 +1224,16 @@ static void virtio_scsi_hotplug(HotplugHandler *hotplug_dev, DeviceState *dev,
12181224
{
12191225
VirtIODevice *vdev = VIRTIO_DEVICE(hotplug_dev);
12201226
VirtIOSCSI *s = VIRTIO_SCSI(vdev);
1227+
AioContext *ctx = s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED];
12211228
SCSIDevice *sd = SCSI_DEVICE(dev);
1222-
int ret;
12231229

1224-
if (s->ctx && !s->dataplane_fenced) {
1225-
ret = blk_set_aio_context(sd->conf.blk, s->ctx, errp);
1226-
if (ret < 0) {
1227-
return;
1228-
}
1230+
if (ctx != qemu_get_aio_context() && !s->dataplane_fenced) {
1231+
/*
1232+
* Try to make the BlockBackend's AioContext match ours. Ignore failure
1233+
* because I/O will still work although block jobs and other users
1234+
* might be slower when multiple AioContexts use a BlockBackend.
1235+
*/
1236+
blk_set_aio_context(sd->conf.blk, ctx, NULL);
12291237
}
12301238

12311239
if (virtio_vdev_has_feature(vdev, VIRTIO_SCSI_F_HOTPLUG)) {
@@ -1260,7 +1268,7 @@ static void virtio_scsi_hotunplug(HotplugHandler *hotplug_dev, DeviceState *dev,
12601268

12611269
qdev_simple_device_unplug_cb(hotplug_dev, dev, errp);
12621270

1263-
if (s->ctx) {
1271+
if (s->vq_aio_context[VIRTIO_SCSI_VQ_NUM_FIXED] != qemu_get_aio_context()) {
12641272
/* If other users keep the BlockBackend in the iothread, that's ok */
12651273
blk_set_aio_context(sd->conf.blk, qemu_get_aio_context(), NULL);
12661274
}
@@ -1294,7 +1302,7 @@ static void virtio_scsi_drained_begin(SCSIBus *bus)
12941302

12951303
for (uint32_t i = 0; i < total_queues; i++) {
12961304
VirtQueue *vq = virtio_get_queue(vdev, i);
1297-
virtio_queue_aio_detach_host_notifier(vq, s->ctx);
1305+
virtio_queue_aio_detach_host_notifier(vq, s->vq_aio_context[i]);
12981306
}
12991307
}
13001308

@@ -1320,10 +1328,12 @@ static void virtio_scsi_drained_end(SCSIBus *bus)
13201328

13211329
for (uint32_t i = 0; i < total_queues; i++) {
13221330
VirtQueue *vq = virtio_get_queue(vdev, i);
1331+
AioContext *ctx = s->vq_aio_context[i];
1332+
13231333
if (vq == vs->event_vq) {
1324-
virtio_queue_aio_attach_host_notifier_no_poll(vq, s->ctx);
1334+
virtio_queue_aio_attach_host_notifier_no_poll(vq, ctx);
13251335
} else {
1326-
virtio_queue_aio_attach_host_notifier(vq, s->ctx);
1336+
virtio_queue_aio_attach_host_notifier(vq, ctx);
13271337
}
13281338
}
13291339
}
@@ -1430,12 +1440,13 @@ void virtio_scsi_common_unrealize(DeviceState *dev)
14301440
virtio_cleanup(vdev);
14311441
}
14321442

1443+
/* main loop */
14331444
static void virtio_scsi_device_unrealize(DeviceState *dev)
14341445
{
14351446
VirtIOSCSI *s = VIRTIO_SCSI(dev);
14361447

14371448
virtio_scsi_reset_tmf_bh(s);
1438-
1449+
virtio_scsi_dataplane_cleanup(s);
14391450
qbus_set_hotplug_handler(BUS(&s->bus), NULL);
14401451
virtio_scsi_common_unrealize(dev);
14411452
qemu_mutex_destroy(&s->tmf_bh_lock);
@@ -1460,6 +1471,8 @@ static const Property virtio_scsi_properties[] = {
14601471
VIRTIO_SCSI_F_CHANGE, true),
14611472
DEFINE_PROP_LINK("iothread", VirtIOSCSI, parent_obj.conf.iothread,
14621473
TYPE_IOTHREAD, IOThread *),
1474+
DEFINE_PROP_IOTHREAD_VQ_MAPPING_LIST("iothread-vq-mapping", VirtIOSCSI,
1475+
parent_obj.conf.iothread_vq_mapping_list),
14631476
};
14641477

14651478
static const VMStateDescription vmstate_virtio_scsi = {

include/hw/virtio/virtio-scsi.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include "hw/virtio/virtio.h"
2323
#include "hw/scsi/scsi.h"
2424
#include "chardev/char-fe.h"
25+
#include "qapi/qapi-types-virtio.h"
2526
#include "system/iothread.h"
2627

2728
#define TYPE_VIRTIO_SCSI_COMMON "virtio-scsi-common"
@@ -60,6 +61,7 @@ struct VirtIOSCSIConf {
6061
CharBackend chardev;
6162
uint32_t boot_tpgt;
6263
IOThread *iothread;
64+
IOThreadVirtQueueMappingList *iothread_vq_mapping_list;
6365
};
6466

6567
struct VirtIOSCSI;
@@ -97,7 +99,7 @@ struct VirtIOSCSI {
9799
QTAILQ_HEAD(, VirtIOSCSIReq) tmf_bh_list;
98100

99101
/* Fields for dataplane below */
100-
AioContext *ctx; /* one iothread per virtio-scsi-pci for now */
102+
AioContext **vq_aio_context; /* per-virtqueue AioContext pointer */
101103

102104
bool dataplane_started;
103105
bool dataplane_starting;
@@ -115,6 +117,7 @@ void virtio_scsi_common_realize(DeviceState *dev,
115117
void virtio_scsi_common_unrealize(DeviceState *dev);
116118

117119
void virtio_scsi_dataplane_setup(VirtIOSCSI *s, Error **errp);
120+
void virtio_scsi_dataplane_cleanup(VirtIOSCSI *s);
118121
int virtio_scsi_dataplane_start(VirtIODevice *s);
119122
void virtio_scsi_dataplane_stop(VirtIODevice *s);
120123

tests/qemu-iotests/051.pc.out

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ QEMU X.Y.Z monitor - type 'help' for more information
181181

182182
Testing: -drive file=TEST_DIR/t.qcow2,if=none,node-name=disk -object iothread,id=thread0 -device virtio-scsi,iothread=thread0,id=virtio-scsi0 -device scsi-hd,bus=virtio-scsi0.0,drive=disk,share-rw=on -device virtio-scsi,id=virtio-scsi1 -device scsi-hd,bus=virtio-scsi1.0,drive=disk,share-rw=on
183183
QEMU X.Y.Z monitor - type 'help' for more information
184-
(qemu) QEMU_PROG: -device scsi-hd,bus=virtio-scsi1.0,drive=disk,share-rw=on: Cannot change iothread of active block backend
184+
(qemu) quit
185185

186186
Testing: -drive file=TEST_DIR/t.qcow2,if=none,node-name=disk -object iothread,id=thread0 -device virtio-scsi,iothread=thread0,id=virtio-scsi0 -device scsi-hd,bus=virtio-scsi0.0,drive=disk,share-rw=on -device virtio-blk-pci,drive=disk,iothread=thread0,share-rw=on
187187
QEMU X.Y.Z monitor - type 'help' for more information

0 commit comments

Comments
 (0)