On 6/1/2026 3:27 AM, Shradha Gupta wrote:
> In mana driver, the number of IRQs allocated is capped by the
> min(num_cpu + 1, queue count). In cases, where the IRQ count is greater
> than the vcpu count, we want to utilize all the vCPUs, irrespective of
> their NUMA/core bindings.
> 
> This is important, especially in the envs where number of vCPUs are so
> few that the softIRQ handling overhead on two IRQs on the same vCPU is
> much more than their overheads if they were spread across sibling vCPUs.
> 
> This behaviour is more evident with dynamic IRQ allocation. Since MANA
> IRQs are assigned at a later stage compared to static allocation, other
> device IRQs may already be affinitized to the vCPUs. As a result, IRQ
> weights become imbalanced, causing multiple MANA IRQs to land on the
> same vCPU, while some vCPUs have none.
> 
> In such cases when many parallel TCP connections are tested, the
> throughput drops significantly.
> 
> Test envs:
> =======================================================
> Case 1: without this patch
> =======================================================
> 4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
> 
>       TYPE            effective vCPU aff
> =======================================================
> IRQ0: HWC             0
> IRQ1: mana_q1         0
> IRQ2: mana_q2         2
> IRQ3: mana_q3         0
> IRQ4: mana_q4         3
> 
> %soft on each vCPU(mpstat -P ALL 1) on receiver
> vCPU          0       1       2       3
> =======================================================
> pass 1:               38.85   0.03    24.89   24.65
> pass 2:               39.15   0.03    24.57   25.28
> pass 3:               40.36   0.03    23.20   23.17
> 
> =======================================================
> Case 2: with this patch
> =======================================================
> 4 vcpu(2 cores), 5 MANA IRQs (1 HWC + 4 Queue)
> 
>         TYPE            effective vCPU aff
> =======================================================
> IRQ0:   HWC             0
> IRQ1:   mana_q1         0
> IRQ2:   mana_q2         1
> IRQ3:   mana_q3         2
> IRQ4:   mana_q4         3
> 
> %soft on each vCPU(mpstat -P ALL 1) on receiver
> vCPU            0       1       2       3
> =======================================================
> pass 1:         15.42 15.85   14.99   14.51
> pass 2:         15.53 15.94   15.81   15.93
> pass 3:         16.41 16.35   16.40   16.36
> 
> =======================================================
> Throughput Impact(in Gbps, same env)
> =======================================================
> TCP conn      with patch      w/o patch
> 20480         15.65           7.73
> 10240         15.63           8.93
> 8192          15.64           9.69
> 6144          15.64           13.16
> 4096          15.69           15.75
> 2048          15.69           15.83
> 1024          15.71           15.28
> 
> Fixes: 755391121038 ("net: mana: Allocate MSI-X vectors dynamically")
> Cc: [email protected]
> Co-developed-by: Erni Sri Satya Vennela <[email protected]>
> Signed-off-by: Erni Sri Satya Vennela <[email protected]>
> Signed-off-by: Shradha Gupta <[email protected]>
> Reviewed-by: Haiyang Zhang <[email protected]>
> Reviewed-by: Simon Horman <[email protected]>
> ---
> Changes in v3
>  * Optimize the comments in mana_gd_setup_dyn_irqs()
>  * add more details in the dev_dbg for extra IRQs 
> ---
> Changes in v2
>  * Removed the unused skip_first_cpu variable
>  * fixed exit condition in irq_setup_linear() with len == 0
>  * changed return type of irq_setup_linear() as it will always be 0
>  * removed the unnecessary rcu_read_lock() in irq_setup_linear()
>  * added appropriate comments to indicate expected behaviour when
>    IRQs are more than or equal to num_online_cpus()
> ---
>  .../net/ethernet/microsoft/mana/gdma_main.c   | 60 ++++++++++++++++---
>  1 file changed, 53 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/net/ethernet/microsoft/mana/gdma_main.c 
> b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> index 712a0881d720..00a28b3ca0a6 100644
> --- a/drivers/net/ethernet/microsoft/mana/gdma_main.c
> +++ b/drivers/net/ethernet/microsoft/mana/gdma_main.c
> @@ -197,6 +197,8 @@ static int mana_gd_query_max_resources(struct pci_dev 
> *pdev)
>       } else {
>               /* If dynamic allocation is enabled we have already allocated
>                * hwc msi
> +              * Also, we make sure in this case the following is always true
> +              * (num_msix_usable - 1 HWC) <= num_online_cpus()
>                */
>               gc->num_msix_usable = min(resp.max_msix, num_online_cpus() + 1);
>       }
> @@ -1717,11 +1719,24 @@ static int irq_setup(unsigned int *irqs, unsigned int 
> len, int node,
>       return 0;
>  }
>  
> +/* should be called with cpus_read_lock() held */
> +static void irq_setup_linear(unsigned int *irqs, unsigned int len)
> +{
> +     int cpu;
> +
> +     for_each_online_cpu(cpu) {
> +             if (len == 0)
> +                     break;
> +
> +             irq_set_affinity_and_hint(*irqs++, cpumask_of(cpu));
> +             len--;
> +     }
> +}

I would find all of this a bit easier to follow if irq_setup_linear()
and irq_setup() had a mana prefix so it was more obvious these are
specific to the driver. Of course irq_setup is pre-existing, and its not
my driver so do as you will :)

> +
>  static int mana_gd_setup_dyn_irqs(struct pci_dev *pdev, int nvec)
>  {
>       struct gdma_context *gc = pci_get_drvdata(pdev);
>       struct gdma_irq_context *gic;
> -     bool skip_first_cpu = false;
>       int *irqs, irq, err, i;
>  
>       irqs = kmalloc_objs(int, nvec);
> @@ -1729,6 +1744,8 @@ static int mana_gd_setup_dyn_irqs(struct pci_dev *pdev, 
> int nvec)
>               return -ENOMEM;
>  
>       /*
> +      * In this function, num_msix_usable = HWC IRQ + Queue IRQ.
> +      * nvec is only Queue IRQ (HWC already setup).
>        * While processing the next pci irq vector, we start with index 1,
>        * as IRQ vector at index 0 is already processed for HWC.
>        * However, the population of irqs array starts with index 0, to be
> @@ -1767,13 +1784,42 @@ static int mana_gd_setup_dyn_irqs(struct pci_dev 
> *pdev, int nvec)
>        * first CPU sibling group since they are already affinitized to HWC IRQ
>        */
>       cpus_read_lock();
> -     if (gc->num_msix_usable <= num_online_cpus())
> -             skip_first_cpu = true;
> +     if (gc->num_msix_usable <= num_online_cpus()) {
> +             err = irq_setup(irqs, nvec, gc->numa_node, true);
> +             if (err) {
> +                     cpus_read_unlock();
> +                     goto free_irq;
> +             }
> +     } else {
> +             /*
> +              * When num_msix_usable are more than num_online_cpus, our
> +              * queue IRQs should be equal to num of online vCPUs.
> +              * We try to make sure queue IRQs spread across all vCPUs.
> +              * In such a case NUMA or CPU core affinity does not matter.
> +              * Note: in this case the total mana IRQ should always be
> +              * num_online_cpus + 1. The first HWC IRQ is already handled
> +              * in HWC setup calls
> +              * However, if CPUs went offline since num_msix_usable was
> +              * computed, queue IRQs will be more than num_online_cpus().
> +              * In such cases remaining extra IRQs will retain their default
> +              * affinity.
> +              */
> +             int first_unassigned = num_online_cpus();
> +             if (nvec > first_unassigned) {
> +                     char buf[32];
> +
> +                     if (first_unassigned == nvec - 1)
> +                             snprintf(buf, sizeof(buf), "%d",
> +                                      first_unassigned);
> +                     else
> +                             snprintf(buf, sizeof(buf), "%d-%d",
> +                                      first_unassigned, nvec - 1);
> +
> +                     dev_dbg(&pdev->dev,
> +                             "MANA IRQ indices #%s will retain the default 
> CPU affinity\n", buf);
> +             }
>  
> -     err = irq_setup(irqs, nvec, gc->numa_node, skip_first_cpu);
> -     if (err) {
> -             cpus_read_unlock();
> -             goto free_irq;
> +             irq_setup_linear(irqs, nvec);

irq_setup() doesn't have a driver prefix, but is actually a static
function in gdma_main.c, so its implementation is specific to this
driver despite its name.

So if I understand this change correctly, if the number of usable MSI-X
vectors is smaller than the number of CPUs, you contineu to use the
current irq_setup logic.. otherwise you switch to the simpler "linear"
logic.

I guess this means the logic and heuristic used in irq_setup() breaks
down when the number of vectors is large and number of vCPU is small?

Makes sense.

>       }
>  
>       cpus_read_unlock();
> 
> base-commit: 8415598365503ced2e3d019491b0a2756c85c494


Reply via email to