PCI: fix P2P bridge I/O port window sign extension
authorBjorn Helgaas <bhelgaas@google.com>
Tue, 19 Jun 2012 13:45:44 +0000 (07:45 -0600)
committerBjorn Helgaas <bhelgaas@google.com>
Wed, 20 Jun 2012 23:28:53 +0000 (17:28 -0600)
On P2P bridges with 32-bit I/O decoding, we incorrectly sign-extended
windows starting at 0x80000000 or above.  In "base |= (io_base_hi << 16)",
"io_base_hi" is promoted to a signed int before being extended to an
unsigned long.

This would cause a window starting at I/O address 0x80000000 to be
treated as though it started at 0xffffffff80008000 instead, which
should cause "no compatible bridge window" errors when we enumerate
devices using that I/O space.

The mmio and mmio_pref casts are not strictly necessary, but without
them, correctness depends on the types of the PCI_MEMORY_RANGE_MASK and
PCI_PREF_RANGE_MASK constants, which are not obvious from reading the
local code.

Found by Coverity (CID 138747 and CID 138748).

Signed-off-by: Bjorn Helgaas <bhelgaas@google.com>
drivers/pci/probe.c

index 658ac97..a7a504f 100644 (file)
@@ -281,10 +281,11 @@ static void __devinit pci_read_bridge_io(struct pci_bus *child)
 
        if ((io_base_lo & PCI_IO_RANGE_TYPE_MASK) == PCI_IO_RANGE_TYPE_32) {
                u16 io_base_hi, io_limit_hi;
+
                pci_read_config_word(dev, PCI_IO_BASE_UPPER16, &io_base_hi);
                pci_read_config_word(dev, PCI_IO_LIMIT_UPPER16, &io_limit_hi);
-               base |= (io_base_hi << 16);
-               limit |= (io_limit_hi << 16);
+               base |= ((unsigned long) io_base_hi << 16);
+               limit |= ((unsigned long) io_limit_hi << 16);
        }
 
        if (base && base <= limit) {
@@ -312,8 +313,8 @@ static void __devinit pci_read_bridge_mmio(struct pci_bus *child)
        res = child->resource[1];
        pci_read_config_word(dev, PCI_MEMORY_BASE, &mem_base_lo);
        pci_read_config_word(dev, PCI_MEMORY_LIMIT, &mem_limit_lo);
-       base = (mem_base_lo & PCI_MEMORY_RANGE_MASK) << 16;
-       limit = (mem_limit_lo & PCI_MEMORY_RANGE_MASK) << 16;
+       base = ((unsigned long) mem_base_lo & PCI_MEMORY_RANGE_MASK) << 16;
+       limit = ((unsigned long) mem_limit_lo & PCI_MEMORY_RANGE_MASK) << 16;
        if (base && base <= limit) {
                res->flags = (mem_base_lo & PCI_MEMORY_RANGE_TYPE_MASK) | IORESOURCE_MEM;
                region.start = base;
@@ -334,11 +335,12 @@ static void __devinit pci_read_bridge_mmio_pref(struct pci_bus *child)
        res = child->resource[2];
        pci_read_config_word(dev, PCI_PREF_MEMORY_BASE, &mem_base_lo);
        pci_read_config_word(dev, PCI_PREF_MEMORY_LIMIT, &mem_limit_lo);
-       base = (mem_base_lo & PCI_PREF_RANGE_MASK) << 16;
-       limit = (mem_limit_lo & PCI_PREF_RANGE_MASK) << 16;
+       base = ((unsigned long) mem_base_lo & PCI_PREF_RANGE_MASK) << 16;
+       limit = ((unsigned long) mem_limit_lo & PCI_PREF_RANGE_MASK) << 16;
 
        if ((mem_base_lo & PCI_PREF_RANGE_TYPE_MASK) == PCI_PREF_RANGE_TYPE_64) {
                u32 mem_base_hi, mem_limit_hi;
+
                pci_read_config_dword(dev, PCI_PREF_BASE_UPPER32, &mem_base_hi);
                pci_read_config_dword(dev, PCI_PREF_LIMIT_UPPER32, &mem_limit_hi);
 
@@ -349,8 +351,8 @@ static void __devinit pci_read_bridge_mmio_pref(struct pci_bus *child)
                 */
                if (mem_base_hi <= mem_limit_hi) {
 #if BITS_PER_LONG == 64
-                       base |= ((long) mem_base_hi) << 32;
-                       limit |= ((long) mem_limit_hi) << 32;
+                       base |= ((unsigned long) mem_base_hi) << 32;
+                       limit |= ((unsigned long) mem_limit_hi) << 32;
 #else
                        if (mem_base_hi || mem_limit_hi) {
                                dev_err(&dev->dev, "can't handle 64-bit "