The PCI ath10k devices are capable of multiple MSI. The QCA9889 of a Xiaomi AX3600 advertises 8 vectors in its MSI capability (Multiple Message Capable 3) and, when granted all of them, raises the firmware interrupt and each copy engine on its own vector, the layout ath10k used until commit cfe9011a05a8 ("ath10k: remove MSI range support"). Since then ath10k asks for one vector, so all its copy engine interrupts and the NAPI they schedule run on one CPU. Ask for as many MSI vectors as the device offers and spread them over the CPUs, starting each further device on the next CPU. Every vector keeps the shared handler, which schedules the NAPI that services all copy engines, so the copy engine to vector mapping does not matter for correctness. With one vector nothing changes. On the AX3600, with a station uploading 87-88 Mbit/s over the QCA9889, the HTT copy engine raised 36891-37590 interrupts in 14 s on vector 6 and the WMI and HTC ones 165-313 each on vectors 2-4. The receive softirq moved from CPU0 (21-25%) to CPU2 (21-35%), with CPU0 at 1-3%. Measured with idle-priority spinners, the CPU time of the whole system stayed within the run-to-run spread: 25-42% of four CPUs against 22-34% uploading, 14-35% against 10-37% downloading (3 runs each). A firmware crash forced through debugfs recovered as without the change. Tested-on: QCA9887 hw1.0 PCI 10.2.4-1.0-00047 Signed-off-by: Julius Bairaktaris --- Tested on OpenWrt's ath10k (Linux 6.18 with backports 7.2) and on ath10k-ct with the same change, where the DesignWare PCIe MSI of the IPQ8074 lets each vector run on its own CPU since commit eaf290c404f7c ("PCI: dwc: Enable MSI affinity support"). I used Claude (Anthropic) to help write this patch. drivers/net/wireless/ath/ath10k/pci.c | 64 +++++++++++++++++++++------ drivers/net/wireless/ath/ath10k/pci.h | 3 ++ 2 files changed, 54 insertions(+), 13 deletions(-) diff --git a/drivers/net/wireless/ath/ath10k/pci.c b/drivers/net/wireless/ath/ath10k/pci.c index 335bc7c488..4de789cd06 100644 --- a/drivers/net/wireless/ath/ath10k/pci.c +++ b/drivers/net/wireless/ath/ath10k/pci.c @@ -38,6 +38,9 @@ MODULE_PARM_DESC(irq_mode, "0: auto, 1: legacy, 2: msi (default: 0)"); module_param_named(reset_mode, ath10k_pci_reset_mode, uint, 0644); MODULE_PARM_DESC(reset_mode, "0: auto, 1: warm only (default: 0)"); +/* Spreads the interrupts of each further device from the next CPU on */ +static atomic_t ath10k_pci_num_devs = ATOMIC_INIT(0); + /* how long wait to wait for target to initialise, in ms */ #define ATH10K_PCI_TARGET_WAIT 3000 #define ATH10K_PCI_NUM_WARM_RESET_ATTEMPTS 3 @@ -1943,8 +1946,10 @@ static void ath10k_pci_irq_disable(struct ath10k *ar) static void ath10k_pci_irq_sync(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + int i; - synchronize_irq(ar_pci->pdev->irq); + for (i = 0; i < ar_pci->num_irqs; i++) + synchronize_irq(pci_irq_vector(ar_pci->pdev, i)); } static void ath10k_pci_irq_enable(struct ath10k *ar) @@ -3164,18 +3169,42 @@ static int ath10k_pci_napi_poll(struct napi_struct *ctx, int budget) static int ath10k_pci_request_irq_msi(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); - int ret; + int cpu_offset, cpu, irq, ret, i; - ret = request_irq(ar_pci->pdev->irq, - ath10k_pci_interrupt_handler, - IRQF_SHARED, "ath10k_pci", ar); - if (ret) { - ath10k_warn(ar, "failed to request MSI irq %d: %d\n", - ar_pci->pdev->irq, ret); - return ret; + /* With several vectors the device raises the firmware interrupt and + * each copy engine on its own one. Each vector schedules the same + * NAPI, so they only differ in where it runs: spread them over the + * CPUs, from a different CPU for each device. + */ + cpu_offset = atomic_fetch_inc(&ath10k_pci_num_devs); + + for (i = 0; i < ar_pci->num_irqs; i++) { + irq = pci_irq_vector(ar_pci->pdev, i); + ret = request_irq(irq, ath10k_pci_interrupt_handler, + IRQF_SHARED, "ath10k_pci", ar); + if (ret) { + ath10k_warn(ar, "failed to request MSI irq %d: %d\n", + irq, ret); + goto err_free; + } + + if (ar_pci->num_irqs > 1) { + cpu = cpumask_local_spread(i + cpu_offset, + dev_to_node(ar_pci->dev)); + irq_set_affinity_and_hint(irq, cpumask_of(cpu)); + } } return 0; + +err_free: + while (--i >= 0) { + irq = pci_irq_vector(ar_pci->pdev, i); + irq_update_affinity_hint(irq, NULL); + free_irq(irq, ar); + } + + return ret; } static int ath10k_pci_request_irq_intx(struct ath10k *ar) @@ -3212,8 +3241,13 @@ static int ath10k_pci_request_irq(struct ath10k *ar) static void ath10k_pci_free_irq(struct ath10k *ar) { struct ath10k_pci *ar_pci = ath10k_pci_priv(ar); + int irq, i; - free_irq(ar_pci->pdev->irq, ar); + for (i = 0; i < ar_pci->num_irqs; i++) { + irq = pci_irq_vector(ar_pci->pdev, i); + irq_update_affinity_hint(irq, NULL); + free_irq(irq, ar); + } } void ath10k_pci_init_napi(struct ath10k *ar) @@ -3235,9 +3269,12 @@ static int ath10k_pci_init_irq(struct ath10k *ar) /* Try MSI */ if (ath10k_pci_irq_mode != ATH10K_PCI_IRQ_INTX) { ar_pci->oper_irq_mode = ATH10K_PCI_IRQ_MSI; - ret = pci_enable_msi(ar_pci->pdev); - if (ret == 0) + ret = pci_alloc_irq_vectors(ar_pci->pdev, 1, CE_COUNT_MAX + 1, + PCI_IRQ_MSI); + if (ret > 0) { + ar_pci->num_irqs = ret; return 0; + } /* MHI failed, try legacy irq next */ } @@ -3252,6 +3289,7 @@ static int ath10k_pci_init_irq(struct ath10k *ar) * synchronization checking. */ ar_pci->oper_irq_mode = ATH10K_PCI_IRQ_INTX; + ar_pci->num_irqs = 1; ath10k_pci_write32(ar, SOC_CORE_BASE_ADDRESS + PCIE_INTR_ENABLE_ADDRESS, PCIE_INTR_FIRMWARE_MASK | PCIE_INTR_CE_MASK_ALL); @@ -3274,7 +3312,7 @@ static int ath10k_pci_deinit_irq(struct ath10k *ar) ath10k_pci_deinit_irq_intx(ar); break; default: - pci_disable_msi(ar_pci->pdev); + pci_free_irq_vectors(ar_pci->pdev); break; } diff --git a/drivers/net/wireless/ath/ath10k/pci.h b/drivers/net/wireless/ath/ath10k/pci.h index d114778edb..252044dd63 100644 --- a/drivers/net/wireless/ath/ath10k/pci.h +++ b/drivers/net/wireless/ath/ath10k/pci.h @@ -115,6 +115,9 @@ struct ath10k_pci { /* Operating interrupt mode */ enum ath10k_pci_irq_mode oper_irq_mode; + /* Number of MSI vectors, or 1 for INTx */ + int num_irqs; + struct ath10k_pci_pipe pipe_info[CE_COUNT_MAX]; /* Copy Engine used for Diagnostic Accesses */ -- 2.53.0