]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/pci/pcie/portdrv_core.c
ARM: OMAP3: Clean up spurious interrupt check logic
[linux-2.6-omap-h63xx.git] / drivers / pci / pcie / portdrv_core.c
index 8b3f8c18032f3a295c5af3ab7ab09ca0e11b2fc4..e39982503863e4a1539d4f1f968a9dbf47bc3242 100644 (file)
 #include <linux/slab.h>
 #include <linux/pcieport_if.h>
 
+#include "../pci.h"
 #include "portdrv.h"
 
-extern int pcie_mch_quirk;     /* MSI-quirk Indicator */
-
 /**
  * release_pcie_device - free PCI Express port service device structure
  * @dev: Port service device to release
@@ -31,26 +30,150 @@ static void release_pcie_device(struct device *dev)
        kfree(to_pcie_device(dev));                     
 }
 
-static int is_msi_quirked(struct pci_dev *dev)
+/**
+ * pcie_port_msix_add_entry - add entry to given array of MSI-X entries
+ * @entries: Array of MSI-X entries
+ * @new_entry: Index of the entry to add to the array
+ * @nr_entries: Number of entries aleady in the array
+ *
+ * Return value: Position of the added entry in the array
+ */
+static int pcie_port_msix_add_entry(
+       struct msix_entry *entries, int new_entry, int nr_entries)
 {
-       int port_type, quirk = 0;
+       int j;
+
+       for (j = 0; j < nr_entries; j++)
+               if (entries[j].entry == new_entry)
+                       return j;
+
+       entries[j].entry = new_entry;
+       return j;
+}
+
+/**
+ * pcie_port_enable_msix - try to set up MSI-X as interrupt mode for given port
+ * @dev: PCI Express port to handle
+ * @vectors: Array of interrupt vectors to populate
+ * @mask: Bitmask of port capabilities returned by get_port_device_capability()
+ *
+ * Return value: 0 on success, error code on failure
+ */
+static int pcie_port_enable_msix(struct pci_dev *dev, int *vectors, int mask)
+{
+       struct msix_entry *msix_entries;
+       int idx[PCIE_PORT_DEVICE_MAXSERVICES];
+       int nr_entries, status, pos, i, nvec;
        u16 reg16;
+       u32 reg32;
 
-       pci_read_config_word(dev, 
-               pci_find_capability(dev, PCI_CAP_ID_EXP) + 
-               PCIE_CAPABILITIES_REG, &reg16);
-       port_type = (reg16 >> 4) & PORT_TYPE_MASK;
-       switch(port_type) {
-       case PCIE_RC_PORT:
-               if (pcie_mch_quirk == 1)
-                       quirk = 1;
-               break;
-       case PCIE_SW_UPSTREAM_PORT:
-       case PCIE_SW_DOWNSTREAM_PORT:
-       default:
-               break;  
+       nr_entries = pci_msix_table_size(dev);
+       if (!nr_entries)
+               return -EINVAL;
+       if (nr_entries > PCIE_PORT_MAX_MSIX_ENTRIES)
+               nr_entries = PCIE_PORT_MAX_MSIX_ENTRIES;
+
+       msix_entries = kzalloc(sizeof(*msix_entries) * nr_entries, GFP_KERNEL);
+       if (!msix_entries)
+               return -ENOMEM;
+
+       /*
+        * Allocate as many entries as the port wants, so that we can check
+        * which of them will be useful.  Moreover, if nr_entries is correctly
+        * equal to the number of entries this port actually uses, we'll happily
+        * go through without any tricks.
+        */
+       for (i = 0; i < nr_entries; i++)
+               msix_entries[i].entry = i;
+
+       status = pci_enable_msix(dev, msix_entries, nr_entries);
+       if (status)
+               goto Exit;
+
+       for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
+               idx[i] = -1;
+       status = -EIO;
+       nvec = 0;
+
+       if (mask & (PCIE_PORT_SERVICE_PME | PCIE_PORT_SERVICE_HP)) {
+               int entry;
+
+               /*
+                * The code below follows the PCI Express Base Specification 2.0
+                * stating in Section 6.1.6 that "PME and Hot-Plug Event
+                * interrupts (when both are implemented) always share the same
+                * MSI or MSI-X vector, as indicated by the Interrupt Message
+                * Number field in the PCI Express Capabilities register", where
+                * according to Section 7.8.2 of the specification "For MSI-X,
+                * the value in this field indicates which MSI-X Table entry is
+                * used to generate the interrupt message."
+                */
+               pos = pci_find_capability(dev, PCI_CAP_ID_EXP);
+               pci_read_config_word(dev, pos + PCIE_CAPABILITIES_REG, &reg16);
+               entry = (reg16 >> 9) & PCIE_PORT_MSI_VECTOR_MASK;
+               if (entry >= nr_entries)
+                       goto Error;
+
+               i = pcie_port_msix_add_entry(msix_entries, entry, nvec);
+               if (i == nvec)
+                       nvec++;
+
+               idx[PCIE_PORT_SERVICE_PME_SHIFT] = i;
+               idx[PCIE_PORT_SERVICE_HP_SHIFT] = i;
+       }
+
+       if (mask & PCIE_PORT_SERVICE_AER) {
+               int entry;
+
+               /*
+                * The code below follows Section 7.10.10 of the PCI Express
+                * Base Specification 2.0 stating that bits 31-27 of the Root
+                * Error Status Register contain a value indicating which of the
+                * MSI/MSI-X vectors assigned to the port is going to be used
+                * for AER, where "For MSI-X, the value in this register
+                * indicates which MSI-X Table entry is used to generate the
+                * interrupt message."
+                */
+               pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR);
+               pci_read_config_dword(dev, pos + PCI_ERR_ROOT_STATUS, &reg32);
+               entry = reg32 >> 27;
+               if (entry >= nr_entries)
+                       goto Error;
+
+               i = pcie_port_msix_add_entry(msix_entries, entry, nvec);
+               if (i == nvec)
+                       nvec++;
+
+               idx[PCIE_PORT_SERVICE_AER_SHIFT] = i;
        }
-       return quirk;
+
+       /*
+        * If nvec is equal to the allocated number of entries, we can just use
+        * what we have.  Otherwise, the port has some extra entries not for the
+        * services we know and we need to work around that.
+        */
+       if (nvec == nr_entries) {
+               status = 0;
+       } else {
+               /* Drop the temporary MSI-X setup */
+               pci_disable_msix(dev);
+
+               /* Now allocate the MSI-X vectors for real */
+               status = pci_enable_msix(dev, msix_entries, nvec);
+               if (status)
+                       goto Exit;
+       }
+
+       for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
+               vectors[i] = idx[i] >= 0 ? msix_entries[idx[i]].vector : -1;
+
+ Exit:
+       kfree(msix_entries);
+       return status;
+
+ Error:
+       pci_disable_msix(dev);
+       goto Exit;
 }
 
 /**
@@ -64,47 +187,32 @@ static int is_msi_quirked(struct pci_dev *dev)
  */
 static int assign_interrupt_mode(struct pci_dev *dev, int *vectors, int mask)
 {
-       int i, pos, nvec, status = -EINVAL;
-       int interrupt_mode = PCIE_PORT_INTx_MODE;
+       struct pcie_port_data *port_data = pci_get_drvdata(dev);
+       int irq, interrupt_mode = PCIE_PORT_NO_IRQ;
+       int i;
 
-       /* Set INTx as default */
-       for (i = 0, nvec = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
-               if (mask & (1 << i)) 
-                       nvec++;
-               vectors[i] = dev->irq;
-       }
-       
        /* Check MSI quirk */
-       if (is_msi_quirked(dev))
-               return interrupt_mode;
-
-       /* Select MSI-X over MSI if supported */                
-       pos = pci_find_capability(dev, PCI_CAP_ID_MSIX);
-       if (pos) {
-               struct msix_entry msix_entries[PCIE_PORT_DEVICE_MAXSERVICES] = 
-                       {{0, 0}, {0, 1}, {0, 2}, {0, 3}};
-               status = pci_enable_msix(dev, msix_entries, nvec);
-               if (!status) {
-                       int j = 0;
-
-                       interrupt_mode = PCIE_PORT_MSIX_MODE;
-                       for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
-                               if (mask & (1 << i)) 
-                                       vectors[i] = msix_entries[j++].vector;
-                       }
-               }
-       } 
-       if (status) {
-               pos = pci_find_capability(dev, PCI_CAP_ID_MSI);
-               if (pos) {
-                       status = pci_enable_msi(dev);
-                       if (!status) {
-                               interrupt_mode = PCIE_PORT_MSI_MODE;
-                               for (i = 0;i < PCIE_PORT_DEVICE_MAXSERVICES;i++)
-                                       vectors[i] = dev->irq;
-                       }
-               }
-       } 
+       if (port_data->port_type == PCIE_RC_PORT && pcie_mch_quirk)
+               goto Fallback;
+
+       /* Try to use MSI-X if supported */
+       if (!pcie_port_enable_msix(dev, vectors, mask))
+               return PCIE_PORT_MSIX_MODE;
+
+       /* We're not going to use MSI-X, so try MSI and fall back to INTx */
+       if (!pci_enable_msi(dev))
+               interrupt_mode = PCIE_PORT_MSI_MODE;
+
+ Fallback:
+       if (interrupt_mode == PCIE_PORT_NO_IRQ && dev->pin)
+               interrupt_mode = PCIE_PORT_INTx_MODE;
+
+       irq = interrupt_mode != PCIE_PORT_NO_IRQ ? dev->irq : -1;
+       for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++)
+               vectors[i] = irq;
+
+       vectors[PCIE_PORT_SERVICE_VC_SHIFT] = -1;
+
        return interrupt_mode;
 }
 
@@ -132,13 +240,11 @@ static int get_port_device_capability(struct pci_dev *dev)
                        pos + PCIE_SLOT_CAPABILITIES_REG, &reg32);
                if (reg32 & SLOT_HP_CAPABLE_MASK)
                        services |= PCIE_PORT_SERVICE_HP;
-       } 
-       /* PME Capable - root port capability */
-       if (((reg16 >> 4) & PORT_TYPE_MASK) == PCIE_RC_PORT)
-               services |= PCIE_PORT_SERVICE_PME;
-
+       }
+       /* AER capable */
        if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_ERR))
                services |= PCIE_PORT_SERVICE_AER;
+       /* VC support */
        if (pci_find_ext_capability(dev, PCI_EXT_CAP_ID_VC))
                services |= PCIE_PORT_SERVICE_VC;
 
@@ -152,20 +258,17 @@ static int get_port_device_capability(struct pci_dev *dev)
  * @port_type: Type of the port
  * @service_type: Type of service to associate with the service device
  * @irq: Interrupt vector to associate with the service device
- * @irq_mode: Interrupt mode of the service (INTx, MSI-X, MSI)
  */
 static void pcie_device_init(struct pci_dev *parent, struct pcie_device *dev, 
-       int port_type, int service_type, int irq, int irq_mode)
+       int service_type, int irq)
 {
+       struct pcie_port_data *port_data = pci_get_drvdata(parent);
        struct device *device;
+       int port_type = port_data->port_type;
 
        dev->port = parent;
-       dev->interrupt_mode = irq_mode;
        dev->irq = irq;
-       dev->id.vendor = parent->vendor;
-       dev->id.device = parent->device;
-       dev->id.port_type = port_type;
-       dev->id.service_type = (1 << service_type);
+       dev->service = service_type;
 
        /* Initialize generic device interface */
        device = &dev->device;
@@ -185,10 +288,9 @@ static void pcie_device_init(struct pci_dev *parent, struct pcie_device *dev,
  * @port_type: Type of the port
  * @service_type: Type of service to associate with the service device
  * @irq: Interrupt vector to associate with the service device
- * @irq_mode: Interrupt mode of the service (INTx, MSI-X, MSI)
  */
 static struct pcie_device* alloc_pcie_device(struct pci_dev *parent,
-       int port_type, int service_type, int irq, int irq_mode)
+       int service_type, int irq)
 {
        struct pcie_device *device;
 
@@ -196,7 +298,7 @@ static struct pcie_device* alloc_pcie_device(struct pci_dev *parent,
        if (!device)
                return NULL;
 
-       pcie_device_init(parent, device, port_type, service_type, irq,irq_mode);
+       pcie_device_init(parent, device, service_type, irq);
        return device;
 }
 
@@ -230,63 +332,90 @@ int pcie_port_device_probe(struct pci_dev *dev)
  */
 int pcie_port_device_register(struct pci_dev *dev)
 {
-       struct pcie_port_device_ext *p_ext;
-       int status, type, capabilities, irq_mode, i;
+       struct pcie_port_data *port_data;
+       int status, capabilities, irq_mode, i, nr_serv;
        int vectors[PCIE_PORT_DEVICE_MAXSERVICES];
        u16 reg16;
 
-       /* Allocate port device extension */
-       if (!(p_ext = kmalloc(sizeof(struct pcie_port_device_ext), GFP_KERNEL)))
+       port_data = kzalloc(sizeof(*port_data), GFP_KERNEL);
+       if (!port_data)
                return -ENOMEM;
-
-       pci_set_drvdata(dev, p_ext);
+       pci_set_drvdata(dev, port_data);
 
        /* Get port type */
        pci_read_config_word(dev,
                pci_find_capability(dev, PCI_CAP_ID_EXP) +
                PCIE_CAPABILITIES_REG, &reg16);
-       type = (reg16 >> 4) & PORT_TYPE_MASK;
+       port_data->port_type = (reg16 >> 4) & PORT_TYPE_MASK;
 
-       /* Now get port services */
        capabilities = get_port_device_capability(dev);
+       /* Root ports are capable of generating PME too */
+       if (port_data->port_type == PCIE_RC_PORT)
+               capabilities |= PCIE_PORT_SERVICE_PME;
+
        irq_mode = assign_interrupt_mode(dev, vectors, capabilities);
-       p_ext->interrupt_mode = irq_mode;
+       if (irq_mode == PCIE_PORT_NO_IRQ) {
+               /*
+                * Don't use service devices that require interrupts if there is
+                * no way to generate them.
+                */
+               if (!(capabilities & PCIE_PORT_SERVICE_VC)) {
+                       status = -ENODEV;
+                       goto Error;
+               }
+               capabilities = PCIE_PORT_SERVICE_VC;
+       }
+       port_data->port_irq_mode = irq_mode;
+
+       status = pci_enable_device(dev);
+       if (status)
+               goto Error;
+       pci_set_master(dev);
 
        /* Allocate child services if any */
-       for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
+       for (i = 0, nr_serv = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) {
                struct pcie_device *child;
+               int service = 1 << i;
+
+               if (!(capabilities & service))
+                       continue;
 
-               if (capabilities & (1 << i)) {
-                       child = alloc_pcie_device(
-                               dev,            /* parent */
-                               type,           /* port type */
-                               i,              /* service type */
-                               vectors[i],     /* irq */
-                               irq_mode        /* interrupt mode */);
-                       if (child) {
-                               status = device_register(&child->device);
-                               if (status) {
-                                       kfree(child);
-                                       continue;
-                               }
-                               get_device(&child->device);
-                       }
+               child = alloc_pcie_device(dev, service, vectors[i]);
+               if (!child)
+                       continue;
+
+               status = device_register(&child->device);
+               if (status) {
+                       kfree(child);
+                       continue;
                }
+
+               get_device(&child->device);
+               nr_serv++;
+       }
+       if (!nr_serv) {
+               pci_disable_device(dev);
+               status = -ENODEV;
+               goto Error;
        }
+
        return 0;
+
+ Error:
+       kfree(port_data);
+       return status;
 }
 
 #ifdef CONFIG_PM
 static int suspend_iter(struct device *dev, void *data)
 {
        struct pcie_port_service_driver *service_driver;
-       pm_message_t state = * (pm_message_t *) data;
 
        if ((dev->bus == &pcie_port_bus_type) &&
            (dev->driver)) {
                service_driver = to_service_driver(dev->driver);
                if (service_driver->suspend)
-                       service_driver->suspend(to_pcie_device(dev), state);
+                       service_driver->suspend(to_pcie_device(dev));
        }
        return 0;
 }
@@ -294,11 +423,10 @@ static int suspend_iter(struct device *dev, void *data)
 /**
  * pcie_port_device_suspend - suspend port services associated with a PCIe port
  * @dev: PCI Express port to handle
- * @state: Representation of system power management transition in progress
  */
-int pcie_port_device_suspend(struct pci_dev *dev, pm_message_t state)
+int pcie_port_device_suspend(struct device *dev)
 {
-       return device_for_each_child(&dev->dev, &state, suspend_iter);
+       return device_for_each_child(dev, NULL, suspend_iter);
 }
 
 static int resume_iter(struct device *dev, void *data)
@@ -318,24 +446,17 @@ static int resume_iter(struct device *dev, void *data)
  * pcie_port_device_suspend - resume port services associated with a PCIe port
  * @dev: PCI Express port to handle
  */
-int pcie_port_device_resume(struct pci_dev *dev)
+int pcie_port_device_resume(struct device *dev)
 {
-       return device_for_each_child(&dev->dev, NULL, resume_iter);
+       return device_for_each_child(dev, NULL, resume_iter);
 }
-#endif
+#endif /* PM */
 
 static int remove_iter(struct device *dev, void *data)
 {
-       struct pcie_port_service_driver *service_driver;
-
        if (dev->bus == &pcie_port_bus_type) {
-               if (dev->driver) {
-                       service_driver = to_service_driver(dev->driver);
-                       if (service_driver->remove)
-                               service_driver->remove(to_pcie_device(dev));
-               }
-               *(unsigned long*)data = (unsigned long)dev;
-               return 1;
+               put_device(dev);
+               device_unregister(dev);
        }
        return 0;
 }
@@ -349,25 +470,21 @@ static int remove_iter(struct device *dev, void *data)
  */
 void pcie_port_device_remove(struct pci_dev *dev)
 {
-       struct device *device;
-       unsigned long device_addr;
-       int interrupt_mode = PCIE_PORT_INTx_MODE;
-       int status;
+       struct pcie_port_data *port_data = pci_get_drvdata(dev);
 
-       do {
-               status = device_for_each_child(&dev->dev, &device_addr, remove_iter);
-               if (status) {
-                       device = (struct device*)device_addr;
-                       interrupt_mode = (to_pcie_device(device))->interrupt_mode;
-                       put_device(device);
-                       device_unregister(device);
-               }
-       } while (status);
-       /* Switch to INTx by default if MSI enabled */
-       if (interrupt_mode == PCIE_PORT_MSIX_MODE)
+       device_for_each_child(&dev->dev, NULL, remove_iter);
+       pci_disable_device(dev);
+
+       switch (port_data->port_irq_mode) {
+       case PCIE_PORT_MSIX_MODE:
                pci_disable_msix(dev);
-       else if (interrupt_mode == PCIE_PORT_MSI_MODE)
+               break;
+       case PCIE_PORT_MSI_MODE:
                pci_disable_msi(dev);
+               break;
+       }
+
+       kfree(port_data);
 }
 
 /**
@@ -392,7 +509,7 @@ static int pcie_port_probe_service(struct device *dev)
                return -ENODEV;
 
        pciedev = to_pcie_device(dev);
-       status = driver->probe(pciedev, driver->id_table);
+       status = driver->probe(pciedev);
        if (!status) {
                dev_printk(KERN_DEBUG, dev, "service driver %s loaded\n",
                        driver->name);