aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYouness Alaoui <youness.alaoui@puri.sm>2017-05-03 17:57:13 -0400
committerMartin Roth <martinroth@google.com>2017-06-09 16:59:30 +0200
commitbb5fb64e11aa7eb6534a5dd5a06d5ea29dc4d411 (patch)
treec298d9acae216d54ecb283b8380ca03570a0b239
parent3c0d7d21efd739101658069683b08c5aab320837 (diff)
pciexp_device: Prevent race condition with retrain link
The PCIe specification[1] describes a race condition that can occur when using the Retrain Link bit in the Link Control Register. The race condition is avoided by checking the retrain link bit in the link status register and waiting until it is set to 0, before initiating a new link retraining. [1] PCI Express Base Specification Revision 3.0 Page 633 Change-Id: I9d5840fb9a6e63838b5a4084d3bbe483f1d870ed Signed-off-by: Youness Alaoui <youness.alaoui@puri.sm> Reviewed-on: https://review.coreboot.org/19556 Tested-by: build bot (Jenkins) <no-reply@coreboot.org> Reviewed-by: Arthur Heymans <arthur@aheymans.xyz> Reviewed-by: Martin Roth <martinroth@google.com>
-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;