From 9aac537e0e33f4e4f28b8e7472c283fb6460c650 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Mon, 9 Jul 2012 19:49:37 -0600 Subject: PCI: disable MEM decoding while updating 64-bit MEM BARs When we update 64-bit BARs, we have to perform two config writes. Between the writes, the half-written BAR value could match a MEM access intended for another device. This could result in corruption of this device (for writes) or an unexpected response machine check (for reads). To prevent this, disable MEM decoding while updating such BARs. This uses the same safety test as 253d2e5498, which disables both MEM and IO while sizing BARs, namely, we don't disable decoding for host bridge devices. Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-res.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'drivers/pci/setup-res.c') diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index eea85dafc763..1a0e60e265ea 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -30,6 +30,8 @@ void pci_update_resource(struct pci_dev *dev, int resno) { struct pci_bus_region region; + bool disable; + u16 cmd; u32 new, check, mask; int reg; enum pci_bar_type type; @@ -67,6 +69,18 @@ void pci_update_resource(struct pci_dev *dev, int resno) new |= PCI_ROM_ADDRESS_ENABLE; } + /* + * We can't update a 64-bit BAR atomically, so when possible, + * disable decoding so that a half-updated BAR won't conflict + * with another device. + */ + disable = (res->flags & IORESOURCE_MEM_64) && !dev->mmio_always_on; + if (disable) { + pci_read_config_word(dev, PCI_COMMAND, &cmd); + pci_write_config_word(dev, PCI_COMMAND, + cmd & ~PCI_COMMAND_MEMORY); + } + pci_write_config_dword(dev, reg, new); pci_read_config_dword(dev, reg, &check); @@ -84,6 +98,10 @@ void pci_update_resource(struct pci_dev *dev, int resno) "(high %#08x != %#08x)\n", resno, new, check); } } + + if (disable) + pci_write_config_word(dev, PCI_COMMAND, cmd); + res->flags &= ~IORESOURCE_UNSET; dev_dbg(&dev->dev, "BAR %d: set to %pR (PCI address [%#llx-%#llx])\n", resno, res, (unsigned long long)region.start, -- cgit v1.2.3 From d6776e6d5c2f8db0252f447b09736075e1bbe387 Mon Sep 17 00:00:00 2001 From: Nikhil P Rao Date: Wed, 20 Jun 2012 12:56:00 -0700 Subject: PCI: fix truncation of resource size to 32 bits _pci_assign_resource() took an int "size" argument, which meant that sizes larger than 4GB were truncated. Change type to resource_size_t. [bhelgaas: changelog] Signed-off-by: Nikhil P Rao Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-res.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/pci/setup-res.c') diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index eea85dafc763..be76ebacf486 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -206,7 +206,8 @@ static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev, return ret; } -static int _pci_assign_resource(struct pci_dev *dev, int resno, int size, resource_size_t min_align) +static int _pci_assign_resource(struct pci_dev *dev, int resno, + resource_size_t size, resource_size_t min_align) { struct resource *res = dev->resource + resno; struct pci_bus *bus; -- cgit v1.2.3 From fe6dacdb1a31957825c0876de7cdea4c356aca30 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Wed, 11 Jul 2012 17:05:43 -0600 Subject: PCI: reorder __pci_assign_resource() (no change) Reorder functions so __pci_assign_resource(), _pci_assign_resource(), and pci_assign_resource() are closer together. No code change. Signed-off-by: Bjorn Helgaas --- drivers/pci/setup-res.c | 104 ++++++++++++++++++++++++------------------------ 1 file changed, 52 insertions(+), 52 deletions(-) (limited to 'drivers/pci/setup-res.c') diff --git a/drivers/pci/setup-res.c b/drivers/pci/setup-res.c index be76ebacf486..3ce9fa317d1d 100644 --- a/drivers/pci/setup-res.c +++ b/drivers/pci/setup-res.c @@ -127,33 +127,6 @@ void pci_disable_bridge_window(struct pci_dev *dev) pci_write_config_dword(dev, PCI_PREF_BASE_UPPER32, 0xffffffff); } -static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, - int resno, resource_size_t size, resource_size_t align) -{ - struct resource *res = dev->resource + resno; - resource_size_t min; - int ret; - - min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM; - - /* First, try exact prefetching match.. */ - ret = pci_bus_alloc_resource(bus, res, size, align, min, - IORESOURCE_PREFETCH, - pcibios_align_resource, dev); - - if (ret < 0 && (res->flags & IORESOURCE_PREFETCH)) { - /* - * That failed. - * - * But a prefetching area can handle a non-prefetching - * window (it will just not perform as well). - */ - ret = pci_bus_alloc_resource(bus, res, size, align, min, 0, - pcibios_align_resource, dev); - } - return ret; -} - /* * Generic function that returns a value indicating that the device's * original BIOS BAR address was not saved and so is not available for @@ -206,6 +179,33 @@ static int pci_revert_fw_address(struct resource *res, struct pci_dev *dev, return ret; } +static int __pci_assign_resource(struct pci_bus *bus, struct pci_dev *dev, + int resno, resource_size_t size, resource_size_t align) +{ + struct resource *res = dev->resource + resno; + resource_size_t min; + int ret; + + min = (res->flags & IORESOURCE_IO) ? PCIBIOS_MIN_IO : PCIBIOS_MIN_MEM; + + /* First, try exact prefetching match.. */ + ret = pci_bus_alloc_resource(bus, res, size, align, min, + IORESOURCE_PREFETCH, + pcibios_align_resource, dev); + + if (ret < 0 && (res->flags & IORESOURCE_PREFETCH)) { + /* + * That failed. + * + * But a prefetching area can handle a non-prefetching + * window (it will just not perform as well). + */ + ret = pci_bus_alloc_resource(bus, res, size, align, min, 0, + pcibios_align_resource, dev); + } + return ret; +} + static int _pci_assign_resource(struct pci_dev *dev, int resno, resource_size_t size, resource_size_t min_align) { @@ -239,31 +239,6 @@ static int _pci_assign_resource(struct pci_dev *dev, int resno, return ret; } -int pci_reassign_resource(struct pci_dev *dev, int resno, resource_size_t addsize, - resource_size_t min_align) -{ - struct resource *res = dev->resource + resno; - resource_size_t new_size; - int ret; - - if (!res->parent) { - dev_info(&dev->dev, "BAR %d: can't reassign an unassigned resource %pR " - "\n", resno, res); - return -EINVAL; - } - - /* already aligned with min_align */ - new_size = resource_size(res) + addsize; - ret = _pci_assign_resource(dev, resno, new_size, min_align); - if (!ret) { - res->flags &= ~IORESOURCE_STARTALIGN; - dev_info(&dev->dev, "BAR %d: reassigned %pR\n", resno, res); - if (resno < PCI_BRIDGE_RESOURCES) - pci_update_resource(dev, resno); - } - return ret; -} - int pci_assign_resource(struct pci_dev *dev, int resno) { struct resource *res = dev->resource + resno; @@ -299,6 +274,31 @@ int pci_assign_resource(struct pci_dev *dev, int resno) return ret; } +int pci_reassign_resource(struct pci_dev *dev, int resno, resource_size_t addsize, + resource_size_t min_align) +{ + struct resource *res = dev->resource + resno; + resource_size_t new_size; + int ret; + + if (!res->parent) { + dev_info(&dev->dev, "BAR %d: can't reassign an unassigned resource %pR " + "\n", resno, res); + return -EINVAL; + } + + /* already aligned with min_align */ + new_size = resource_size(res) + addsize; + ret = _pci_assign_resource(dev, resno, new_size, min_align); + if (!ret) { + res->flags &= ~IORESOURCE_STARTALIGN; + dev_info(&dev->dev, "BAR %d: reassigned %pR\n", resno, res); + if (resno < PCI_BRIDGE_RESOURCES) + pci_update_resource(dev, resno); + } + return ret; +} + int pci_enable_resources(struct pci_dev *dev, int mask) { u16 cmd, old_cmd; -- cgit v1.2.3