From 3a2a51e5619936b4fc0a29a358dc04e80e2d5b14 Mon Sep 17 00:00:00 2001 From: kettenis Date: Thu, 4 Aug 2016 12:17:36 +0000 Subject: [PATCH] Add support for pre-registering interrupts. This allows device drivers to establish interrupts before their interrupt controller attaches, solving dependency problems in various device trees. Also add support for handing interrupt handlers over to parent interrupt controllers. ok jsg@ patrick@ (on an earlier diff) --- sys/arch/arm/mainbus/mainbus.c | 6 +- sys/arch/armv7/armv7/intr.c | 124 ++++++++++++++++++++++++++++++++- sys/arch/armv7/include/intr.h | 6 +- 3 files changed, 131 insertions(+), 5 deletions(-) diff --git a/sys/arch/arm/mainbus/mainbus.c b/sys/arch/arm/mainbus/mainbus.c index ec867d6c2a1..4c15f302610 100644 --- a/sys/arch/arm/mainbus/mainbus.c +++ b/sys/arch/arm/mainbus/mainbus.c @@ -1,4 +1,4 @@ -/* $OpenBSD: mainbus.c,v 1.11 2016/07/13 20:42:44 patrick Exp $ */ +/* $OpenBSD: mainbus.c,v 1.12 2016/08/04 12:17:36 kettenis Exp $ */ /* * Copyright (c) 2016 Patrick Wildt * @@ -93,6 +93,10 @@ mainbus_attach(struct device *parent, struct device *self, void *aux) return; } +#ifdef CPU_ARMv7 + arm_intr_init_fdt(); +#endif + #ifdef CPU_ARMv7 extern struct bus_space armv7_bs_tag; sc->sc_iot = &armv7_bs_tag; diff --git a/sys/arch/armv7/armv7/intr.c b/sys/arch/armv7/armv7/intr.c index 178aab99926..c0aaa59f180 100644 --- a/sys/arch/armv7/armv7/intr.c +++ b/sys/arch/armv7/armv7/intr.c @@ -1,4 +1,4 @@ -/* $OpenBSD: intr.c,v 1.7 2016/08/01 21:08:20 kettenis Exp $ */ +/* $OpenBSD: intr.c,v 1.8 2016/08/04 12:17:36 kettenis Exp $ */ /* * Copyright (c) 2011 Dale Rahn * @@ -28,6 +28,11 @@ #include +uint32_t arm_intr_get_parent(int); + +void *arm_intr_prereg_fdt(void *, int *, int, int (*)(void *), + void *, char *); + int arm_dflt_splraise(int); int arm_dflt_spllower(int); void arm_dflt_splx(int); @@ -40,8 +45,6 @@ const char *arm_dflt_intr_string(void *cookie); void arm_dflt_intr(void *); void arm_intr(void *); -uint32_t arm_intr_get_parent(int); - #define SI_TO_IRQBIT(x) (1 << (x)) uint32_t arm_smask[NIPL]; @@ -100,18 +103,110 @@ arm_intr_get_parent(int node) return phandle; } +/* + * Interrupt pre-registration. + * + * To allow device drivers to establish interrupt handlers before all + * relevant interrupt controllers have been attached, we support + * pre-registration of interrupt handlers. For each node in the + * device tree that has an "interrupt-controller" property, we + * register a dummy interrupt controller that simply stashes away all + * relevant details of the interrupt handler being established. + * Later, when the real interrupt controller registers itself, we + * establush those interrupt handlers based on that information. + */ + +#define MAX_INTERRUPT_CELLS 4 + +struct intr_prereg { + LIST_ENTRY(intr_prereg) ip_list; + uint32_t ip_phandle; + uint32_t ip_cell[MAX_INTERRUPT_CELLS]; + + int ip_level; + int (*ip_func)(void *); + void *ip_arg; + char *ip_name; +}; + +LIST_HEAD(, intr_prereg) prereg_interrupts = + LIST_HEAD_INITIALIZER(prereg_interrupts); + +void * +arm_intr_prereg_fdt(void *cookie, int *cell, int level, + int (*func)(void *), void *arg, char *name) +{ + struct interrupt_controller *ic = cookie; + struct intr_prereg *ip; + int i; + + ip = malloc(sizeof(struct intr_prereg), M_DEVBUF, M_ZERO | M_WAITOK); + ip->ip_phandle = ic->ic_phandle; + for (i = 0; i < ic->ic_cells; i++) + ip->ip_cell[i] = cell[i]; + ip->ip_level = level; + ip->ip_func = func; + ip->ip_arg = arg; + ip->ip_name = name; + LIST_INSERT_HEAD(&prereg_interrupts, ip, ip_list); + + return ip; +} + +void +arm_intr_init_fdt_recurse(int node) +{ + struct interrupt_controller *ic; + + if (OF_getproplen(node, "interrupt-controller") >= 0) { + ic = malloc(sizeof(struct interrupt_controller), + M_DEVBUF, M_ZERO | M_WAITOK); + ic->ic_node = node; + ic->ic_cookie = ic; + ic->ic_establish = arm_intr_prereg_fdt; + arm_intr_register_fdt(ic); + } + + for (node = OF_child(node); node; node = OF_peer(node)) + arm_intr_init_fdt_recurse(node); +} + +void +arm_intr_init_fdt(void) +{ + int node = OF_peer(0); + + if (node) + arm_intr_init_fdt_recurse(node); +} + LIST_HEAD(, interrupt_controller) interrupt_controllers = LIST_HEAD_INITIALIZER(interrupt_controllers); void arm_intr_register_fdt(struct interrupt_controller *ic) { + struct intr_prereg *ip; + void *ih; + ic->ic_cells = OF_getpropint(ic->ic_node, "#interrupt-cells", 0); ic->ic_phandle = OF_getpropint(ic->ic_node, "phandle", 0); if (ic->ic_cells == 0 || ic->ic_phandle == 0) return; + KASSERT(ic->ic_cells <= MAX_INTERRUPT_CELLS); LIST_INSERT_HEAD(&interrupt_controllers, ic, ic_list); + + /* Establish pre-registered interrupt handlers. */ + LIST_FOREACH(ip, &prereg_interrupts, ip_list) { + if (ip->ip_phandle != ic->ic_phandle) + continue; + + ih = ic->ic_establish(ic->ic_cookie, ip->ip_cell, + ip->ip_level, ip->ip_func, ip->ip_arg, ip->ip_name); + if (ih == NULL) + printf("can't establish interrupt %s\n", ip->ip_name); + } } void * @@ -187,6 +282,29 @@ arm_intr_establish_fdt_idx(int node, int idx, int level, int (*func)(void *), return val; } +/* + * Some interrupt controllers transparently forward interrupts to + * their parent. Such interrupt controllers can use this function to + * delegate the interrupt handler to their parent. + */ +void * +arm_intr_parent_establish_fdt(void *cookie, int *cell, int level, + int (*func)(void *), void *arg, char *name) +{ + struct interrupt_controller *ic = cookie; + uint32_t phandle; + + phandle = arm_intr_get_parent(ic->ic_node); + LIST_FOREACH(ic, &interrupt_controllers, ic_list) { + if (ic->ic_phandle == phandle) + break; + } + if (ic == NULL) + return NULL; + + return ic->ic_establish(ic->ic_cookie, cell, level, func, arg, name); +} + int arm_dflt_splraise(int newcpl) { diff --git a/sys/arch/armv7/include/intr.h b/sys/arch/armv7/include/intr.h index 76f694b4a72..647c2c26e91 100644 --- a/sys/arch/armv7/include/intr.h +++ b/sys/arch/armv7/include/intr.h @@ -1,4 +1,4 @@ -/* $OpenBSD: intr.h,v 1.3 2016/08/01 14:17:00 patrick Exp $ */ +/* $OpenBSD: intr.h,v 1.4 2016/08/04 12:17:36 kettenis Exp $ */ /* $NetBSD: intr.h,v 1.12 2003/06/16 20:00:59 thorpej Exp $ */ /* @@ -156,12 +156,16 @@ struct interrupt_controller { uint32_t ic_cells; }; +void arm_intr_init_fdt(void); void arm_intr_register_fdt(struct interrupt_controller *); void *arm_intr_establish_fdt(int, int, int (*)(void *), void *, char *); void *arm_intr_establish_fdt_idx(int, int, int, int (*)(void *), void *, char *); +void *arm_intr_parent_establish_fdt(void *, int *, int, + int (*)(void *), void *, char *); + #ifdef DIAGNOSTIC /* * Although this function is implemented in MI code, it must be in this MD -- 2.20.1