1/* $NetBSD: apple_intc.c,v 1.9 2022/06/28 10:42:22 jmcneill Exp $ */
2
3/*-
4 * Copyright (c) 2021 Jared McNeill <jmcneill@invisible.ca>
5 * All rights reserved.
6 *
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
9 * are met:
10 * 1. Redistributions of source code must retain the above copyright
11 *    notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 *    notice, this list of conditions and the following disclaimer in the
14 *    documentation and/or other materials provided with the distribution.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
18 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
19 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
20 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
21 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
23 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26 * SUCH DAMAGE.
27 */
28
29#include "opt_ddb.h"
30#include "opt_multiprocessor.h"
31
32#define	_INTR_PRIVATE
33
34#include <sys/cdefs.h>
35__KERNEL_RCSID(0, "$NetBSD: apple_intc.c,v 1.9 2022/06/28 10:42:22 jmcneill Exp $");
36
37#include <sys/param.h>
38#include <sys/bus.h>
39#include <sys/device.h>
40#include <sys/intr.h>
41#include <sys/kernel.h>
42#include <sys/lwp.h>
43#include <sys/systm.h>
44#include <sys/cpu.h>
45#include <sys/kmem.h>
46#include <sys/atomic.h>
47
48#include <dev/fdt/fdtvar.h>
49
50#include <dev/pci/pcireg.h>
51#include <dev/pci/pcivar.h>
52
53#include <arm/cpu.h>
54#include <arm/cpufunc.h>
55#include <arm/armreg.h>
56#include <arm/locore.h>
57#include <arm/pic/picvar.h>
58#include <arm/fdt/arm_fdtvar.h>
59
60/*
61 * AIC registers
62 */
63#define	AIC_INFO		0x0004
64#define	 AIC_INFO_NIRQ		__BITS(15,0)
65#define	AIC_WHOAMI		0x2000
66#define	AIC_EVENT		0x2004
67#define	 AIC_EVENT_TYPE		__BITS(31,16)
68#define	  AIC_EVENT_TYPE_NONE	0
69#define	  AIC_EVENT_TYPE_IRQ	1
70#define	  AIC_EVENT_TYPE_IPI	4
71#define	 AIC_EVENT_DATA		__BITS(15,0)
72#define	 AIC_EVENT_IPI_OTHER	1
73#define	AIC_IPI_SEND		0x2008
74#define	AIC_IPI_ACK		0x200c
75#define	AIC_IPI_MASK_CLR	0x2028
76#define	AIC_IPI_OTHER		__BIT(0)
77#define	AIC_AFFINITY(irqno)	(0x3000 + (irqno) * 4)
78#define	AIC_SW_SET(irqno)	(0x4000 + (irqno) / 32 * 4)
79#define	AIC_SW_CLR(irqno)	(0x4080 + (irqno) / 32 * 4)
80#define	AIC_MASK_SET(irqno)	(0x4100 + (irqno) / 32 * 4)
81#define	AIC_MASK_CLR(irqno)	(0x4180 + (irqno) / 32 * 4)
82#define	 AIC_MASK_BIT(irqno)	__BIT((irqno) & 0x1f)
83
84static const struct device_compatible_entry compat_data[] = {
85	{ .compat = "apple,aic" },
86	DEVICE_COMPAT_EOL
87};
88
89struct apple_intc_softc;
90
91struct apple_intc_percpu {
92	struct apple_intc_softc *pc_sc;
93	u_int pc_cpuid;
94	u_int pc_ipimask;
95
96	struct pic_softc pc_pic;
97};
98
99#define	LOCALPIC_SOURCE_TIMER	0
100#define	LOCALPIC_SOURCE_IPI	1
101
102struct apple_intc_softc {
103	device_t sc_dev;		/* device handle */
104	bus_space_tag_t sc_bst;		/* mmio tag */
105	bus_space_handle_t sc_bsh;	/* mmio handle */
106	u_int sc_nirq;			/* number of supported IRQs */
107	u_int *sc_cpuid;		/* map of cpu index to AIC CPU ID */
108	struct apple_intc_percpu *sc_pc; /* per-CPU data for timer and IPIs */
109
110	struct pic_softc sc_pic;
111};
112
113static struct apple_intc_softc *intc_softc;
114
115#define	PICTOSOFTC(pic) container_of(pic, struct apple_intc_softc, sc_pic)
116#define	PICTOPERCPU(pic) container_of(pic, struct apple_intc_percpu, pc_pic)
117
118#define AIC_READ(sc, reg) \
119	bus_space_read_4((sc)->sc_bst, (sc)->sc_bsh, (reg))
120#define	AIC_WRITE(sc, reg, val) \
121	bus_space_write_4((sc)->sc_bst, (sc)->sc_bsh, (reg), (val))
122
123static void
124apple_intc_unblock_irqs(struct pic_softc *pic, size_t irqbase, uint32_t mask)
125{
126	struct apple_intc_softc * const sc = PICTOSOFTC(pic);
127
128	AIC_WRITE(sc, AIC_SW_SET(irqbase), mask);
129	AIC_WRITE(sc, AIC_MASK_CLR(irqbase), mask);
130}
131
132static void
133apple_intc_block_irqs(struct pic_softc *pic, size_t irqbase, uint32_t mask)
134{
135}
136
137static void
138apple_intc_establish_irq(struct pic_softc *pic, struct intrsource *is)
139{
140	struct apple_intc_softc * const sc = PICTOSOFTC(pic);
141
142	KASSERT(is->is_type == IST_LEVEL);
143
144	/* Route to primary PE by default */
145	AIC_WRITE(sc, AIC_AFFINITY(is->is_irq), __BIT(0));
146	AIC_WRITE(sc, AIC_MASK_CLR(is->is_irq),
147	    AIC_MASK_BIT(is->is_irq));
148}
149
150static void
151apple_intc_set_priority(struct pic_softc *pic, int ipl)
152{
153	curcpu()->ci_cpl = ipl;
154}
155
156static void
157apple_intc_cpu_init(struct pic_softc *pic, struct cpu_info *ci)
158{
159	struct apple_intc_softc * const sc = PICTOSOFTC(pic);
160	const u_int cpuno = cpu_index(ci);
161
162	sc->sc_cpuid[cpuno] = AIC_READ(sc, AIC_WHOAMI);
163}
164
165static const struct pic_ops apple_intc_picops = {
166	.pic_unblock_irqs = apple_intc_unblock_irqs,
167	.pic_block_irqs = apple_intc_block_irqs,
168	.pic_establish_irq = apple_intc_establish_irq,
169	.pic_set_priority = apple_intc_set_priority,
170#ifdef MULTIPROCESSOR
171	.pic_cpu_init = apple_intc_cpu_init,
172#endif
173};
174
175static void
176apple_intc_local_unblock_irqs(struct pic_softc *pic, size_t irqbase,
177    uint32_t mask)
178{
179	KASSERT(irqbase == 0);
180
181	if ((mask & __BIT(LOCALPIC_SOURCE_TIMER)) != 0) {
182		gtmr_cntv_ctl_write(gtmr_cntv_ctl_read() & ~CNTCTL_IMASK);
183		isb();
184	}
185}
186
187static void
188apple_intc_local_block_irqs(struct pic_softc *pic, size_t irqbase,
189    uint32_t mask)
190{
191	KASSERT(irqbase == 0);
192
193	if ((mask & __BIT(LOCALPIC_SOURCE_TIMER)) != 0) {
194		gtmr_cntv_ctl_write(gtmr_cntv_ctl_read() | CNTCTL_IMASK);
195		isb();
196	}
197}
198
199static void
200apple_intc_local_establish_irq(struct pic_softc *pic, struct intrsource *is)
201{
202}
203
204#ifdef MULTIPROCESSOR
205static void
206apple_intc_local_ipi_send(struct pic_softc *pic, const kcpuset_t *kcp, u_long ipi)
207{
208	struct apple_intc_percpu * const pc = PICTOPERCPU(pic);
209	struct apple_intc_softc * const sc = pc->pc_sc;
210	const u_int target = sc->sc_cpuid[pc->pc_cpuid];
211
212	atomic_or_32(&pc->pc_ipimask, __BIT(ipi));
213	AIC_WRITE(sc, AIC_IPI_SEND, __BIT(target));
214}
215#endif /* MULTIPROCESSOR */
216
217static const struct pic_ops apple_intc_localpicops = {
218	.pic_unblock_irqs = apple_intc_local_unblock_irqs,
219	.pic_block_irqs = apple_intc_local_block_irqs,
220	.pic_establish_irq = apple_intc_local_establish_irq,
221#ifdef MULTIPROCESSOR
222	.pic_ipi_send = apple_intc_local_ipi_send,
223#endif
224};
225
226static void *
227apple_intc_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags,
228    int (*func)(void *), void *arg, const char *xname)
229{
230	struct apple_intc_softc * const sc = device_private(dev);
231
232	/* 1st cell is the interrupt type (0=IRQ, 1=FIQ) */
233	const u_int type = be32toh(specifier[0]);
234	/* 2nd cell is the interrupt number */
235	const u_int intno = be32toh(specifier[1]);
236	/* 3rd cell is the interrupt flags */
237
238	const u_int mpsafe = (flags & FDT_INTR_MPSAFE) ? IST_MPSAFE : 0;
239
240	if (type == 0)
241		return intr_establish_xname(intno, ipl, IST_LEVEL | mpsafe,
242		    func, arg, xname);
243
244	/* interate over CPUs for LOCALPIC_SOURCE_TIMER */
245	CPU_INFO_ITERATOR cii;
246	struct cpu_info *ci;
247	void *ih = NULL;
248	for (CPU_INFO_FOREACH(cii, ci)) {
249		const cpuid_t cpuno = cpu_index(ci);
250		struct apple_intc_percpu * const pc = &sc->sc_pc[cpuno];
251		struct pic_softc * const pic = &pc->pc_pic;
252		const int irq = pic->pic_irqbase + LOCALPIC_SOURCE_TIMER;
253
254		void *ihn = intr_establish_xname(irq, ipl, IST_LEVEL | mpsafe,
255		    func, arg, xname);
256		if (cpuno == 0)
257			ih = ihn;
258	}
259	return ih;
260}
261
262static void
263apple_intc_fdt_disestablish(device_t dev, void *ih)
264{
265	intr_disestablish(ih);
266}
267
268static bool
269apple_intc_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen)
270{
271	if (!specifier)
272		return false;
273
274	/* 1st cell is the interrupt type (0=IRQ, 1=FIQ) */
275	const u_int type = be32toh(specifier[0]);
276	/* 2nd cell is the interrupt number */
277	const u_int intno = be32toh(specifier[1]);
278
279	snprintf(buf, buflen, "%s %u", type == 0 ? "irq" : "fiq", intno);
280
281	return true;
282}
283
284static const struct fdtbus_interrupt_controller_func apple_intc_fdt_funcs = {
285	.establish = apple_intc_fdt_establish,
286	.disestablish = apple_intc_fdt_disestablish,
287	.intrstr = apple_intc_fdt_intrstr,
288};
289
290static void
291apple_intc_mark_pending(struct pic_softc *pic, u_int intno)
292{
293	const int base = intno & ~0x1f;
294	const uint32_t pending = __BIT(intno & 0x1f);
295	pic_mark_pending_sources(pic, base, pending);
296}
297
298static void
299apple_intc_irq_handler(void *frame)
300{
301	struct cpu_info * const ci = curcpu();
302	struct apple_intc_softc * const sc = intc_softc;
303	struct pic_softc *pic;
304	struct intrsource *is;
305	const int oldipl = ci->ci_cpl;
306	uint16_t evtype, evdata;
307	bus_size_t clr_reg;
308	uint32_t clr_val;
309
310	ci->ci_data.cpu_nintr++;
311
312	for (;;) {
313		const uint32_t ev = AIC_READ(sc, AIC_EVENT);
314		evtype = __SHIFTOUT(ev, AIC_EVENT_TYPE);
315		evdata = __SHIFTOUT(ev, AIC_EVENT_DATA);
316
317		dsb(sy);
318		isb();
319
320		if (evtype == AIC_EVENT_TYPE_IRQ) {
321			KASSERT(evdata < sc->sc_nirq);
322			pic = &sc->sc_pic;
323			is = pic->pic_sources[evdata];
324			KASSERT(is != NULL);
325
326			AIC_WRITE(sc, AIC_SW_CLR(evdata),
327			    __BIT(evdata & 0x1f));
328
329			clr_reg = AIC_MASK_CLR(evdata);
330			clr_val = AIC_MASK_BIT(evdata);
331		} else if (evtype == AIC_EVENT_TYPE_IPI) {
332			KASSERT(evdata == AIC_EVENT_IPI_OTHER);
333			pic = &sc->sc_pc[cpu_index(ci)].pc_pic;
334			is = pic->pic_sources[LOCALPIC_SOURCE_IPI];
335			KASSERT(is != NULL);
336
337			AIC_WRITE(sc, AIC_IPI_ACK, AIC_IPI_OTHER);
338
339			clr_reg = 0;
340			clr_val = 0;
341		} else {
342			break;
343		}
344
345		if (ci->ci_cpl >= is->is_ipl) {
346			apple_intc_mark_pending(pic, is->is_irq);
347		} else {
348			pic_set_priority(ci, is->is_ipl);
349			ENABLE_INTERRUPT();
350			pic_dispatch(is, frame);
351			DISABLE_INTERRUPT();
352
353			if (clr_val != 0) {
354				AIC_WRITE(sc, clr_reg, clr_val);
355			}
356		}
357	}
358
359	if (oldipl != IPL_HIGH) {
360		pic_do_pending_ints(DAIF_I|DAIF_F, oldipl, frame);
361	}
362}
363
364static void
365apple_intc_fiq_handler(void *frame)
366{
367	struct cpu_info * const ci = curcpu();
368	struct apple_intc_softc * const sc = intc_softc;
369	struct pic_softc * const pic = &sc->sc_pc[cpu_index(ci)].pc_pic;
370	const int oldipl = ci->ci_cpl;
371
372	ci->ci_data.cpu_nintr++;
373
374	struct intrsource * const is = pic->pic_sources[LOCALPIC_SOURCE_TIMER];
375
376	dsb(sy);
377	isb();
378
379	if (oldipl >= is->is_ipl) {
380		apple_intc_mark_pending(pic, LOCALPIC_SOURCE_TIMER);
381	} else {
382		pic_set_priority(ci, is->is_ipl);
383		pic_dispatch(is, frame);
384	}
385
386	if (oldipl != IPL_HIGH) {
387		pic_do_pending_ints(DAIF_I|DAIF_F, oldipl, frame);
388	}
389}
390
391#ifdef MULTIPROCESSOR
392static int
393apple_intc_ipi_handler(void *priv)
394{
395	struct apple_intc_percpu * const pc = priv;
396	struct apple_intc_softc * const sc = pc->pc_sc;
397	uint32_t ipimask, bit;
398
399	AIC_WRITE(sc, AIC_IPI_MASK_CLR, AIC_IPI_OTHER);
400	ipimask = atomic_swap_32(&pc->pc_ipimask, 0);
401
402	while ((bit = ffs(ipimask)) > 0) {
403		const u_int ipi = bit - 1;
404
405		switch (ipi) {
406		case IPI_AST:
407			pic_ipi_ast(priv);
408			break;
409		case IPI_NOP:
410			pic_ipi_nop(priv);
411			break;
412#ifdef __HAVE_PREEMPTION
413		case IPI_KPREEMPT:
414			pic_ipi_kpreempt(priv);
415			break;
416#endif
417		case IPI_XCALL:
418			pic_ipi_xcall(priv);
419			break;
420		case IPI_GENERIC:
421			pic_ipi_generic(priv);
422			break;
423		case IPI_SHOOTDOWN:
424			pic_ipi_shootdown(priv);
425			break;
426#ifdef DDB
427		case IPI_DDB:
428			pic_ipi_ddb(priv);
429			break;
430#endif
431		}
432		ipimask &= ~__BIT(ipi);
433	}
434
435	return 1;
436}
437#endif /* MULTIPROCESSOR */
438
439static int
440apple_intc_match(device_t parent, cfdata_t cf, void *aux)
441{
442	struct fdt_attach_args * const faa = aux;
443
444	return of_compatible_match(faa->faa_phandle, compat_data);
445}
446
447static void
448apple_intc_attach(device_t parent, device_t self, void *aux)
449{
450	struct apple_intc_softc * const sc = device_private(self);
451	struct fdt_attach_args * const faa = aux;
452	const int phandle = faa->faa_phandle;
453	bus_addr_t addr;
454	bus_size_t size;
455	int error;
456
457	if (fdtbus_get_reg(phandle, 0, &addr, &size) != 0) {
458		aprint_error(": couldn't get registers\n");
459		return;
460	}
461
462	sc->sc_dev = self;
463	sc->sc_bst = faa->faa_bst;
464	if (bus_space_map(sc->sc_bst, addr, size, 0, &sc->sc_bsh) != 0) {
465		aprint_error(": couldn't map registers\n");
466		return;
467	}
468
469	sc->sc_nirq = AIC_READ(sc, AIC_INFO) & AIC_INFO_NIRQ;
470
471	aprint_naive("\n");
472	aprint_normal(": Apple AIC (%u IRQs, 1 FIQ)\n", sc->sc_nirq);
473	KASSERT(sc->sc_nirq % 32 == 0);
474
475	sc->sc_pic.pic_ops = &apple_intc_picops;
476	sc->sc_pic.pic_maxsources = sc->sc_nirq;
477	snprintf(sc->sc_pic.pic_name, sizeof(sc->sc_pic.pic_name), "AIC");
478	pic_add(&sc->sc_pic, 0);
479
480	error = fdtbus_register_interrupt_controller(self, phandle,
481	    &apple_intc_fdt_funcs);
482	if (error) {
483		aprint_error_dev(self, "couldn't register with fdtbus: %d\n",
484		    error);
485		return;
486	}
487
488	KASSERT(intc_softc == NULL);
489	intc_softc = sc;
490	arm_fdt_irq_set_handler(apple_intc_irq_handler);
491	arm_fdt_fiq_set_handler(apple_intc_fiq_handler);
492
493	KASSERT(ncpu != 0);
494	sc->sc_cpuid = kmem_zalloc(sizeof(*sc->sc_cpuid) * ncpu, KM_SLEEP);
495	sc->sc_pc = kmem_zalloc(sizeof(*sc->sc_pc) * ncpu, KM_SLEEP);
496
497	CPU_INFO_ITERATOR cii;
498	struct cpu_info *ci;
499	for (CPU_INFO_FOREACH(cii, ci)) {
500		const cpuid_t cpuno = cpu_index(ci);
501		struct apple_intc_percpu * const pc = &sc->sc_pc[cpuno];
502		struct pic_softc * const pic = &pc->pc_pic;
503
504		pc->pc_sc = sc;
505		pc->pc_cpuid = cpuno;
506
507#ifdef MULTIPROCESSOR
508		pic->pic_cpus = ci->ci_kcpuset;
509#endif
510		pic->pic_ops = &apple_intc_localpicops;
511		pic->pic_maxsources = 2;
512		snprintf(pic->pic_name, sizeof(pic->pic_name), "AIC/%lu", cpuno);
513
514		pic_add(pic, PIC_IRQBASE_ALLOC);
515
516#ifdef MULTIPROCESSOR
517		intr_establish_xname(pic->pic_irqbase + LOCALPIC_SOURCE_IPI,
518		    IPL_HIGH, IST_LEVEL | IST_MPSAFE, apple_intc_ipi_handler,
519		    pc, "ipi");
520#endif
521	}
522
523	apple_intc_cpu_init(&sc->sc_pic, curcpu());
524}
525
526CFATTACH_DECL_NEW(apple_intc, sizeof(struct apple_intc_softc),
527	apple_intc_match, apple_intc_attach, NULL, NULL);
528