Module Name:    src
Committed By:   thorpej
Date:           Thu Nov 28 01:08:06 UTC 2019

Modified Files:
        src/sys/arch/arm/broadcom: bcm2835_intr.c

Log Message:
There are some devices on the bcm283x that share IRQs (notably, the i2c
controllers), but the generic ARM PIC code doesn't support this.  Add a
workaround inspired by the GICv3 driver (thanks Jared for pointint it out).


To generate a diff of this commit:
cvs rdiff -u -r1.24 -r1.25 src/sys/arch/arm/broadcom/bcm2835_intr.c

Please note that diffs are not public domain; they are subject to the
copyright notices on the relevant files.

Modified files:

Index: src/sys/arch/arm/broadcom/bcm2835_intr.c
diff -u src/sys/arch/arm/broadcom/bcm2835_intr.c:1.24 src/sys/arch/arm/broadcom/bcm2835_intr.c:1.25
--- src/sys/arch/arm/broadcom/bcm2835_intr.c:1.24	Wed Sep 25 16:57:10 2019
+++ src/sys/arch/arm/broadcom/bcm2835_intr.c	Thu Nov 28 01:08:06 2019
@@ -1,7 +1,7 @@
-/*	$NetBSD: bcm2835_intr.c,v 1.24 2019/09/25 16:57:10 skrll Exp $	*/
+/*	$NetBSD: bcm2835_intr.c,v 1.25 2019/11/28 01:08:06 thorpej Exp $	*/
 
 /*-
- * Copyright (c) 2012, 2015 The NetBSD Foundation, Inc.
+ * Copyright (c) 2012, 2015, 2019 The NetBSD Foundation, Inc.
  * All rights reserved.
  *
  * This code is derived from software contributed to The NetBSD Foundation
@@ -30,7 +30,7 @@
  */
 
 #include <sys/cdefs.h>
-__KERNEL_RCSID(0, "$NetBSD: bcm2835_intr.c,v 1.24 2019/09/25 16:57:10 skrll Exp $");
+__KERNEL_RCSID(0, "$NetBSD: bcm2835_intr.c,v 1.25 2019/11/28 01:08:06 thorpej Exp $");
 
 #define _INTR_PRIVATE
 
@@ -87,6 +87,8 @@ static void *bcm2835_icu_fdt_establish(d
 static void bcm2835_icu_fdt_disestablish(device_t, void *);
 static bool bcm2835_icu_fdt_intrstr(device_t, u_int *, char *, size_t);
 
+static int bcm2835_icu_intr(void *);
+
 static int bcm2836mp_icu_fdt_decode_irq(u_int *);
 static void *bcm2836mp_icu_fdt_establish(device_t, u_int *, int, int,
     int (*)(void *), void *);
@@ -162,11 +164,35 @@ struct bcm2836mp_interrupt {
 static TAILQ_HEAD(, bcm2836mp_interrupt) bcm2836mp_interrupts =
     TAILQ_HEAD_INITIALIZER(bcm2836mp_interrupts);
 
+struct bcm2835icu_irqhandler;
+struct bcm2835icu_irq;
+struct bcm2835icu_softc;
+
+struct bcm2835icu_irqhandler {
+	struct bcm2835icu_irq	*ih_irq;
+	int			(*ih_fn)(void *);
+	void			*ih_arg;
+	TAILQ_ENTRY(bcm2835icu_irqhandler) ih_next;
+};
+
+struct bcm2835icu_irq {
+	struct bcm2835icu_softc	*intr_sc;
+	void			*intr_ih;
+	void			*intr_arg;
+	int			intr_refcnt;
+	int			intr_ipl;
+	int			intr_irq;
+	int			intr_mpsafe;
+	TAILQ_HEAD(, bcm2835icu_irqhandler) intr_handlers;
+};
+
 struct bcm2835icu_softc {
 	device_t		sc_dev;
 	bus_space_tag_t		sc_iot;
 	bus_space_handle_t	sc_ioh;
 
+	struct bcm2835icu_irq	*sc_irq[BCM2835_NIRQ];
+
 	int sc_phandle;
 };
 
@@ -437,6 +463,9 @@ static void *
 bcm2835_icu_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags,
     int (*func)(void *), void *arg)
 {
+	struct bcm2835icu_softc * const sc = device_private(dev);
+	struct bcm2835icu_irq *firq;
+	struct bcm2835icu_irqhandler *firqh;
 	int iflags = (flags & FDT_INTR_MPSAFE) ? IST_MPSAFE : 0;
 	int irq;
 
@@ -444,13 +473,89 @@ bcm2835_icu_fdt_establish(device_t dev, 
 	if (irq == -1)
 		return NULL;
 
-	return intr_establish(irq, ipl, IST_LEVEL | iflags, func, arg);
+	firq = sc->sc_irq[irq];
+	if (firq == NULL) {
+		firq = kmem_alloc(sizeof(*firq), KM_SLEEP);
+		firq->intr_sc = sc;
+		firq->intr_refcnt = 0;
+		firq->intr_arg = arg;
+		firq->intr_ipl = ipl;
+		firq->intr_mpsafe = iflags;
+		firq->intr_irq = irq;
+		TAILQ_INIT(&firq->intr_handlers);
+		if (arg == NULL) {
+			firq->intr_ih = intr_establish(irq, ipl,
+			    IST_LEVEL | iflags, func, NULL);
+		} else {
+			firq->intr_ih = intr_establish(irq, ipl,
+			    IST_LEVEL | iflags, bcm2835_icu_intr, firq);
+		}
+		if (firq->intr_ih == NULL) {
+			kmem_free(firq, sizeof(*firq));
+			return NULL;
+		}
+		sc->sc_irq[irq] = firq;
+	} else {
+		if (firq->intr_arg == NULL || arg == NULL) {
+			device_printf(dev,
+			    "cannot share irq with NULL-arg handler\n");
+			return NULL;
+		}
+		if (firq->intr_ipl != ipl) {
+			device_printf(dev,
+			    "cannot share irq with different ipl\n");
+			return NULL;
+		}
+		if (firq->intr_mpsafe != iflags) {
+			device_printf(dev,
+			    "cannot share irq between mpsafe/non-mpsafe\n");
+			return NULL;
+		}
+	}
+
+	firqh = kmem_alloc(sizeof(*firqh), KM_SLEEP);
+	firqh->ih_irq = firq;
+	firqh->ih_fn = func;
+	firqh->ih_arg = arg;
+	TAILQ_INSERT_TAIL(&firq->intr_handlers, firqh, ih_next);
+
+	return firqh;
 }
 
 static void
 bcm2835_icu_fdt_disestablish(device_t dev, void *ih)
 {
-	intr_disestablish(ih);
+	struct bcm2835icu_softc * const sc = device_private(dev);
+	struct bcm2835icu_irqhandler *firqh = ih;
+	struct bcm2835icu_irq *firq = firqh->ih_irq;
+
+	KASSERT(firq->intr_refcnt > 0);
+
+	/* XXX */
+	if (firq->intr_refcnt > 1)
+		panic("%s: cannot disestablish shared irq", __func__);
+
+	intr_disestablish(firq->intr_ih);
+
+	TAILQ_REMOVE(&firq->intr_handlers, firqh, ih_next);
+	kmem_free(firqh, sizeof(*firqh));
+
+	sc->sc_irq[firq->intr_irq] = NULL;
+	kmem_free(firq, sizeof(*firq));
+}
+
+static int
+bcm2835_icu_intr(void *priv)
+{
+	struct bcm2835icu_irq *firq = priv;
+	struct bcm2835icu_irqhandler *firqh;
+	int handled = 0;
+
+	TAILQ_FOREACH(firqh, &firq->intr_handlers, ih_next) {
+		handled |= firqh->ih_fn(firqh->ih_arg);
+	}
+
+	return handled;
 }
 
 static bool

Reply via email to