xref: /kvm-unit-tests/arm/gic.c (revision 6d4d7c4be7c181ab4afeb3a99e92c3c61a0496cf)
1ac4a67b6SAndrew Jones /*
2ac4a67b6SAndrew Jones  * GIC tests
3ac4a67b6SAndrew Jones  *
4ac4a67b6SAndrew Jones  * GICv2
5ac4a67b6SAndrew Jones  *   + test sending/receiving IPIs
62e2d471dSAndrew Jones  * GICv3
72e2d471dSAndrew Jones  *   + test sending/receiving IPIs
8ac4a67b6SAndrew Jones  *
9ac4a67b6SAndrew Jones  * Copyright (C) 2016, Red Hat Inc, Andrew Jones <drjones@redhat.com>
10ac4a67b6SAndrew Jones  *
11ac4a67b6SAndrew Jones  * This work is licensed under the terms of the GNU LGPL, version 2.
12ac4a67b6SAndrew Jones  */
13ac4a67b6SAndrew Jones #include <libcflat.h>
14ac4a67b6SAndrew Jones #include <asm/setup.h>
15ac4a67b6SAndrew Jones #include <asm/processor.h>
16ac4a67b6SAndrew Jones #include <asm/delay.h>
17ac4a67b6SAndrew Jones #include <asm/gic.h>
18ac4a67b6SAndrew Jones #include <asm/smp.h>
19ac4a67b6SAndrew Jones #include <asm/barrier.h>
20ac4a67b6SAndrew Jones #include <asm/io.h>
21ac4a67b6SAndrew Jones 
22ca1b7a7bSAndrew Jones #define IPI_SENDER	1
23ca1b7a7bSAndrew Jones #define IPI_IRQ		1
24ca1b7a7bSAndrew Jones 
252e2d471dSAndrew Jones struct gic {
262e2d471dSAndrew Jones 	struct {
272e2d471dSAndrew Jones 		void (*send_self)(void);
282e2d471dSAndrew Jones 		void (*send_broadcast)(void);
292e2d471dSAndrew Jones 	} ipi;
302e2d471dSAndrew Jones };
312e2d471dSAndrew Jones 
322e2d471dSAndrew Jones static struct gic *gic;
33ac4a67b6SAndrew Jones static int acked[NR_CPUS], spurious[NR_CPUS];
34ca1b7a7bSAndrew Jones static int bad_sender[NR_CPUS], bad_irq[NR_CPUS];
35ac4a67b6SAndrew Jones static cpumask_t ready;
36ac4a67b6SAndrew Jones 
37ac4a67b6SAndrew Jones static void nr_cpu_check(int nr)
38ac4a67b6SAndrew Jones {
39ac4a67b6SAndrew Jones 	if (nr_cpus < nr)
40ac4a67b6SAndrew Jones 		report_abort("At least %d cpus required", nr);
41ac4a67b6SAndrew Jones }
42ac4a67b6SAndrew Jones 
43ac4a67b6SAndrew Jones static void wait_on_ready(void)
44ac4a67b6SAndrew Jones {
45ac4a67b6SAndrew Jones 	cpumask_set_cpu(smp_processor_id(), &ready);
46ac4a67b6SAndrew Jones 	while (!cpumask_full(&ready))
47ac4a67b6SAndrew Jones 		cpu_relax();
48ac4a67b6SAndrew Jones }
49ac4a67b6SAndrew Jones 
50ca1b7a7bSAndrew Jones static void stats_reset(void)
51ca1b7a7bSAndrew Jones {
52ca1b7a7bSAndrew Jones 	int i;
53ca1b7a7bSAndrew Jones 
54ca1b7a7bSAndrew Jones 	for (i = 0; i < nr_cpus; ++i) {
55ca1b7a7bSAndrew Jones 		acked[i] = 0;
56ca1b7a7bSAndrew Jones 		bad_sender[i] = -1;
57ca1b7a7bSAndrew Jones 		bad_irq[i] = -1;
58ca1b7a7bSAndrew Jones 	}
59ca1b7a7bSAndrew Jones 	smp_wmb();
60ca1b7a7bSAndrew Jones }
61ca1b7a7bSAndrew Jones 
62ac4a67b6SAndrew Jones static void check_acked(cpumask_t *mask)
63ac4a67b6SAndrew Jones {
64ac4a67b6SAndrew Jones 	int missing = 0, extra = 0, unexpected = 0;
65ac4a67b6SAndrew Jones 	int nr_pass, cpu, i;
66ca1b7a7bSAndrew Jones 	bool bad = false;
67ac4a67b6SAndrew Jones 
68ac4a67b6SAndrew Jones 	/* Wait up to 5s for all interrupts to be delivered */
69ac4a67b6SAndrew Jones 	for (i = 0; i < 50; ++i) {
70ac4a67b6SAndrew Jones 		mdelay(100);
71ac4a67b6SAndrew Jones 		nr_pass = 0;
72ac4a67b6SAndrew Jones 		for_each_present_cpu(cpu) {
73ac4a67b6SAndrew Jones 			smp_rmb();
74ac4a67b6SAndrew Jones 			nr_pass += cpumask_test_cpu(cpu, mask) ?
75ac4a67b6SAndrew Jones 				acked[cpu] == 1 : acked[cpu] == 0;
76ca1b7a7bSAndrew Jones 
77ca1b7a7bSAndrew Jones 			if (bad_sender[cpu] != -1) {
78ca1b7a7bSAndrew Jones 				printf("cpu%d received IPI from wrong sender %d\n",
79ca1b7a7bSAndrew Jones 					cpu, bad_sender[cpu]);
80ca1b7a7bSAndrew Jones 				bad = true;
81ca1b7a7bSAndrew Jones 			}
82ca1b7a7bSAndrew Jones 
83ca1b7a7bSAndrew Jones 			if (bad_irq[cpu] != -1) {
84ca1b7a7bSAndrew Jones 				printf("cpu%d received wrong irq %d\n",
85ca1b7a7bSAndrew Jones 					cpu, bad_irq[cpu]);
86ca1b7a7bSAndrew Jones 				bad = true;
87ca1b7a7bSAndrew Jones 			}
88ac4a67b6SAndrew Jones 		}
89ac4a67b6SAndrew Jones 		if (nr_pass == nr_cpus) {
90ca1b7a7bSAndrew Jones 			report("Completed in %d ms", !bad, ++i * 100);
91ac4a67b6SAndrew Jones 			return;
92ac4a67b6SAndrew Jones 		}
93ac4a67b6SAndrew Jones 	}
94ac4a67b6SAndrew Jones 
95ac4a67b6SAndrew Jones 	for_each_present_cpu(cpu) {
96ac4a67b6SAndrew Jones 		if (cpumask_test_cpu(cpu, mask)) {
97ac4a67b6SAndrew Jones 			if (!acked[cpu])
98ac4a67b6SAndrew Jones 				++missing;
99ac4a67b6SAndrew Jones 			else if (acked[cpu] > 1)
100ac4a67b6SAndrew Jones 				++extra;
101ac4a67b6SAndrew Jones 		} else {
102ac4a67b6SAndrew Jones 			if (acked[cpu])
103ac4a67b6SAndrew Jones 				++unexpected;
104ac4a67b6SAndrew Jones 		}
105ac4a67b6SAndrew Jones 	}
106ac4a67b6SAndrew Jones 
107ac4a67b6SAndrew Jones 	report("Timed-out (5s). ACKS: missing=%d extra=%d unexpected=%d",
108ac4a67b6SAndrew Jones 	       false, missing, extra, unexpected);
109ac4a67b6SAndrew Jones }
110ac4a67b6SAndrew Jones 
111ac4a67b6SAndrew Jones static void check_spurious(void)
112ac4a67b6SAndrew Jones {
113ac4a67b6SAndrew Jones 	int cpu;
114ac4a67b6SAndrew Jones 
115ac4a67b6SAndrew Jones 	smp_rmb();
116ac4a67b6SAndrew Jones 	for_each_present_cpu(cpu) {
117ac4a67b6SAndrew Jones 		if (spurious[cpu])
118ac4a67b6SAndrew Jones 			report_info("WARN: cpu%d got %d spurious interrupts",
119ac4a67b6SAndrew Jones 				cpu, spurious[cpu]);
120ac4a67b6SAndrew Jones 	}
121ac4a67b6SAndrew Jones }
122ac4a67b6SAndrew Jones 
123ca1b7a7bSAndrew Jones static void check_ipi_sender(u32 irqstat)
124ca1b7a7bSAndrew Jones {
125ca1b7a7bSAndrew Jones 	if (gic_version() == 2) {
126ca1b7a7bSAndrew Jones 		int src = (irqstat >> 10) & 7;
127ca1b7a7bSAndrew Jones 
128ca1b7a7bSAndrew Jones 		if (src != IPI_SENDER)
129ca1b7a7bSAndrew Jones 			bad_sender[smp_processor_id()] = src;
130ca1b7a7bSAndrew Jones 	}
131ca1b7a7bSAndrew Jones }
132ca1b7a7bSAndrew Jones 
133ca1b7a7bSAndrew Jones static void check_irqnr(u32 irqnr)
134ca1b7a7bSAndrew Jones {
135ca1b7a7bSAndrew Jones 	if (irqnr != IPI_IRQ)
136ca1b7a7bSAndrew Jones 		bad_irq[smp_processor_id()] = irqnr;
137ca1b7a7bSAndrew Jones }
138ca1b7a7bSAndrew Jones 
139ac4a67b6SAndrew Jones static void ipi_handler(struct pt_regs *regs __unused)
140ac4a67b6SAndrew Jones {
1412e2d471dSAndrew Jones 	u32 irqstat = gic_read_iar();
1422e2d471dSAndrew Jones 	u32 irqnr = gic_iar_irqnr(irqstat);
143ac4a67b6SAndrew Jones 
144ac4a67b6SAndrew Jones 	if (irqnr != GICC_INT_SPURIOUS) {
1452e2d471dSAndrew Jones 		gic_write_eoir(irqstat);
146ca1b7a7bSAndrew Jones 		smp_rmb(); /* pairs with wmb in stats_reset */
147ac4a67b6SAndrew Jones 		++acked[smp_processor_id()];
148ca1b7a7bSAndrew Jones 		check_ipi_sender(irqstat);
149ca1b7a7bSAndrew Jones 		check_irqnr(irqnr);
150ac4a67b6SAndrew Jones 		smp_wmb(); /* pairs with rmb in check_acked */
151ac4a67b6SAndrew Jones 	} else {
152ac4a67b6SAndrew Jones 		++spurious[smp_processor_id()];
153ac4a67b6SAndrew Jones 		smp_wmb();
154ac4a67b6SAndrew Jones 	}
155ac4a67b6SAndrew Jones }
156ac4a67b6SAndrew Jones 
1572e2d471dSAndrew Jones static void gicv2_ipi_send_self(void)
1582e2d471dSAndrew Jones {
159ca1b7a7bSAndrew Jones 	writel(2 << 24 | IPI_IRQ, gicv2_dist_base() + GICD_SGIR);
1602e2d471dSAndrew Jones }
1612e2d471dSAndrew Jones 
1622e2d471dSAndrew Jones static void gicv2_ipi_send_broadcast(void)
1632e2d471dSAndrew Jones {
164ca1b7a7bSAndrew Jones 	writel(1 << 24 | IPI_IRQ, gicv2_dist_base() + GICD_SGIR);
1652e2d471dSAndrew Jones }
1662e2d471dSAndrew Jones 
1672e2d471dSAndrew Jones static void gicv3_ipi_send_self(void)
1682e2d471dSAndrew Jones {
169ca1b7a7bSAndrew Jones 	gic_ipi_send_single(IPI_IRQ, smp_processor_id());
1702e2d471dSAndrew Jones }
1712e2d471dSAndrew Jones 
1722e2d471dSAndrew Jones static void gicv3_ipi_send_broadcast(void)
1732e2d471dSAndrew Jones {
174ca1b7a7bSAndrew Jones 	gicv3_write_sgi1r(1ULL << 40 | IPI_IRQ << 24);
1752e2d471dSAndrew Jones 	isb();
1762e2d471dSAndrew Jones }
1772e2d471dSAndrew Jones 
178ac4a67b6SAndrew Jones static void ipi_test_self(void)
179ac4a67b6SAndrew Jones {
180ac4a67b6SAndrew Jones 	cpumask_t mask;
181ac4a67b6SAndrew Jones 
182ac4a67b6SAndrew Jones 	report_prefix_push("self");
183ca1b7a7bSAndrew Jones 	stats_reset();
184ac4a67b6SAndrew Jones 	cpumask_clear(&mask);
185ca1b7a7bSAndrew Jones 	cpumask_set_cpu(smp_processor_id(), &mask);
1862e2d471dSAndrew Jones 	gic->ipi.send_self();
187ac4a67b6SAndrew Jones 	check_acked(&mask);
188ac4a67b6SAndrew Jones 	report_prefix_pop();
189ac4a67b6SAndrew Jones }
190ac4a67b6SAndrew Jones 
191ac4a67b6SAndrew Jones static void ipi_test_smp(void)
192ac4a67b6SAndrew Jones {
193ac4a67b6SAndrew Jones 	cpumask_t mask;
1942e2d471dSAndrew Jones 	int i;
195ac4a67b6SAndrew Jones 
196ac4a67b6SAndrew Jones 	report_prefix_push("target-list");
197ca1b7a7bSAndrew Jones 	stats_reset();
1982e2d471dSAndrew Jones 	cpumask_copy(&mask, &cpu_present_mask);
199ca1b7a7bSAndrew Jones 	for (i = smp_processor_id() & 1; i < nr_cpus; i += 2)
2002e2d471dSAndrew Jones 		cpumask_clear_cpu(i, &mask);
201ca1b7a7bSAndrew Jones 	gic_ipi_send_mask(IPI_IRQ, &mask);
202ac4a67b6SAndrew Jones 	check_acked(&mask);
203ac4a67b6SAndrew Jones 	report_prefix_pop();
204ac4a67b6SAndrew Jones 
205ac4a67b6SAndrew Jones 	report_prefix_push("broadcast");
206ca1b7a7bSAndrew Jones 	stats_reset();
207ac4a67b6SAndrew Jones 	cpumask_copy(&mask, &cpu_present_mask);
208ca1b7a7bSAndrew Jones 	cpumask_clear_cpu(smp_processor_id(), &mask);
2092e2d471dSAndrew Jones 	gic->ipi.send_broadcast();
210ac4a67b6SAndrew Jones 	check_acked(&mask);
211ac4a67b6SAndrew Jones 	report_prefix_pop();
212ac4a67b6SAndrew Jones }
213ac4a67b6SAndrew Jones 
214ac4a67b6SAndrew Jones static void ipi_enable(void)
215ac4a67b6SAndrew Jones {
2162e2d471dSAndrew Jones 	gic_enable_defaults();
217ac4a67b6SAndrew Jones #ifdef __arm__
218ac4a67b6SAndrew Jones 	install_exception_handler(EXCPTN_IRQ, ipi_handler);
219ac4a67b6SAndrew Jones #else
220ac4a67b6SAndrew Jones 	install_irq_handler(EL1H_IRQ, ipi_handler);
221ac4a67b6SAndrew Jones #endif
222ac4a67b6SAndrew Jones 	local_irq_enable();
223ac4a67b6SAndrew Jones }
224ac4a67b6SAndrew Jones 
225ca1b7a7bSAndrew Jones static void ipi_send(void)
226ca1b7a7bSAndrew Jones {
227ca1b7a7bSAndrew Jones 	ipi_enable();
228ca1b7a7bSAndrew Jones 	wait_on_ready();
229ca1b7a7bSAndrew Jones 	ipi_test_self();
230ca1b7a7bSAndrew Jones 	ipi_test_smp();
231ca1b7a7bSAndrew Jones 	check_spurious();
232ca1b7a7bSAndrew Jones 	exit(report_summary());
233ca1b7a7bSAndrew Jones }
234ca1b7a7bSAndrew Jones 
235ac4a67b6SAndrew Jones static void ipi_recv(void)
236ac4a67b6SAndrew Jones {
237ac4a67b6SAndrew Jones 	ipi_enable();
238ac4a67b6SAndrew Jones 	cpumask_set_cpu(smp_processor_id(), &ready);
239ac4a67b6SAndrew Jones 	while (1)
240ac4a67b6SAndrew Jones 		wfi();
241ac4a67b6SAndrew Jones }
242ac4a67b6SAndrew Jones 
24300b34f56SAndrew Jones static void ipi_test(void *data __unused)
244bfd500b4SAndrew Jones {
245bfd500b4SAndrew Jones 	if (smp_processor_id() == IPI_SENDER)
246bfd500b4SAndrew Jones 		ipi_send();
247bfd500b4SAndrew Jones 	else
248bfd500b4SAndrew Jones 		ipi_recv();
249bfd500b4SAndrew Jones }
250bfd500b4SAndrew Jones 
2512e2d471dSAndrew Jones static struct gic gicv2 = {
2522e2d471dSAndrew Jones 	.ipi = {
2532e2d471dSAndrew Jones 		.send_self = gicv2_ipi_send_self,
2542e2d471dSAndrew Jones 		.send_broadcast = gicv2_ipi_send_broadcast,
2552e2d471dSAndrew Jones 	},
2562e2d471dSAndrew Jones };
2572e2d471dSAndrew Jones 
2582e2d471dSAndrew Jones static struct gic gicv3 = {
2592e2d471dSAndrew Jones 	.ipi = {
2602e2d471dSAndrew Jones 		.send_self = gicv3_ipi_send_self,
2612e2d471dSAndrew Jones 		.send_broadcast = gicv3_ipi_send_broadcast,
2622e2d471dSAndrew Jones 	},
2632e2d471dSAndrew Jones };
2642e2d471dSAndrew Jones 
265c152d8bcSChristoffer Dall static void ipi_clear_active_handler(struct pt_regs *regs __unused)
266c152d8bcSChristoffer Dall {
267c152d8bcSChristoffer Dall 	u32 irqstat = gic_read_iar();
268c152d8bcSChristoffer Dall 	u32 irqnr = gic_iar_irqnr(irqstat);
269c152d8bcSChristoffer Dall 
270c152d8bcSChristoffer Dall 	if (irqnr != GICC_INT_SPURIOUS) {
271c152d8bcSChristoffer Dall 		void *base;
272c152d8bcSChristoffer Dall 		u32 val = 1 << IPI_IRQ;
273c152d8bcSChristoffer Dall 
274c152d8bcSChristoffer Dall 		if (gic_version() == 2)
275c152d8bcSChristoffer Dall 			base = gicv2_dist_base();
276c152d8bcSChristoffer Dall 		else
277*6d4d7c4bSAndrew Jones 			base = gicv3_sgi_base();
278c152d8bcSChristoffer Dall 
279c152d8bcSChristoffer Dall 		writel(val, base + GICD_ICACTIVER);
280c152d8bcSChristoffer Dall 
281c152d8bcSChristoffer Dall 		smp_rmb(); /* pairs with wmb in stats_reset */
282c152d8bcSChristoffer Dall 		++acked[smp_processor_id()];
283c152d8bcSChristoffer Dall 		check_irqnr(irqnr);
284c152d8bcSChristoffer Dall 		smp_wmb(); /* pairs with rmb in check_acked */
285c152d8bcSChristoffer Dall 	} else {
286c152d8bcSChristoffer Dall 		++spurious[smp_processor_id()];
287c152d8bcSChristoffer Dall 		smp_wmb();
288c152d8bcSChristoffer Dall 	}
289c152d8bcSChristoffer Dall }
290c152d8bcSChristoffer Dall 
291c152d8bcSChristoffer Dall static void run_active_clear_test(void)
292c152d8bcSChristoffer Dall {
293c152d8bcSChristoffer Dall 	report_prefix_push("active");
294c152d8bcSChristoffer Dall 	gic_enable_defaults();
295c152d8bcSChristoffer Dall #ifdef __arm__
296c152d8bcSChristoffer Dall 	install_exception_handler(EXCPTN_IRQ, ipi_clear_active_handler);
297c152d8bcSChristoffer Dall #else
298c152d8bcSChristoffer Dall 	install_irq_handler(EL1H_IRQ, ipi_clear_active_handler);
299c152d8bcSChristoffer Dall #endif
300c152d8bcSChristoffer Dall 	local_irq_enable();
301c152d8bcSChristoffer Dall 
302c152d8bcSChristoffer Dall 	ipi_test_self();
303c152d8bcSChristoffer Dall 	report_prefix_pop();
304c152d8bcSChristoffer Dall }
305c152d8bcSChristoffer Dall 
306ac4a67b6SAndrew Jones int main(int argc, char **argv)
307ac4a67b6SAndrew Jones {
3082e2d471dSAndrew Jones 	if (!gic_init()) {
309ac4a67b6SAndrew Jones 		printf("No supported gic present, skipping tests...\n");
310ac4a67b6SAndrew Jones 		return report_summary();
311ac4a67b6SAndrew Jones 	}
312ac4a67b6SAndrew Jones 
3132b19b829SAndrew Jones 	report_prefix_pushf("gicv%d", gic_version());
314ac4a67b6SAndrew Jones 
3152e2d471dSAndrew Jones 	switch (gic_version()) {
3162e2d471dSAndrew Jones 	case 2:
3172e2d471dSAndrew Jones 		gic = &gicv2;
3182e2d471dSAndrew Jones 		break;
3192e2d471dSAndrew Jones 	case 3:
3202e2d471dSAndrew Jones 		gic = &gicv3;
3212e2d471dSAndrew Jones 		break;
3222e2d471dSAndrew Jones 	}
3232e2d471dSAndrew Jones 
324ac4a67b6SAndrew Jones 	if (argc < 2)
325ac4a67b6SAndrew Jones 		report_abort("no test specified");
326ac4a67b6SAndrew Jones 
327ac4a67b6SAndrew Jones 	if (strcmp(argv[1], "ipi") == 0) {
328ac4a67b6SAndrew Jones 		report_prefix_push(argv[1]);
329ac4a67b6SAndrew Jones 		nr_cpu_check(2);
33000b34f56SAndrew Jones 		on_cpus(ipi_test, NULL);
331c152d8bcSChristoffer Dall 	} else if (strcmp(argv[1], "active") == 0) {
332c152d8bcSChristoffer Dall 		run_active_clear_test();
333ac4a67b6SAndrew Jones 	} else {
334ac4a67b6SAndrew Jones 		report_abort("Unknown subtest '%s'", argv[1]);
335ac4a67b6SAndrew Jones 	}
336ac4a67b6SAndrew Jones 
337ac4a67b6SAndrew Jones 	return report_summary();
338ac4a67b6SAndrew Jones }
339