diff options
author | Benjamin Herrenschmidt <benh@kernel.crashing.org> | 2016-07-08 02:37:08 -0400 |
---|---|---|
committer | Michael Ellerman <mpe@ellerman.id.au> | 2016-07-17 02:42:45 -0400 |
commit | d74361881f0dfe5f9dcac37d1b753a15a2345d8c (patch) | |
tree | 49713088a40028b016f84e4fb226b855a76b8f8b | |
parent | 1d607bb3bd60f404d1ceb0d6ebceadf261068422 (diff) |
powerpc/xics: Add ICP OPAL backend
This adds a new XICS backend that uses OPAL calls, which can be
used when we don't have native support for the platform interrupt
controller.
Signed-off-by: Benjamin Herrenschmidt <benh@kernel.crashing.org>
Signed-off-by: Michael Ellerman <mpe@ellerman.id.au>
-rw-r--r-- | arch/powerpc/include/asm/xics.h | 6 | ||||
-rw-r--r-- | arch/powerpc/sysdev/xics/Makefile | 2 | ||||
-rw-r--r-- | arch/powerpc/sysdev/xics/icp-opal.c | 144 | ||||
-rw-r--r-- | arch/powerpc/sysdev/xics/xics-common.c | 5 |
4 files changed, 155 insertions, 2 deletions
diff --git a/arch/powerpc/include/asm/xics.h b/arch/powerpc/include/asm/xics.h index 04ef3ae511da..f5f729c11578 100644 --- a/arch/powerpc/include/asm/xics.h +++ b/arch/powerpc/include/asm/xics.h | |||
@@ -42,6 +42,12 @@ extern int icp_hv_init(void); | |||
42 | static inline int icp_hv_init(void) { return -ENODEV; } | 42 | static inline int icp_hv_init(void) { return -ENODEV; } |
43 | #endif | 43 | #endif |
44 | 44 | ||
45 | #ifdef CONFIG_PPC_POWERNV | ||
46 | extern int icp_opal_init(void); | ||
47 | #else | ||
48 | static inline int icp_opal_init(void) { return -ENODEV; } | ||
49 | #endif | ||
50 | |||
45 | /* ICP ops */ | 51 | /* ICP ops */ |
46 | struct icp_ops { | 52 | struct icp_ops { |
47 | unsigned int (*get_irq)(void); | 53 | unsigned int (*get_irq)(void); |
diff --git a/arch/powerpc/sysdev/xics/Makefile b/arch/powerpc/sysdev/xics/Makefile index c606aa8ba60a..5d7f5a6564de 100644 --- a/arch/powerpc/sysdev/xics/Makefile +++ b/arch/powerpc/sysdev/xics/Makefile | |||
@@ -4,4 +4,4 @@ obj-y += xics-common.o | |||
4 | obj-$(CONFIG_PPC_ICP_NATIVE) += icp-native.o | 4 | obj-$(CONFIG_PPC_ICP_NATIVE) += icp-native.o |
5 | obj-$(CONFIG_PPC_ICP_HV) += icp-hv.o | 5 | obj-$(CONFIG_PPC_ICP_HV) += icp-hv.o |
6 | obj-$(CONFIG_PPC_ICS_RTAS) += ics-rtas.o | 6 | obj-$(CONFIG_PPC_ICS_RTAS) += ics-rtas.o |
7 | obj-$(CONFIG_PPC_POWERNV) += ics-opal.o | 7 | obj-$(CONFIG_PPC_POWERNV) += ics-opal.o icp-opal.o |
diff --git a/arch/powerpc/sysdev/xics/icp-opal.c b/arch/powerpc/sysdev/xics/icp-opal.c new file mode 100644 index 000000000000..57d72f10a97f --- /dev/null +++ b/arch/powerpc/sysdev/xics/icp-opal.c | |||
@@ -0,0 +1,144 @@ | |||
1 | /* | ||
2 | * Copyright 2016 IBM Corporation. | ||
3 | * | ||
4 | * This program is free software; you can redistribute it and/or | ||
5 | * modify it under the terms of the GNU General Public License | ||
6 | * as published by the Free Software Foundation; either version | ||
7 | * 2 of the License, or (at your option) any later version. | ||
8 | */ | ||
9 | #include <linux/types.h> | ||
10 | #include <linux/kernel.h> | ||
11 | #include <linux/irq.h> | ||
12 | #include <linux/smp.h> | ||
13 | #include <linux/interrupt.h> | ||
14 | #include <linux/cpu.h> | ||
15 | #include <linux/of.h> | ||
16 | |||
17 | #include <asm/smp.h> | ||
18 | #include <asm/irq.h> | ||
19 | #include <asm/errno.h> | ||
20 | #include <asm/xics.h> | ||
21 | #include <asm/io.h> | ||
22 | #include <asm/opal.h> | ||
23 | |||
24 | static void icp_opal_teardown_cpu(void) | ||
25 | { | ||
26 | int cpu = smp_processor_id(); | ||
27 | |||
28 | /* Clear any pending IPI */ | ||
29 | opal_int_set_mfrr(cpu, 0xff); | ||
30 | } | ||
31 | |||
32 | static void icp_opal_flush_ipi(void) | ||
33 | { | ||
34 | /* | ||
35 | * We take the ipi irq but and never return so we need to EOI the IPI, | ||
36 | * but want to leave our priority 0. | ||
37 | * | ||
38 | * Should we check all the other interrupts too? | ||
39 | * Should we be flagging idle loop instead? | ||
40 | * Or creating some task to be scheduled? | ||
41 | */ | ||
42 | opal_int_eoi((0x00 << 24) | XICS_IPI); | ||
43 | } | ||
44 | |||
45 | static unsigned int icp_opal_get_irq(void) | ||
46 | { | ||
47 | unsigned int xirr; | ||
48 | unsigned int vec; | ||
49 | unsigned int irq; | ||
50 | int64_t rc; | ||
51 | |||
52 | rc = opal_int_get_xirr(&xirr, false); | ||
53 | if (rc < 0) | ||
54 | return NO_IRQ; | ||
55 | xirr = be32_to_cpu(xirr); | ||
56 | vec = xirr & 0x00ffffff; | ||
57 | if (vec == XICS_IRQ_SPURIOUS) | ||
58 | return NO_IRQ; | ||
59 | |||
60 | irq = irq_find_mapping(xics_host, vec); | ||
61 | if (likely(irq != NO_IRQ)) { | ||
62 | xics_push_cppr(vec); | ||
63 | return irq; | ||
64 | } | ||
65 | |||
66 | /* We don't have a linux mapping, so have rtas mask it. */ | ||
67 | xics_mask_unknown_vec(vec); | ||
68 | |||
69 | /* We might learn about it later, so EOI it */ | ||
70 | opal_int_eoi(xirr); | ||
71 | |||
72 | return NO_IRQ; | ||
73 | } | ||
74 | |||
75 | static void icp_opal_set_cpu_priority(unsigned char cppr) | ||
76 | { | ||
77 | xics_set_base_cppr(cppr); | ||
78 | opal_int_set_cppr(cppr); | ||
79 | iosync(); | ||
80 | } | ||
81 | |||
82 | static void icp_opal_eoi(struct irq_data *d) | ||
83 | { | ||
84 | unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d); | ||
85 | int64_t rc; | ||
86 | |||
87 | iosync(); | ||
88 | rc = opal_int_eoi((xics_pop_cppr() << 24) | hw_irq); | ||
89 | |||
90 | /* | ||
91 | * EOI tells us whether there are more interrupts to fetch. | ||
92 | * | ||
93 | * Some HW implementations might not be able to send us another | ||
94 | * external interrupt in that case, so we force a replay. | ||
95 | */ | ||
96 | if (rc > 0) | ||
97 | force_external_irq_replay(); | ||
98 | } | ||
99 | |||
100 | #ifdef CONFIG_SMP | ||
101 | |||
102 | static void icp_opal_cause_ipi(int cpu, unsigned long data) | ||
103 | { | ||
104 | opal_int_set_mfrr(cpu, IPI_PRIORITY); | ||
105 | } | ||
106 | |||
107 | static irqreturn_t icp_opal_ipi_action(int irq, void *dev_id) | ||
108 | { | ||
109 | int cpu = smp_processor_id(); | ||
110 | |||
111 | opal_int_set_mfrr(cpu, 0xff); | ||
112 | |||
113 | return smp_ipi_demux(); | ||
114 | } | ||
115 | |||
116 | #endif /* CONFIG_SMP */ | ||
117 | |||
118 | static const struct icp_ops icp_opal_ops = { | ||
119 | .get_irq = icp_opal_get_irq, | ||
120 | .eoi = icp_opal_eoi, | ||
121 | .set_priority = icp_opal_set_cpu_priority, | ||
122 | .teardown_cpu = icp_opal_teardown_cpu, | ||
123 | .flush_ipi = icp_opal_flush_ipi, | ||
124 | #ifdef CONFIG_SMP | ||
125 | .ipi_action = icp_opal_ipi_action, | ||
126 | .cause_ipi = icp_opal_cause_ipi, | ||
127 | #endif | ||
128 | }; | ||
129 | |||
130 | int icp_opal_init(void) | ||
131 | { | ||
132 | struct device_node *np; | ||
133 | |||
134 | np = of_find_compatible_node(NULL, NULL, "ibm,opal-intc"); | ||
135 | if (!np) | ||
136 | return -ENODEV; | ||
137 | |||
138 | icp_ops = &icp_opal_ops; | ||
139 | |||
140 | printk("XICS: Using OPAL ICP fallbacks\n"); | ||
141 | |||
142 | return 0; | ||
143 | } | ||
144 | |||
diff --git a/arch/powerpc/sysdev/xics/xics-common.c b/arch/powerpc/sysdev/xics/xics-common.c index 47e43b7b076b..a795a5f0301c 100644 --- a/arch/powerpc/sysdev/xics/xics-common.c +++ b/arch/powerpc/sysdev/xics/xics-common.c | |||
@@ -404,8 +404,11 @@ void __init xics_init(void) | |||
404 | /* Fist locate ICP */ | 404 | /* Fist locate ICP */ |
405 | if (firmware_has_feature(FW_FEATURE_LPAR)) | 405 | if (firmware_has_feature(FW_FEATURE_LPAR)) |
406 | rc = icp_hv_init(); | 406 | rc = icp_hv_init(); |
407 | if (rc < 0) | 407 | if (rc < 0) { |
408 | rc = icp_native_init(); | 408 | rc = icp_native_init(); |
409 | if (rc == -ENODEV) | ||
410 | rc = icp_opal_init(); | ||
411 | } | ||
409 | if (rc < 0) { | 412 | if (rc < 0) { |
410 | pr_warning("XICS: Cannot find a Presentation Controller !\n"); | 413 | pr_warning("XICS: Cannot find a Presentation Controller !\n"); |
411 | return; | 414 | return; |