Merge branch 'pci/bwctrl'

- Simplify link bandwidth controller by replacing the count of Link
  Bandwidth Management Status (LBMS) events with a PCI_LINK_LBMS_SEEN flag
  (Ilpo Järvinen)

- Update the Link Speed after retraining, since the Link Speed may have
  changed (Ilpo Järvinen)

* pci/bwctrl:
  PCI: Update Link Speed after retraining
  PCI/bwctrl: Replace lbms_count with PCI_LINK_LBMS_SEEN flag
This commit is contained in:
Bjorn Helgaas 2025-06-04 10:49:49 -05:00
commit 1acf6a5e79
5 changed files with 44 additions and 83 deletions

View File

@ -131,7 +131,7 @@ static void remove_board(struct controller *ctrl, bool safe_removal)
INDICATOR_NOOP);
/* Don't carry LBMS indications across */
pcie_reset_lbms_count(ctrl->pcie->port);
pcie_reset_lbms(ctrl->pcie->port);
}
static int pciehp_enable_slot(struct controller *ctrl);

View File

@ -4718,6 +4718,11 @@ static int pcie_wait_for_link_status(struct pci_dev *pdev,
* @pdev: Device whose link to retrain.
* @use_lt: Use the LT bit if TRUE, or the DLLLA bit if FALSE, for status.
*
* Trigger retraining of the PCIe Link and wait for the completion of the
* retraining. As link retraining is known to asserts LBMS and may change
* the Link Speed, LBMS is cleared after the retraining and the Link Speed
* of the subordinate bus is updated.
*
* Retrain completion status is retrieved from the Link Status Register
* according to @use_lt. It is not verified whether the use of the DLLLA
* bit is valid.
@ -4757,7 +4762,19 @@ 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_reset_lbms_count(pdev);
pcie_reset_lbms(pdev);
/*
* Ensure the Link Speed updates after retraining in case the Link
* Speed was changed because of the retraining. While the bwctrl's
* IRQ handler normally picks up the new Link Speed, clearing LBMS
* races with the IRQ handler reading the Link Status register and
* can result in the handler returning early without updating the
* Link Speed.
*/
if (pdev->subordinate)
pcie_update_link_speed(pdev->subordinate);
return rc;
}

View File

@ -557,6 +557,7 @@ static inline int pci_dev_set_disconnected(struct pci_dev *dev, void *unused)
#define PCI_DPC_RECOVERED 1
#define PCI_DPC_RECOVERING 2
#define PCI_DEV_REMOVED 3
#define PCI_LINK_LBMS_SEEN 6
static inline void pci_dev_assign_added(struct pci_dev *dev)
{
@ -828,14 +829,9 @@ 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);
void pcie_reset_lbms(struct pci_dev *port);
#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;
}
static inline void pcie_reset_lbms(struct pci_dev *port) {}
#endif
struct pci_dev_reset_methods {

View File

@ -38,24 +38,14 @@
/**
* struct pcie_bwctrl_data - PCIe bandwidth controller
* @set_speed_mutex: Serializes link speed changes
* @lbms_count: Count for LBMS (since last reset)
* @cdev: Thermal cooling device associated with the port
*/
struct pcie_bwctrl_data {
struct mutex set_speed_mutex;
atomic_t lbms_count;
struct thermal_cooling_device *cdev;
};
/*
* Prevent port removal during LBMS count accessors and Link Speed changes.
*
* These have to be differentiated because pcie_bwctrl_change_speed() calls
* pcie_retrain_link() which uses LBMS count reset accessor on success
* (using just one rwsem triggers "possible recursive locking detected"
* warning).
*/
static DECLARE_RWSEM(pcie_bwctrl_lbms_rwsem);
/* Prevent port removal during Link Speed changes. */
static DECLARE_RWSEM(pcie_bwctrl_setspeed_rwsem);
static bool pcie_valid_speed(enum pci_bus_speed speed)
@ -127,18 +117,7 @@ static int pcie_bwctrl_change_speed(struct pci_dev *port, u16 target_speed, bool
if (ret != PCIBIOS_SUCCESSFUL)
return pcibios_err_to_errno(ret);
ret = pcie_retrain_link(port, use_lt);
if (ret < 0)
return ret;
/*
* Ensure link speed updates also with platforms that have problems
* with notifications.
*/
if (port->subordinate)
pcie_update_link_speed(port->subordinate);
return 0;
return pcie_retrain_link(port, use_lt);
}
/**
@ -202,15 +181,14 @@ int pcie_set_target_speed(struct pci_dev *port, enum pci_bus_speed speed_req,
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 */
/* Note if LBMS has been seen so far */
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);
set_bit(PCI_LINK_LBMS_SEEN, &port->priv_flags);
pcie_capability_set_word(port, PCI_EXP_LNKCTL,
PCI_EXP_LNKCTL_LBMIE | PCI_EXP_LNKCTL_LABIE);
@ -233,7 +211,6 @@ static void pcie_bwnotif_disable(struct pci_dev *port)
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;
@ -247,7 +224,7 @@ static irqreturn_t pcie_bwnotif_irq(int irq, void *context)
return IRQ_NONE;
if (events & PCI_EXP_LNKSTA_LBMS)
atomic_inc(&data->lbms_count);
set_bit(PCI_LINK_LBMS_SEEN, &port->priv_flags);
pcie_capability_write_word(port, PCI_EXP_LNKSTA, events);
@ -262,31 +239,10 @@ static irqreturn_t pcie_bwnotif_irq(int irq, void *context)
return IRQ_HANDLED;
}
void pcie_reset_lbms_count(struct pci_dev *port)
void pcie_reset_lbms(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;
clear_bit(PCI_LINK_LBMS_SEEN, &port->priv_flags);
pcie_capability_write_word(port, PCI_EXP_LNKSTA, PCI_EXP_LNKSTA_LBMS);
}
static int pcie_bwnotif_probe(struct pcie_device *srv)
@ -308,18 +264,16 @@ static int pcie_bwnotif_probe(struct pcie_device *srv)
return ret;
scoped_guard(rwsem_write, &pcie_bwctrl_setspeed_rwsem) {
scoped_guard(rwsem_write, &pcie_bwctrl_lbms_rwsem) {
port->link_bwctrl = data;
port->link_bwctrl = data;
ret = request_irq(srv->irq, pcie_bwnotif_irq,
IRQF_SHARED, "PCIe bwctrl", srv);
if (ret) {
port->link_bwctrl = NULL;
return ret;
}
pcie_bwnotif_enable(srv);
ret = request_irq(srv->irq, pcie_bwnotif_irq,
IRQF_SHARED, "PCIe bwctrl", srv);
if (ret) {
port->link_bwctrl = NULL;
return ret;
}
pcie_bwnotif_enable(srv);
}
pci_dbg(port, "enabled with IRQ %d\n", srv->irq);
@ -339,13 +293,11 @@ static void pcie_bwnotif_remove(struct pcie_device *srv)
pcie_cooling_device_unregister(data->cdev);
scoped_guard(rwsem_write, &pcie_bwctrl_setspeed_rwsem) {
scoped_guard(rwsem_write, &pcie_bwctrl_lbms_rwsem) {
pcie_bwnotif_disable(srv->port);
pcie_bwnotif_disable(srv->port);
free_irq(srv->irq, srv);
free_irq(srv->irq, srv);
srv->port->link_bwctrl = NULL;
}
srv->port->link_bwctrl = NULL;
}
}

View File

@ -38,14 +38,10 @@
static bool pcie_lbms_seen(struct pci_dev *dev, u16 lnksta)
{
unsigned long count;
int ret;
if (test_bit(PCI_LINK_LBMS_SEEN, &dev->priv_flags))
return true;
ret = pcie_lbms_count(dev, &count);
if (ret < 0)
return lnksta & PCI_EXP_LNKSTA_LBMS;
return count > 0;
return lnksta & PCI_EXP_LNKSTA_LBMS;
}
/*