From: [email protected] <[email protected]> Sent: Friday, August 21, 2026 
5:36 PM
> 
> On a nested root partition the vPCI MSI/MSI-X interrupts of vmbus

s/vmbus/VMBus/   [and other places in this commit msg]

> devices (e.g. the MANA NIC) are mapped in the hypervisor with a
> MAP_DEVICE_INTERRUPT hypercall.  This is done from hv_arch_irq_unmask()
> -> hv_map_msi_interrupt() because the nested hypervisor performs the
> interrupt remapping and a RETARGET_INTERRUPT is not usable there.
> 
> The mapping was never removed: hv_arch_irq_unmask() called

s/was/is/ 
s/called/calls/

The wording in this whole paragraph shifts to past tense. I'd suggest
keeping present tense for consistency and per general kernel usage.

> hv_map_msi_interrupt(data, NULL), so the returned hv_interrupt_entry was
> discarded, and hv_msi_free() tears the interrupt down with a vmbus
> PCI_DELETE_INTERRUPT message (hv_int_desc_free()) without issuing
> UNMAP_DEVICE_INTERRUPT.
> 
> This has led to MSHV rejecting already-mapped (vp, vector) pair from
> being used. When this happens during early boot, the system hangs.
> 
> Keep the hypervisor mapping in sync with the kernel's interrupt
> lifecycle.
> 
> The mapping is only created on x86 (hv_arch_irq_unmask() is a stub on
> arm64), so the unmap hypercall is guarded accordingly.

See comment below about the x86-only situation.

> 
> Signed-off-by: Wei Liu <[email protected]>
> ---
>  drivers/pci/controller/pci-hyperv.c | 85 ++++++++++++++++++++++++++---
>  1 file changed, 78 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/pci/controller/pci-hyperv.c 
> b/drivers/pci/controller/pci-hyperv.c
> index cfc8fa403dad..5a36382742bf 100644
> --- a/drivers/pci/controller/pci-hyperv.c
> +++ b/drivers/pci/controller/pci-hyperv.c
> @@ -283,6 +283,35 @@ struct tran_int_desc {
>       u64     address;
>  } __packed;
> 
> +/*
> + * On a nested root partition a vPCI MSI is mapped in the hypervisor with a
> + * MAP_DEVICE_INTERRUPT hypercall in hv_arch_irq_unmask().  Keep the entry 
> the
> + * hypervisor returns next to the per-interrupt transaction descriptor so the
> + * mapping can be removed again with UNMAP_DEVICE_INTERRUPT when the 
> interrupt
> + * is torn down.  tran_int_desc stays first: chip_data is used as a struct
> + * tran_int_desc throughout this driver.
> + */
> +struct hv_msi_int_entry {
> +     struct tran_int_desc            int_desc;
> +     struct hv_interrupt_entry       hv_entry;
> +};
> +
> +/* chip_data is passed around as a struct tran_int_desc *, so it must be 
> first. */
> +static_assert(offsetof(struct hv_msi_int_entry, int_desc) == 0);
> +
> +static void hv_vmbus_unmap_msi_interrupt(struct pci_dev *pdev __maybe_unused,
> +                                      void *chip_data)
> +{
> +     struct hv_msi_int_entry *ie = chip_data;
> +
> +     if (!ie || !ie->hv_entry.source)
> +             return;
> +#ifdef CONFIG_X86
> +     hv_unmap_msi_interrupt(pdev, &ie->hv_entry);
> +#endif
> +     memset(&ie->hv_entry, 0, sizeof(ie->hv_entry));
> +}

This function seems out-of-place, as it is added in the middle of a
bunch of structure definitions. There's large #ifdef CONFIG_X86 ...
#elif defined(CONFIG_ARM64) ... #endif block in this source code
file. I'd suggest putting this function alongside hv_arch_irq_unmask()
in the x86 section, which is where hv_map_msi_interrupt() is
called. Then put a stub in the arm64 section -- there's already a stub
for hv_arch_irq_unmask(). And maybe name the function
hv_arch_unmap_msi_interrupt() since it really doesn't have to do
with VMBus stuff like channels, sending ring buffer messages, etc.

> +
>  /*
>   * A generic message format for virtual PCI.
>   * Specific message formats are defined later in the file.
> @@ -715,16 +744,30 @@ static void hv_irq_retarget_interrupt(struct irq_data 
> *data)
> 
>  static void hv_arch_irq_unmask(struct irq_data *data)
>  {
> -     if (hv_root_partition())
> +     if (hv_root_partition()) {
>               /*
>                * In case of the nested root partition, the nested hypervisor
>                * is taking care of interrupt remapping and thus the
>                * MAP_DEVICE_INTERRUPT hypercall is required instead of
>                * RETARGET_INTERRUPT.
> +              *
> +              * Keep the returned entry so the mapping can be removed again
> +              * when the interrupt is torn down.
>                */
> -             (void)hv_map_msi_interrupt(data, NULL);
> -     else
> +             struct hv_msi_int_entry *ie = data->chip_data;

There's the inline function irq_data_get_irq_chip_data() which seems
to be used instead of directly referencing the field (at least most of the
time throughout the kernel).

> +
> +             /*
> +              * A NULL chip_data means hv_compose_msi_msg() failed and the
> +              * interrupt was never set up, so there is nothing to map.
> +              */
> +             if (!ie)
> +                     return;
> +
> +             if (hv_map_msi_interrupt(data, &ie->hv_entry))
> +                     memset(&ie->hv_entry, 0, sizeof(ie->hv_entry));
> +     } else {
>               hv_irq_retarget_interrupt(data);
> +     }
>  }
>  #elif defined(CONFIG_ARM64)
>  /*
> @@ -1708,6 +1751,7 @@ static void hv_msi_free(struct irq_domain *domain, 
> unsigned int irq)
>               return;
>       }
> 
> +     hv_vmbus_unmap_msi_interrupt(pdev, int_desc);
>       hv_int_desc_free(hpdev, int_desc);
>       put_pcichild(hpdev);
>  }
> @@ -1882,6 +1926,7 @@ static void hv_compose_msi_msg(struct irq_data *data, 
> struct msi_msg *msg)
>       const struct cpumask *dest;
>       struct compose_comp_ctxt comp;
>       struct tran_int_desc *int_desc;
> +     struct hv_msi_int_entry *int_entry;
>       struct msi_desc *msi_desc;
>       /*
>        * vector_count should be u16: see hv_msi_desc, hv_msi_desc2
> @@ -1932,9 +1977,10 @@ static void hv_compose_msi_msg(struct irq_data *data, 
> struct msi_msg *msg)
>               hv_int_desc_free(hpdev, int_desc);
>       }
> 
> -     int_desc = kzalloc_obj(*int_desc, GFP_ATOMIC);
> -     if (!int_desc)
> +     int_entry = kzalloc_obj(*int_entry, GFP_ATOMIC);
> +     if (!int_entry)
>               goto drop_reference;
> +     int_desc = &int_entry->int_desc;
> 
>       if (multi_msi) {
>               /*
> @@ -2184,9 +2230,34 @@ static void hv_pcie_domain_free(struct irq_domain *d, 
> unsigned int virq, unsigne
>       irq_domain_free_irqs_top(d, virq, nr_irqs);
>  }
> 
> +/*
> + * Runs from irq_domain_deactivate_irq() during irq_shutdown(), before the
> + * parent (x86 vector) domain is deactivated and the (cpu, vector) is 
> returned
> + * to the matrix allocator, so a freed vector can never collide with a stale
> + * hypervisor entry when it is reused.
> + */
> +static void hv_pcie_domain_deactivate(struct irq_domain *d,
> +                                   struct irq_data *data)
> +{
> +     struct msi_desc *msi_desc;
> +     struct pci_dev *pdev;
> +
> +     if (!hv_root_partition())
> +             return;
> +
> +     msi_desc = irq_data_get_msi_desc(data);
> +     if (!msi_desc)
> +             return;
> +
> +     pdev = msi_desc_to_pci_dev(msi_desc);
> +     if (pdev)
> +             hv_vmbus_unmap_msi_interrupt(pdev, data->chip_data);

Same here regarding direct reference to the chip_data field.

> +}
> +
>  static const struct irq_domain_ops hv_pcie_domain_ops = {
> -     .alloc  = hv_pcie_domain_alloc,
> -     .free   = hv_pcie_domain_free,
> +     .alloc          = hv_pcie_domain_alloc,
> +     .free           = hv_pcie_domain_free,
> +     .deactivate     = hv_pcie_domain_deactivate,
>  };
> 
>  /**
> --
> 2.53.0
> 


Reply via email to