Re: [PATCH v2 11/13] PCI: aardvark: Fix link training

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Tue, Oct 05, 2021 at 08:09:50PM +0200, Marek Behún wrote:
> From: Pali Rohár <pali@xxxxxxxxxx>
> 
> Fix multiple link training issues in aardvark driver. The main reason of
> these issues was misunderstanding of what certain registers do, since their
> names and comments were misleading: before commit 96be36dbffac ("PCI:
> aardvark: Replace custom macros by standard linux/pci_regs.h macros"), the
> pci-aardvark.c driver used custom macros for accessing standard PCIe Root
> Bridge registers, and misleading comments did not help to understand what
> the code was really doing.
> 
> After doing more tests and experiments I've come to the conclusion that the
> SPEED_GEN register in aardvark sets the PCIe revision / generation
> compliance and forces maximal link speed. Both GEN3 and GEN2 values set the
> read-only PCI_EXP_FLAGS_VERS bits (PCIe capabilities version of Root
> Bridge) to value 2, while GEN1 value sets PCI_EXP_FLAGS_VERS to 1, which
> matches with PCI Express specifications revisions 3, 2 and 1 respectively.
> Changing SPEED_GEN also sets the read-only bits PCI_EXP_LNKCAP_SLS and
> PCI_EXP_LNKCAP2_SLS to corresponding speed.
> 
> (Note that PCI Express rev 1 specification does not define PCI_EXP_LNKCAP2
>  and PCI_EXP_LNKCTL2 registers and when SPEED_GEN is set to GEN1 (which
>  also sets PCI_EXP_FLAGS_VERS set to 1), lspci cannot access
>  PCI_EXP_LNKCAP2 and PCI_EXP_LNKCTL2 registers.)
> 
> Changing PCIe link speed can be done via PCI_EXP_LNKCTL2_TLS bits of
> PCI_EXP_LNKCTL2 register. Armada 3700 Functional Specifications says that
> the default value of PCI_EXP_LNKCTL2_TLS is based on SPEED_GEN value, but
> tests showed that the default value is always 8.0 GT/s, independently of
> speed set by SPEED_GEN. So after setting SPEED_GEN, we must also set value
> in PCI_EXP_LNKCTL2 register via PCI_EXP_LNKCTL2_TLS bits.
> 
> Triggering PCI_EXP_LNKCTL_RL bit immediately after setting LINK_TRAINING_EN
> bit actually doesn't do anything. Tests have shown that a delay is needed
> after enabling LINK_TRAINING_EN bit. As triggering PCI_EXP_LNKCTL_RL
> currently does nothing, remove it.
> 
> Commit 43fc679ced18 ("PCI: aardvark: Improve link training") introduced
> code which sets SPEED_GEN register based on negotiated link speed from
> PCI_EXP_LNKSTA_CLS bits of PCI_EXP_LNKSTA register. This code was added to
> fix detection of Compex WLE900VX (Atheros QCA9880) WiFi GEN1 PCIe cards, as
> otherwise these cards were "invisible" on PCIe bus (probably because they
> crashed). But apparently more people reported the same issues with these
> cards also with other PCIe controllers [1] and I was able to reproduce this
> issue also with other "noname" WiFi cards based on Atheros QCA9890 chip
> (with the same PCI vendor/device ids as Atheros QCA9880). So this is not an
> issue in aardvark but rather an issue in Atheros QCA98xx chips. Also, this
> issue only exists if the kernel is compiled with PCIe ASPM support, and a
> generic workaround for this is to change PCIe Bridge to 2.5 GT/s link speed
> via PCI_EXP_LNKCTL2_TLS_2_5GT bits in PCI_EXP_LNKCTL2 register [2], before
> triggering PCI_EXP_LNKCTL_RL bit. This workaround also works when SPEED_GEN
> is set to value GEN2 (5 GT/s). So remove this hack completely in the
> aardvark driver and always set SPEED_GEN to value from 'max-link-speed' DT
> property. Fix for Atheros QCA98xx chips is handled separately by patch [2].
> 
> These two things (code for triggering PCI_EXP_LNKCTL_RL bit and changing
> SPEED_GEN value) also explain why commit 6964494582f5 ("PCI: aardvark:
> Train link immediately after enabling training") somehow fixed detection of
> those problematic Compex cards with Atheros chips: if triggering link
> retraining (via PCI_EXP_LNKCTL_RL bit) was done immediately after enabling
> link training (via LINK_TRAINING_EN), it did nothing. If there was a
> specific delay, aardvark HW already initialized PCIe link and therefore
> triggering link retraining caused the above issue. Compex cards triggered
> link down event and disappeared from the PCIe bus.
> 
> Commit f4c7d053d7f7 ("PCI: aardvark: Wait for endpoint to be ready before
> training link") added 100ms sleep before calling 'Start link training'
> command and explained that it is a requirement of PCI Express
> specification. But the code after this 100ms sleep was not doing 'Start
> link training', rather it triggered PCI_EXP_LNKCTL_RL bit via PCIe Root
> Bridge to put link into Recovery state.
> 
> The required delay after fundamental reset is already done in function
> advk_pcie_wait_for_link() which also checks whether PCIe link is up.
> So after removing the code which triggers PCI_EXP_LNKCTL_RL bit on PCIe
> Root Bridge, there is no need to wait 100ms again. Remove the extra
> msleep() call and update comment about the delay required by the PCI
> Express specification.
> 
> According to Marvell Armada 3700 Functional Specifications, Link training
> should be enabled via aardvark register LINK_TRAINING_EN after selecting
> PCIe generation and x1 lane. There is no need to disable it prior resetting
> card via PERST# signal. This disabling code was introduced in commit
> 5169a9851daa ("PCI: aardvark: Issue PERST via GPIO") as a workaround for
> some Atheros cards. It turns out that this also is Atheros specific issue
> and affects any PCIe controller, not only aardvark. Moreover this Atheros
> issue was triggered by juggling with PCI_EXP_LNKCTL_RL, LINK_TRAINING_EN
> and SPEED_GEN bits interleaved with sleeps. Now, after removing triggering
> PCI_EXP_LNKCTL_RL, there is no need to explicitly disable LINK_TRAINING_EN
> bit. So remove this code too. The problematic Compex cards described in
> previous git commits are correctly detected in advk_pcie_train_link()
> function even after applying all these changes.
> 
> Note that with this patch, and also prior this patch, some NVMe disks which
> support PCIe GEN3 with 8 GT/s speed are negotiated only at the lowest link
> speed 2.5 GT/s, independently of SPEED_GEN value. After manually triggering
> PCI_EXP_LNKCTL_RL bit (e.g. from userspace via setpci), these NVMe disks
> change link speed to 5 GT/s when SPEED_GEN was configured to GEN2. This
> issue first needs to be properly investigated. I will send a fix in the
> future.
> 
> On the other hand, some other GEN2 PCIe cards with 5 GT/s speed are
> autonomously by HW autonegotiated at full 5 GT/s speed without need of any
> software interaction.
> 
> Armada 3700 Functional Specifications describes the following steps for
> link training: set SPEED_GEN to GEN2, enable LINK_TRAINING_EN, poll until
> link training is complete, trigger PCI_EXP_LNKCTL_RL, poll until signal
> rate is 5 GT/s, poll until link training is complete, enable ASPM L0s.
> 
> The requirement for triggering PCI_EXP_LNKCTL_RL can be explained by the
> need to achieve 5 GT/s speed (as changing link speed is done by throw to
> recovery state entered by PCI_EXP_LNKCTL_RL) or maybe as a part of enabling
> ASPM L0s (but in this case ASPM L0s should have been enabled prior
> PCI_EXP_LNKCTL_RL).
> 
> It is unknown why the original pci-aardvark.c driver was triggering
> PCI_EXP_LNKCTL_RL bit before waiting for the link to be up. This does not
> align with neither PCIe base specifications nor with Armada 3700 Functional
> Specification. (Note that in older versions of aardvark, this bit was
> called incorrectly PCIE_CORE_LINK_TRAINING, so this may be the reason.)
> 
> It is also unknown why Armada 3700 Functional Specification says that it is
> needed to trigger PCI_EXP_LNKCTL_RL for GEN2 mode, as according to PCIe
> base specification 5 GT/s speed negotiation is supposed to be entirely
> autonomous, even if initial speed is 2.5 GT/s.
> 
> [1] - https://lore.kernel.org/linux-pci/87h7l8axqp.fsf@xxxxxxx/
> [2] - https://lore.kernel.org/linux-pci/20210326124326.21163-1-pali@xxxxxxxxxx/
> 
> Signed-off-by: Pali Rohár <pali@xxxxxxxxxx>
> Reviewed-by: Marek Behún <kabel@xxxxxxxxxx>
> Signed-off-by: Marek Behún <kabel@xxxxxxxxxx>
> Cc: stable@xxxxxxxxxxxxxxx # f4c7d053d7f7 ("PCI: aardvark: Wait for endpoint to be ready before training link")
> Cc: stable@xxxxxxxxxxxxxxx # 6964494582f5 ("PCI: aardvark: Train link immediately after enabling training")
> Cc: stable@xxxxxxxxxxxxxxx # 43fc679ced18 ("PCI: aardvark: Improve link training")
> Cc: stable@xxxxxxxxxxxxxxx # 5169a9851daa ("PCI: aardvark: Issue PERST via GPIO")
> Cc: stable@xxxxxxxxxxxxxxx # 96be36dbffac ("PCI: aardvark: Replace custom macros by standard linux/pci_regs.h macros")
> Cc: stable@xxxxxxxxxxxxxxx # d0c6a3475b03 ("PCI: aardvark: Move PCIe reset card code to advk_pcie_train_link()")
> Cc: stable@xxxxxxxxxxxxxxx # 1d1cd163d0de ("PCI: aardvark: Update comment about disabling link training")
> ---
>  drivers/pci/controller/pci-aardvark.c | 117 ++++++++------------------
>  1 file changed, 34 insertions(+), 83 deletions(-)

This is a very convoluted fix; it is well explained but the number
of patches going into stable and the complexity of it make me ask
how confident you are this won't trigger any regressions.

It is just a heads-up to make you think whether it is better to
hold the stable tags till we are confident enough.

Please let me know.

Thanks,
Lorenzo

> 
> diff --git a/drivers/pci/controller/pci-aardvark.c b/drivers/pci/controller/pci-aardvark.c
> index 9c509d5a9afa..2c66cdbb8dd6 100644
> --- a/drivers/pci/controller/pci-aardvark.c
> +++ b/drivers/pci/controller/pci-aardvark.c
> @@ -258,11 +258,6 @@ static inline u32 advk_readl(struct advk_pcie *pcie, u64 reg)
>  	return readl(pcie->base + reg);
>  }
>  
> -static inline u16 advk_read16(struct advk_pcie *pcie, u64 reg)
> -{
> -	return advk_readl(pcie, (reg & ~0x3)) >> ((reg & 0x3) * 8);
> -}
> -
>  static int advk_pcie_link_up(struct advk_pcie *pcie)
>  {
>  	u32 val, ltssm_state;
> @@ -300,23 +295,9 @@ static void advk_pcie_wait_for_retrain(struct advk_pcie *pcie)
>  
>  static void advk_pcie_issue_perst(struct advk_pcie *pcie)
>  {
> -	u32 reg;
> -
>  	if (!pcie->reset_gpio)
>  		return;
>  
> -	/*
> -	 * As required by PCI Express spec (PCI Express Base Specification, REV.
> -	 * 4.0 PCI Express, February 19 2014, 6.6.1 Conventional Reset) a delay
> -	 * for at least 100ms after de-asserting PERST# signal is needed before
> -	 * link training is enabled. So ensure that link training is disabled
> -	 * prior de-asserting PERST# signal to fulfill that PCI Express spec
> -	 * requirement.
> -	 */
> -	reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
> -	reg &= ~LINK_TRAINING_EN;
> -	advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
> -
>  	/* 10ms delay is needed for some cards */
>  	dev_info(&pcie->pdev->dev, "issuing PERST via reset GPIO for 10ms\n");
>  	gpiod_set_value_cansleep(pcie->reset_gpio, 1);
> @@ -324,53 +305,46 @@ static void advk_pcie_issue_perst(struct advk_pcie *pcie)
>  	gpiod_set_value_cansleep(pcie->reset_gpio, 0);
>  }
>  
> -static int advk_pcie_train_at_gen(struct advk_pcie *pcie, int gen)
> +static void advk_pcie_train_link(struct advk_pcie *pcie)
>  {
> -	int ret, neg_gen;
> +	struct device *dev = &pcie->pdev->dev;
>  	u32 reg;
> +	int ret;
>  
> -	/* Setup link speed */
> +	/*
> +	 * Setup PCIe rev / gen compliance based on device tree property
> +	 * 'max-link-speed' which also forces maximal link speed.
> +	 */
>  	reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
>  	reg &= ~PCIE_GEN_SEL_MSK;
> -	if (gen == 3)
> +	if (pcie->link_gen == 3)
>  		reg |= SPEED_GEN_3;
> -	else if (gen == 2)
> +	else if (pcie->link_gen == 2)
>  		reg |= SPEED_GEN_2;
>  	else
>  		reg |= SPEED_GEN_1;
>  	advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
>  
>  	/*
> -	 * Enable link training. This is not needed in every call to this
> -	 * function, just once suffices, but it does not break anything either.
> +	 * Set maximal link speed value also into PCIe Link Control 2 register.
> +	 * Armada 3700 Functional Specification says that default value is based
> +	 * on SPEED_GEN but tests showed that default value is always 8.0 GT/s.
>  	 */
> +	reg = advk_readl(pcie, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKCTL2);
> +	reg &= ~PCI_EXP_LNKCTL2_TLS;
> +	if (pcie->link_gen == 3)
> +		reg |= PCI_EXP_LNKCTL2_TLS_8_0GT;
> +	else if (pcie->link_gen == 2)
> +		reg |= PCI_EXP_LNKCTL2_TLS_5_0GT;
> +	else
> +		reg |= PCI_EXP_LNKCTL2_TLS_2_5GT;
> +	advk_writel(pcie, reg, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKCTL2);
> +
> +	/* Enable link training after selecting PCIe generation */
>  	reg = advk_readl(pcie, PCIE_CORE_CTRL0_REG);
>  	reg |= LINK_TRAINING_EN;
>  	advk_writel(pcie, reg, PCIE_CORE_CTRL0_REG);
>  
> -	/*
> -	 * Start link training immediately after enabling it.
> -	 * This solves problems for some buggy cards.
> -	 */
> -	reg = advk_readl(pcie, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKCTL);
> -	reg |= PCI_EXP_LNKCTL_RL;
> -	advk_writel(pcie, reg, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKCTL);
> -
> -	ret = advk_pcie_wait_for_link(pcie);
> -	if (ret)
> -		return ret;
> -
> -	reg = advk_read16(pcie, PCIE_CORE_PCIEXP_CAP + PCI_EXP_LNKSTA);
> -	neg_gen = reg & PCI_EXP_LNKSTA_CLS;
> -
> -	return neg_gen;
> -}
> -
> -static void advk_pcie_train_link(struct advk_pcie *pcie)
> -{
> -	struct device *dev = &pcie->pdev->dev;
> -	int neg_gen = -1, gen;
> -
>  	/*
>  	 * Reset PCIe card via PERST# signal. Some cards are not detected
>  	 * during link training when they are in some non-initial state.
> @@ -381,41 +355,18 @@ static void advk_pcie_train_link(struct advk_pcie *pcie)
>  	 * PERST# signal could have been asserted by pinctrl subsystem before
>  	 * probe() callback has been called or issued explicitly by reset gpio
>  	 * function advk_pcie_issue_perst(), making the endpoint going into
> -	 * fundamental reset. As required by PCI Express spec a delay for at
> -	 * least 100ms after such a reset before link training is needed.
> -	 */
> -	msleep(PCI_PM_D3COLD_WAIT);
> -
> -	/*
> -	 * Try link training at link gen specified by device tree property
> -	 * 'max-link-speed'. If this fails, iteratively train at lower gen.
> -	 */
> -	for (gen = pcie->link_gen; gen > 0; --gen) {
> -		neg_gen = advk_pcie_train_at_gen(pcie, gen);
> -		if (neg_gen > 0)
> -			break;
> -	}
> -
> -	if (neg_gen < 0)
> -		goto err;
> -
> -	/*
> -	 * After successful training if negotiated gen is lower than requested,
> -	 * train again on negotiated gen. This solves some stability issues for
> -	 * some buggy gen1 cards.
> +	 * fundamental reset. As required by PCI Express spec (PCI Express
> +	 * Base Specification, REV. 4.0 PCI Express, February 19 2014, 6.6.1
> +	 * Conventional Reset) a delay for at least 100ms after such a reset
> +	 * before sending a Configuration Request to the device is needed.
> +	 * So wait until PCIe link is up. Function advk_pcie_wait_for_link()
> +	 * waits for link at least 900ms.
>  	 */
> -	if (neg_gen < gen) {
> -		gen = neg_gen;
> -		neg_gen = advk_pcie_train_at_gen(pcie, gen);
> -	}
> -
> -	if (neg_gen == gen) {
> -		dev_info(dev, "link up at gen %i\n", gen);
> -		return;
> -	}
> -
> -err:
> -	dev_err(dev, "link never came up\n");
> +	ret = advk_pcie_wait_for_link(pcie);
> +	if (ret < 0)
> +		dev_err(dev, "link never came up\n");
> +	else
> +		dev_info(dev, "link up\n");
>  }
>  
>  /*
> -- 
> 2.32.0
> 



[Index of Archives]     [DMA Engine]     [Linux Coverity]     [Linux USB]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Greybus]

  Powered by Linux