[RFC PATCH 0/4] virtio: SQ/CQ doorbell polling for vhost-scsi

rom.wang posted 4 patches 4 days, 11 hours ago
There is a newer version of this series
[RFC PATCH 0/4] virtio: SQ/CQ doorbell polling for vhost-scsi
Posted by rom.wang 4 days, 11 hours ago
From: Yufeng Wang <wangyufeng@kylinos.cn>

This RFC proposes a new virtio transport feature, VIRTIO_F_SQCQ_POLL,
that eliminates VM exits on both submission and completion paths for
vhost-scsi by using shared-memory doorbells and kernel polling
threads, following the io_uring SQPOLL model.

This is an early RFC to gather design feedback. The implementation is
functional and has been tested on arm64 and x86_64. We are not
requesting merge at this time.


Problem
-------

vhost-scsi uses MMIO writes (Guest -> Host) and MSI-X interrupts
(Host -> Guest) for notification. Each notification involves a VM exit,
which becomes a bottleneck at high IOPS:

  - 4K random read, QD32, 8 jobs: 341K IOPS baseline
  - With ~340K VM exits/second, the exit overhead dominates

Existing mitigations (vhost-net's tx polling, blk-mq iopoll) only
address one direction or require the submitting task to poll. Neither
eliminates VM exits on both paths simultaneously.


Solution
--------

Introduce two cache-line-aligned doorbell structures, SQ (Submission
Queue) and CQ (Completion Queue), placed alongside the standard split
virtqueue:

  - Guest writes sq->idx instead of MMIO kick; Host poll thread
    detects the change and processes submissions.
  - Host writes cq->idx instead of MSI-X interrupt; Guest poll
    thread detects the change and invokes completion callbacks.

A NEED_WAKEUP protocol (mirroring io_uring's SQ_NEED_WAKEUP) allows
either side to sleep when idle, with the other side responsible for
waking it via eventfd.

Feature negotiation via VIRTIO_F_SQCQ_POLL (bit 42) ensures zero
overhead when not negotiated — the driver falls back to traditional
MMIO kick + MSI-X interrupt.


Performance
-----------

Benchmark: fio, 4K random I/O

Test configuration:

  arm64:
    CPU:   Kunpeng 920 (2.6GHz), 8 vCPUs
    Disk:  NVMe INTEL SSDPED1K375GA (375GB)

  x86_64:
    CPU:   Intel Xeon E5-2680 v4 @ 2.40GHz, 8 vCPUs
    Disk:  NVMe SAMSUNG MZ1LB960HAJQ-000MV (960GB)

  Backend: vhost-scsi with TCM loopback to NVMe device
  QEMU:    vhost-scsi-pci with VIRTIO_F_SQCQ_POLL negotiated

arm64 results:

  Test              Baseline     SQ/CQ Poll   Change
  -----------       ----------   ----------   -------
  randread QD1      22,427       28,289       +26%
  randread QD32 NJ1 89,910       75,665       -16%
  randread QD32 NJ4 186,763      379,549      +103%
  randread QD32 NJ8 199,967      550,633      +175%
  randwrite QD1     21,912       27,261       +24%
  randwrite QD32 NJ1 85,349      81,389       -5%
  randwrite QD32 NJ4 190,443     355,811      +87%
  randwrite QD32 NJ8 196,552     566,640      +188%

x86_64 results:

  Test              Baseline     SQ/CQ Poll   Change
  -----------       ----------   ----------   -------
  randread QD1      8,263        9,552        +16%
  randread QD32 NJ1 127,412      162,805      +28%
  randread QD32 NJ4 303,208      375,056      +24%
  randread QD32 NJ8 341,625      371,193      +9%
  randwrite QD1     20,773       30,332       +46%
  randwrite QD32 NJ1 133,316      159,207      +19%
  randwrite QD32 NJ4 233,373      229,224      -2%
  randwrite QD32 NJ8 231,442      231,676      +0%

Multi-queue workloads (NJ4/NJ8) see significant improvement on arm64
(87-188%) and moderate improvement on x86_64 (9-24%). Single-VQ
high-queue-depth workloads show a minor regression on arm64 due to
polling overhead vs. VM-exit savings trade-off, while x86_64 shows
improvement across most configurations (16-46% for QD1, 19-28%
for QD32-NJ1) due to lower per-VM-exit cost on x86.


Why Not vDPA?
-------------

vhost-vDPA already provides doorbell mmap and polling. A reasonable
reviewer would ask: why not extend vhost-vDPA instead?

Three reasons:

1. No vdpa-scsi device exists. The vDPA framework
   (drivers/vdpa/) currently has hardware devices for net (mlx5,
   ifcvf, etc.) and software devices for net and blk (vdpa_sim).
   There is no virtio-scsi vDPA device, hardware or software.
   Building one means re-implementing vhost-scsi's TCM integration
   (SCSI CDB processing, ALUA, persistent reservations) under the
   vDPA device abstraction — 3-5x the work of extending vhost-scsi.

2. vhost-scsi is a deployed interface. libvirt, QEMU, and
   OpenStack have vhost-scsi configuration APIs and operational
   tooling. Switching to vhost-vdpa requires a new backend, user
   migration, and toolchain updates. SQ/CQ poll as a vhost-scsi
   feature is fully backward-compatible — no existing deployments
   break.

3. The protocol is transport-agnostic. The SQ/CQ doorbell design
   (struct vring_sq, struct vring_cq, NEED_WAKEUP handshake) is
   orthogonal to vhost vs. vDPA. The same UAPI can be consumed by
   vhost-scsi today and a future vdpa-scsi device. Implementing in
   vhost-scsi first does not block future vDPA integration.

We acknowledge that vDPA is the long-term direction for virtio
backends. If this SQ/CQ poll protocol is accepted, it can be ported
to the vDPA framework; a vdpa-scsi device is independent work.


Patch Structure
---------------

  Patch 1: UAPI definitions (virtio_config.h, virtio_ring.h,
           virtio_pci.h) — shared interface for all components
  Patch 2: vhost kernel support (vhost.c, vhost.h, scsi.c,
           vhost.h UAPI, vhost_types.h UAPI) — Host poll thread
  Patch 3: virtio guest driver (virtio_ring.c, virtio_sqcq_poll.c,
           virtio_pci_modern.c, virtio.c, virtio_scsi.c) — Guest
           poll thread and submission path
  Patch 4: QEMU support (virtio-pci.c, vhost.c) — PCI config
           forwarding and vhost ioctl bridge

Patches 1-3 apply to the Linux kernel tree. Patch 4 applies to
the QEMU tree separately.


Spec Status
-----------

A virtio-spec format document has been prepared and will be submitted
to the OASIS virtio TC as a proposal. This RFC stage seeks design
feedback before initiating the formal spec process.


Known Limitations (Future Work)
-------------------------------

  - CPU hotplug: no notifier registered; poll thread may be
    migrated when its CPU goes offline. Planned: kthread_park +
    dynamic rebind.
  - Live migration: no explicit stop/flush coordination during
    migration. Planned: VHOST_BACKEND_F_SUSPEND/RESUME integration.
  - SMAP overhead: Host uses get_user/put_user for doorbell access.
    Future optimization: GUP + kmap to map pages into kernel space.
  - Backpressure: no "slow down" signal from device to driver.
    Future: CQ throttle flag or avail_event reuse.
  - Packed ring: not supported; explicitly rejected at feature
    negotiation. Future: add packed ring doorbell support.


RFC Goals
---------

  1. Validate the overall design direction (doorbell + polling
     model, NEED_WAKEUP protocol)
  2. Get feedback on the feature bit allocation (42) and UAPI
     structure design
  3. Understand whether vDPA concern is a blocker or can be
     addressed with the transport-agnostic argument
  4. Collect guidance on prioritizing future work items
     (hotplug, migration, spec process)

We welcome all feedback, especially on:
  - Whether the NEED_WAKEUP protocol design is sound
  - Whether the per-device Guest poll thread model (vs per-VQ)
    is acceptable
  - Whether feature bit 42 is appropriate or if a different
    allocation is needed
  - Any concerns about the SMAP overhead in the Host poll path


How to Test
-----------

Patch usage:
  Guest Kernel: apply patch 1 (UAPI) + patch 3 (guest driver)
  Host Kernel:  apply patch 1 (UAPI) + patch 2 (vhost support)
  QEMU:        apply patch 4 (vhost-scsi bridge)

1. Set up vhost-scsi target on the host (see:
   https://wiki.libvirt.org/Vhost-scsi_target.html#Host_Setup)
   using targetcli to create a TCM loopback device, e.g.:
   targetcli /backstores/loopback create dev=/dev/sda
   targetcli /vhost create naa.5001405376e34400
   targetcli /vhost/naa.5001405376e34400/lun create \
     /backstores/loopback/dev,/dev/sda

2. Boot VM with vhost-scsi using patched QEMU:
   qemu-system-aarch64 ... \
     -device vhost-scsi-pci,wwpn=naa.5001405376e34400

3. Verify SQ/CQ poll mode is active on the host:
   dmesg | grep "vhost-sqcq"
   # Expected: "vhost-sqcq: vq[N] poll thread bound to cpuN"
   # and 10s stats: "vhost-sqcq: vq[N] cq=... ema_lat=... interval=..."

4. Verify feature negotiated in guest:
   dmesg | grep "VIRTIO_F_SQCQ_POLL"
   # Expected: "VIRTIO_F_SQCQ_POLL negotiated, starting poll thread"
   # and 10s stats: "io_stats: cq=... avg_lat=... interval=..."

5. Run fio benchmark (compare with unpatched QEMU/kernel baseline):
   fio --name=randread --rw=randread --bs=4k --iodepth=32 \
       --numjobs=4 --runtime=60 --time_based --direct=1 \
       --filename=/dev/sda

Test scripts (run-sqcq-compare.sh, compare-sqcq-results.sh) are
available and will be sent as a follow-up to this RFC.


Thank you for your time.

---
Yufeng Wang (4):
  common: add UAPI for SQ/CQ doorbell polling
  vhost: host kernel support for SQ/CQ polling
  virtio: guest driver support for SQ/CQ polling
  qemu: add SQ/CQ polling mode support for vhost-scsi

Patch 1 (UAPI, 3 files): +41 -1
Patch 2 (vhost, 5 files): +794 -20
Patch 3 (virtio guest, 10 files): +840 -3
Patch 4 (QEMU, 14 files): +179 -3

Total: 32 files changed, 1854 insertions(+), 27 deletions(-)

-- 
2.34.1

Re: [RFC PATCH 0/4] virtio: SQ/CQ doorbell polling for vhost-scsi
Posted by Eugenio Perez Martin 3 days, 16 hours ago
On Mon, Jul 20, 2026 at 4:19 PM rom.wang <r4o5m6e8o@163.com> wrote:
>
> From: Yufeng Wang <wangyufeng@kylinos.cn>
>
> This RFC proposes a new virtio transport feature, VIRTIO_F_SQCQ_POLL,
> that eliminates VM exits on both submission and completion paths for
> vhost-scsi by using shared-memory doorbells and kernel polling
> threads, following the io_uring SQPOLL model.
>
> This is an early RFC to gather design feedback. The implementation is
> functional and has been tested on arm64 and x86_64. We are not
> requesting merge at this time.
>
>
> Problem
> -------
>
> vhost-scsi uses MMIO writes (Guest -> Host) and MSI-X interrupts
> (Host -> Guest) for notification. Each notification involves a VM exit,
> which becomes a bottleneck at high IOPS:
>
>   - 4K random read, QD32, 8 jobs: 341K IOPS baseline
>   - With ~340K VM exits/second, the exit overhead dominates
>
> Existing mitigations (vhost-net's tx polling, blk-mq iopoll) only
> address one direction or require the submitting task to poll. Neither
> eliminates VM exits on both paths simultaneously.
>
>
> Solution
> --------
>
> Introduce two cache-line-aligned doorbell structures, SQ (Submission
> Queue) and CQ (Completion Queue), placed alongside the standard split
> virtqueue:
>
>   - Guest writes sq->idx instead of MMIO kick; Host poll thread
>     detects the change and processes submissions.
>   - Host writes cq->idx instead of MSI-X interrupt; Guest poll
>     thread detects the change and invokes completion callbacks.
>
> A NEED_WAKEUP protocol (mirroring io_uring's SQ_NEED_WAKEUP) allows
> either side to sleep when idle, with the other side responsible for
> waking it via eventfd.
>
> Feature negotiation via VIRTIO_F_SQCQ_POLL (bit 42) ensures zero
> overhead when not negotiated — the driver falls back to traditional
> MMIO kick + MSI-X interrupt.
>
>
> Performance
> -----------
>
> Benchmark: fio, 4K random I/O
>
> Test configuration:
>
>   arm64:
>     CPU:   Kunpeng 920 (2.6GHz), 8 vCPUs
>     Disk:  NVMe INTEL SSDPED1K375GA (375GB)
>
>   x86_64:
>     CPU:   Intel Xeon E5-2680 v4 @ 2.40GHz, 8 vCPUs
>     Disk:  NVMe SAMSUNG MZ1LB960HAJQ-000MV (960GB)
>
>   Backend: vhost-scsi with TCM loopback to NVMe device
>   QEMU:    vhost-scsi-pci with VIRTIO_F_SQCQ_POLL negotiated
>
> arm64 results:
>
>   Test              Baseline     SQ/CQ Poll   Change
>   -----------       ----------   ----------   -------
>   randread QD1      22,427       28,289       +26%
>   randread QD32 NJ1 89,910       75,665       -16%
>   randread QD32 NJ4 186,763      379,549      +103%
>   randread QD32 NJ8 199,967      550,633      +175%
>   randwrite QD1     21,912       27,261       +24%
>   randwrite QD32 NJ1 85,349      81,389       -5%
>   randwrite QD32 NJ4 190,443     355,811      +87%
>   randwrite QD32 NJ8 196,552     566,640      +188%
>
> x86_64 results:
>
>   Test              Baseline     SQ/CQ Poll   Change
>   -----------       ----------   ----------   -------
>   randread QD1      8,263        9,552        +16%
>   randread QD32 NJ1 127,412      162,805      +28%
>   randread QD32 NJ4 303,208      375,056      +24%
>   randread QD32 NJ8 341,625      371,193      +9%
>   randwrite QD1     20,773       30,332       +46%
>   randwrite QD32 NJ1 133,316      159,207      +19%
>   randwrite QD32 NJ4 233,373      229,224      -2%
>   randwrite QD32 NJ8 231,442      231,676      +0%
>
> Multi-queue workloads (NJ4/NJ8) see significant improvement on arm64
> (87-188%) and moderate improvement on x86_64 (9-24%). Single-VQ
> high-queue-depth workloads show a minor regression on arm64 due to
> polling overhead vs. VM-exit savings trade-off, while x86_64 shows
> improvement across most configurations (16-46% for QD1, 19-28%
> for QD32-NJ1) due to lower per-VM-exit cost on x86.
>
>
> Why Not vDPA?
> -------------
>
> vhost-vDPA already provides doorbell mmap and polling. A reasonable
> reviewer would ask: why not extend vhost-vDPA instead?
>
> Three reasons:
>
> 1. No vdpa-scsi device exists. The vDPA framework
>    (drivers/vdpa/) currently has hardware devices for net (mlx5,
>    ifcvf, etc.) and software devices for net and blk (vdpa_sim).
>    There is no virtio-scsi vDPA device, hardware or software.
>    Building one means re-implementing vhost-scsi's TCM integration
>    (SCSI CDB processing, ALUA, persistent reservations) under the
>    vDPA device abstraction — 3-5x the work of extending vhost-scsi.
>

I don't have a lot of experience in vhost-scsi, but maybe using
generic vdpa device and ~passthrough all those calls to something
similar to vhost-scsi helps? Live migration is still a challenge that
way but you already mention that in Known Limitations, so maybe going
to generic saves a significant amount of work.

https://patchew.org/QEMU/20221215134944.2809-1-longpeng2@huawei.com/

Also, I'm failing to see the advantage of the Send / Completion queues
over a more agressive usage of event_idx, VIRTQ_USED_F_NO_NOTIFY /
VIRTQ_AVAIL_F_NO_INTERRUPT or VIRTIO_F_NOTIFICATION_DATA combined. Can
we explore what does these lacks compared with the send and completion
queues?

> 2. vhost-scsi is a deployed interface. libvirt, QEMU, and
>    OpenStack have vhost-scsi configuration APIs and operational
>    tooling. Switching to vhost-vdpa requires a new backend, user
>    migration, and toolchain updates. SQ/CQ poll as a vhost-scsi
>    feature is fully backward-compatible — no existing deployments
>    break.
>
> 3. The protocol is transport-agnostic. The SQ/CQ doorbell design
>    (struct vring_sq, struct vring_cq, NEED_WAKEUP handshake) is
>    orthogonal to vhost vs. vDPA. The same UAPI can be consumed by
>    vhost-scsi today and a future vdpa-scsi device. Implementing in
>    vhost-scsi first does not block future vDPA integration.
>
> We acknowledge that vDPA is the long-term direction for virtio
> backends. If this SQ/CQ poll protocol is accepted, it can be ported
> to the vDPA framework; a vdpa-scsi device is independent work.
>
>
> Patch Structure
> ---------------
>
>   Patch 1: UAPI definitions (virtio_config.h, virtio_ring.h,
>            virtio_pci.h) — shared interface for all components
>   Patch 2: vhost kernel support (vhost.c, vhost.h, scsi.c,
>            vhost.h UAPI, vhost_types.h UAPI) — Host poll thread
>   Patch 3: virtio guest driver (virtio_ring.c, virtio_sqcq_poll.c,
>            virtio_pci_modern.c, virtio.c, virtio_scsi.c) — Guest
>            poll thread and submission path
>   Patch 4: QEMU support (virtio-pci.c, vhost.c) — PCI config
>            forwarding and vhost ioctl bridge
>
> Patches 1-3 apply to the Linux kernel tree. Patch 4 applies to
> the QEMU tree separately.
>
>
> Spec Status
> -----------
>
> A virtio-spec format document has been prepared and will be submitted
> to the OASIS virtio TC as a proposal. This RFC stage seeks design
> feedback before initiating the formal spec process.
>
>
> Known Limitations (Future Work)
> -------------------------------
>
>   - CPU hotplug: no notifier registered; poll thread may be
>     migrated when its CPU goes offline. Planned: kthread_park +
>     dynamic rebind.
>   - Live migration: no explicit stop/flush coordination during
>     migration. Planned: VHOST_BACKEND_F_SUSPEND/RESUME integration.
>   - SMAP overhead: Host uses get_user/put_user for doorbell access.
>     Future optimization: GUP + kmap to map pages into kernel space.
>   - Backpressure: no "slow down" signal from device to driver.
>     Future: CQ throttle flag or avail_event reuse.
>   - Packed ring: not supported; explicitly rejected at feature
>     negotiation. Future: add packed ring doorbell support.
>
>
> RFC Goals
> ---------
>
>   1. Validate the overall design direction (doorbell + polling
>      model, NEED_WAKEUP protocol)
>   2. Get feedback on the feature bit allocation (42) and UAPI
>      structure design
>   3. Understand whether vDPA concern is a blocker or can be
>      addressed with the transport-agnostic argument
>   4. Collect guidance on prioritizing future work items
>      (hotplug, migration, spec process)
>
> We welcome all feedback, especially on:
>   - Whether the NEED_WAKEUP protocol design is sound
>   - Whether the per-device Guest poll thread model (vs per-VQ)
>     is acceptable
>   - Whether feature bit 42 is appropriate or if a different
>     allocation is needed
>   - Any concerns about the SMAP overhead in the Host poll path
>
>
> How to Test
> -----------
>
> Patch usage:
>   Guest Kernel: apply patch 1 (UAPI) + patch 3 (guest driver)
>   Host Kernel:  apply patch 1 (UAPI) + patch 2 (vhost support)
>   QEMU:        apply patch 4 (vhost-scsi bridge)
>
> 1. Set up vhost-scsi target on the host (see:
>    https://wiki.libvirt.org/Vhost-scsi_target.html#Host_Setup)
>    using targetcli to create a TCM loopback device, e.g.:
>    targetcli /backstores/loopback create dev=/dev/sda
>    targetcli /vhost create naa.5001405376e34400
>    targetcli /vhost/naa.5001405376e34400/lun create \
>      /backstores/loopback/dev,/dev/sda
>
> 2. Boot VM with vhost-scsi using patched QEMU:
>    qemu-system-aarch64 ... \
>      -device vhost-scsi-pci,wwpn=naa.5001405376e34400
>
> 3. Verify SQ/CQ poll mode is active on the host:
>    dmesg | grep "vhost-sqcq"
>    # Expected: "vhost-sqcq: vq[N] poll thread bound to cpuN"
>    # and 10s stats: "vhost-sqcq: vq[N] cq=... ema_lat=... interval=..."
>
> 4. Verify feature negotiated in guest:
>    dmesg | grep "VIRTIO_F_SQCQ_POLL"
>    # Expected: "VIRTIO_F_SQCQ_POLL negotiated, starting poll thread"
>    # and 10s stats: "io_stats: cq=... avg_lat=... interval=..."
>
> 5. Run fio benchmark (compare with unpatched QEMU/kernel baseline):
>    fio --name=randread --rw=randread --bs=4k --iodepth=32 \
>        --numjobs=4 --runtime=60 --time_based --direct=1 \
>        --filename=/dev/sda
>
> Test scripts (run-sqcq-compare.sh, compare-sqcq-results.sh) are
> available and will be sent as a follow-up to this RFC.
>
>
> Thank you for your time.
>
> ---
> Yufeng Wang (4):
>   common: add UAPI for SQ/CQ doorbell polling
>   vhost: host kernel support for SQ/CQ polling
>   virtio: guest driver support for SQ/CQ polling
>   qemu: add SQ/CQ polling mode support for vhost-scsi
>
> Patch 1 (UAPI, 3 files): +41 -1
> Patch 2 (vhost, 5 files): +794 -20
> Patch 3 (virtio guest, 10 files): +840 -3
> Patch 4 (QEMU, 14 files): +179 -3
>
> Total: 32 files changed, 1854 insertions(+), 27 deletions(-)
>
> --
> 2.34.1
>
Re: [RFC PATCH 0/4] virtio: SQ/CQ doorbell polling for vhost-scsi
Posted by rom.wang 2 days, 14 hours ago
From: Yufeng Wang <wangyufeng@kylinos.cn>

Thank you for the detailed feedback. We've completed benchmark
testing comparing the existing notification mechanisms against the
proposed SQ/CQ polling. The results are informative.


Test Modes Explained
--------------------

To answer your second question with data, we tested four modes:

1. **baseline**: Default vhost-scsi with event_idx auto-negotiated.
   I/O engine: libaio. Submission uses MMIO kick (VM exit), completion
   uses MSI-X interrupt (VM exit). The guest driver dynamically toggles
   NO_NOTIFY/NO_INTERRUPT during completion processing, but these are
   short windows — most I/O still incurs VM exits on both paths.

2. **poll-queue**: This mode combines the existing notification features
   you mentioned:
   - VIRTIO_RING_F_EVENT_IDX: auto-negotiated (same as baseline)
   - VIRTQ_AVAIL_F_NO_INTERRUPT: effectively permanent — poll queue
     VQs have callback=NULL and no MSI-X vector, so the guest never
     receives completion interrupts regardless of the flag state
   - VIRTQ_USED_F_NO_NOTIFY: vhost dynamically toggles this during
     active processing (vhost_disable_notify/enable_notify)
   - VIRTIO_F_NOTIFICATION_DATA: negotiated but vhost kernel does not
     process notification data, so no practical effect
   
   Implemented via virtio-scsi poll queues (virtscsi_poll_queues=16,
   callback=NULL, no MSI-X vector) + io_uring IOPOLL mode (--hipri).
   blk-mq polls the used ring for completions via virtscsi_mq_poll().
   This eliminates completion-side VM exits. However, submission still
   uses MMIO kick (VM exit). Note: libaio cannot use this mode because
   aio.c strips IOCB_HIPRI ("no one is going to poll for this I/O").

3. **sqcq-poll**: The proposed SQ/CQ doorbell polling (this RFC) with
   libaio. Guest writes sq->idx instead of MMIO kick (no VM exit).
   Guest poll thread checks used ring via more_used() (no interrupt).
   NEED_WAKEUP protocol enables adaptive sleep when idle. Eliminates
   VM exits on BOTH submission and completion paths.

4. **sqcq-uring**: Same SQ/CQ polling but with io_uring + --hipri.
   Included to isolate the I/O engine variable from the polling
   mechanism.


Results
-------

Test configuration:
  x86_64: Intel Xeon E5-2680 v4 @ 2.40GHz, 16 vCPUs
          NVMe SAMSUNG MZ1LB960HAJQ-000MV (960GB)
  arm64:  Kunpeng 920 (2.6GHz), 16 vCPUs
          NVMe INTEL SSDPED1K375GA (375GB)
  Backend: vhost-scsi with TCM loopback to NVMe

arm64 IOPS (best of 3 runs, fio 4K random I/O):

  Test              baseline   poll-queue    sqcq-poll   sqcq-uring
  ─────────────     ────────   ──────────    ─────────   ──────────
  randread QD1       23,751      29,892       29,680      26,427
  randread QD32 NJ1  93,221     162,849 *     84,967      70,271
  randread QD32 NJ4 184,939     186,859      340,780     235,310
  randread QD32 NJ8 190,562     188,382      520,946     518,197
  randwrite QD1      24,131          —        25,520      25,485
  randwrite QD32 NJ1 84,725          —        70,652      68,518
  randwrite QD32 NJ4 204,745          —       323,449     312,204
  randwrite QD32 NJ8 193,802          —       522,322     516,448

  * poll-queue NJ1 read shows high IOPS but hangs at NJ≥4 (see below)

x86_64 IOPS (best of 3 runs):

  Test              baseline   poll-queue    sqcq-poll   sqcq-uring
  ─────────────     ────────   ──────────    ─────────   ──────────
  randread QD1        8,316       9,022        9,533       9,489
  randread QD32 NJ1 124,909     146,744      165,761     165,929
  randread QD32 NJ4 305,951     332,139      378,305     379,546
  randread QD32 NJ8 344,223     356,328      374,932     374,976
  randwrite QD1      20,106      24,464       27,265      27,768
  randwrite QD32 NJ1 131,836    153,203      162,145     161,402
  randwrite QD32 NJ4 232,908    234,559      232,976     231,336
  randwrite QD32 NJ8 231,242    229,858      231,828     231,465


Key Findings
------------

1. **poll-queue excels at NJ=1 (arm64)**: On arm64 4K randread QD32
   NJ1, poll-queue achieved 163K IOPS — significantly higher than
   baseline (93K, +75%) and SQ/CQ polling (85K). io_uring IOPOLL
   mode is very efficient for single-queue workloads because the
   submitting task polls for completion directly, avoiding both
   interrupt latency and poll thread scheduling overhead.

2. **SQ/CQ dramatically outperforms poll-queue at NJ≥4 (arm64)**:
   At 4K randread QD32, poll-queue and SQ/CQ diverge sharply:
   - NJ4: poll-queue 187K vs SQ/CQ 341K (+82% over poll-queue)
   - NJ8: poll-queue 188K vs SQ/CQ 521K (+177% over poll-queue)
   SQ/CQ also outperforms baseline by +84% (NJ4) and +173% (NJ8).

3. **libaio incompatibility**: The existing poll queue mechanism
   requires io_uring IOPOLL mode. libaio explicitly strips IOCB_HIPRI
   (fs/aio.c: "no one is going to poll for this I/O"), so poll queues
   are inaccessible to libaio users. SQ/CQ works with any I/O engine
   because the poll thread is independent of the submitting task.


Development Insights
--------------------

Beyond eliminating VM exits, two engineering decisions were critical
to achieving the performance shown above:

1. **TCM completion CPU steering**: Our initial goal was 5-10% IOPS
   improvement at 4K random I/O QD1 NJ1. After implementing the basic
   polling and NEED_WAKEUP protocol, this target was not met. The
   bottleneck was that TCM completions were being scheduled on the same
   CPU as the busy poll thread, starving the completion workqueue. We
   added vhost_sqcq_pick_completion_cpu() to steer TCM completions to
   a free CPU (avoiding busy poll cores), with fallback to next-CPU
   when all cores are occupied. This closed the gap and achieved the
   QD1 target.

2. **Adaptive idle policy with EMA**: After QD1 targets were met,
   we tested higher concurrency (QD32 NJ4/NJ8) and found that
   aggressive polling caused performance degradation under
   multi-queue workloads — likely due to the poll thread consuming
   excessive CPU cycles that competing vCPUs needed. We solved this
   by implementing an EMA-based adaptive spin budget: the poll thread
   spins within a time window derived from recent I/O latency, then
   yields. Combined with the NEED_WAKEUP protocol, this allows the
   poll thread to busy-spin when active but sleep when idle, avoiding
   CPU contention with other vCPUs.


On the vDPA Question
--------------------

The generic vDPA device approach you suggested is indeed a better
direction. The QEMU-side zero-code advantage (leveraging existing
vdpa-dev.c) is very attractive. Our concern is that vhost-scsi's
TCM integration (SCSI CDB, ALUA, persistent reservations) would
need to be wrapped as a vDPA backend. This feels like a 0-to-1
engineering effort, and we're uncertain about the actual scope of
work involved. If there are additional guidelines or examples
available, we'd be very willing to explore this direction.


One More Thing: SQ/CQ + poll-queue Combined
--------------------------------------------

During arm64 testing, we accidentally ran the poll-queue benchmark
(--hipri, io_uring, virtscsi_poll_queues=16) on a kernel that also
had VIRTIO_F_SQCQ_POLL negotiated. This "poll-sqcq" mode combines
both mechanisms simultaneously:

  - Submission: SQ/CQ doorbell (sq->idx write, no VM exit)
  - Completion: both SQ/CQ poll thread AND blk-mq poll via io_uring

The results were surprisingly strong (arm64, 4K random I/O):

  Test              baseline   poll-queue   poll-sqcq   sqcq-poll
  ─────────────     ────────   ──────────   ─────────   ─────────
  randread QD1       23,751      29,892      41,683      29,680
  randread QD32 NJ1  93,221     162,849     193,029      84,967
  randread QD32 NJ4 184,939     186,859     585,445     340,780
  randread QD32 NJ8 190,562     188,382     560,727     520,946
  randwrite QD1      24,131          —       38,591      25,520
  randwrite QD32 NJ1 84,725          —      187,421      70,652
  randwrite QD32 NJ4 204,745          —      598,419     323,449
  randwrite QD32 NJ8 193,802          —      569,890     522,322

poll-sqcq outperforms both individual mechanisms across all test
cases. We haven't fully analyzed why, but suspect the combination
benefits from:
  - SQ/CQ eliminating submission VM exits
  - io_uring IOPOLL providing low-latency completion detection via
    blk-mq (directly in submitting task context)
  - SQ/CQ poll thread as a secondary completion poller covering VQs
    that blk-mq doesn't reach

This is preliminary and accidental data, but we think it's worth
sharing as it suggests the two mechanisms are complementary rather
than mutually exclusive.

Benchmark scripts and raw fio JSON data are available and can be
sent as a follow-up upon request.


Best regards,
Yufeng

Signed-off-by: Yufeng Wang <wangyufeng@kylinos.cn>