[virtio_pci?] WARNING: possible circular locking dependency detected

Zenghui Yu posted 1 patch 2 days, 1 hour ago
[virtio_pci?] WARNING: possible circular locking dependency detected
Posted by Zenghui Yu 2 days, 1 hour ago
Hi folks,

I hit the following lockdep splat in an arm64 guest whose PCI devices sit
behind an SMMUv3.

    ======================================================
    WARNING: possible circular locking dependency detected
    7.3.0-rc1+ #84 Not tainted
    ------------------------------------------------------
    kworker/u34:0/57 is trying to acquire lock:
    ffff800104f83ee8 (&md->mutex){+.+.}-{4:4}, at: msi_domain_get_virq+0xf0/0x118

    but task is already holding lock:
    ffffc00081edd930 (cpu_hotplug_lock){++++}-{0:0}, at: cpus_read_lock+0x10/0x1c

    which lock already depends on the new lock.

    Chain exists of:
      &md->mutex --> &group->mutex --> cpu_hotplug_lock

The cycle is built from three dependencies:

* cpu_hotplug_lock -> &md_mutex (which triggers the splat).  virtio_net's
  init_vqs() calls cpus_read_lock() around virtnet_set_affinity(), which
  reaches vp_set_vq_affinity().  That calls pci_irq_vector(), which takes
  the per-device MSI mutex (&dev->msi.data->mutex) via
  msi_domain_get_virq().

* &md->mutex -> &domain->mutex -> &group->mutex.  MSI-X allocation of any
  device behind the IOMMU holds the MSI mutex while allocating interrupts
  through the IRQ domain hierarchy.  The MSI parent domain (GICv2m, or ITS
  likewise) calls iommu_dma_prepare_msi() from its .alloc() to map the MSI
  doorbell, which takes the IOMMU group mutex.

* &group->mutex -> cpu_hotplug_lock.  bus_iommu_probe() calls
  iommu_setup_dma_ops() under the group mutex; initializing the IOVA
  rcaches registers a cpuhp instance, which takes cpu_hotplug_lock.

The cycle only became complete since commit 288683c92b1a ("iommu: Make
iommu_dma_prepare_msi() into a generic operation"), where
iommu_dma_prepare_msi() switched to use the group mutex.

Caching the IRQ number in virtio_pci_vq_info {} (and making
vp_set_vq_affinity() use the cached value) removes the first dependency.
But I'm not sure if this is an appropriate fix.  Please have a look.

---8<---

diff --git a/drivers/virtio/virtio_pci_common.c b/drivers/virtio/virtio_pci_common.c
index b90c174450b2..2f42300a8a84 100644
--- a/drivers/virtio/virtio_pci_common.c
+++ b/drivers/virtio/virtio_pci_common.c
@@ -224,6 +224,16 @@ static struct virtqueue *vp_setup_vq(struct virtio_device *vdev, unsigned int in
 		goto out_info;

 	info->vq = vq;
+
+	/*
+	 * Cache the Linux IRQ number so that later users (e.g.
+	 * vp_set_vq_affinity() from cpu hotplug locked contexts)
+	 * don't have to take the MSI mutex via pci_irq_vector().
+	 */
+	info->virq = 0;
+	if (msix_vec != VIRTIO_MSI_NO_VECTOR)
+	info->virq = pci_irq_vector(vp_dev->pci_dev, msix_vec);
+
 	if (callback) {
 		spin_lock_irqsave(&vp_dev->lock, flags);
 		if (!vp_is_slow_path_vector(msix_vec))
@@ -571,7 +581,7 @@ int vp_set_vq_affinity(struct virtqueue *vq, const struct cpumask *cpu_mask)
 
 	if (vp_dev->msix_enabled) {
 		mask = vp_dev->msix_affinity_masks[info->msix_vector];
-		irq = pci_irq_vector(vp_dev->pci_dev, info->msix_vector);
+		irq = info->virq;
 		if (!cpu_mask)
 			irq_update_affinity_hint(irq, NULL);
 		else {
diff --git a/drivers/virtio/virtio_pci_common.h b/drivers/virtio/virtio_pci_common.h
index 8cd01de27baf..18b15a5f84ae 100644
--- a/drivers/virtio/virtio_pci_common.h
+++ b/drivers/virtio/virtio_pci_common.h
@@ -40,6 +40,9 @@ struct virtio_pci_vq_info {
 
 	/* MSI-X vector (or none) */
 	unsigned int msix_vector;
+
+	/* Linux IRQ number of msix_vector, or 0 if not mapped */
+	unsigned int virq;
 };
 
 struct virtio_pci_admin_vq {

Thanks,
Zenghui
Re: [virtio_pci?] WARNING: possible circular locking dependency detected
Posted by Zenghui Yu 2 days, 1 hour ago
On 9/22/26 8:37 PM, Zenghui Yu wrote:
> Hi folks,
> 
> I hit the following lockdep splat in an arm64 guest whose PCI devices sit
> behind an SMMUv3.

The complete log is:

 ======================================================
 WARNING: possible circular locking dependency detected
 7.3.0-rc1+ #84 Not tainted
 ------------------------------------------------------
 kworker/u34:0/57 is trying to acquire lock:
 ffff800104f83ee8 (&md->mutex){+.+.}-{4:4}, at: msi_domain_get_virq+0xf0/0x118
        
 but task is already holding lock:
 ffffc00081edd930 (cpu_hotplug_lock){++++}-{0:0}, at: cpus_read_lock+0x10/0x1c
        
 which lock already depends on the new lock.

        
 the existing dependency chain (in reverse order) is:
        
 -> #3 (cpu_hotplug_lock){++++}-{0:0}:
        percpu_down_read_internal.constprop.0+0x3c/0x154
        __cpuhp_state_add_instance+0x24/0x54
        iova_domain_init_rcaches+0x240/0x2e4
        iommu_setup_dma_ops+0x1e0/0x5c8
        iommu_device_register+0x174/0x2a8
        arm_smmu_device_probe+0x10b4/0x1558
        platform_probe+0x5c/0xac
        really_probe+0xe0/0x34c
        __driver_probe_device+0x198/0x1c8
        driver_probe_device+0x40/0x11c
        __driver_attach+0x7c/0x1e0
        bus_for_each_dev+0x74/0xd0
        driver_attach+0x24/0x30
        bus_add_driver+0xfc/0x27c
        driver_register+0x60/0x128
        __platform_driver_register+0x28/0x34
        arm_smmu_driver_init+0x28/0x34
        do_one_initcall+0x64/0x530
        kernel_init_freeable+0x28c/0x458
        kernel_init+0x2c/0x1e4
        ret_from_fork+0x10/0x20
   
 -> #2 (&group->mutex){+.+.}-{4:4}:
        __mutex_lock+0x90/0x858
        mutex_lock_nested+0x24/0x30
        iommu_dma_prepare_msi+0x38/0xf8
        gicv2m_irq_domain_alloc+0xf4/0x274
        irq_domain_alloc_irqs_parent+0x24/0x38
        msi_domain_alloc+0x7c/0x144
        irq_domain_alloc_irqs_locked+0x140/0x3ec
        __irq_domain_alloc_irqs+0x70/0xc4
        __msi_domain_alloc_irqs+0x19c/0x514
        __msi_domain_alloc_locked+0x134/0x188
        msi_domain_alloc_irqs_all_locked+0x54/0xb4
        pci_msi_setup_msi_irqs+0x38/0x44
        __pci_enable_msix_range+0x3b4/0x5ac
        pci_alloc_irq_vectors_affinity+0xc0/0x138
        vp_find_vqs_msix+0x2dc/0x548
        vp_find_vqs+0x44/0x2ac
        vp_modern_find_vqs+0x1c/0x70
        init_vq+0x298/0x468
        virtblk_probe+0x168/0x948
        virtio_dev_probe+0x1bc/0x2ec
        really_probe+0xe0/0x34c
        __driver_probe_device+0x198/0x1c8
        driver_probe_device+0x40/0x11c
        __device_attach_driver+0xd4/0x14c
        bus_for_each_drv+0x80/0xdc
        __device_attach+0xa8/0x1c8
        device_initial_probe+0x50/0x54
        bus_probe_device+0x38/0xa8
        device_add+0x654/0x874
        register_virtio_device+0x20c/0x264
        virtio_pci_probe+0x100/0x1e0
        local_pci_probe+0x40/0xa8
        pci_call_probe+0x64/0x2ec
        pci_device_probe+0xac/0x134
        really_probe+0xe0/0x34c
        __driver_probe_device+0x198/0x1c8
        driver_probe_device+0x40/0x11c
        __device_attach_driver+0xd4/0x14c
        bus_for_each_drv+0x80/0xdc
        __device_attach+0xa8/0x1c8
        device_initial_probe+0x50/0x54
        bus_probe_device+0x38/0xa8
        deferred_probe_work_func+0xcc/0x11c
        process_one_work+0x264/0x87c
        worker_thread+0x230/0x374
        kthread+0x138/0x144
        ret_from_fork+0x10/0x20

 -> #1 (&domain->mutex){+.+.}-{4:4}:
        __mutex_lock+0x90/0x858
        mutex_lock_nested+0x24/0x30
        __irq_domain_alloc_irqs+0x50/0xc4
        __msi_domain_alloc_irqs+0x19c/0x514
        __msi_domain_alloc_locked+0x134/0x188
        msi_domain_alloc_irqs_all_locked+0x54/0xb4
        pci_msi_setup_msi_irqs+0x38/0x44
        __pci_enable_msix_range+0x3b4/0x5ac
        pci_alloc_irq_vectors_affinity+0xc0/0x138
        vp_find_vqs_msix+0x2dc/0x548
        vp_find_vqs+0x44/0x2ac
        vp_modern_find_vqs+0x1c/0x70
        init_vq+0x298/0x468
        virtblk_probe+0x168/0x948
        virtio_dev_probe+0x1bc/0x2ec
        really_probe+0xe0/0x34c
        __driver_probe_device+0x198/0x1c8
        driver_probe_device+0x40/0x11c
        __device_attach_driver+0xd4/0x14c
        bus_for_each_drv+0x80/0xdc
        __device_attach+0xa8/0x1c8
        device_initial_probe+0x50/0x54
        bus_probe_device+0x38/0xa8
        device_add+0x654/0x874
        register_virtio_device+0x20c/0x264
        virtio_pci_probe+0x100/0x1e0
        local_pci_probe+0x40/0xa8
        pci_call_probe+0x64/0x2ec
        pci_device_probe+0xac/0x134
        really_probe+0xe0/0x34c
        __driver_probe_device+0x198/0x1c8
        driver_probe_device+0x40/0x11c
        __device_attach_driver+0xd4/0x14c
        bus_for_each_drv+0x80/0xdc
        __device_attach+0xa8/0x1c8
        device_initial_probe+0x50/0x54
        bus_probe_device+0x38/0xa8
        deferred_probe_work_func+0xcc/0x11c
        process_one_work+0x264/0x87c
        worker_thread+0x230/0x374
        kthread+0x138/0x144
        ret_from_fork+0x10/0x20

 -> #0 (&md->mutex){+.+.}-{4:4}:
        __lock_acquire+0x13b0/0x20e4
        lock_acquire+0x1e4/0x468
        __mutex_lock+0x90/0x858
        mutex_lock_nested+0x24/0x30
        msi_domain_get_virq+0xf0/0x118
        pci_irq_vector+0x34/0x5c
        vp_set_vq_affinity+0x60/0xd0
        virtnet_set_affinity+0x18c/0x2dc
        virtnet_probe+0x650/0xdcc
        virtio_dev_probe+0x1bc/0x2ec
        really_probe+0xe0/0x34c
        __driver_probe_device+0x198/0x1c8
        driver_probe_device+0x40/0x11c
        __device_attach_driver+0xd4/0x14c
        bus_for_each_drv+0x80/0xdc
        __device_attach+0xa8/0x1c8
        device_initial_probe+0x50/0x54
        bus_probe_device+0x38/0xa8
        device_add+0x654/0x874
        register_virtio_device+0x20c/0x264
        virtio_pci_probe+0x100/0x1e0
        local_pci_probe+0x40/0xa8
        pci_call_probe+0x64/0x2ec
        pci_device_probe+0xac/0x134
        really_probe+0xe0/0x34c
        __driver_probe_device+0x198/0x1c8
        driver_probe_device+0x40/0x11c
        __device_attach_driver+0xd4/0x14c
        bus_for_each_drv+0x80/0xdc
        __device_attach+0xa8/0x1c8
        device_initial_probe+0x50/0x54
        bus_probe_device+0x38/0xa8
        deferred_probe_work_func+0xcc/0x11c
        process_one_work+0x264/0x87c
        worker_thread+0x230/0x374
        kthread+0x138/0x144
        ret_from_fork+0x10/0x20

 other info that might help us debug this:

 Chain exists of:
   &md->mutex --> &group->mutex --> cpu_hotplug_lock

  Possible unsafe locking scenario:

        CPU0                    CPU1
        ----                    ----
   rlock(cpu_hotplug_lock);
                                lock(&group->mutex);
                                lock(cpu_hotplug_lock);
   lock(&md->mutex);

  *** DEADLOCK ***

 locks held by kworker/u34:0/57: 5, last CPU#7:
  #0: ffff8000004a2940 ((wq_completion)events_unbound){+.+.}-{0:0}, at: process_one_work+0x6d4/0x87c
  #1: ffffc00083d63df0 (deferred_probe_work){+.+.}-{0:0}, at: process_one_work+0x214/0x87c
  #2: ffff800001c441f8 (&dev->mutex){....}-{4:4}, at: __device_attach+0x38/0x1c8
  #3: ffff800104f2b1b0 (&dev->mutex){....}-{4:4}, at: __device_attach+0x38/0x1c8
  #4: ffffc00081edd930 (cpu_hotplug_lock){++++}-{0:0}, at: cpus_read_lock+0x10/0x1c

 stack backtrace:
 CPU: 7 UID: 0 PID: 57 Comm: kworker/u34:0 Not tainted 7.3.0-rc1+ #84 PREEMPT
 Hardware name: QEMU QEMU Virtual Machine, BIOS edk2-stable202608-prebuilt.qemu.org 08/12/2026
 Workqueue: events_unbound deferred_probe_work_func
 Call trace:
  show_stack+0x18/0x24 (C)
  dump_stack_lvl+0xd8/0x168
  dump_stack+0x18/0x24
  print_circular_bug+0x294/0x378
  check_noncircular+0x15c/0x170
  __lock_acquire+0x13b0/0x20e4
  lock_acquire+0x1e4/0x468
  __mutex_lock+0x90/0x858
  mutex_lock_nested+0x24/0x30
  msi_domain_get_virq+0xf0/0x118
  pci_irq_vector+0x34/0x5c
  vp_set_vq_affinity+0x60/0xd0
  virtnet_set_affinity+0x18c/0x2dc
  virtnet_probe+0x650/0xdcc
  virtio_dev_probe+0x1bc/0x2ec
  really_probe+0xe0/0x34c
  __driver_probe_device+0x198/0x1c8
  driver_probe_device+0x40/0x11c
  __device_attach_driver+0xd4/0x14c
  bus_for_each_drv+0x80/0xdc
  __device_attach+0xa8/0x1c8
  device_initial_probe+0x50/0x54
  bus_probe_device+0x38/0xa8
  device_add+0x654/0x874
  register_virtio_device+0x20c/0x264
  virtio_pci_probe+0x100/0x1e0
  local_pci_probe+0x40/0xa8
  pci_call_probe+0x64/0x2ec
  pci_device_probe+0xac/0x134
  really_probe+0xe0/0x34c
  __driver_probe_device+0x198/0x1c8
  driver_probe_device+0x40/0x11c
  __device_attach_driver+0xd4/0x14c
  bus_for_each_drv+0x80/0xdc
  __device_attach+0xa8/0x1c8
  device_initial_probe+0x50/0x54
  bus_probe_device+0x38/0xa8
  deferred_probe_work_func+0xcc/0x11c
  process_one_work+0x264/0x87c
  worker_thread+0x230/0x374
  kthread+0x138/0x144
  ret_from_fork+0x10/0x20