]> Git Repo - linux.git/commitdiff
PCI/bwctrl: Re-add BW notification portdrv as PCIe BW controller
authorIlpo Järvinen <[email protected]>
Fri, 18 Oct 2024 14:47:52 +0000 (17:47 +0300)
committerBjorn Helgaas <[email protected]>
Sat, 16 Nov 2024 16:09:04 +0000 (10:09 -0600)
This mostly reverts the commit b4c7d2076b4e ("PCI/LINK: Remove bandwidth
notification"). An upcoming commit extends this driver building PCIe
bandwidth controller on top of it.

PCIe bandwidth notifications were first added in the commit e8303bb7a75c
("PCI/LINK: Report degraded links via link bandwidth notification") but
later had to be removed. The significant changes compared with the old
bandwidth notification driver include:

1) Don't print the notifications into kernel log, just keep the Link
   Speed cached in struct pci_bus updated. While somewhat unfortunate,
   the log spam was the source of complaints that eventually lead to
   the removal of the bandwidth notifications driver (see the links
   below for further information).

2) Besides the Link Bandwidth Management Interrupt, also enable Link
   Autonomous Bandwidth Interrupt to cover the other source of bandwidth
   changes.

3) Handle Link Speed updates robustly. Refresh the cached Link Speed
   when enabling Bandwidth Notification Interrupts, and solve the race
   between Link Speed read and LBMS/LABS update in
   pcie_bwnotif_irq_thread().

4) Use concurrency safe LNKCTL RMW operations.

5) The driver is now called PCIe bwctrl (bandwidth controller) instead
   of just bandwidth notifications because of increased scope and
   functionality within the driver.

6) Coexist with the Target Link Speed quirk in pcie_failed_link_retrain().
   Provide LBMS counting API for it.

7) Tweaks to variable/functions names for consistency and length reasons.

Bandwidth Notifications enable the cur_bus_speed in the struct pci_bus to
keep track PCIe Link Speed changes.

[bhelgaas: This is based on previous work by Alexandru Gagniuc
<[email protected]>; see e8303bb7a75c ("PCI/LINK: Report degraded links
via link bandwidth notification")]

Link: https://lore.kernel.org/r/[email protected]
Link: https://lore.kernel.org/all/[email protected]/
Link: https://lore.kernel.org/linux-pci/[email protected]/
Link: https://lore.kernel.org/linux-pci/[email protected]/
Suggested-by: Lukas Wunner <[email protected]> # Building bwctrl on top of bwnotif
Signed-off-by: Ilpo Järvinen <[email protected]>
[bhelgaas: squash fix to drop IRQF_ONESHOT and convert to hardirq handler:
https://lore.kernel.org/r/20241115165717[email protected]]
Signed-off-by: Bjorn Helgaas <[email protected]>
Tested-by: Stefan Wahren <[email protected]>
Reviewed-by: Jonathan Cameron <[email protected]>
MAINTAINERS
drivers/pci/hotplug/pciehp_ctrl.c
drivers/pci/pci.c
drivers/pci/pci.h
drivers/pci/pcie/Makefile
drivers/pci/pcie/bwctrl.c [new file with mode: 0644]
drivers/pci/pcie/portdrv.c
drivers/pci/pcie/portdrv.h
drivers/pci/quirks.c
include/linux/pci.h

index c27f3190737f8b85779bde5489639c8b899f4fd8..8c555b3325d65feaf2a37965c3806f2cc3af340d 100644 (file)
@@ -17933,6 +17933,12 @@ F:     include/linux/of_pci.h
 F:     include/linux/pci*
 F:     include/uapi/linux/pci*
 
+PCIE BANDWIDTH CONTROLLER
+M:     Ilpo Järvinen <[email protected]>
+L:     [email protected]
+S:     Supported
+F:     drivers/pci/pcie/bwctrl.c
+
 PCIE DRIVER FOR AMAZON ANNAPURNA LABS
 M:     Jonathan Chocron <[email protected]>
 L:     [email protected]
index dcdbfcf404ddf449481055f16d9a4d05b06a9530..d603a7aa74838c748f6ac2d22ffb8b8cfe64e469 100644 (file)
@@ -19,6 +19,8 @@
 #include <linux/types.h>
 #include <linux/pm_runtime.h>
 #include <linux/pci.h>
+
+#include "../pci.h"
 #include "pciehp.h"
 
 /* The following routines constitute the bulk of the
@@ -127,6 +129,9 @@ static void remove_board(struct controller *ctrl, bool safe_removal)
 
        pciehp_set_indicators(ctrl, PCI_EXP_SLTCTL_PWR_IND_OFF,
                              INDICATOR_NOOP);
+
+       /* Don't carry LBMS indications across */
+       pcie_reset_lbms_count(ctrl->pcie->port);
 }
 
 static int pciehp_enable_slot(struct controller *ctrl);
index 3d67e8b50ba2b9b6779d95afcd505b1702db050a..f85f380cdb9b365f4930bbd7c8c1eef6609ab687 100644 (file)
@@ -4740,7 +4740,7 @@ int pcie_retrain_link(struct pci_dev *pdev, bool use_lt)
         * to track link speed or width changes made by hardware itself
         * in attempt to correct unreliable link operation.
         */
-       pcie_capability_write_word(pdev, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS);
+       pcie_reset_lbms_count(pdev);
        return rc;
 }
 
index 8137ad2fead8871bd9d2cb0f58fcea9d4b0e0270..8ec189c8f3f5a303f63c1628725bb8e5e8963195 100644 (file)
@@ -698,6 +698,17 @@ static inline void pcie_set_ecrc_checking(struct pci_dev *dev) { }
 static inline void pcie_ecrc_get_policy(char *str) { }
 #endif
 
+#ifdef CONFIG_PCIEPORTBUS
+void pcie_reset_lbms_count(struct pci_dev *port);
+int pcie_lbms_count(struct pci_dev *port, unsigned long *val);
+#else
+static inline void pcie_reset_lbms_count(struct pci_dev *port) {}
+static inline int pcie_lbms_count(struct pci_dev *port, unsigned long *val)
+{
+       return -EOPNOTSUPP;
+}
+#endif
+
 struct pci_dev_reset_methods {
        u16 vendor;
        u16 device;
index 6461aa93fe76eccd39c4af08e7e1c9c327e7b77d..53ccab62314d972a6dbc5ce9a33d39b9e2dc86eb 100644 (file)
@@ -4,7 +4,7 @@
 
 pcieportdrv-y                  := portdrv.o rcec.o
 
-obj-$(CONFIG_PCIEPORTBUS)      += pcieportdrv.o
+obj-$(CONFIG_PCIEPORTBUS)      += pcieportdrv.o bwctrl.o
 
 obj-y                          += aspm.o
 obj-$(CONFIG_PCIEAER)          += aer.o err.o
diff --git a/drivers/pci/pcie/bwctrl.c b/drivers/pci/pcie/bwctrl.c
new file mode 100644 (file)
index 0000000..b3da249
--- /dev/null
@@ -0,0 +1,186 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * PCIe bandwidth controller
+ *
+ * Author: Alexandru Gagniuc <[email protected]>
+ *
+ * Copyright (C) 2019 Dell Inc
+ * Copyright (C) 2023-2024 Intel Corporation
+ *
+ * This service port driver hooks into the Bandwidth Notification interrupt
+ * watching for changes or links becoming degraded in operation. It updates
+ * the cached Current Link Speed that is exposed to user space through sysfs.
+ */
+
+#define dev_fmt(fmt) "bwctrl: " fmt
+
+#include <linux/atomic.h>
+#include <linux/cleanup.h>
+#include <linux/errno.h>
+#include <linux/interrupt.h>
+#include <linux/pci.h>
+#include <linux/rwsem.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+
+#include "../pci.h"
+#include "portdrv.h"
+
+/**
+ * struct pcie_bwctrl_data - PCIe bandwidth controller
+ * @lbms_count:                Count for LBMS (since last reset)
+ */
+struct pcie_bwctrl_data {
+       atomic_t lbms_count;
+};
+
+/* Prevents port removal during LBMS count accessors */
+static DECLARE_RWSEM(pcie_bwctrl_lbms_rwsem);
+
+static void pcie_bwnotif_enable(struct pcie_device *srv)
+{
+       struct pcie_bwctrl_data *data = srv->port->link_bwctrl;
+       struct pci_dev *port = srv->port;
+       u16 link_status;
+       int ret;
+
+       /* Count LBMS seen so far as one */
+       ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status);
+       if (ret == PCIBIOS_SUCCESSFUL && link_status & PCI_EXP_LNKSTA_LBMS)
+               atomic_inc(&data->lbms_count);
+
+       pcie_capability_set_word(port, PCI_EXP_LNKCTL,
+                                PCI_EXP_LNKCTL_LBMIE | PCI_EXP_LNKCTL_LABIE);
+       pcie_capability_write_word(port, PCI_EXP_LNKSTA,
+                                  PCI_EXP_LNKSTA_LBMS | PCI_EXP_LNKSTA_LABS);
+
+       /*
+        * Update after enabling notifications & clearing status bits ensures
+        * link speed is up to date.
+        */
+       pcie_update_link_speed(port->subordinate);
+}
+
+static void pcie_bwnotif_disable(struct pci_dev *port)
+{
+       pcie_capability_clear_word(port, PCI_EXP_LNKCTL,
+                                  PCI_EXP_LNKCTL_LBMIE | PCI_EXP_LNKCTL_LABIE);
+}
+
+static irqreturn_t pcie_bwnotif_irq(int irq, void *context)
+{
+       struct pcie_device *srv = context;
+       struct pcie_bwctrl_data *data = srv->port->link_bwctrl;
+       struct pci_dev *port = srv->port;
+       u16 link_status, events;
+       int ret;
+
+       ret = pcie_capability_read_word(port, PCI_EXP_LNKSTA, &link_status);
+       if (ret != PCIBIOS_SUCCESSFUL)
+               return IRQ_NONE;
+
+       events = link_status & (PCI_EXP_LNKSTA_LBMS | PCI_EXP_LNKSTA_LABS);
+       if (!events)
+               return IRQ_NONE;
+
+       if (events & PCI_EXP_LNKSTA_LBMS)
+               atomic_inc(&data->lbms_count);
+
+       pcie_capability_write_word(port, PCI_EXP_LNKSTA, events);
+
+       /*
+        * Interrupts will not be triggered from any further Link Speed
+        * change until LBMS is cleared by the write. Therefore, re-read the
+        * speed (inside pcie_update_link_speed()) after LBMS has been
+        * cleared to avoid missing link speed changes.
+        */
+       pcie_update_link_speed(port->subordinate);
+
+       return IRQ_HANDLED;
+}
+
+void pcie_reset_lbms_count(struct pci_dev *port)
+{
+       struct pcie_bwctrl_data *data;
+
+       guard(rwsem_read)(&pcie_bwctrl_lbms_rwsem);
+       data = port->link_bwctrl;
+       if (data)
+               atomic_set(&data->lbms_count, 0);
+       else
+               pcie_capability_write_word(port, PCI_EXP_LNKSTA,
+                                          PCI_EXP_LNKSTA_LBMS);
+}
+
+int pcie_lbms_count(struct pci_dev *port, unsigned long *val)
+{
+       struct pcie_bwctrl_data *data;
+
+       guard(rwsem_read)(&pcie_bwctrl_lbms_rwsem);
+       data = port->link_bwctrl;
+       if (!data)
+               return -ENOTTY;
+
+       *val = atomic_read(&data->lbms_count);
+
+       return 0;
+}
+
+static int pcie_bwnotif_probe(struct pcie_device *srv)
+{
+       struct pci_dev *port = srv->port;
+       int ret;
+
+       struct pcie_bwctrl_data *data = devm_kzalloc(&srv->device,
+                                                    sizeof(*data), GFP_KERNEL);
+       if (!data)
+               return -ENOMEM;
+
+       ret = devm_request_irq(&srv->device, srv->irq, pcie_bwnotif_irq,
+                              IRQF_SHARED, "PCIe bwctrl", srv);
+       if (ret)
+               return ret;
+
+       scoped_guard(rwsem_write, &pcie_bwctrl_lbms_rwsem) {
+               port->link_bwctrl = no_free_ptr(data);
+               pcie_bwnotif_enable(srv);
+       }
+
+       pci_dbg(port, "enabled with IRQ %d\n", srv->irq);
+
+       return 0;
+}
+
+static void pcie_bwnotif_remove(struct pcie_device *srv)
+{
+       pcie_bwnotif_disable(srv->port);
+       scoped_guard(rwsem_write, &pcie_bwctrl_lbms_rwsem)
+               srv->port->link_bwctrl = NULL;
+}
+
+static int pcie_bwnotif_suspend(struct pcie_device *srv)
+{
+       pcie_bwnotif_disable(srv->port);
+       return 0;
+}
+
+static int pcie_bwnotif_resume(struct pcie_device *srv)
+{
+       pcie_bwnotif_enable(srv);
+       return 0;
+}
+
+static struct pcie_port_service_driver pcie_bwctrl_driver = {
+       .name           = "pcie_bwctrl",
+       .port_type      = PCIE_ANY_PORT,
+       .service        = PCIE_PORT_SERVICE_BWCTRL,
+       .probe          = pcie_bwnotif_probe,
+       .suspend        = pcie_bwnotif_suspend,
+       .resume         = pcie_bwnotif_resume,
+       .remove         = pcie_bwnotif_remove,
+};
+
+int __init pcie_bwctrl_init(void)
+{
+       return pcie_port_service_register(&pcie_bwctrl_driver);
+}
index 6af5e0425872855b94445510029a9eda6bdf2ca2..5e10306b63081b1ddd13e0a545418e2a8610c14c 100644 (file)
@@ -68,7 +68,7 @@ static int pcie_message_numbers(struct pci_dev *dev, int mask,
         */
 
        if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP |
-                   PCIE_PORT_SERVICE_BWNOTIF)) {
+                   PCIE_PORT_SERVICE_BWCTRL)) {
                pcie_capability_read_word(dev, PCI_EXP_FLAGS, &reg16);
                *pme = FIELD_GET(PCI_EXP_FLAGS_IRQ, reg16);
                nvec = *pme + 1;
@@ -150,11 +150,11 @@ static int pcie_port_enable_irq_vec(struct pci_dev *dev, int *irqs, int mask)
 
        /* PME, hotplug and bandwidth notification share an MSI/MSI-X vector */
        if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP |
-                   PCIE_PORT_SERVICE_BWNOTIF)) {
+                   PCIE_PORT_SERVICE_BWCTRL)) {
                pcie_irq = pci_irq_vector(dev, pme);
                irqs[PCIE_PORT_SERVICE_PME_SHIFT] = pcie_irq;
                irqs[PCIE_PORT_SERVICE_HP_SHIFT] = pcie_irq;
-               irqs[PCIE_PORT_SERVICE_BWNOTIF_SHIFT] = pcie_irq;
+               irqs[PCIE_PORT_SERVICE_BWCTRL_SHIFT] = pcie_irq;
        }
 
        if (mask & PCIE_PORT_SERVICE_AER)
@@ -271,7 +271,7 @@ static int get_port_device_capability(struct pci_dev *dev)
 
                pcie_capability_read_dword(dev, PCI_EXP_LNKCAP, &linkcap);
                if (linkcap & PCI_EXP_LNKCAP_LBNC)
-                       services |= PCIE_PORT_SERVICE_BWNOTIF;
+                       services |= PCIE_PORT_SERVICE_BWCTRL;
        }
 
        return services;
@@ -828,6 +828,7 @@ static void __init pcie_init_services(void)
        pcie_aer_init();
        pcie_pme_init();
        pcie_dpc_init();
+       pcie_bwctrl_init();
        pcie_hp_init();
 }
 
index 12c89ea0313b9c4c5aa73744ea6aff9dee844d84..bd29d1cc7b8bd75fc14cb0cbfe44e50a8ddfcffd 100644 (file)
@@ -20,8 +20,8 @@
 #define PCIE_PORT_SERVICE_HP           (1 << PCIE_PORT_SERVICE_HP_SHIFT)
 #define PCIE_PORT_SERVICE_DPC_SHIFT    3       /* Downstream Port Containment */
 #define PCIE_PORT_SERVICE_DPC          (1 << PCIE_PORT_SERVICE_DPC_SHIFT)
-#define PCIE_PORT_SERVICE_BWNOTIF_SHIFT        4       /* Bandwidth notification */
-#define PCIE_PORT_SERVICE_BWNOTIF      (1 << PCIE_PORT_SERVICE_BWNOTIF_SHIFT)
+#define PCIE_PORT_SERVICE_BWCTRL_SHIFT 4       /* Bandwidth Controller (notifications) */
+#define PCIE_PORT_SERVICE_BWCTRL       (1 << PCIE_PORT_SERVICE_BWCTRL_SHIFT)
 
 #define PCIE_PORT_DEVICE_MAXSERVICES   5
 
@@ -51,6 +51,8 @@ int pcie_dpc_init(void);
 static inline int pcie_dpc_init(void) { return 0; }
 #endif
 
+int pcie_bwctrl_init(void);
+
 /* Port Type */
 #define PCIE_ANY_PORT                  (~0)
 
index a560ea403b8efbf953d27fd36c6b4fed516e758c..e6d502dca939147c1908803fa326def561e8778a 100644 (file)
 
 static bool pcie_lbms_seen(struct pci_dev *dev, u16 lnksta)
 {
-       return lnksta & PCI_EXP_LNKSTA_LBMS;
+       unsigned long count;
+       int ret;
+
+       ret = pcie_lbms_count(dev, &count);
+       if (ret < 0)
+               return lnksta & PCI_EXP_LNKSTA_LBMS;
+
+       return count > 0;
 }
 
 /*
index 99c6fa30d25bec99d9e2ec56723b62203a1e467f..579b2883342924ff665de71c732dfad40b8e31ac 100644 (file)
@@ -313,6 +313,7 @@ struct pci_vpd {
 };
 
 struct irq_affinity;
+struct pcie_bwctrl_data;
 struct pcie_link_state;
 struct pci_sriov;
 struct pci_p2pdma;
@@ -502,6 +503,7 @@ struct pci_dev {
        unsigned int    dpc_rp_extensions:1;
        u8              dpc_rp_log_size;
 #endif
+       struct pcie_bwctrl_data         *link_bwctrl;
 #ifdef CONFIG_PCI_ATS
        union {
                struct pci_sriov        *sriov;         /* PF: SR-IOV info */
This page took 0.154143 seconds and 4 git commands to generate.