aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMichael Ellerman <michael@ellerman.id.au>2007-05-07 22:58:35 -0400
committerPaul Mackerras <paulus@samba.org>2007-05-07 23:40:31 -0400
commit85f2bf9f60f55b6727ed310ebbaa2df7142326e5 (patch)
tree32c8deb52c0fff95ae35d7d4f3670aab0bfd515c
parentdf87ef5508b40fc655b6c4771be31741d8ec1596 (diff)
[POWERPC] RTAS MSI implementation
Implement MSI support via RTAS (RTAS = run-time firmware on pSeries machines). For now we assumes that if the required RTAS tokens for MSI are present, then we want to use the RTAS MSI routines. When RTAS is managing MSIs for us, it will/may enable MSI on devices that support it by default. This is contrary to the Linux model where a device is in LSI mode until the driver requests MSIs. To remedy this we add a pci_irq_fixup call, which disables MSI if they've been assigned by firmware and the device also supports LSI. Devices that don't support LSI at all will be left as is, drivers are still expected to call pci_enable_msi() before using the device. At the moment there is no pci_irq_fixup on pSeries, so we can just set it unconditionally. If other platforms use the RTAS MSI backend they'll need to check that still holds. Signed-off-by: Michael Ellerman <michael@ellerman.id.au> Signed-off-by: Paul Mackerras <paulus@samba.org>
-rw-r--r--arch/powerpc/platforms/pseries/Makefile1
-rw-r--r--arch/powerpc/platforms/pseries/msi.c270
2 files changed, 271 insertions, 0 deletions
diff --git a/arch/powerpc/platforms/pseries/Makefile b/arch/powerpc/platforms/pseries/Makefile
index 90235d598751..ae1fc92dc1c9 100644
--- a/arch/powerpc/platforms/pseries/Makefile
+++ b/arch/powerpc/platforms/pseries/Makefile
@@ -11,6 +11,7 @@ obj-$(CONFIG_SCANLOG) += scanlog.o
11obj-$(CONFIG_EEH) += eeh.o eeh_cache.o eeh_driver.o eeh_event.o 11obj-$(CONFIG_EEH) += eeh.o eeh_cache.o eeh_driver.o eeh_event.o
12obj-$(CONFIG_KEXEC) += kexec.o 12obj-$(CONFIG_KEXEC) += kexec.o
13obj-$(CONFIG_PCI) += pci.o pci_dlpar.o 13obj-$(CONFIG_PCI) += pci.o pci_dlpar.o
14obj-$(CONFIG_PCI_MSI) += msi.o
14 15
15obj-$(CONFIG_HOTPLUG_CPU) += hotplug-cpu.o 16obj-$(CONFIG_HOTPLUG_CPU) += hotplug-cpu.o
16 17
diff --git a/arch/powerpc/platforms/pseries/msi.c b/arch/powerpc/platforms/pseries/msi.c
new file mode 100644
index 000000000000..6063ea2f67ad
--- /dev/null
+++ b/arch/powerpc/platforms/pseries/msi.c
@@ -0,0 +1,270 @@
1/*
2 * Copyright 2006 Jake Moilanen <moilanen@austin.ibm.com>, IBM Corp.
3 * Copyright 2006-2007 Michael Ellerman, IBM Corp.
4 *
5 * This program is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU General Public License
7 * as published by the Free Software Foundation; version 2 of the
8 * License.
9 *
10 */
11
12#include <linux/device.h>
13#include <linux/irq.h>
14#include <linux/msi.h>
15
16#include <asm/rtas.h>
17#include <asm/hw_irq.h>
18#include <asm/ppc-pci.h>
19
20static int query_token, change_token;
21
22#define RTAS_QUERY_FN 0
23#define RTAS_CHANGE_FN 1
24#define RTAS_RESET_FN 2
25#define RTAS_CHANGE_MSI_FN 3
26#define RTAS_CHANGE_MSIX_FN 4
27
28static struct pci_dn *get_pdn(struct pci_dev *pdev)
29{
30 struct device_node *dn;
31 struct pci_dn *pdn;
32
33 dn = pci_device_to_OF_node(pdev);
34 if (!dn) {
35 dev_dbg(&pdev->dev, "rtas_msi: No OF device node\n");
36 return NULL;
37 }
38
39 pdn = PCI_DN(dn);
40 if (!pdn) {
41 dev_dbg(&pdev->dev, "rtas_msi: No PCI DN\n");
42 return NULL;
43 }
44
45 return pdn;
46}
47
48/* RTAS Helpers */
49
50static int rtas_change_msi(struct pci_dn *pdn, u32 func, u32 num_irqs)
51{
52 u32 addr, seq_num, rtas_ret[3];
53 unsigned long buid;
54 int rc;
55
56 addr = rtas_config_addr(pdn->busno, pdn->devfn, 0);
57 buid = pdn->phb->buid;
58
59 seq_num = 1;
60 do {
61 if (func == RTAS_CHANGE_MSI_FN || func == RTAS_CHANGE_MSIX_FN)
62 rc = rtas_call(change_token, 6, 4, rtas_ret, addr,
63 BUID_HI(buid), BUID_LO(buid),
64 func, num_irqs, seq_num);
65 else
66 rc = rtas_call(change_token, 6, 3, rtas_ret, addr,
67 BUID_HI(buid), BUID_LO(buid),
68 func, num_irqs, seq_num);
69
70 seq_num = rtas_ret[1];
71 } while (rtas_busy_delay(rc));
72
73 if (rc == 0) /* Success */
74 rc = rtas_ret[0];
75
76 pr_debug("rtas_msi: ibm,change_msi(func=%d,num=%d) = (%d)\n",
77 func, num_irqs, rc);
78
79 return rc;
80}
81
82static void rtas_disable_msi(struct pci_dev *pdev)
83{
84 struct pci_dn *pdn;
85
86 pdn = get_pdn(pdev);
87 if (!pdn)
88 return;
89
90 if (rtas_change_msi(pdn, RTAS_CHANGE_FN, 0) != 0)
91 pr_debug("rtas_msi: Setting MSIs to 0 failed!\n");
92}
93
94static int rtas_query_irq_number(struct pci_dn *pdn, int offset)
95{
96 u32 addr, rtas_ret[2];
97 unsigned long buid;
98 int rc;
99
100 addr = rtas_config_addr(pdn->busno, pdn->devfn, 0);
101 buid = pdn->phb->buid;
102
103 do {
104 rc = rtas_call(query_token, 4, 3, rtas_ret, addr,
105 BUID_HI(buid), BUID_LO(buid), offset);
106 } while (rtas_busy_delay(rc));
107
108 if (rc) {
109 pr_debug("rtas_msi: error (%d) querying source number\n", rc);
110 return rc;
111 }
112
113 return rtas_ret[0];
114}
115
116static void rtas_teardown_msi_irqs(struct pci_dev *pdev)
117{
118 struct msi_desc *entry;
119
120 list_for_each_entry(entry, &pdev->msi_list, list) {
121 if (entry->irq == NO_IRQ)
122 continue;
123
124 set_irq_msi(entry->irq, NULL);
125 irq_dispose_mapping(entry->irq);
126 }
127
128 rtas_disable_msi(pdev);
129}
130
131static int check_req_msi(struct pci_dev *pdev, int nvec)
132{
133 struct device_node *dn;
134 struct pci_dn *pdn;
135 const u32 *req_msi;
136
137 pdn = get_pdn(pdev);
138 if (!pdn)
139 return -ENODEV;
140
141 dn = pdn->node;
142
143 req_msi = of_get_property(dn, "ibm,req#msi", NULL);
144 if (!req_msi) {
145 pr_debug("rtas_msi: No ibm,req#msi on %s\n", dn->full_name);
146 return -ENOENT;
147 }
148
149 if (*req_msi < nvec) {
150 pr_debug("rtas_msi: ibm,req#msi requests < %d MSIs\n", nvec);
151 return -ENOSPC;
152 }
153
154 return 0;
155}
156
157static int rtas_msi_check_device(struct pci_dev *pdev, int nvec, int type)
158{
159 if (type == PCI_CAP_ID_MSIX)
160 pr_debug("rtas_msi: MSI-X untested, trying anyway.\n");
161
162 return check_req_msi(pdev, nvec);
163}
164
165static int rtas_setup_msi_irqs(struct pci_dev *pdev, int nvec, int type)
166{
167 struct pci_dn *pdn;
168 int hwirq, virq, i, rc;
169 struct msi_desc *entry;
170
171 pdn = get_pdn(pdev);
172 if (!pdn)
173 return -ENODEV;
174
175 /*
176 * Try the new more explicit firmware interface, if that fails fall
177 * back to the old interface. The old interface is known to never
178 * return MSI-Xs.
179 */
180 if (type == PCI_CAP_ID_MSI) {
181 rc = rtas_change_msi(pdn, RTAS_CHANGE_MSI_FN, nvec);
182
183 if (rc != nvec) {
184 pr_debug("rtas_msi: trying the old firmware call.\n");
185 rc = rtas_change_msi(pdn, RTAS_CHANGE_FN, nvec);
186 }
187 } else
188 rc = rtas_change_msi(pdn, RTAS_CHANGE_MSIX_FN, nvec);
189
190 if (rc != nvec) {
191 pr_debug("rtas_msi: rtas_change_msi() failed\n");
192
193 /*
194 * In case of an error it's not clear whether the device is
195 * left with MSI enabled or not, so we explicitly disable.
196 */
197 goto out_free;
198 }
199
200 i = 0;
201 list_for_each_entry(entry, &pdev->msi_list, list) {
202 hwirq = rtas_query_irq_number(pdn, i);
203 if (hwirq < 0) {
204 rc = hwirq;
205 pr_debug("rtas_msi: error (%d) getting hwirq\n", rc);
206 goto out_free;
207 }
208
209 virq = irq_create_mapping(NULL, hwirq);
210
211 if (virq == NO_IRQ) {
212 pr_debug("rtas_msi: Failed mapping hwirq %d\n", hwirq);
213 rc = -ENOSPC;
214 goto out_free;
215 }
216
217 dev_dbg(&pdev->dev, "rtas_msi: allocated virq %d\n", virq);
218 set_irq_msi(virq, entry);
219 unmask_msi_irq(virq);
220 }
221
222 return 0;
223
224 out_free:
225 rtas_teardown_msi_irqs(pdev);
226 return rc;
227}
228
229static void rtas_msi_pci_irq_fixup(struct pci_dev *pdev)
230{
231 /* No LSI -> leave MSIs (if any) configured */
232 if (pdev->irq == NO_IRQ) {
233 dev_dbg(&pdev->dev, "rtas_msi: no LSI, nothing to do.\n");
234 return;
235 }
236
237 /* No MSI -> MSIs can't have been assigned by fw, leave LSI */
238 if (check_req_msi(pdev, 1)) {
239 dev_dbg(&pdev->dev, "rtas_msi: no req#msi, nothing to do.\n");
240 return;
241 }
242
243 dev_dbg(&pdev->dev, "rtas_msi: disabling existing MSI.\n");
244 rtas_disable_msi(pdev);
245}
246
247static int rtas_msi_init(void)
248{
249 query_token = rtas_token("ibm,query-interrupt-source-number");
250 change_token = rtas_token("ibm,change-msi");
251
252 if ((query_token == RTAS_UNKNOWN_SERVICE) ||
253 (change_token == RTAS_UNKNOWN_SERVICE)) {
254 pr_debug("rtas_msi: no RTAS tokens, no MSI support.\n");
255 return -1;
256 }
257
258 pr_debug("rtas_msi: Registering RTAS MSI callbacks.\n");
259
260 WARN_ON(ppc_md.setup_msi_irqs);
261 ppc_md.setup_msi_irqs = rtas_setup_msi_irqs;
262 ppc_md.teardown_msi_irqs = rtas_teardown_msi_irqs;
263 ppc_md.msi_check_device = rtas_msi_check_device;
264
265 WARN_ON(ppc_md.pci_irq_fixup);
266 ppc_md.pci_irq_fixup = rtas_msi_pci_irq_fixup;
267
268 return 0;
269}
270arch_initcall(rtas_msi_init);