[PATCH] pci: Work around PCIe link training failures

Pali Rohár pali at kernel.org
Sun Nov 14 16:41:52 CET 2021


Hello Maciej!

On Sunday 14 November 2021 15:16:47 Maciej W. Rozycki wrote:
> +	dm_pci_read_config32(dev, pcie_off + PCI_EXP_LNKCAP, &exp_lnkcap);
> +	dm_pci_read_config32(dev, pcie_off + PCI_EXP_LNKCAP2, &exp_lnkcap2);
> +	for (speed = (exp_lnkcap & PCI_EXP_LNKCAP_SLS) - 1;
> +	     speed >= PCI_EXP_LNKCAP_SLS_2_5GB;
> +	     speed--) {
> +		if (exp_lnkcap2 && (exp_lnkcap2 & 1 << speed) == 0)
> +			continue;

This check is not correct because lnkcap2 is optional also when lnkctl2
is implemented. IIRC unimplemented lnkcap2 with implemented lnkctl2 can
happen only for link speeds <= 5GT/s and when lnkcap2 returns zero then
software should detect speeds from lnkcap register. And I have such PCIe
GEN2 HW where PCIe Root Ports implements lnkctl2, but lnkcap2 returns
zeros. So it is not corner case.

> +		debug("PCI Autoconfig: %02x.%02x.%02x: Trying speed: %u\n",
> +		      PCI_BUS(bdf), PCI_DEV(bdf), PCI_FUNC(bdf), speed);
> +
> +		dm_pci_write_config16(dev, pcie_off + PCI_EXP_LNKCTL2,
> +				      exp_lnkctl2 | speed);
> +		dm_pci_write_config16(dev, pcie_off + PCI_EXP_LNKCTL,
> +				      exp_lnkctl | PCI_EXP_LNKCTL_RETRLNK);
> +
> +		if (dm_pciauto_exp_link_stable(dev, pcie_off))
> +			return;
> +	}
> +}
> +
>  void dm_pciauto_prescan_setup_bridge(struct udevice *dev, int sub_bus)
>  {
>  	struct pci_region *pci_mem;
> @@ -189,6 +331,7 @@ void dm_pciauto_prescan_setup_bridge(str
>  	u8 io_32;
>  	struct udevice *ctlr = pci_get_controller(dev);
>  	struct pci_controller *ctlr_hose = dev_get_uclass_priv(ctlr);
> +	int pcie_off;
>  
>  	pci_mem = ctlr_hose->pci_mem;
>  	pci_prefetch = ctlr_hose->pci_prefetch;
> @@ -267,6 +410,11 @@ void dm_pciauto_prescan_setup_bridge(str
>  		cmdstat |= PCI_COMMAND_IO;
>  	}
>  
> +	/* For PCIe devices see if we need to retrain the link by hand */
> +	pcie_off = dm_pci_find_capability(dev, PCI_CAP_ID_EXP);
> +	if (pcie_off)
> +		dm_pciauto_exp_fixup_link(dev, pcie_off);
> +
>  	/* Enable memory and I/O accesses, enable bus master */
>  	dm_pci_write_config16(dev, PCI_COMMAND, cmdstat | PCI_COMMAND_MASTER);

This code cannot be enabled globally for all PCIe devices. There are
more PCIe cards which do not like retraining link and touching link
retrain bit in downstream port break them (= power on/off is needed).

Could you bring this discussion to Linux PCI list? I guess that same
issue had to be fixed also in Linux kernel and it would be great to have
same fix in both projects.

For me it looks like Asmedia specific bug and so it should be applied
only for affected Asmedia devices based on PCI vendor/device ids.


More information about the U-Boot mailing list