Why do we check for "link-up" in *_pcie_valid_device()?
Bjorn Helgaas
helgaas at kernel.org
Fri Dec 15 12:11:00 PST 2017
On Fri, Dec 15, 2017 at 01:04:26PM -0600, Bjorn Helgaas wrote:
> On Fri, Dec 15, 2017 at 01:39:50PM -0500, Jingoo Han wrote:
> > On Thursday, December 14, 2017 5:58 PM, Bjorn Helgaas wrote:
> > > In the PCI config access path, the *_pcie_valid_device() functions in
> > > the dwc, altera, rockchip, and xilinx drivers all check whether the
> > > link is up.
Sorry, Shawn, I included rockchip by mistake; it doesn't actually check for
link-up. I'll try to remember to remove you and linux-rockchip from
the cc list of future emails.
> > > I think this is racy because the link may go down after we check but
> > > before we perform the config access.
> > >
> > > What would blow up if we removed the *_pcie_link_up() checks?
> >
> > The original intention is to avoid config access before link up.
> >
> > Also, I did not find any racy condition as you mentioned.
> > However, if you think that we need to prevent the racy condition,
> > someone can send a patch or add comments.
>
> Here's the race:
>
> pci_read_config_word
> ...
> dw_pcie_rd_conf
> dw_pcie_valid_device
> dw_pcie_link_up # returns true; link currently up
> <-- link goes down for unrelated reason
> dw_pcie_rd_other_conf
> dw_pcie_read
> readl # issue config read
>
> The readl() that issues the config read in the PCIe domain races with
> the link-down event. If the readl() completes first, everything works
> as expected. If the link-down happens first, I don't know what
> happens. This is the core of my question.
>
> The hardware *should* do something sane because link-down is an
> asynchronous event that is unrelated to the config read and may happen
> at any time. It's just part of the PCIe environment and the spec
> defines mechanisms for dealing with and reporting the situation.
To make this concrete, the patch I would propose is below. This isn't
for merging yet; just for discussion and testing.
Jim mentioned that the Broadcom STB host controller effects a
synchronous or asynchronous abort when doing a config access when the
link or power has gone down on the Endpoint. Possibly there's a
similar issue on DesignWare, Altera, and Xilinx.
I think the best way to handle that is to figure out how to deal with
the abort cleanly. Using a test like *_pcie_link_up() to try to avoid
it is a 99% solution that means we'll see rare failures that are very
difficult to reproduce and debug.
diff --git a/drivers/pci/dwc/pcie-designware-host.c b/drivers/pci/dwc/pcie-designware-host.c
index 81e2157a7cfb..3dcdcdd6aa37 100644
--- a/drivers/pci/dwc/pcie-designware-host.c
+++ b/drivers/pci/dwc/pcie-designware-host.c
@@ -524,14 +524,6 @@ static int dw_pcie_wr_other_conf(struct pcie_port *pp, struct pci_bus *bus,
static int dw_pcie_valid_device(struct pcie_port *pp, struct pci_bus *bus,
int dev)
{
- struct dw_pcie *pci = to_dw_pcie_from_pp(pp);
-
- /* If there is no link, then there is no device */
- if (bus->number != pp->root_bus_nr) {
- if (!dw_pcie_link_up(pci))
- return 0;
- }
-
/* access only one slot on each root port */
if (bus->number == pp->root_bus_nr && dev > 0)
return 0;
diff --git a/drivers/pci/host/pcie-altera.c b/drivers/pci/host/pcie-altera.c
index 5cc4f594d79a..17ba6cce9bd0 100644
--- a/drivers/pci/host/pcie-altera.c
+++ b/drivers/pci/host/pcie-altera.c
@@ -140,12 +140,6 @@ static void tlp_write_tx(struct altera_pcie *pcie,
static bool altera_pcie_valid_device(struct altera_pcie *pcie,
struct pci_bus *bus, int dev)
{
- /* If there is no link, then there is no device */
- if (bus->number != pcie->root_bus_nr) {
- if (!altera_pcie_link_up(pcie))
- return false;
- }
-
/* access only one slot on each root port */
if (bus->number == pcie->root_bus_nr && dev > 0)
return false;
diff --git a/drivers/pci/host/pcie-xilinx-nwl.c b/drivers/pci/host/pcie-xilinx-nwl.c
index 65dea98b2643..db94df5db148 100644
--- a/drivers/pci/host/pcie-xilinx-nwl.c
+++ b/drivers/pci/host/pcie-xilinx-nwl.c
@@ -218,12 +218,6 @@ static bool nwl_pcie_valid_device(struct pci_bus *bus, unsigned int devfn)
{
struct nwl_pcie *pcie = bus->sysdata;
- /* Check link before accessing downstream ports */
- if (bus->number != pcie->root_busno) {
- if (!nwl_pcie_link_up(pcie))
- return false;
- }
-
/* Only one device down on each root port */
if (bus->number == pcie->root_busno && devfn > 0)
return false;
diff --git a/drivers/pci/host/pcie-xilinx.c b/drivers/pci/host/pcie-xilinx.c
index 7b5325990f5e..3cdb99708342 100644
--- a/drivers/pci/host/pcie-xilinx.c
+++ b/drivers/pci/host/pcie-xilinx.c
@@ -163,11 +163,6 @@ static bool xilinx_pcie_valid_device(struct pci_bus *bus, unsigned int devfn)
{
struct xilinx_pcie_port *port = bus->sysdata;
- /* Check if link is up when trying to access downstream ports */
- if (bus->number != port->root_busno)
- if (!xilinx_pcie_link_up(port))
- return false;
-
/* Only one device down on each root port */
if (bus->number == port->root_busno && devfn > 0)
return false;
More information about the Linux-rockchip
mailing list