aboutsummaryrefslogtreecommitdiff
path: root/src/device/pciexp_device.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/device/pciexp_device.c')
-rw-r--r--src/device/pciexp_device.c22
1 files changed, 20 insertions, 2 deletions
diff --git a/src/device/pciexp_device.c b/src/device/pciexp_device.c
index 0c3653879b..bc8206a6d4 100644
--- a/src/device/pciexp_device.c
+++ b/src/device/pciexp_device.c
@@ -50,16 +50,34 @@ unsigned int pciexp_find_extended_cap(device_t dev, unsigned int cap)
#define PCIE_TRAIN_RETRY 10000
static int pciexp_retrain_link(device_t dev, unsigned cap)
{
- unsigned try = PCIE_TRAIN_RETRY;
+ unsigned int try;
u16 lnk;
+ /*
+ * Implementation note (page 633) in PCIe Specification 3.0 suggests
+ * polling the Link Training bit in the Link Status register until the
+ * value returned is 0 before setting the Retrain Link bit to 1.
+ * This is meant to avoid a race condition when using the
+ * Retrain Link mechanism.
+ */
+ for (try = PCIE_TRAIN_RETRY; try > 0; try--) {
+ lnk = pci_read_config16(dev, cap + PCI_EXP_LNKSTA);
+ if (!(lnk & PCI_EXP_LNKSTA_LT))
+ break;
+ udelay(100);
+ }
+ if (try == 0) {
+ printk(BIOS_ERR, "%s: Link Retrain timeout\n", dev_path(dev));
+ return -1;
+ }
+
/* Start link retraining */
lnk = pci_read_config16(dev, cap + PCI_EXP_LNKCTL);
lnk |= PCI_EXP_LNKCTL_RL;
pci_write_config16(dev, cap + PCI_EXP_LNKCTL, lnk);
/* Wait for training to complete */
- while (try--) {
+ for (try = PCIE_TRAIN_RETRY; try > 0; try--) {
lnk = pci_read_config16(dev, cap + PCI_EXP_LNKSTA);
if (!(lnk & PCI_EXP_LNKSTA_LT))
return 0;