]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - sys/mips/broadcom/bcm_mips.c
Avoid relying on header pollution from sys/refcount.h.
[FreeBSD/FreeBSD.git] / sys / mips / broadcom / bcm_mips.c
1 /*-
2  * Copyright (c) 2017 The FreeBSD Foundation
3  * All rights reserved.
4  *
5  * This software was developed by Landon Fuller under sponsorship from
6  * the FreeBSD Foundation.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *     notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
18  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
21  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
23  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
24  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
26  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27  * SUCH DAMAGE.
28  */
29
30 #include <sys/cdefs.h>
31 __FBSDID("$FreeBSD$");
32
33 #include <sys/param.h>
34 #include <sys/kernel.h>
35 #include <sys/bus.h>
36 #include <sys/module.h>
37 #include <sys/limits.h>
38 #include <sys/systm.h>
39
40 #include <machine/bus.h>
41 #include <machine/intr.h>
42 #include <machine/resource.h>
43 #include <sys/rman.h>
44
45 #include <dev/bhnd/bhnd.h>
46 #include <dev/bhnd/siba/sibareg.h>
47
48 #include "pic_if.h"
49
50 #include "bcm_mipsvar.h"
51
52 /*
53  * Broadcom MIPS core driver.
54  *
55  * Abstract driver for Broadcom MIPS CPU/PIC cores.
56  */
57
58 static uintptr_t        bcm_mips_pic_xref(struct bcm_mips_softc *sc);
59 static device_t         bcm_mips_find_bhnd_parent(device_t dev);
60 static int              bcm_mips_retain_cpu_intr(struct bcm_mips_softc *sc,
61                             struct bcm_mips_irqsrc *isrc, struct resource *res);
62 static int              bcm_mips_release_cpu_intr(struct bcm_mips_softc *sc,
63                             struct bcm_mips_irqsrc *isrc, struct resource *res);
64
65 static const int bcm_mips_debug = 0;
66
67 #define DPRINTF(fmt, ...) do {                                          \
68         if (bcm_mips_debug)                                             \
69                 printf("%s: " fmt, __FUNCTION__, ##__VA_ARGS__);        \
70 } while (0)
71
72 #define DENTRY(dev, fmt, ...) do {                                      \
73         if (bcm_mips_debug)                                             \
74                 printf("%s(%s, " fmt ")\n", __FUNCTION__,               \
75                     device_get_nameunit(dev), ##__VA_ARGS__);           \
76 } while (0)
77
78 /**
79  * Register all interrupt source definitions.
80  */
81 static int
82 bcm_mips_register_isrcs(struct bcm_mips_softc *sc)
83 {
84         const char      *name;
85         uintptr_t        xref;
86         int              error;
87
88         xref = bcm_mips_pic_xref(sc);
89
90         name = device_get_nameunit(sc->dev);
91         for (size_t ivec = 0; ivec < nitems(sc->isrcs); ivec++) {
92                 sc->isrcs[ivec].ivec = ivec;
93                 sc->isrcs[ivec].cpuirq = NULL;
94                 sc->isrcs[ivec].refs = 0;
95
96                 error = intr_isrc_register(&sc->isrcs[ivec].isrc, sc->dev,
97                     xref, "%s,%u", name, ivec);
98                 if (error) {
99                         for (size_t i = 0; i < ivec; i++)
100                                 intr_isrc_deregister(&sc->isrcs[i].isrc);
101
102                         device_printf(sc->dev, "error registering IRQ %zu: "
103                             "%d\n", ivec, error);
104                         return (error);
105                 }
106         }
107
108         return (0);
109 }
110
111 /**
112  * Initialize the given @p cpuirq state as unavailable.
113  * 
114  * @param sc            BHND MIPS driver instance state.
115  * @param cpuirq        The CPU IRQ state to be initialized.
116  *
117  * @retval 0            success
118  * @retval non-zero     if initializing @p cpuirq otherwise fails, a regular
119  *                      unix error code will be returned.
120  */
121 static int
122 bcm_mips_init_cpuirq_unavail(struct bcm_mips_softc *sc,
123     struct bcm_mips_cpuirq *cpuirq)
124 {
125         BCM_MIPS_LOCK(sc);
126
127         KASSERT(cpuirq->sc == NULL, ("cpuirq already initialized"));
128         cpuirq->sc = sc;
129         cpuirq->mips_irq = 0;
130         cpuirq->irq_rid = -1;
131         cpuirq->irq_res = NULL;
132         cpuirq->irq_cookie = NULL;
133         cpuirq->isrc_solo = NULL;
134         cpuirq->refs = 0;
135
136         BCM_MIPS_UNLOCK(sc);
137
138         return (0);
139 }
140
141 /**
142  * Allocate required resources and initialize the given @p cpuirq state.
143  * 
144  * @param sc            BHND MIPS driver instance state.
145  * @param cpuirq        The CPU IRQ state to be initialized.
146  * @param rid           The resource ID to be assigned for the CPU IRQ resource,
147  *                      or -1 if no resource should be assigned.
148  * @param irq           The MIPS HW IRQ# to be allocated.
149  * @param filter        The interrupt filter to be setup.
150  *
151  * @retval 0            success
152  * @retval non-zero     if initializing @p cpuirq otherwise fails, a regular
153  *                      unix error code will be returned.
154  */
155 static int
156 bcm_mips_init_cpuirq(struct bcm_mips_softc *sc, struct bcm_mips_cpuirq *cpuirq,
157     int rid, u_int irq, driver_filter_t filter)
158 {
159         struct resource *res;
160         void            *cookie;
161         u_int            irq_real;
162         int              error;
163
164         /* Must fall within MIPS HW IRQ range */
165         if (irq >= NHARD_IRQS)
166                 return (EINVAL);
167
168         /* HW IRQs are numbered relative to SW IRQs */
169         irq_real = irq + NSOFT_IRQS;
170
171         /* Try to assign and allocate the resource */
172         BCM_MIPS_LOCK(sc);
173
174         KASSERT(cpuirq->sc == NULL, ("cpuirq already initialized"));
175
176         error = bus_set_resource(sc->dev, SYS_RES_IRQ, rid, irq_real, 1);
177         if (error) {
178                 BCM_MIPS_UNLOCK(sc);
179                 device_printf(sc->dev, "failed to assign interrupt %u: "
180                     "%d\n", irq, error);
181                 return (error);
182         }
183
184         res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
185         if (res == NULL) {
186                 BCM_MIPS_UNLOCK(sc);
187                 device_printf(sc->dev, "failed to allocate interrupt "
188                         "%u resource\n", irq);
189                 bus_delete_resource(sc->dev, SYS_RES_IRQ, rid);
190                 return (ENXIO);
191         }
192
193         error = bus_setup_intr(sc->dev, res,
194             INTR_TYPE_MISC | INTR_MPSAFE | INTR_EXCL, filter, NULL, cpuirq,
195             &cookie);
196         if (error) {
197                 BCM_MIPS_UNLOCK(sc);
198
199                 printf("failed to setup internal interrupt handler: %d\n",
200                     error);
201
202                 bus_release_resource(sc->dev, SYS_RES_IRQ, rid, res);
203                 bus_delete_resource(sc->dev, SYS_RES_IRQ, rid);
204
205                 return (error);
206         }
207
208         /* Initialize CPU IRQ state */
209         cpuirq->sc = sc;
210         cpuirq->mips_irq = irq;
211         cpuirq->irq_rid = rid;
212         cpuirq->irq_res = res;
213         cpuirq->irq_cookie = cookie;
214         cpuirq->isrc_solo = NULL;
215         cpuirq->refs = 0;
216
217         BCM_MIPS_UNLOCK(sc);
218         return (0);
219 }
220
221 /**
222  * Free any resources associated with the given @p cpuirq state.
223  * 
224  * @param sc            BHND MIPS driver instance state.
225  * @param cpuirq        A CPU IRQ instance previously successfully initialized
226  *                      via bcm_mips_init_cpuirq().
227  *
228  * @retval 0            success
229  * @retval non-zero     if finalizing @p cpuirq otherwise fails, a regular
230  *                      unix error code will be returned.
231  */
232 static int
233 bcm_mips_fini_cpuirq(struct bcm_mips_softc *sc, struct bcm_mips_cpuirq *cpuirq)
234 {
235         int error;
236
237         BCM_MIPS_LOCK(sc);
238
239         if (cpuirq->sc == NULL) {
240                 KASSERT(cpuirq->irq_res == NULL, ("leaking cpuirq resource"));
241
242                 BCM_MIPS_UNLOCK(sc);
243                 return (0);     /* not initialized */
244         }
245
246         if (cpuirq->refs != 0) {
247                 BCM_MIPS_UNLOCK(sc);
248                 return (EBUSY);
249         }
250
251         if (cpuirq->irq_cookie != NULL) {
252                 KASSERT(cpuirq->irq_res != NULL, ("resource missing"));
253
254                 error = bus_teardown_intr(sc->dev, cpuirq->irq_res,
255                         cpuirq->irq_cookie);
256                 if (error) {
257                         BCM_MIPS_UNLOCK(sc);
258                         return (error);
259                 }
260
261                 cpuirq->irq_cookie = NULL;
262         }
263
264         if (cpuirq->irq_res != NULL) {
265                 bus_release_resource(sc->dev, SYS_RES_IRQ, cpuirq->irq_rid,
266                     cpuirq->irq_res);
267                 cpuirq->irq_res = NULL;
268         }
269
270         if (cpuirq->irq_rid != -1) {
271                 bus_delete_resource(sc->dev, SYS_RES_IRQ, cpuirq->irq_rid);
272                 cpuirq->irq_rid = -1;
273         }
274
275         BCM_MIPS_UNLOCK(sc);
276
277         return (0);
278 }
279
280 static int
281 bcm_mips_attach_default(device_t dev)
282 {
283         /* subclassing drivers must provide an implementation of
284          * DEVICE_ATTACH() */
285         panic("device_attach() unimplemented");
286 }
287
288 /**
289  * BHND MIPS device attach.
290  * 
291  * This must be called from subclass drivers' DEVICE_ATTACH().
292  * 
293  * @param dev BHND MIPS device.
294  * @param num_cpuirqs The number of usable MIPS HW IRQs.
295  * @param timer_irq The MIPS HW IRQ assigned to the MIPS CPU timer.
296  * @param filter The subclass's core-specific IRQ dispatch filter. Will be
297  * passed the associated bcm_mips_cpuirq instance as its argument.
298  */
299 int
300 bcm_mips_attach(device_t dev, u_int num_cpuirqs, u_int timer_irq,
301     driver_filter_t filter)
302 {
303         struct bcm_mips_softc   *sc;
304         struct intr_pic         *pic;
305         uintptr_t                xref;
306         u_int                    irq_rid;
307         rman_res_t               irq;
308         int                      error;
309
310         sc = device_get_softc(dev);
311         sc->dev = dev;
312         sc->num_cpuirqs = num_cpuirqs;
313         sc->timer_irq = timer_irq;
314
315         /* Must not exceed the actual size of our fixed IRQ array */
316         if (sc->num_cpuirqs > nitems(sc->cpuirqs)) {
317                 device_printf(dev, "%u nirqs exceeds maximum supported %zu",
318                     sc->num_cpuirqs, nitems(sc->cpuirqs));
319                 return (ENXIO);
320         }
321
322         pic = NULL;
323         xref = bcm_mips_pic_xref(sc);
324
325         BCM_MIPS_LOCK_INIT(sc);
326
327         /* Register our interrupt sources */
328         if ((error = bcm_mips_register_isrcs(sc))) {
329                 BCM_MIPS_LOCK_DESTROY(sc);
330                 return (error);
331         }
332
333         /* Initialize our CPU interrupt state */
334         irq_rid = bhnd_get_intr_count(dev); /* last bhnd-assigned RID + 1 */
335         irq = 0;
336         for (u_int i = 0; i < sc->num_cpuirqs; i++) {
337                 /* Must not overflow signed resource ID representation */
338                 if (irq_rid >= INT_MAX) {
339                         device_printf(dev, "exhausted IRQ resource IDs\n");
340                         error = ENOMEM;
341                         goto failed;
342                 }
343
344                 if (irq == sc->timer_irq) {
345                         /* Mark the CPU timer's IRQ as unavailable */
346                         error = bcm_mips_init_cpuirq_unavail(sc,
347                             &sc->cpuirqs[i]);
348                 } else {
349                         /* Initialize state */
350                         error = bcm_mips_init_cpuirq(sc, &sc->cpuirqs[i],
351                             irq_rid, irq, filter);
352                 }
353
354                 if (error)
355                         goto failed;
356
357                 /* Increment IRQ and resource ID for next allocation */
358                 irq_rid++;
359                 irq++;
360         }
361
362         /* Sanity check; our shared IRQ must be available */
363         if (sc->num_cpuirqs <= BCM_MIPS_IRQ_SHARED)
364                 panic("missing shared interrupt %d\n", BCM_MIPS_IRQ_SHARED);
365
366         if (sc->cpuirqs[BCM_MIPS_IRQ_SHARED].irq_rid == -1)
367                 panic("shared interrupt %d unavailable", BCM_MIPS_IRQ_SHARED);
368
369         /* Register PIC */
370         if ((pic = intr_pic_register(dev, xref)) == NULL) {
371                 device_printf(dev, "error registering PIC\n");
372                 error = ENXIO;
373                 goto failed;
374         }
375
376         return (0);
377
378 failed:
379         /* Deregister PIC before performing any other cleanup */
380         if (pic != NULL)
381                 intr_pic_deregister(dev, 0);
382
383         /* Deregister all interrupt sources */
384         for (size_t i = 0; i < nitems(sc->isrcs); i++)
385                 intr_isrc_deregister(&sc->isrcs[i].isrc);
386
387         /* Free our MIPS CPU interrupt handler state */
388         for (u_int i = 0; i < sc->num_cpuirqs; i++)
389                 bcm_mips_fini_cpuirq(sc, &sc->cpuirqs[i]);
390
391         BCM_MIPS_LOCK_DESTROY(sc);
392         return (error);
393 }
394
395 int
396 bcm_mips_detach(device_t dev)
397 {
398         struct bcm_mips_softc *sc;
399
400         sc = device_get_softc(dev);
401
402         /* Deregister PIC before performing any other cleanup */
403         intr_pic_deregister(dev, 0);
404
405         /* Deregister all interrupt sources */
406         for (size_t i = 0; i < nitems(sc->isrcs); i++)
407                 intr_isrc_deregister(&sc->isrcs[i].isrc);
408
409         /* Free our MIPS CPU interrupt handler state */
410         for (u_int i = 0; i < sc->num_cpuirqs; i++)
411                 bcm_mips_fini_cpuirq(sc, &sc->cpuirqs[i]);
412
413         return (0);
414 }
415
416 /* PIC_MAP_INTR() */
417 static int
418 bcm_mips_pic_map_intr(device_t dev, struct intr_map_data *d,
419     struct intr_irqsrc **isrcp)
420 {
421         struct bcm_mips_softc           *sc;
422         struct bcm_mips_intr_map_data   *data;
423
424         sc = device_get_softc(dev);
425
426         if (d->type != INTR_MAP_DATA_BCM_MIPS) {
427                 DENTRY(dev, "type=%d", d->type);
428                 return (ENOTSUP);
429         }
430
431         data = (struct bcm_mips_intr_map_data *)d;
432         DENTRY(dev, "type=%d, ivec=%u", d->type, data->ivec);
433         if (data->ivec < 0 || data->ivec >= nitems(sc->isrcs))
434                 return (EINVAL);
435
436         *isrcp = &sc->isrcs[data->ivec].isrc;
437         return (0);
438 }
439
440 /* PIC_SETUP_INTR() */
441 static int
442 bcm_mips_pic_setup_intr(device_t dev, struct intr_irqsrc *irqsrc,
443     struct resource *res, struct intr_map_data *data)
444 {
445         struct bcm_mips_softc   *sc;
446         struct bcm_mips_irqsrc  *isrc;
447         int                      error;
448
449         sc = device_get_softc(dev);
450         isrc = (struct bcm_mips_irqsrc *)irqsrc;
451
452         /* Assign a CPU interrupt */
453         BCM_MIPS_LOCK(sc);
454         error = bcm_mips_retain_cpu_intr(sc, isrc, res);
455         BCM_MIPS_UNLOCK(sc);
456
457         return (error);
458 }
459
460 /* PIC_TEARDOWN_INTR() */
461 static int
462 bcm_mips_pic_teardown_intr(device_t dev, struct intr_irqsrc *irqsrc,
463     struct resource *res, struct intr_map_data *data)
464 {
465         struct bcm_mips_softc   *sc;
466         struct bcm_mips_irqsrc  *isrc;
467         int                      error;
468
469         sc = device_get_softc(dev);
470         isrc = (struct bcm_mips_irqsrc *)irqsrc;
471
472         /* Release the CPU interrupt */
473         BCM_MIPS_LOCK(sc);
474         error = bcm_mips_release_cpu_intr(sc, isrc, res);
475         BCM_MIPS_UNLOCK(sc);
476
477         return (error);
478 }
479
480
481 /** return our PIC's xref */
482 static uintptr_t
483 bcm_mips_pic_xref(struct bcm_mips_softc *sc)
484 {
485         uintptr_t xref;
486
487         /* Determine our interrupt domain */
488         xref = BHND_BUS_GET_INTR_DOMAIN(device_get_parent(sc->dev), sc->dev,
489             true);
490         KASSERT(xref != 0, ("missing interrupt domain"));
491
492         return (xref);
493 }
494
495 /**
496  * Walk up the device tree from @p dev until we find a bhnd-attached core,
497  * returning either the core, or NULL if @p dev is not attached under a bhnd
498  * bus.
499  */    
500 static device_t
501 bcm_mips_find_bhnd_parent(device_t dev)
502 {
503         device_t        core, bus;
504         devclass_t      bhnd_class;
505
506         bhnd_class = devclass_find("bhnd");
507         core = dev;
508         while ((bus = device_get_parent(core)) != NULL) {
509                 if (device_get_devclass(bus) == bhnd_class)
510                         return (core);
511
512                 core = bus;
513         }
514
515         /* Not found */
516         return (NULL);
517 }
518
519 /**
520  * Retain @p isrc and assign a MIPS CPU interrupt on behalf of @p res; if
521  * the @p isrc already has a MIPS CPU interrupt assigned, the existing
522  * reference will be left unmodified.
523  * 
524  * @param sc            BHND MIPS driver state.
525  * @param isrc          The interrupt source corresponding to @p res.
526  * @param res           The interrupt resource for which a MIPS CPU IRQ will be
527  *                      assigned.
528  */
529 static int
530 bcm_mips_retain_cpu_intr(struct bcm_mips_softc *sc,
531     struct bcm_mips_irqsrc *isrc, struct resource *res)
532 {
533         struct bcm_mips_cpuirq  *cpuirq;
534         bhnd_devclass_t          devclass;
535         device_t                 core;
536
537         BCM_MIPS_LOCK_ASSERT(sc, MA_OWNED);
538
539         /* Prefer existing assignment */
540         if (isrc->cpuirq != NULL) {
541                 KASSERT(isrc->cpuirq->refs > 0, ("assigned IRQ has no "
542                     "references"));
543
544                 /* Increment our reference count */
545                 if (isrc->refs == UINT_MAX)
546                         return (ENOMEM);        /* would overflow */
547
548                 isrc->refs++;
549                 return (0);
550         }
551
552         /* Use the device class of the bhnd core to which the interrupt
553          * vector is routed to determine whether a shared interrupt should
554          * be preferred. */
555         devclass = BHND_DEVCLASS_OTHER;
556         core = bcm_mips_find_bhnd_parent(rman_get_device(res));
557         if (core != NULL)
558                 devclass = bhnd_get_class(core);
559
560         switch (devclass) {
561         case BHND_DEVCLASS_CC:
562         case BHND_DEVCLASS_CC_B:
563         case BHND_DEVCLASS_PMU:
564         case BHND_DEVCLASS_RAM:
565         case BHND_DEVCLASS_MEMC:
566         case BHND_DEVCLASS_CPU:
567         case BHND_DEVCLASS_SOC_ROUTER:
568         case BHND_DEVCLASS_SOC_BRIDGE:
569         case BHND_DEVCLASS_EROM:
570         case BHND_DEVCLASS_NVRAM:
571                 /* Always use a shared interrupt for these devices */
572                 cpuirq = &sc->cpuirqs[BCM_MIPS_IRQ_SHARED];
573                 break;
574
575         case BHND_DEVCLASS_PCI:
576         case BHND_DEVCLASS_PCIE:
577         case BHND_DEVCLASS_PCCARD:      
578         case BHND_DEVCLASS_ENET:
579         case BHND_DEVCLASS_ENET_MAC:
580         case BHND_DEVCLASS_ENET_PHY:
581         case BHND_DEVCLASS_WLAN:
582         case BHND_DEVCLASS_WLAN_MAC:
583         case BHND_DEVCLASS_WLAN_PHY:
584         case BHND_DEVCLASS_USB_HOST:
585         case BHND_DEVCLASS_USB_DEV:
586         case BHND_DEVCLASS_USB_DUAL:
587         case BHND_DEVCLASS_OTHER:
588         case BHND_DEVCLASS_INVALID:
589         default:
590                 /* Fall back on a shared interrupt */
591                 cpuirq = &sc->cpuirqs[BCM_MIPS_IRQ_SHARED];
592
593                 /* Try to assign a dedicated MIPS HW interrupt */
594                 for (u_int i = 0; i < sc->num_cpuirqs; i++) {
595                         if (i == BCM_MIPS_IRQ_SHARED)
596                                 continue;
597
598                         if (sc->cpuirqs[i].irq_rid == -1)
599                                 continue; /* unavailable */
600
601                         if (sc->cpuirqs[i].refs != 0)
602                                 continue; /* already assigned */
603
604                         /* Found an unused CPU IRQ */
605                         cpuirq = &sc->cpuirqs[i];
606                         break;
607                 }
608
609                 break;
610         }
611
612         DPRINTF("routing backplane interrupt vector %u to MIPS IRQ %u\n",
613             isrc->ivec, cpuirq->mips_irq);
614
615         KASSERT(isrc->cpuirq == NULL, ("CPU IRQ already assigned"));
616         KASSERT(isrc->refs == 0, ("isrc has active references with no "
617             "assigned CPU IRQ"));
618         KASSERT(cpuirq->refs == 1 || cpuirq->isrc_solo == NULL,
619             ("single isrc dispatch enabled on cpuirq with multiple refs"));
620
621         /* Verify that bumping the cpuirq refcount below will not overflow */
622         if (cpuirq->refs == UINT_MAX)
623                 return (ENOMEM);
624
625         /* Increment cpuirq refcount on behalf of the isrc */
626         cpuirq->refs++;
627
628         /* Increment isrc refcount on behalf of the caller */
629         isrc->refs++;
630
631         /* Assign the IRQ to the isrc */
632         isrc->cpuirq = cpuirq;
633
634         /* Can we enable the single isrc dispatch path? */
635         if (cpuirq->refs == 1 && cpuirq->mips_irq != BCM_MIPS_IRQ_SHARED)
636                 cpuirq->isrc_solo = isrc;
637
638         return (0);
639 }
640
641 /**
642  * Release the MIPS CPU interrupt assigned to @p isrc on behalf of @p res.
643  *
644  * @param sc    BHND MIPS driver state.
645  * @param isrc  The interrupt source corresponding to @p res.
646  * @param res   The interrupt resource being activated.
647  */
648 static int
649 bcm_mips_release_cpu_intr(struct bcm_mips_softc *sc,
650     struct bcm_mips_irqsrc *isrc, struct resource *res)
651 {
652         struct bcm_mips_cpuirq *cpuirq;
653
654         BCM_MIPS_LOCK_ASSERT(sc, MA_OWNED);
655
656         /* Decrement the refcount */
657         KASSERT(isrc->refs > 0, ("isrc over-release"));
658         isrc->refs--;
659
660         /* Nothing else to do if the isrc is still actively referenced */
661         if (isrc->refs > 0)
662                 return (0);
663
664         /* Otherwise, we need to release our CPU IRQ reference */
665         cpuirq = isrc->cpuirq;
666         isrc->cpuirq = NULL;
667
668         KASSERT(cpuirq != NULL, ("no assigned IRQ"));
669         KASSERT(cpuirq->refs > 0, ("cpuirq over-release"));
670
671         /* Disable single isrc dispatch path */
672         if (cpuirq->refs == 1 && cpuirq->isrc_solo != NULL) {
673                 KASSERT(cpuirq->isrc_solo == isrc, ("invalid solo isrc"));
674                 cpuirq->isrc_solo = NULL;
675         }
676
677         cpuirq->refs--;
678
679         return (0);
680 }
681
682 static device_method_t bcm_mips_methods[] = {
683         /* Device interface */
684         DEVMETHOD(device_attach,        bcm_mips_attach_default),
685         DEVMETHOD(device_detach,        bcm_mips_detach),
686
687         /* Interrupt controller interface */
688         DEVMETHOD(pic_map_intr,         bcm_mips_pic_map_intr),
689         DEVMETHOD(pic_setup_intr,       bcm_mips_pic_setup_intr),
690         DEVMETHOD(pic_teardown_intr,    bcm_mips_pic_teardown_intr),
691
692         DEVMETHOD_END
693 };
694
695 DEFINE_CLASS_0(bcm_mips, bcm_mips_driver, bcm_mips_methods, sizeof(struct bcm_mips_softc));
696
697 MODULE_VERSION(bcm_mips, 1);
698 MODULE_DEPEND(bcm_mips, bhnd, 1, 1, 1);