Merge branch 'master' of git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6
[pandora-kernel.git] / arch / powerpc / platforms / pseries / msi.c
index 6063ea2..bf2e1ac 100644 (file)
@@ -70,11 +70,17 @@ static int rtas_change_msi(struct pci_dn *pdn, u32 func, u32 num_irqs)
                seq_num = rtas_ret[1];
        } while (rtas_busy_delay(rc));
 
-       if (rc == 0) /* Success */
+       /*
+        * If the RTAS call succeeded, return the number of irqs allocated.
+        * If not, make sure we return a negative error code.
+        */
+       if (rc == 0)
                rc = rtas_ret[0];
+       else if (rc > 0)
+               rc = -rc;
 
-       pr_debug("rtas_msi: ibm,change_msi(func=%d,num=%d) = (%d)\n",
-                func, num_irqs, rc);
+       pr_debug("rtas_msi: ibm,change_msi(func=%d,num=%d), got %d rc = %d\n",
+                func, num_irqs, rtas_ret[0], rc);
 
        return rc;
 }
@@ -128,7 +134,7 @@ static void rtas_teardown_msi_irqs(struct pci_dev *pdev)
        rtas_disable_msi(pdev);
 }
 
-static int check_req_msi(struct pci_dev *pdev, int nvec)
+static int check_req(struct pci_dev *pdev, int nvec, char *prop_name)
 {
        struct device_node *dn;
        struct pci_dn *pdn;
@@ -140,26 +146,235 @@ static int check_req_msi(struct pci_dev *pdev, int nvec)
 
        dn = pdn->node;
 
-       req_msi = of_get_property(dn, "ibm,req#msi", NULL);
+       req_msi = of_get_property(dn, prop_name, NULL);
        if (!req_msi) {
-               pr_debug("rtas_msi: No ibm,req#msi on %s\n", dn->full_name);
+               pr_debug("rtas_msi: No %s on %s\n", prop_name, dn->full_name);
                return -ENOENT;
        }
 
        if (*req_msi < nvec) {
-               pr_debug("rtas_msi: ibm,req#msi requests < %d MSIs\n", nvec);
-               return -ENOSPC;
+               pr_debug("rtas_msi: %s requests < %d MSIs\n", prop_name, nvec);
+
+               if (*req_msi == 0) /* Be paranoid */
+                       return -ENOSPC;
+
+               return *req_msi;
        }
 
        return 0;
 }
 
+static int check_req_msi(struct pci_dev *pdev, int nvec)
+{
+       return check_req(pdev, nvec, "ibm,req#msi");
+}
+
+static int check_req_msix(struct pci_dev *pdev, int nvec)
+{
+       return check_req(pdev, nvec, "ibm,req#msi-x");
+}
+
+/* Quota calculation */
+
+static struct device_node *find_pe_total_msi(struct pci_dev *dev, int *total)
+{
+       struct device_node *dn;
+       const u32 *p;
+
+       dn = of_node_get(pci_device_to_OF_node(dev));
+       while (dn) {
+               p = of_get_property(dn, "ibm,pe-total-#msi", NULL);
+               if (p) {
+                       pr_debug("rtas_msi: found prop on dn %s\n",
+                               dn->full_name);
+                       *total = *p;
+                       return dn;
+               }
+
+               dn = of_get_next_parent(dn);
+       }
+
+       return NULL;
+}
+
+static struct device_node *find_pe_dn(struct pci_dev *dev, int *total)
+{
+       struct device_node *dn;
+
+       /* Found our PE and assume 8 at that point. */
+
+       dn = pci_device_to_OF_node(dev);
+       if (!dn)
+               return NULL;
+
+       dn = find_device_pe(dn);
+       if (!dn)
+               return NULL;
+
+       /* We actually want the parent */
+       dn = of_get_parent(dn);
+       if (!dn)
+               return NULL;
+
+       /* Hardcode of 8 for old firmwares */
+       *total = 8;
+       pr_debug("rtas_msi: using PE dn %s\n", dn->full_name);
+
+       return dn;
+}
+
+struct msi_counts {
+       struct device_node *requestor;
+       int num_devices;
+       int request;
+       int quota;
+       int spare;
+       int over_quota;
+};
+
+static void *count_non_bridge_devices(struct device_node *dn, void *data)
+{
+       struct msi_counts *counts = data;
+       const u32 *p;
+       u32 class;
+
+       pr_debug("rtas_msi: counting %s\n", dn->full_name);
+
+       p = of_get_property(dn, "class-code", NULL);
+       class = p ? *p : 0;
+
+       if ((class >> 8) != PCI_CLASS_BRIDGE_PCI)
+               counts->num_devices++;
+
+       return NULL;
+}
+
+static void *count_spare_msis(struct device_node *dn, void *data)
+{
+       struct msi_counts *counts = data;
+       const u32 *p;
+       int req;
+
+       if (dn == counts->requestor)
+               req = counts->request;
+       else {
+               /* We don't know if a driver will try to use MSI or MSI-X,
+                * so we just have to punt and use the larger of the two. */
+               req = 0;
+               p = of_get_property(dn, "ibm,req#msi", NULL);
+               if (p)
+                       req = *p;
+
+               p = of_get_property(dn, "ibm,req#msi-x", NULL);
+               if (p)
+                       req = max(req, (int)*p);
+       }
+
+       if (req < counts->quota)
+               counts->spare += counts->quota - req;
+       else if (req > counts->quota)
+               counts->over_quota++;
+
+       return NULL;
+}
+
+static int msi_quota_for_device(struct pci_dev *dev, int request)
+{
+       struct device_node *pe_dn;
+       struct msi_counts counts;
+       int total;
+
+       pr_debug("rtas_msi: calc quota for %s, request %d\n", pci_name(dev),
+                 request);
+
+       pe_dn = find_pe_total_msi(dev, &total);
+       if (!pe_dn)
+               pe_dn = find_pe_dn(dev, &total);
+
+       if (!pe_dn) {
+               pr_err("rtas_msi: couldn't find PE for %s\n", pci_name(dev));
+               goto out;
+       }
+
+       pr_debug("rtas_msi: found PE %s\n", pe_dn->full_name);
+
+       memset(&counts, 0, sizeof(struct msi_counts));
+
+       /* Work out how many devices we have below this PE */
+       traverse_pci_devices(pe_dn, count_non_bridge_devices, &counts);
+
+       if (counts.num_devices == 0) {
+               pr_err("rtas_msi: found 0 devices under PE for %s\n",
+                       pci_name(dev));
+               goto out;
+       }
+
+       counts.quota = total / counts.num_devices;
+       if (request <= counts.quota)
+               goto out;
+
+       /* else, we have some more calculating to do */
+       counts.requestor = pci_device_to_OF_node(dev);
+       counts.request = request;
+       traverse_pci_devices(pe_dn, count_spare_msis, &counts);
+
+       /* If the quota isn't an integer multiple of the total, we can
+        * use the remainder as spare MSIs for anyone that wants them. */
+       counts.spare += total % counts.num_devices;
+
+       /* Divide any spare by the number of over-quota requestors */
+       if (counts.over_quota)
+               counts.quota += counts.spare / counts.over_quota;
+
+       /* And finally clamp the request to the possibly adjusted quota */
+       request = min(counts.quota, request);
+
+       pr_debug("rtas_msi: request clamped to quota %d\n", request);
+out:
+       of_node_put(pe_dn);
+
+       return request;
+}
+
 static int rtas_msi_check_device(struct pci_dev *pdev, int nvec, int type)
 {
+       int quota, rc;
+
        if (type == PCI_CAP_ID_MSIX)
-               pr_debug("rtas_msi: MSI-X untested, trying anyway.\n");
+               rc = check_req_msix(pdev, nvec);
+       else
+               rc = check_req_msi(pdev, nvec);
 
-       return check_req_msi(pdev, nvec);
+       if (rc)
+               return rc;
+
+       quota = msi_quota_for_device(pdev, nvec);
+
+       if (quota && quota < nvec)
+               return quota;
+
+       return 0;
+}
+
+static int check_msix_entries(struct pci_dev *pdev)
+{
+       struct msi_desc *entry;
+       int expected;
+
+       /* There's no way for us to express to firmware that we want
+        * a discontiguous, or non-zero based, range of MSI-X entries.
+        * So we must reject such requests. */
+
+       expected = 0;
+       list_for_each_entry(entry, &pdev->msi_list, list) {
+               if (entry->msi_attrib.entry_nr != expected) {
+                       pr_debug("rtas_msi: bad MSI-X entries.\n");
+                       return -EINVAL;
+               }
+               expected++;
+       }
+
+       return 0;
 }
 
 static int rtas_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
@@ -167,11 +382,15 @@ static int rtas_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
        struct pci_dn *pdn;
        int hwirq, virq, i, rc;
        struct msi_desc *entry;
+       struct msi_msg msg;
 
        pdn = get_pdn(pdev);
        if (!pdn)
                return -ENODEV;
 
+       if (type == PCI_CAP_ID_MSIX && check_msix_entries(pdev))
+               return -EINVAL;
+
        /*
         * Try the new more explicit firmware interface, if that fails fall
         * back to the old interface. The old interface is known to never
@@ -180,7 +399,7 @@ static int rtas_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
        if (type == PCI_CAP_ID_MSI) {
                rc = rtas_change_msi(pdn, RTAS_CHANGE_MSI_FN, nvec);
 
-               if (rc != nvec) {
+               if (rc < 0) {
                        pr_debug("rtas_msi: trying the old firmware call.\n");
                        rc = rtas_change_msi(pdn, RTAS_CHANGE_FN, nvec);
                }
@@ -189,41 +408,35 @@ static int rtas_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
 
        if (rc != nvec) {
                pr_debug("rtas_msi: rtas_change_msi() failed\n");
-
-               /*
-                * In case of an error it's not clear whether the device is
-                * left with MSI enabled or not, so we explicitly disable.
-                */
-               goto out_free;
+               return rc;
        }
 
        i = 0;
        list_for_each_entry(entry, &pdev->msi_list, list) {
-               hwirq = rtas_query_irq_number(pdn, i);
+               hwirq = rtas_query_irq_number(pdn, i++);
                if (hwirq < 0) {
-                       rc = hwirq;
                        pr_debug("rtas_msi: error (%d) getting hwirq\n", rc);
-                       goto out_free;
+                       return hwirq;
                }
 
                virq = irq_create_mapping(NULL, hwirq);
 
                if (virq == NO_IRQ) {
                        pr_debug("rtas_msi: Failed mapping hwirq %d\n", hwirq);
-                       rc = -ENOSPC;
-                       goto out_free;
+                       return -ENOSPC;
                }
 
                dev_dbg(&pdev->dev, "rtas_msi: allocated virq %d\n", virq);
                set_irq_msi(virq, entry);
+
+               /* Read config space back so we can restore after reset */
+               read_msi_msg(virq, &msg);
+               entry->msg = msg;
+
                unmask_msi_irq(virq);
        }
 
        return 0;
-
- out_free:
-       rtas_teardown_msi_irqs(pdev);
-       return rc;
 }
 
 static void rtas_msi_pci_irq_fixup(struct pci_dev *pdev)
@@ -235,8 +448,8 @@ static void rtas_msi_pci_irq_fixup(struct pci_dev *pdev)
        }
 
        /* No MSI -> MSIs can't have been assigned by fw, leave LSI */
-       if (check_req_msi(pdev, 1)) {
-               dev_dbg(&pdev->dev, "rtas_msi: no req#msi, nothing to do.\n");
+       if (check_req_msi(pdev, 1) && check_req_msix(pdev, 1)) {
+               dev_dbg(&pdev->dev, "rtas_msi: no req#msi/x, nothing to do.\n");
                return;
        }