xref: /openbmc/linux/arch/powerpc/sysdev/xics/icp-hv.c (revision 799a545b)
1 /*
2  * Copyright 2011 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  */
10 #include <linux/types.h>
11 #include <linux/kernel.h>
12 #include <linux/irq.h>
13 #include <linux/smp.h>
14 #include <linux/interrupt.h>
15 #include <linux/cpu.h>
16 #include <linux/of.h>
17 
18 #include <asm/smp.h>
19 #include <asm/irq.h>
20 #include <asm/errno.h>
21 #include <asm/xics.h>
22 #include <asm/io.h>
23 #include <asm/hvcall.h>
24 
25 static inline unsigned int icp_hv_get_xirr(unsigned char cppr)
26 {
27 	unsigned long retbuf[PLPAR_HCALL_BUFSIZE];
28 	long rc;
29 	unsigned int ret = XICS_IRQ_SPURIOUS;
30 
31 	rc = plpar_hcall(H_XIRR, retbuf, cppr);
32 	if (rc == H_SUCCESS) {
33 		ret = (unsigned int)retbuf[0];
34 	} else {
35 		pr_err("%s: bad return code xirr cppr=0x%x returned %ld\n",
36 			__func__, cppr, rc);
37 		WARN_ON_ONCE(1);
38 	}
39 
40 	return ret;
41 }
42 
43 static inline void icp_hv_set_cppr(u8 value)
44 {
45 	long rc = plpar_hcall_norets(H_CPPR, value);
46 	if (rc != H_SUCCESS) {
47 		pr_err("%s: bad return code cppr cppr=0x%x returned %ld\n",
48 			__func__, value, rc);
49 		WARN_ON_ONCE(1);
50 	}
51 }
52 
53 static inline void icp_hv_set_xirr(unsigned int value)
54 {
55 	long rc = plpar_hcall_norets(H_EOI, value);
56 	if (rc != H_SUCCESS) {
57 		pr_err("%s: bad return code eoi xirr=0x%x returned %ld\n",
58 			__func__, value, rc);
59 		WARN_ON_ONCE(1);
60 		icp_hv_set_cppr(value >> 24);
61 	}
62 }
63 
64 static inline void icp_hv_set_qirr(int n_cpu , u8 value)
65 {
66 	int hw_cpu = get_hard_smp_processor_id(n_cpu);
67 	long rc;
68 
69 	/* Make sure all previous accesses are ordered before IPI sending */
70 	mb();
71 	rc = plpar_hcall_norets(H_IPI, hw_cpu, value);
72 	if (rc != H_SUCCESS) {
73 		pr_err("%s: bad return code qirr cpu=%d hw_cpu=%d mfrr=0x%x "
74 			"returned %ld\n", __func__, n_cpu, hw_cpu, value, rc);
75 		WARN_ON_ONCE(1);
76 	}
77 }
78 
79 static void icp_hv_eoi(struct irq_data *d)
80 {
81 	unsigned int hw_irq = (unsigned int)irqd_to_hwirq(d);
82 
83 	iosync();
84 	icp_hv_set_xirr((xics_pop_cppr() << 24) | hw_irq);
85 }
86 
87 static void icp_hv_teardown_cpu(void)
88 {
89 	int cpu = smp_processor_id();
90 
91 	/* Clear any pending IPI */
92 	icp_hv_set_qirr(cpu, 0xff);
93 }
94 
95 static void icp_hv_flush_ipi(void)
96 {
97 	/* We take the ipi irq but and never return so we
98 	 * need to EOI the IPI, but want to leave our priority 0
99 	 *
100 	 * should we check all the other interrupts too?
101 	 * should we be flagging idle loop instead?
102 	 * or creating some task to be scheduled?
103 	 */
104 
105 	icp_hv_set_xirr((0x00 << 24) | XICS_IPI);
106 }
107 
108 static unsigned int icp_hv_get_irq(void)
109 {
110 	unsigned int xirr = icp_hv_get_xirr(xics_cppr_top());
111 	unsigned int vec = xirr & 0x00ffffff;
112 	unsigned int irq;
113 
114 	if (vec == XICS_IRQ_SPURIOUS)
115 		return NO_IRQ;
116 
117 	irq = irq_find_mapping(xics_host, vec);
118 	if (likely(irq != NO_IRQ)) {
119 		xics_push_cppr(vec);
120 		return irq;
121 	}
122 
123 	/* We don't have a linux mapping, so have rtas mask it. */
124 	xics_mask_unknown_vec(vec);
125 
126 	/* We might learn about it later, so EOI it */
127 	icp_hv_set_xirr(xirr);
128 
129 	return NO_IRQ;
130 }
131 
132 static void icp_hv_set_cpu_priority(unsigned char cppr)
133 {
134 	xics_set_base_cppr(cppr);
135 	icp_hv_set_cppr(cppr);
136 	iosync();
137 }
138 
139 #ifdef CONFIG_SMP
140 
141 static void icp_hv_cause_ipi(int cpu, unsigned long data)
142 {
143 	icp_hv_set_qirr(cpu, IPI_PRIORITY);
144 }
145 
146 static irqreturn_t icp_hv_ipi_action(int irq, void *dev_id)
147 {
148 	int cpu = smp_processor_id();
149 
150 	icp_hv_set_qirr(cpu, 0xff);
151 
152 	return smp_ipi_demux();
153 }
154 
155 #endif /* CONFIG_SMP */
156 
157 static const struct icp_ops icp_hv_ops = {
158 	.get_irq	= icp_hv_get_irq,
159 	.eoi		= icp_hv_eoi,
160 	.set_priority	= icp_hv_set_cpu_priority,
161 	.teardown_cpu	= icp_hv_teardown_cpu,
162 	.flush_ipi	= icp_hv_flush_ipi,
163 #ifdef CONFIG_SMP
164 	.ipi_action	= icp_hv_ipi_action,
165 	.cause_ipi	= icp_hv_cause_ipi,
166 #endif
167 };
168 
169 int icp_hv_init(void)
170 {
171 	struct device_node *np;
172 
173 	np = of_find_compatible_node(NULL, NULL, "ibm,ppc-xicp");
174 	if (!np)
175 		np = of_find_node_by_type(NULL,
176 				    "PowerPC-External-Interrupt-Presentation");
177 	if (!np)
178 		return -ENODEV;
179 
180 	icp_ops = &icp_hv_ops;
181 
182 	return 0;
183 }
184 
185