Skip to content

Commit da6eebb

Browse files
stefanhaRHkevmw
authored andcommitted
virtio-scsi: perform TMFs in appropriate AioContexts
With IOThread Virtqueue Mapping there will be multiple AioContexts processing SCSI requests. scsi_req_cancel() and other SCSI request operations must be performed from the AioContext where the request is running. Introduce a virtio_scsi_defer_tmf_to_aio_context() function and the necessary VirtIOSCSIReq->remaining refcount infrastructure to move the TMF code into the AioContext where the request is running. For the time being there is still just one AioContext: the main loop or the IOThread. When the iothread-vq-mapping parameter is added in a later patch this will be changed to per-virtqueue AioContexts. Signed-off-by: Stefan Hajnoczi <[email protected]> Reviewed-by: Kevin Wolf <[email protected]> Message-ID: <[email protected]> Tested-by: Peter Krempa <[email protected]> Signed-off-by: Kevin Wolf <[email protected]>
1 parent 7d8ab5b commit da6eebb

File tree

1 file changed

+206
-64
lines changed

1 file changed

+206
-64
lines changed

hw/scsi/virtio-scsi.c

Lines changed: 206 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ typedef struct VirtIOSCSIReq {
4747
/* Used for two-stage request submission and TMFs deferred to BH */
4848
QTAILQ_ENTRY(VirtIOSCSIReq) next;
4949

50-
/* Used for cancellation of request during TMFs */
50+
/* Used for cancellation of request during TMFs. Atomic. */
5151
int remaining;
5252

5353
SCSIRequest *sreq;
@@ -298,19 +298,23 @@ typedef struct {
298298
VirtIOSCSIReq *tmf_req;
299299
} VirtIOSCSICancelNotifier;
300300

301+
static void virtio_scsi_tmf_dec_remaining(VirtIOSCSIReq *tmf)
302+
{
303+
if (qatomic_fetch_dec(&tmf->remaining) == 1) {
304+
trace_virtio_scsi_tmf_resp(virtio_scsi_get_lun(tmf->req.tmf.lun),
305+
tmf->req.tmf.tag, tmf->resp.tmf.response);
306+
307+
virtio_scsi_complete_req(tmf, &tmf->dev->ctrl_lock);
308+
}
309+
}
310+
301311
static void virtio_scsi_cancel_notify(Notifier *notifier, void *data)
302312
{
303313
VirtIOSCSICancelNotifier *n = container_of(notifier,
304314
VirtIOSCSICancelNotifier,
305315
notifier);
306316

307-
if (--n->tmf_req->remaining == 0) {
308-
VirtIOSCSIReq *req = n->tmf_req;
309-
310-
trace_virtio_scsi_tmf_resp(virtio_scsi_get_lun(req->req.tmf.lun),
311-
req->req.tmf.tag, req->resp.tmf.response);
312-
virtio_scsi_complete_req(req, &req->dev->ctrl_lock);
313-
}
317+
virtio_scsi_tmf_dec_remaining(n->tmf_req);
314318
g_free(n);
315319
}
316320

@@ -416,7 +420,7 @@ static void virtio_scsi_reset_tmf_bh(VirtIOSCSI *s)
416420
}
417421
}
418422

419-
static void virtio_scsi_defer_tmf_to_bh(VirtIOSCSIReq *req)
423+
static void virtio_scsi_defer_tmf_to_main_loop(VirtIOSCSIReq *req)
420424
{
421425
VirtIOSCSI *s = req->dev;
422426

@@ -430,13 +434,145 @@ static void virtio_scsi_defer_tmf_to_bh(VirtIOSCSIReq *req)
430434
}
431435
}
432436

437+
static void virtio_scsi_tmf_cancel_req(VirtIOSCSIReq *tmf, SCSIRequest *r)
438+
{
439+
VirtIOSCSICancelNotifier *notifier;
440+
441+
assert(r->ctx == qemu_get_current_aio_context());
442+
443+
/* Decremented in virtio_scsi_cancel_notify() */
444+
qatomic_inc(&tmf->remaining);
445+
446+
notifier = g_new(VirtIOSCSICancelNotifier, 1);
447+
notifier->notifier.notify = virtio_scsi_cancel_notify;
448+
notifier->tmf_req = tmf;
449+
scsi_req_cancel_async(r, &notifier->notifier);
450+
}
451+
452+
/* Execute a TMF on the requests in the current AioContext */
453+
static void virtio_scsi_do_tmf_aio_context(void *opaque)
454+
{
455+
AioContext *ctx = qemu_get_current_aio_context();
456+
VirtIOSCSIReq *tmf = opaque;
457+
VirtIOSCSI *s = tmf->dev;
458+
SCSIDevice *d = virtio_scsi_device_get(s, tmf->req.tmf.lun);
459+
SCSIRequest *r;
460+
bool match_tag;
461+
462+
if (!d) {
463+
tmf->resp.tmf.response = VIRTIO_SCSI_S_BAD_TARGET;
464+
virtio_scsi_tmf_dec_remaining(tmf);
465+
return;
466+
}
467+
468+
/*
469+
* This function could handle other subtypes that need to be processed in
470+
* the request's AioContext in the future, but for now only request
471+
* cancelation subtypes are performed here.
472+
*/
473+
switch (tmf->req.tmf.subtype) {
474+
case VIRTIO_SCSI_T_TMF_ABORT_TASK:
475+
match_tag = true;
476+
break;
477+
case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET:
478+
case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET:
479+
match_tag = false;
480+
break;
481+
default:
482+
g_assert_not_reached();
483+
}
484+
485+
WITH_QEMU_LOCK_GUARD(&d->requests_lock) {
486+
QTAILQ_FOREACH(r, &d->requests, next) {
487+
VirtIOSCSIReq *cmd_req = r->hba_private;
488+
assert(cmd_req); /* request has hba_private while enqueued */
489+
490+
if (r->ctx != ctx) {
491+
continue;
492+
}
493+
if (match_tag && cmd_req->req.cmd.tag != tmf->req.tmf.tag) {
494+
continue;
495+
}
496+
virtio_scsi_tmf_cancel_req(tmf, r);
497+
}
498+
}
499+
500+
/* Incremented by virtio_scsi_do_tmf() */
501+
virtio_scsi_tmf_dec_remaining(tmf);
502+
503+
object_unref(d);
504+
}
505+
506+
static void dummy_bh(void *opaque)
507+
{
508+
/* Do nothing */
509+
}
510+
511+
/*
512+
* Wait for pending virtio_scsi_defer_tmf_to_aio_context() BHs.
513+
*/
514+
static void virtio_scsi_flush_defer_tmf_to_aio_context(VirtIOSCSI *s)
515+
{
516+
GLOBAL_STATE_CODE();
517+
518+
assert(!s->dataplane_started);
519+
520+
if (s->ctx) {
521+
/* Our BH only runs after previously scheduled BHs */
522+
aio_wait_bh_oneshot(s->ctx, dummy_bh, NULL);
523+
}
524+
}
525+
526+
/*
527+
* Run the TMF in a specific AioContext, handling only requests in that
528+
* AioContext. This is necessary because requests can run in different
529+
* AioContext and it is only possible to cancel them from the AioContext where
530+
* they are running.
531+
*/
532+
static void virtio_scsi_defer_tmf_to_aio_context(VirtIOSCSIReq *tmf,
533+
AioContext *ctx)
534+
{
535+
/* Decremented in virtio_scsi_do_tmf_aio_context() */
536+
qatomic_inc(&tmf->remaining);
537+
538+
/* See virtio_scsi_flush_defer_tmf_to_aio_context() cleanup during reset */
539+
aio_bh_schedule_oneshot(ctx, virtio_scsi_do_tmf_aio_context, tmf);
540+
}
541+
542+
/*
543+
* Returns the AioContext for a given TMF's tag field or NULL. Note that the
544+
* request identified by the tag may have completed by the time you can execute
545+
* a BH in the AioContext, so don't assume the request still exists in your BH.
546+
*/
547+
static AioContext *find_aio_context_for_tmf_tag(SCSIDevice *d,
548+
VirtIOSCSIReq *tmf)
549+
{
550+
WITH_QEMU_LOCK_GUARD(&d->requests_lock) {
551+
SCSIRequest *r;
552+
SCSIRequest *next;
553+
554+
QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) {
555+
VirtIOSCSIReq *cmd_req = r->hba_private;
556+
557+
/* hba_private is non-NULL while the request is enqueued */
558+
assert(cmd_req);
559+
560+
if (cmd_req->req.cmd.tag == tmf->req.tmf.tag) {
561+
return r->ctx;
562+
}
563+
}
564+
}
565+
return NULL;
566+
}
567+
433568
/* Return 0 if the request is ready to be completed and return to guest;
434569
* -EINPROGRESS if the request is submitted and will be completed later, in the
435570
* case of async cancellation. */
436571
static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req)
437572
{
438573
SCSIDevice *d = virtio_scsi_device_get(s, req->req.tmf.lun);
439574
SCSIRequest *r, *next;
575+
AioContext *ctx;
440576
int ret = 0;
441577

442578
virtio_scsi_ctx_check(s, d);
@@ -454,52 +590,72 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req)
454590
req->req.tmf.tag, req->req.tmf.subtype);
455591

456592
switch (req->req.tmf.subtype) {
457-
case VIRTIO_SCSI_T_TMF_ABORT_TASK:
458-
case VIRTIO_SCSI_T_TMF_QUERY_TASK:
593+
case VIRTIO_SCSI_T_TMF_ABORT_TASK: {
459594
if (!d) {
460595
goto fail;
461596
}
462597
if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
463598
goto incorrect_lun;
464599
}
465-
QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) {
466-
VirtIOSCSIReq *cmd_req = r->hba_private;
467-
if (cmd_req && cmd_req->req.cmd.tag == req->req.tmf.tag) {
468-
break;
469-
}
600+
601+
ctx = find_aio_context_for_tmf_tag(d, req);
602+
if (ctx) {
603+
virtio_scsi_defer_tmf_to_aio_context(req, ctx);
604+
ret = -EINPROGRESS;
470605
}
471-
if (r) {
472-
/*
473-
* Assert that the request has not been completed yet, we
474-
* check for it in the loop above.
475-
*/
476-
assert(r->hba_private);
477-
if (req->req.tmf.subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK) {
478-
/* "If the specified command is present in the task set, then
479-
* return a service response set to FUNCTION SUCCEEDED".
480-
*/
481-
req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
482-
} else {
483-
VirtIOSCSICancelNotifier *notifier;
484-
485-
req->remaining = 1;
486-
notifier = g_new(VirtIOSCSICancelNotifier, 1);
487-
notifier->tmf_req = req;
488-
notifier->notifier.notify = virtio_scsi_cancel_notify;
489-
scsi_req_cancel_async(r, &notifier->notifier);
490-
ret = -EINPROGRESS;
606+
break;
607+
}
608+
609+
case VIRTIO_SCSI_T_TMF_QUERY_TASK:
610+
if (!d) {
611+
goto fail;
612+
}
613+
if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
614+
goto incorrect_lun;
615+
}
616+
617+
WITH_QEMU_LOCK_GUARD(&d->requests_lock) {
618+
QTAILQ_FOREACH(r, &d->requests, next) {
619+
VirtIOSCSIReq *cmd_req = r->hba_private;
620+
assert(cmd_req); /* request has hba_private while enqueued */
621+
622+
if (cmd_req->req.cmd.tag == req->req.tmf.tag) {
623+
/*
624+
* "If the specified command is present in the task set,
625+
* then return a service response set to FUNCTION
626+
* SUCCEEDED".
627+
*/
628+
req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
629+
}
491630
}
492631
}
493632
break;
494633

495634
case VIRTIO_SCSI_T_TMF_LOGICAL_UNIT_RESET:
496635
case VIRTIO_SCSI_T_TMF_I_T_NEXUS_RESET:
497-
virtio_scsi_defer_tmf_to_bh(req);
636+
virtio_scsi_defer_tmf_to_main_loop(req);
498637
ret = -EINPROGRESS;
499638
break;
500639

501640
case VIRTIO_SCSI_T_TMF_ABORT_TASK_SET:
502-
case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET:
641+
case VIRTIO_SCSI_T_TMF_CLEAR_TASK_SET: {
642+
if (!d) {
643+
goto fail;
644+
}
645+
if (d->lun != virtio_scsi_get_lun(req->req.tmf.lun)) {
646+
goto incorrect_lun;
647+
}
648+
649+
qatomic_inc(&req->remaining);
650+
651+
ctx = s->ctx ?: qemu_get_aio_context();
652+
virtio_scsi_defer_tmf_to_aio_context(req, ctx);
653+
654+
virtio_scsi_tmf_dec_remaining(req);
655+
ret = -EINPROGRESS;
656+
break;
657+
}
658+
503659
case VIRTIO_SCSI_T_TMF_QUERY_TASK_SET:
504660
if (!d) {
505661
goto fail;
@@ -508,34 +664,19 @@ static int virtio_scsi_do_tmf(VirtIOSCSI *s, VirtIOSCSIReq *req)
508664
goto incorrect_lun;
509665
}
510666

511-
/* Add 1 to "remaining" until virtio_scsi_do_tmf returns.
512-
* This way, if the bus starts calling back to the notifiers
513-
* even before we finish the loop, virtio_scsi_cancel_notify
514-
* will not complete the TMF too early.
515-
*/
516-
req->remaining = 1;
517-
QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) {
518-
if (r->hba_private) {
519-
if (req->req.tmf.subtype == VIRTIO_SCSI_T_TMF_QUERY_TASK_SET) {
520-
/* "If there is any command present in the task set, then
521-
* return a service response set to FUNCTION SUCCEEDED".
522-
*/
523-
req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
524-
break;
525-
} else {
526-
VirtIOSCSICancelNotifier *notifier;
527-
528-
req->remaining++;
529-
notifier = g_new(VirtIOSCSICancelNotifier, 1);
530-
notifier->notifier.notify = virtio_scsi_cancel_notify;
531-
notifier->tmf_req = req;
532-
scsi_req_cancel_async(r, &notifier->notifier);
533-
}
667+
WITH_QEMU_LOCK_GUARD(&d->requests_lock) {
668+
QTAILQ_FOREACH_SAFE(r, &d->requests, next, next) {
669+
/* Request has hba_private while enqueued */
670+
assert(r->hba_private);
671+
672+
/*
673+
* "If there is any command present in the task set, then
674+
* return a service response set to FUNCTION SUCCEEDED".
675+
*/
676+
req->resp.tmf.response = VIRTIO_SCSI_S_FUNCTION_SUCCEEDED;
677+
break;
534678
}
535679
}
536-
if (--req->remaining > 0) {
537-
ret = -EINPROGRESS;
538-
}
539680
break;
540681

541682
case VIRTIO_SCSI_T_TMF_CLEAR_ACA:
@@ -941,6 +1082,7 @@ static void virtio_scsi_reset(VirtIODevice *vdev)
9411082
assert(!s->dataplane_started);
9421083

9431084
virtio_scsi_reset_tmf_bh(s);
1085+
virtio_scsi_flush_defer_tmf_to_aio_context(s);
9441086

9451087
qatomic_inc(&s->resetting);
9461088
bus_cold_reset(BUS(&s->bus));

0 commit comments

Comments
 (0)