virtio-scsi: implement BlockDevOps->drained_begin()

The virtio-scsi Host Bus Adapter provides access to devices on a SCSI
bus. Those SCSI devices typically have a BlockBackend. When the
BlockBackend enters a drained section, the SCSI device must temporarily
stop submitting new I/O requests.

Implement this behavior by temporarily stopping virtio-scsi virtqueue
processing when one of the SCSI devices enters a drained section. The
new scsi_device_drained_begin() API allows scsi-disk to message the
virtio-scsi HBA.

scsi_device_drained_begin() uses a drain counter so that multiple SCSI
devices can have overlapping drained sections. The HBA only sees one
pair of .drained_begin/end() calls.

After this commit, virtio-scsi no longer depends on hw/virtio's
ioeventfd aio_set_event_notifier(is_external=true). This commit is a
step towards removing the aio_disable_external() API.

Signed-off-by: Stefan Hajnoczi <stefanha@redhat.com>
Message-Id: <20230516190238.8401-19-stefanha@redhat.com>
Signed-off-by: Kevin Wolf <kwolf@redhat.com>
This commit is contained in:
Stefan Hajnoczi 2023-05-16 15:02:36 -04:00 committed by Kevin Wolf
parent 1665d9326f
commit 766aa2de0f
6 changed files with 127 additions and 12 deletions

View file

@ -1669,6 +1669,46 @@ void scsi_device_purge_requests(SCSIDevice *sdev, SCSISense sense)
scsi_device_set_ua(sdev, sense);
}
void scsi_device_drained_begin(SCSIDevice *sdev)
{
SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, sdev->qdev.parent_bus);
if (!bus) {
return;
}
assert(qemu_get_current_aio_context() == qemu_get_aio_context());
assert(bus->drain_count < INT_MAX);
/*
* Multiple BlockBackends can be on a SCSIBus and each may begin/end
* draining at any time. Keep a counter so HBAs only see begin/end once.
*/
if (bus->drain_count++ == 0) {
trace_scsi_bus_drained_begin(bus, sdev);
if (bus->info->drained_begin) {
bus->info->drained_begin(bus);
}
}
}
void scsi_device_drained_end(SCSIDevice *sdev)
{
SCSIBus *bus = DO_UPCAST(SCSIBus, qbus, sdev->qdev.parent_bus);
if (!bus) {
return;
}
assert(qemu_get_current_aio_context() == qemu_get_aio_context());
assert(bus->drain_count > 0);
if (bus->drain_count-- == 1) {
trace_scsi_bus_drained_end(bus, sdev);
if (bus->info->drained_end) {
bus->info->drained_end(bus);
}
}
}
static char *scsibus_get_dev_path(DeviceState *dev)
{
SCSIDevice *d = SCSI_DEVICE(dev);