[RFC/PATCH 3/4] Powerpc MSI support via RTAS

Michael Ellerman michael at ellerman.id.au
Fri Aug 18 18:13:30 EST 2006


Powerpc MSI support via RTAS. Based on Jake's code, reworked to:
 * only ever set 1 MSI.
 * implement disable, we can't do without it.
 * bork if firmware enables MSI-X on us, that's not ok.
 * more error handling.

NOT tested. Mainline apparently isn't booting atm on the machines that
have the required firmware :/

Signed-off-by: Jake Moilanen <moilanen at austin.ibm.com>
Signed-off-by: Michael Ellerman <michael at ellerman.id.au>
---

 arch/powerpc/platforms/pseries/setup.c |    1 
 drivers/pci/Makefile                   |    1 
 drivers/pci/msi-rtas.c                 |  199 +++++++++++++++++++++++++++++++++
 include/asm-powerpc/pci-bridge.h       |    3 
 include/asm-powerpc/rtas.h             |    6 
 5 files changed, 210 insertions(+)

Index: git/arch/powerpc/platforms/pseries/setup.c
===================================================================
--- git.orig/arch/powerpc/platforms/pseries/setup.c
+++ git/arch/powerpc/platforms/pseries/setup.c
@@ -273,6 +273,7 @@ static void __init pseries_discover_pic(
 			return;
 		} else if (strstr(typep, "ppc-xicp")) {
 			ppc_md.init_IRQ       = xics_init_IRQ;
+			rtas_msi_init();
 #ifdef CONFIG_KEXEC
 			ppc_md.kexec_cpu_down = pseries_kexec_cpu_down_xics;
 #endif
Index: git/drivers/pci/Makefile
===================================================================
--- git.orig/drivers/pci/Makefile
+++ git/drivers/pci/Makefile
@@ -31,6 +31,7 @@ msiobj-$(CONFIG_X86) := msi.o msi-apic.o
 msiobj-$(CONFIG_IA64) := msi.o msi-apic.o
 msiobj-$(CONFIG_IA64_GENERIC) := msi-altix.o
 msiobj-$(CONFIG_IA64_SGI_SN2) := msi-altix.o
+msiobj-$(CONFIG_PPC_PSERIES) := msi-rtas.o
 
 obj-$(CONFIG_PCI_MSI) += $(msiobj-y)
 
Index: git/drivers/pci/msi-rtas.c
===================================================================
--- /dev/null
+++ git/drivers/pci/msi-rtas.c
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2006 Jake Moilanen <moilanen at austin.ibm.com>, IBM Corp.
+ * Copyright (C) 2006 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.
+ *
+ */
+
+#define DEBUG 1
+
+#include <linux/pci.h>
+#include <linux/irq.h>
+#include <asm/rtas.h>
+#include <asm/hw_irq.h>
+#include <asm/ppc-pci.h>
+
+static int query_token, change_token;
+
+#define RTAS_QUERY_MSI_FN	0
+#define RTAS_CHANGE_MSI_FN	1
+#define RTAS_RESET_MSI_FN	2
+
+static int rtas_change_msi(struct pci_dn *pdn, u32 function, u32 num_irqs)
+{
+	u32 addr, seq_num, rtas_ret[2];
+	unsigned long buid;
+	int rc;
+
+	addr = rtas_config_addr(pdn->busno, pdn->devfn, 0);
+	buid = pdn->phb->buid;
+
+	seq_num = 1;
+	do {
+		rc = rtas_call(change_token, 6, 3, rtas_ret, addr,
+				BUID_HI(buid), BUID_LO(buid),
+				function, num_irqs, seq_num);
+
+		seq_num = rtas_ret[1];
+	} while (rtas_busy_delay(rc));
+
+	if (rc) {
+		printk(KERN_WARNING "Error[%d]: getting the number of"
+			" MSI interrupts for %s\n", rc, pci_name(pdn->pcidev));
+		return rc;
+	}
+
+	return rtas_ret[0];
+}
+
+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) {
+		printk(KERN_WARNING "Error[%d]: Querying irq source number "
+				"for %s\n", rc, pci_name(pdn->pcidev));
+		return rc;
+	}
+
+	return rtas_ret[0];
+}
+
+static int msi_enabled(struct pci_dev *pdev)
+{
+	int pos;
+	u16 reg;
+
+	pos = pci_find_capability(pdev, PCI_CAP_ID_MSI);
+	if (!pos)
+		return 0;
+
+	pci_read_config_word(pdev, pos + PCI_MSI_FLAGS, &reg);
+	if (reg & PCI_MSI_FLAGS_ENABLE)
+		return 1;
+
+	return 0;
+}
+
+static void rtas_disable_msi(struct pci_dev* pdev)
+{
+	struct device_node *dn;
+	struct pci_dn *pdn;
+
+	dn = pci_device_to_OF_node(pdev);
+	if (!dn) {
+		pr_debug("rtas_disable_msi: No OF device node for %s\n",
+				pci_name(pdev));
+		return;
+	}
+
+	pdn = PCI_DN(dn);
+	if (!pdn) {
+		pr_debug("rtas_disable_msi: No PCI DN for %s\n",
+				pci_name(pdev));
+		return;
+	}
+
+	/* XXX can we do anything sane if this fails? */
+	rtas_change_msi(pdn, RTAS_CHANGE_MSI_FN, 0);
+
+	pdev->irq = pdn->saved_irq;
+}
+
+static int rtas_enable_msi(struct pci_dev *pdev)
+{
+	struct device_node *dn;
+	struct pci_dn *pdn;
+	int rc, virq;
+
+	dn = pci_device_to_OF_node(pdev);
+	if (!dn) {
+		pr_debug("rtas_enable_msi: No OF device node for %s\n",
+				pci_name(pdev));
+		goto out_err;
+	}
+
+	if (!of_find_property(dn, "ibm,req#msi", NULL)) {
+		pr_debug("rtas_enable_msi: No ibm,req#msi for %s\n",
+				pci_name(pdev));
+		goto out_err;
+	}
+
+	pdn = PCI_DN(dn);
+	if (!pdn) {
+		pr_debug("rtas_change_msi: No PCI DN for %s\n", pci_name(pdev));
+		goto out_err;
+	}
+
+	pdn->saved_irq = pdev->irq;
+
+	/*
+	 * Try and configure 1 MSI for the device. This should get us
+	 * either 1 MSI, or an error. In the case of an error it's not
+	 * clear whether the device is left with MSI enabled or disabled,
+	 * so I think we should explicitly disable.
+	 */
+	rc = rtas_change_msi(pdn, RTAS_CHANGE_MSI_FN, 1);
+	if (rc != 1)
+		goto out_disable;
+
+	/*
+	 * The spec gives firmware the option to enable either MSI or MSI-X,
+	 * this doesn't wash with the Linux API. For the time beinging, we
+	 * kludge around that by checking ourselves if MSI has been enabled.
+	 */
+	if (!msi_enabled(pdev)) {
+		pr_debug("rtas_enable_msi: MSI not enabled by firmware!\n");
+		goto out_disable;
+	}
+
+	rc = rtas_query_irq_number(pdn, 0);
+	if (rc < 0)
+		goto out_disable;
+
+	virq = irq_create_mapping(NULL, rc);
+	if (virq == NO_IRQ) {
+		pr_debug("rtas_enable_msi: Failed mapping hwirq %d\n", rc);
+		goto out_disable;
+	}
+
+	pdev->irq = virq;
+	return 0;
+
+ out_disable:
+	rtas_disable_msi(pdev);
+ out_err:
+	return -1;
+}
+
+void 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_init: Couldn't find RTAS tokens, no "
+				"MSI support available.\n");
+		return;
+	}
+
+	pr_debug("rtas_msi_init: Registering MSI callbacks.\n");
+
+	ppc_md.enable_msi = rtas_enable_msi;
+	ppc_md.disable_msi = rtas_disable_msi;
+}
Index: git/include/asm-powerpc/pci-bridge.h
===================================================================
--- git.orig/include/asm-powerpc/pci-bridge.h
+++ git/include/asm-powerpc/pci-bridge.h
@@ -81,6 +81,9 @@ struct pci_dn {
 	struct	pci_dev *pcidev;	/* back-pointer to the pci device */
 	struct	device_node *node;	/* back-pointer to the device_node */
 	u32	config_space[16];	/* saved PCI config space */
+#ifdef CONFIG_PCI_MSI
+	int	saved_irq;		/* saved LSI for MSI */
+#endif
 };
 
 /* Get the pointer to a device_node's pci_dn */
Index: git/include/asm-powerpc/rtas.h
===================================================================
--- git.orig/include/asm-powerpc/rtas.h
+++ git/include/asm-powerpc/rtas.h
@@ -246,5 +246,11 @@ static inline u32 rtas_config_addr(int b
 			(devfn << 8) | (reg & 0xff);
 }
 
+#ifdef CONFIG_PCI_MSI
+extern void rtas_msi_init(void);
+#else
+static inline void rtas_msi_init(void) { }
+#endif
+
 #endif /* __KERNEL__ */
 #endif /* _POWERPC_RTAS_H */



More information about the Linuxppc-dev mailing list