kernel-ark/arch/powerpc/platforms/pseries/msi.c
Michael Ellerman 6071ed0487 powerpc/pseries: Return the number of MSIs we could allocate
If we can't allocate the requested number of MSIs, we can still tell the
generic code how many we were able to allocate. That can then be passed
onto the driver, allowing it to request that many in future, and
probably succeeed.

Signed-off-by: Michael Ellerman <michael@ellerman.id.au>
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
2009-02-11 13:38:02 +11:00

282 lines
6.2 KiB
C

/*
* Copyright 2006 Jake Moilanen <moilanen@austin.ibm.com>, IBM Corp.
* Copyright 2006-2007 Michael Ellerman, IBM Corp.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 2 of the
* License.
*
*/
#include <linux/device.h>
#include <linux/irq.h>
#include <linux/msi.h>
#include <asm/rtas.h>
#include <asm/hw_irq.h>
#include <asm/ppc-pci.h>
static int query_token, change_token;
#define RTAS_QUERY_FN 0
#define RTAS_CHANGE_FN 1
#define RTAS_RESET_FN 2
#define RTAS_CHANGE_MSI_FN 3
#define RTAS_CHANGE_MSIX_FN 4
static struct pci_dn *get_pdn(struct pci_dev *pdev)
{
struct device_node *dn;
struct pci_dn *pdn;
dn = pci_device_to_OF_node(pdev);
if (!dn) {
dev_dbg(&pdev->dev, "rtas_msi: No OF device node\n");
return NULL;
}
pdn = PCI_DN(dn);
if (!pdn) {
dev_dbg(&pdev->dev, "rtas_msi: No PCI DN\n");
return NULL;
}
return pdn;
}
/* RTAS Helpers */
static int rtas_change_msi(struct pci_dn *pdn, u32 func, u32 num_irqs)
{
u32 addr, seq_num, rtas_ret[3];
unsigned long buid;
int rc;
addr = rtas_config_addr(pdn->busno, pdn->devfn, 0);
buid = pdn->phb->buid;
seq_num = 1;
do {
if (func == RTAS_CHANGE_MSI_FN || func == RTAS_CHANGE_MSIX_FN)
rc = rtas_call(change_token, 6, 4, rtas_ret, addr,
BUID_HI(buid), BUID_LO(buid),
func, num_irqs, seq_num);
else
rc = rtas_call(change_token, 6, 3, rtas_ret, addr,
BUID_HI(buid), BUID_LO(buid),
func, num_irqs, seq_num);
seq_num = rtas_ret[1];
} while (rtas_busy_delay(rc));
/*
* 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), got %d rc = %d\n",
func, num_irqs, rtas_ret[0], rc);
return rc;
}
static void rtas_disable_msi(struct pci_dev *pdev)
{
struct pci_dn *pdn;
pdn = get_pdn(pdev);
if (!pdn)
return;
if (rtas_change_msi(pdn, RTAS_CHANGE_FN, 0) != 0)
pr_debug("rtas_msi: Setting MSIs to 0 failed!\n");
}
static int rtas_query_irq_number(struct pci_dn *pdn, int offset)
{
u32 addr, rtas_ret[2];
unsigned long buid;
int rc;
addr = rtas_config_addr(pdn->busno, pdn->devfn, 0);
buid = pdn->phb->buid;
do {
rc = rtas_call(query_token, 4, 3, rtas_ret, addr,
BUID_HI(buid), BUID_LO(buid), offset);
} while (rtas_busy_delay(rc));
if (rc) {
pr_debug("rtas_msi: error (%d) querying source number\n", rc);
return rc;
}
return rtas_ret[0];
}
static void rtas_teardown_msi_irqs(struct pci_dev *pdev)
{
struct msi_desc *entry;
list_for_each_entry(entry, &pdev->msi_list, list) {
if (entry->irq == NO_IRQ)
continue;
set_irq_msi(entry->irq, NULL);
irq_dispose_mapping(entry->irq);
}
rtas_disable_msi(pdev);
}
static int check_req(struct pci_dev *pdev, int nvec, char *prop_name)
{
struct device_node *dn;
struct pci_dn *pdn;
const u32 *req_msi;
pdn = get_pdn(pdev);
if (!pdn)
return -ENODEV;
dn = pdn->node;
req_msi = of_get_property(dn, prop_name, NULL);
if (!req_msi) {
pr_debug("rtas_msi: No %s on %s\n", prop_name, dn->full_name);
return -ENOENT;
}
if (*req_msi < nvec) {
pr_debug("rtas_msi: %s requests < %d MSIs\n", prop_name, nvec);
return -ENOSPC;
}
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");
}
static int rtas_msi_check_device(struct pci_dev *pdev, int nvec, int type)
{
if (type == PCI_CAP_ID_MSIX)
return check_req_msix(pdev, nvec);
return check_req_msi(pdev, nvec);
}
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;
/*
* Try the new more explicit firmware interface, if that fails fall
* back to the old interface. The old interface is known to never
* return MSI-Xs.
*/
if (type == PCI_CAP_ID_MSI) {
rc = rtas_change_msi(pdn, RTAS_CHANGE_MSI_FN, nvec);
if (rc < 0) {
pr_debug("rtas_msi: trying the old firmware call.\n");
rc = rtas_change_msi(pdn, RTAS_CHANGE_FN, nvec);
}
} else
rc = rtas_change_msi(pdn, RTAS_CHANGE_MSIX_FN, nvec);
if (rc != nvec) {
pr_debug("rtas_msi: rtas_change_msi() failed\n");
return rc;
}
i = 0;
list_for_each_entry(entry, &pdev->msi_list, list) {
hwirq = rtas_query_irq_number(pdn, i++);
if (hwirq < 0) {
pr_debug("rtas_msi: error (%d) getting hwirq\n", rc);
return hwirq;
}
virq = irq_create_mapping(NULL, hwirq);
if (virq == NO_IRQ) {
pr_debug("rtas_msi: Failed mapping hwirq %d\n", hwirq);
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;
}
static void rtas_msi_pci_irq_fixup(struct pci_dev *pdev)
{
/* No LSI -> leave MSIs (if any) configured */
if (pdev->irq == NO_IRQ) {
dev_dbg(&pdev->dev, "rtas_msi: no LSI, nothing to do.\n");
return;
}
/* No MSI -> MSIs can't have been assigned by fw, leave LSI */
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;
}
dev_dbg(&pdev->dev, "rtas_msi: disabling existing MSI.\n");
rtas_disable_msi(pdev);
}
static int rtas_msi_init(void)
{
query_token = rtas_token("ibm,query-interrupt-source-number");
change_token = rtas_token("ibm,change-msi");
if ((query_token == RTAS_UNKNOWN_SERVICE) ||
(change_token == RTAS_UNKNOWN_SERVICE)) {
pr_debug("rtas_msi: no RTAS tokens, no MSI support.\n");
return -1;
}
pr_debug("rtas_msi: Registering RTAS MSI callbacks.\n");
WARN_ON(ppc_md.setup_msi_irqs);
ppc_md.setup_msi_irqs = rtas_setup_msi_irqs;
ppc_md.teardown_msi_irqs = rtas_teardown_msi_irqs;
ppc_md.msi_check_device = rtas_msi_check_device;
WARN_ON(ppc_md.pci_irq_fixup);
ppc_md.pci_irq_fixup = rtas_msi_pci_irq_fixup;
return 0;
}
arch_initcall(rtas_msi_init);