699 lines
18 KiB
C
699 lines
18 KiB
C
|
/*-
|
||
|
* Copyright (c) 2017 The FreeBSD Foundation
|
||
|
* All rights reserved.
|
||
|
*
|
||
|
* This software was developed by Landon Fuller under sponsorship from
|
||
|
* the FreeBSD Foundation.
|
||
|
*
|
||
|
* Redistribution and use in source and binary forms, with or without
|
||
|
* modification, are permitted provided that the following conditions
|
||
|
* are met:
|
||
|
* 1. Redistributions of source code must retain the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer.
|
||
|
* 2. Redistributions in binary form must reproduce the above copyright
|
||
|
* notice, this list of conditions and the following disclaimer in the
|
||
|
* documentation and/or other materials provided with the distribution.
|
||
|
*
|
||
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||
|
* SUCH DAMAGE.
|
||
|
*/
|
||
|
|
||
|
#include <sys/cdefs.h>
|
||
|
__FBSDID("$FreeBSD$");
|
||
|
|
||
|
#include <sys/param.h>
|
||
|
#include <sys/kernel.h>
|
||
|
#include <sys/bus.h>
|
||
|
#include <sys/module.h>
|
||
|
#include <sys/limits.h>
|
||
|
#include <sys/systm.h>
|
||
|
|
||
|
#include <machine/bus.h>
|
||
|
#include <machine/intr.h>
|
||
|
#include <machine/resource.h>
|
||
|
#include <sys/rman.h>
|
||
|
|
||
|
#include <dev/bhnd/bhnd.h>
|
||
|
#include <dev/bhnd/siba/sibareg.h>
|
||
|
|
||
|
#include "pic_if.h"
|
||
|
|
||
|
#include "bcm_mipsvar.h"
|
||
|
|
||
|
/*
|
||
|
* Broadcom MIPS core driver.
|
||
|
*
|
||
|
* Abstract driver for Broadcom MIPS CPU/PIC cores.
|
||
|
*/
|
||
|
|
||
|
static uintptr_t bcm_mips_pic_xref(struct bcm_mips_softc *sc);
|
||
|
static device_t bcm_mips_find_bhnd_parent(device_t dev);
|
||
|
static int bcm_mips_retain_cpu_intr(struct bcm_mips_softc *sc,
|
||
|
struct bcm_mips_irqsrc *isrc, struct resource *res);
|
||
|
static int bcm_mips_release_cpu_intr(struct bcm_mips_softc *sc,
|
||
|
struct bcm_mips_irqsrc *isrc, struct resource *res);
|
||
|
|
||
|
static const int bcm_mips_debug = 0;
|
||
|
|
||
|
#define DPRINTF(fmt, ...) do { \
|
||
|
if (bcm_mips_debug) \
|
||
|
printf("%s: " fmt, __FUNCTION__, ##__VA_ARGS__); \
|
||
|
} while (0)
|
||
|
|
||
|
#define DENTRY(dev, fmt, ...) do { \
|
||
|
if (bcm_mips_debug) \
|
||
|
printf("%s(%s, " fmt ")\n", __FUNCTION__, \
|
||
|
device_get_nameunit(dev), ##__VA_ARGS__); \
|
||
|
} while (0)
|
||
|
|
||
|
/**
|
||
|
* Register all interrupt source definitions.
|
||
|
*/
|
||
|
static int
|
||
|
bcm_mips_register_isrcs(struct bcm_mips_softc *sc)
|
||
|
{
|
||
|
const char *name;
|
||
|
uintptr_t xref;
|
||
|
int error;
|
||
|
|
||
|
xref = bcm_mips_pic_xref(sc);
|
||
|
|
||
|
name = device_get_nameunit(sc->dev);
|
||
|
for (size_t ivec = 0; ivec < nitems(sc->isrcs); ivec++) {
|
||
|
sc->isrcs[ivec].ivec = ivec;
|
||
|
sc->isrcs[ivec].cpuirq = NULL;
|
||
|
sc->isrcs[ivec].refs = 0;
|
||
|
|
||
|
error = intr_isrc_register(&sc->isrcs[ivec].isrc, sc->dev,
|
||
|
xref, "%s,%u", name, ivec);
|
||
|
if (error) {
|
||
|
for (size_t i = 0; i < ivec; i++)
|
||
|
intr_isrc_deregister(&sc->isrcs[i].isrc);
|
||
|
|
||
|
device_printf(sc->dev, "error registering IRQ %zu: "
|
||
|
"%d\n", ivec, error);
|
||
|
return (error);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initialize the given @p cpuirq state as unavailable.
|
||
|
*
|
||
|
* @param sc BHND MIPS driver instance state.
|
||
|
* @param cpuirq The CPU IRQ state to be initialized.
|
||
|
*
|
||
|
* @retval 0 success
|
||
|
* @retval non-zero if initializing @p cpuirq otherwise fails, a regular
|
||
|
* unix error code will be returned.
|
||
|
*/
|
||
|
static int
|
||
|
bcm_mips_init_cpuirq_unavail(struct bcm_mips_softc *sc,
|
||
|
struct bcm_mips_cpuirq *cpuirq)
|
||
|
{
|
||
|
BCM_MIPS_LOCK(sc);
|
||
|
|
||
|
KASSERT(cpuirq->sc == NULL, ("cpuirq already initialized"));
|
||
|
cpuirq->sc = sc;
|
||
|
cpuirq->mips_irq = 0;
|
||
|
cpuirq->irq_rid = -1;
|
||
|
cpuirq->irq_res = NULL;
|
||
|
cpuirq->irq_cookie = NULL;
|
||
|
cpuirq->isrc_solo = NULL;
|
||
|
cpuirq->refs = 0;
|
||
|
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Allocate required resources and initialize the given @p cpuirq state.
|
||
|
*
|
||
|
* @param sc BHND MIPS driver instance state.
|
||
|
* @param cpuirq The CPU IRQ state to be initialized.
|
||
|
* @param rid The resource ID to be assigned for the CPU IRQ resource,
|
||
|
* or -1 if no resource should be assigned.
|
||
|
* @param irq The MIPS HW IRQ# to be allocated.
|
||
|
* @param filter The interrupt filter to be setup.
|
||
|
*
|
||
|
* @retval 0 success
|
||
|
* @retval non-zero if initializing @p cpuirq otherwise fails, a regular
|
||
|
* unix error code will be returned.
|
||
|
*/
|
||
|
static int
|
||
|
bcm_mips_init_cpuirq(struct bcm_mips_softc *sc, struct bcm_mips_cpuirq *cpuirq,
|
||
|
int rid, u_int irq, driver_filter_t filter)
|
||
|
{
|
||
|
struct resource *res;
|
||
|
void *cookie;
|
||
|
u_int irq_real;
|
||
|
int error;
|
||
|
|
||
|
/* Must fall within MIPS HW IRQ range */
|
||
|
if (irq >= NHARD_IRQS)
|
||
|
return (EINVAL);
|
||
|
|
||
|
/* HW IRQs are numbered relative to SW IRQs */
|
||
|
irq_real = irq + NSOFT_IRQS;
|
||
|
|
||
|
/* Try to assign and allocate the resource */
|
||
|
BCM_MIPS_LOCK(sc);
|
||
|
|
||
|
KASSERT(cpuirq->sc == NULL, ("cpuirq already initialized"));
|
||
|
|
||
|
error = bus_set_resource(sc->dev, SYS_RES_IRQ, rid, irq_real, 1);
|
||
|
if (error) {
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
device_printf(sc->dev, "failed to assign interrupt %u: "
|
||
|
"%d\n", irq, error);
|
||
|
return (error);
|
||
|
}
|
||
|
|
||
|
res = bus_alloc_resource_any(sc->dev, SYS_RES_IRQ, &rid, RF_ACTIVE);
|
||
|
if (res == NULL) {
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
device_printf(sc->dev, "failed to allocate interrupt "
|
||
|
"%u resource\n", irq);
|
||
|
bus_delete_resource(sc->dev, SYS_RES_IRQ, rid);
|
||
|
return (ENXIO);
|
||
|
}
|
||
|
|
||
|
error = bus_setup_intr(sc->dev, res,
|
||
|
INTR_TYPE_MISC | INTR_MPSAFE | INTR_EXCL, filter, NULL, cpuirq,
|
||
|
&cookie);
|
||
|
if (error) {
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
|
||
|
printf("failed to setup internal interrupt handler: %d\n",
|
||
|
error);
|
||
|
|
||
|
bus_release_resource(sc->dev, SYS_RES_IRQ, rid, res);
|
||
|
bus_delete_resource(sc->dev, SYS_RES_IRQ, rid);
|
||
|
|
||
|
return (error);
|
||
|
}
|
||
|
|
||
|
/* Initialize CPU IRQ state */
|
||
|
cpuirq->sc = sc;
|
||
|
cpuirq->mips_irq = irq;
|
||
|
cpuirq->irq_rid = rid;
|
||
|
cpuirq->irq_res = res;
|
||
|
cpuirq->irq_cookie = cookie;
|
||
|
cpuirq->isrc_solo = NULL;
|
||
|
cpuirq->refs = 0;
|
||
|
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Free any resources associated with the given @p cpuirq state.
|
||
|
*
|
||
|
* @param sc BHND MIPS driver instance state.
|
||
|
* @param cpuirq A CPU IRQ instance previously successfully initialized
|
||
|
* via bcm_mips_init_cpuirq().
|
||
|
*
|
||
|
* @retval 0 success
|
||
|
* @retval non-zero if finalizing @p cpuirq otherwise fails, a regular
|
||
|
* unix error code will be returned.
|
||
|
*/
|
||
|
static int
|
||
|
bcm_mips_fini_cpuirq(struct bcm_mips_softc *sc, struct bcm_mips_cpuirq *cpuirq)
|
||
|
{
|
||
|
int error;
|
||
|
|
||
|
BCM_MIPS_LOCK(sc);
|
||
|
|
||
|
if (cpuirq->sc == NULL) {
|
||
|
KASSERT(cpuirq->irq_res == NULL, ("leaking cpuirq resource"));
|
||
|
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
return (0); /* not initialized */
|
||
|
}
|
||
|
|
||
|
if (cpuirq->refs != 0) {
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
return (EBUSY);
|
||
|
}
|
||
|
|
||
|
if (cpuirq->irq_cookie != NULL) {
|
||
|
KASSERT(cpuirq->irq_res != NULL, ("resource missing"));
|
||
|
|
||
|
error = bus_teardown_intr(sc->dev, cpuirq->irq_res,
|
||
|
cpuirq->irq_cookie);
|
||
|
if (error) {
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
return (error);
|
||
|
}
|
||
|
|
||
|
cpuirq->irq_cookie = NULL;
|
||
|
}
|
||
|
|
||
|
if (cpuirq->irq_res != NULL) {
|
||
|
bus_release_resource(sc->dev, SYS_RES_IRQ, cpuirq->irq_rid,
|
||
|
cpuirq->irq_res);
|
||
|
cpuirq->irq_res = NULL;
|
||
|
}
|
||
|
|
||
|
if (cpuirq->irq_rid != -1) {
|
||
|
bus_delete_resource(sc->dev, SYS_RES_IRQ, cpuirq->irq_rid);
|
||
|
cpuirq->irq_rid = -1;
|
||
|
}
|
||
|
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static int
|
||
|
bcm_mips_attach_default(device_t dev)
|
||
|
{
|
||
|
/* subclassing drivers must provide an implementation of
|
||
|
* DEVICE_ATTACH() */
|
||
|
panic("device_attach() unimplemented");
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* BHND MIPS device attach.
|
||
|
*
|
||
|
* This must be called from subclass drivers' DEVICE_ATTACH().
|
||
|
*
|
||
|
* @param dev BHND MIPS device.
|
||
|
* @param num_cpuirqs The number of usable MIPS HW IRQs.
|
||
|
* @param timer_irq The MIPS HW IRQ assigned to the MIPS CPU timer.
|
||
|
* @param filter The subclass's core-specific IRQ dispatch filter. Will be
|
||
|
* passed the associated bcm_mips_cpuirq instance as its argument.
|
||
|
*/
|
||
|
int
|
||
|
bcm_mips_attach(device_t dev, u_int num_cpuirqs, u_int timer_irq,
|
||
|
driver_filter_t filter)
|
||
|
{
|
||
|
struct bcm_mips_softc *sc;
|
||
|
struct intr_pic *pic;
|
||
|
uintptr_t xref;
|
||
|
u_int irq_rid;
|
||
|
rman_res_t irq;
|
||
|
int error;
|
||
|
|
||
|
sc = device_get_softc(dev);
|
||
|
sc->dev = dev;
|
||
|
sc->num_cpuirqs = num_cpuirqs;
|
||
|
sc->timer_irq = timer_irq;
|
||
|
|
||
|
/* Must not exceed the actual size of our fixed IRQ array */
|
||
|
if (sc->num_cpuirqs > nitems(sc->cpuirqs)) {
|
||
|
device_printf(dev, "%u nirqs exceeds maximum supported %zu",
|
||
|
sc->num_cpuirqs, nitems(sc->cpuirqs));
|
||
|
return (ENXIO);
|
||
|
}
|
||
|
|
||
|
pic = NULL;
|
||
|
xref = bcm_mips_pic_xref(sc);
|
||
|
|
||
|
BCM_MIPS_LOCK_INIT(sc);
|
||
|
|
||
|
/* Register our interrupt sources */
|
||
|
if ((error = bcm_mips_register_isrcs(sc))) {
|
||
|
BCM_MIPS_LOCK_DESTROY(sc);
|
||
|
return (error);
|
||
|
}
|
||
|
|
||
|
/* Initialize our CPU interrupt state */
|
||
|
irq_rid = bhnd_get_intr_count(dev); /* last bhnd-assigned RID + 1 */
|
||
|
irq = 0;
|
||
|
for (u_int i = 0; i < sc->num_cpuirqs; i++) {
|
||
|
/* Must not overflow signed resource ID representation */
|
||
|
if (irq_rid >= INT_MAX) {
|
||
|
device_printf(dev, "exhausted IRQ resource IDs\n");
|
||
|
error = ENOMEM;
|
||
|
goto failed;
|
||
|
}
|
||
|
|
||
|
if (irq == sc->timer_irq) {
|
||
|
/* Mark the CPU timer's IRQ as unavailable */
|
||
|
error = bcm_mips_init_cpuirq_unavail(sc,
|
||
|
&sc->cpuirqs[i]);
|
||
|
} else {
|
||
|
/* Initialize state */
|
||
|
error = bcm_mips_init_cpuirq(sc, &sc->cpuirqs[i],
|
||
|
irq_rid, irq, filter);
|
||
|
}
|
||
|
|
||
|
if (error)
|
||
|
goto failed;
|
||
|
|
||
|
/* Increment IRQ and resource ID for next allocation */
|
||
|
irq_rid++;
|
||
|
irq++;
|
||
|
}
|
||
|
|
||
|
/* Sanity check; our shared IRQ must be available */
|
||
|
if (sc->num_cpuirqs <= BCM_MIPS_IRQ_SHARED)
|
||
|
panic("missing shared interrupt %d\n", BCM_MIPS_IRQ_SHARED);
|
||
|
|
||
|
if (sc->cpuirqs[BCM_MIPS_IRQ_SHARED].irq_rid == -1)
|
||
|
panic("shared interrupt %d unavailable", BCM_MIPS_IRQ_SHARED);
|
||
|
|
||
|
/* Register PIC */
|
||
|
if ((pic = intr_pic_register(dev, xref)) == NULL) {
|
||
|
device_printf(dev, "error registering PIC\n");
|
||
|
error = ENXIO;
|
||
|
goto failed;
|
||
|
}
|
||
|
|
||
|
return (0);
|
||
|
|
||
|
failed:
|
||
|
/* Deregister PIC before performing any other cleanup */
|
||
|
if (pic != NULL)
|
||
|
intr_pic_deregister(dev, 0);
|
||
|
|
||
|
/* Deregister all interrupt sources */
|
||
|
for (size_t i = 0; i < nitems(sc->isrcs); i++)
|
||
|
intr_isrc_deregister(&sc->isrcs[i].isrc);
|
||
|
|
||
|
/* Free our MIPS CPU interrupt handler state */
|
||
|
for (u_int i = 0; i < sc->num_cpuirqs; i++)
|
||
|
bcm_mips_fini_cpuirq(sc, &sc->cpuirqs[i]);
|
||
|
|
||
|
BCM_MIPS_LOCK_DESTROY(sc);
|
||
|
return (error);
|
||
|
}
|
||
|
|
||
|
int
|
||
|
bcm_mips_detach(device_t dev)
|
||
|
{
|
||
|
struct bcm_mips_softc *sc;
|
||
|
|
||
|
sc = device_get_softc(dev);
|
||
|
|
||
|
/* Deregister PIC before performing any other cleanup */
|
||
|
intr_pic_deregister(dev, 0);
|
||
|
|
||
|
/* Deregister all interrupt sources */
|
||
|
for (size_t i = 0; i < nitems(sc->isrcs); i++)
|
||
|
intr_isrc_deregister(&sc->isrcs[i].isrc);
|
||
|
|
||
|
/* Free our MIPS CPU interrupt handler state */
|
||
|
for (u_int i = 0; i < sc->num_cpuirqs; i++)
|
||
|
bcm_mips_fini_cpuirq(sc, &sc->cpuirqs[i]);
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/* PIC_MAP_INTR() */
|
||
|
static int
|
||
|
bcm_mips_pic_map_intr(device_t dev, struct intr_map_data *d,
|
||
|
struct intr_irqsrc **isrcp)
|
||
|
{
|
||
|
struct bcm_mips_softc *sc;
|
||
|
struct bcm_mips_intr_map_data *data;
|
||
|
|
||
|
sc = device_get_softc(dev);
|
||
|
|
||
|
if (d->type != INTR_MAP_DATA_BCM_MIPS) {
|
||
|
DENTRY(dev, "type=%d", d->type);
|
||
|
return (ENOTSUP);
|
||
|
}
|
||
|
|
||
|
data = (struct bcm_mips_intr_map_data *)d;
|
||
|
DENTRY(dev, "type=%d, ivec=%u", d->type, data->ivec);
|
||
|
if (data->ivec < 0 || data->ivec >= nitems(sc->isrcs))
|
||
|
return (EINVAL);
|
||
|
|
||
|
*isrcp = &sc->isrcs[data->ivec].isrc;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/* PIC_SETUP_INTR() */
|
||
|
static int
|
||
|
bcm_mips_pic_setup_intr(device_t dev, struct intr_irqsrc *irqsrc,
|
||
|
struct resource *res, struct intr_map_data *data)
|
||
|
{
|
||
|
struct bcm_mips_softc *sc;
|
||
|
struct bcm_mips_irqsrc *isrc;
|
||
|
int error;
|
||
|
|
||
|
sc = device_get_softc(dev);
|
||
|
isrc = (struct bcm_mips_irqsrc *)irqsrc;
|
||
|
|
||
|
/* Assign a CPU interrupt */
|
||
|
BCM_MIPS_LOCK(sc);
|
||
|
error = bcm_mips_retain_cpu_intr(sc, isrc, res);
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
|
||
|
return (error);
|
||
|
}
|
||
|
|
||
|
/* PIC_TEARDOWN_INTR() */
|
||
|
static int
|
||
|
bcm_mips_pic_teardown_intr(device_t dev, struct intr_irqsrc *irqsrc,
|
||
|
struct resource *res, struct intr_map_data *data)
|
||
|
{
|
||
|
struct bcm_mips_softc *sc;
|
||
|
struct bcm_mips_irqsrc *isrc;
|
||
|
int error;
|
||
|
|
||
|
sc = device_get_softc(dev);
|
||
|
isrc = (struct bcm_mips_irqsrc *)irqsrc;
|
||
|
|
||
|
/* Release the CPU interrupt */
|
||
|
BCM_MIPS_LOCK(sc);
|
||
|
error = bcm_mips_release_cpu_intr(sc, isrc, res);
|
||
|
BCM_MIPS_UNLOCK(sc);
|
||
|
|
||
|
return (error);
|
||
|
}
|
||
|
|
||
|
|
||
|
/** return our PIC's xref */
|
||
|
static uintptr_t
|
||
|
bcm_mips_pic_xref(struct bcm_mips_softc *sc)
|
||
|
{
|
||
|
uintptr_t xref;
|
||
|
|
||
|
/* Determine our interrupt domain */
|
||
|
xref = BHND_BUS_GET_INTR_DOMAIN(device_get_parent(sc->dev), sc->dev,
|
||
|
true);
|
||
|
KASSERT(xref != 0, ("missing interrupt domain"));
|
||
|
|
||
|
return (xref);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Walk up the device tree from @p dev until we find a bhnd-attached core,
|
||
|
* returning either the core, or NULL if @p dev is not attached under a bhnd
|
||
|
* bus.
|
||
|
*/
|
||
|
static device_t
|
||
|
bcm_mips_find_bhnd_parent(device_t dev)
|
||
|
{
|
||
|
device_t core, bus;
|
||
|
devclass_t bhnd_class;
|
||
|
|
||
|
bhnd_class = devclass_find("bhnd");
|
||
|
core = dev;
|
||
|
while ((bus = device_get_parent(core)) != NULL) {
|
||
|
if (device_get_devclass(bus) == bhnd_class)
|
||
|
return (core);
|
||
|
|
||
|
core = bus;
|
||
|
}
|
||
|
|
||
|
/* Not found */
|
||
|
return (NULL);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Retain @p isrc and assign a MIPS CPU interrupt on behalf of @p res; if
|
||
|
* the @p isrc already has a MIPS CPU interrupt assigned, the existing
|
||
|
* reference will be left unmodified.
|
||
|
*
|
||
|
* @param sc BHND MIPS driver state.
|
||
|
* @param isrc The interrupt source corresponding to @p res.
|
||
|
* @param res The interrupt resource for which a MIPS CPU IRQ will be
|
||
|
* assigned.
|
||
|
*/
|
||
|
static int
|
||
|
bcm_mips_retain_cpu_intr(struct bcm_mips_softc *sc,
|
||
|
struct bcm_mips_irqsrc *isrc, struct resource *res)
|
||
|
{
|
||
|
struct bcm_mips_cpuirq *cpuirq;
|
||
|
bhnd_devclass_t devclass;
|
||
|
device_t core;
|
||
|
|
||
|
BCM_MIPS_LOCK_ASSERT(sc, MA_OWNED);
|
||
|
|
||
|
/* Prefer existing assignment */
|
||
|
if (isrc->cpuirq != NULL) {
|
||
|
KASSERT(isrc->cpuirq->refs > 0, ("assigned IRQ has no "
|
||
|
"references"));
|
||
|
|
||
|
/* Increment our reference count */
|
||
|
if (isrc->refs == UINT_MAX)
|
||
|
return (ENOMEM); /* would overflow */
|
||
|
|
||
|
isrc->refs++;
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/* Use the device class of the bhnd core to which the interrupt
|
||
|
* vector is routed to determine whether a shared interrupt should
|
||
|
* be preferred. */
|
||
|
devclass = BHND_DEVCLASS_OTHER;
|
||
|
core = bcm_mips_find_bhnd_parent(rman_get_device(res));
|
||
|
if (core != NULL)
|
||
|
devclass = bhnd_get_class(core);
|
||
|
|
||
|
switch (devclass) {
|
||
|
case BHND_DEVCLASS_CC:
|
||
|
case BHND_DEVCLASS_CC_B:
|
||
|
case BHND_DEVCLASS_PMU:
|
||
|
case BHND_DEVCLASS_RAM:
|
||
|
case BHND_DEVCLASS_MEMC:
|
||
|
case BHND_DEVCLASS_CPU:
|
||
|
case BHND_DEVCLASS_SOC_ROUTER:
|
||
|
case BHND_DEVCLASS_SOC_BRIDGE:
|
||
|
case BHND_DEVCLASS_EROM:
|
||
|
case BHND_DEVCLASS_NVRAM:
|
||
|
/* Always use a shared interrupt for these devices */
|
||
|
cpuirq = &sc->cpuirqs[BCM_MIPS_IRQ_SHARED];
|
||
|
break;
|
||
|
|
||
|
case BHND_DEVCLASS_PCI:
|
||
|
case BHND_DEVCLASS_PCIE:
|
||
|
case BHND_DEVCLASS_PCCARD:
|
||
|
case BHND_DEVCLASS_ENET:
|
||
|
case BHND_DEVCLASS_ENET_MAC:
|
||
|
case BHND_DEVCLASS_ENET_PHY:
|
||
|
case BHND_DEVCLASS_WLAN:
|
||
|
case BHND_DEVCLASS_WLAN_MAC:
|
||
|
case BHND_DEVCLASS_WLAN_PHY:
|
||
|
case BHND_DEVCLASS_USB_HOST:
|
||
|
case BHND_DEVCLASS_USB_DEV:
|
||
|
case BHND_DEVCLASS_USB_DUAL:
|
||
|
case BHND_DEVCLASS_OTHER:
|
||
|
case BHND_DEVCLASS_INVALID:
|
||
|
default:
|
||
|
/* Fall back on a shared interrupt */
|
||
|
cpuirq = &sc->cpuirqs[BCM_MIPS_IRQ_SHARED];
|
||
|
|
||
|
/* Try to assign a dedicated MIPS HW interrupt */
|
||
|
for (u_int i = 0; i < sc->num_cpuirqs; i++) {
|
||
|
if (i == BCM_MIPS_IRQ_SHARED)
|
||
|
continue;
|
||
|
|
||
|
if (sc->cpuirqs[i].irq_rid == -1)
|
||
|
continue; /* unavailable */
|
||
|
|
||
|
if (sc->cpuirqs[i].refs != 0)
|
||
|
continue; /* already assigned */
|
||
|
|
||
|
/* Found an unused CPU IRQ */
|
||
|
cpuirq = &sc->cpuirqs[i];
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
DPRINTF("routing backplane interrupt vector %u to MIPS IRQ %u\n",
|
||
|
isrc->ivec, cpuirq->mips_irq);
|
||
|
|
||
|
KASSERT(isrc->cpuirq == NULL, ("CPU IRQ already assigned"));
|
||
|
KASSERT(isrc->refs == 0, ("isrc has active references with no "
|
||
|
"assigned CPU IRQ"));
|
||
|
KASSERT(cpuirq->refs == 1 || cpuirq->isrc_solo == NULL,
|
||
|
("single isrc dispatch enabled on cpuirq with multiple refs"));
|
||
|
|
||
|
/* Verify that bumping the cpuirq refcount below will not overflow */
|
||
|
if (cpuirq->refs == UINT_MAX)
|
||
|
return (ENOMEM);
|
||
|
|
||
|
/* Increment cpuirq refcount on behalf of the isrc */
|
||
|
cpuirq->refs++;
|
||
|
|
||
|
/* Increment isrc refcount on behalf of the caller */
|
||
|
isrc->refs++;
|
||
|
|
||
|
/* Assign the IRQ to the isrc */
|
||
|
isrc->cpuirq = cpuirq;
|
||
|
|
||
|
/* Can we enable the single isrc dispatch path? */
|
||
|
if (cpuirq->refs == 1 && cpuirq->mips_irq != BCM_MIPS_IRQ_SHARED)
|
||
|
cpuirq->isrc_solo = isrc;
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Release the MIPS CPU interrupt assigned to @p isrc on behalf of @p res.
|
||
|
*
|
||
|
* @param sc BHND MIPS driver state.
|
||
|
* @param isrc The interrupt source corresponding to @p res.
|
||
|
* @param res The interrupt resource being activated.
|
||
|
*/
|
||
|
static int
|
||
|
bcm_mips_release_cpu_intr(struct bcm_mips_softc *sc,
|
||
|
struct bcm_mips_irqsrc *isrc, struct resource *res)
|
||
|
{
|
||
|
struct bcm_mips_cpuirq *cpuirq;
|
||
|
|
||
|
BCM_MIPS_LOCK_ASSERT(sc, MA_OWNED);
|
||
|
|
||
|
/* Decrement the refcount */
|
||
|
KASSERT(isrc->refs > 0, ("isrc over-release"));
|
||
|
isrc->refs--;
|
||
|
|
||
|
/* Nothing else to do if the isrc is still actively referenced */
|
||
|
if (isrc->refs > 0)
|
||
|
return (0);
|
||
|
|
||
|
/* Otherwise, we need to release our CPU IRQ reference */
|
||
|
cpuirq = isrc->cpuirq;
|
||
|
isrc->cpuirq = NULL;
|
||
|
|
||
|
KASSERT(cpuirq != NULL, ("no assigned IRQ"));
|
||
|
KASSERT(cpuirq->refs > 0, ("cpuirq over-release"));
|
||
|
|
||
|
/* Disable single isrc dispatch path */
|
||
|
if (cpuirq->refs == 1 && cpuirq->isrc_solo != NULL) {
|
||
|
KASSERT(cpuirq->isrc_solo == isrc, ("invalid solo isrc"));
|
||
|
cpuirq->isrc_solo = NULL;
|
||
|
}
|
||
|
|
||
|
cpuirq->refs--;
|
||
|
|
||
|
return (0);
|
||
|
}
|
||
|
|
||
|
static device_method_t bcm_mips_methods[] = {
|
||
|
/* Device interface */
|
||
|
DEVMETHOD(device_attach, bcm_mips_attach_default),
|
||
|
DEVMETHOD(device_detach, bcm_mips_detach),
|
||
|
|
||
|
/* Interrupt controller interface */
|
||
|
DEVMETHOD(pic_map_intr, bcm_mips_pic_map_intr),
|
||
|
DEVMETHOD(pic_setup_intr, bcm_mips_pic_setup_intr),
|
||
|
DEVMETHOD(pic_teardown_intr, bcm_mips_pic_teardown_intr),
|
||
|
|
||
|
DEVMETHOD_END
|
||
|
};
|
||
|
|
||
|
DEFINE_CLASS_0(bcm_mips, bcm_mips_driver, bcm_mips_methods, sizeof(struct bcm_mips_softc));
|
||
|
|
||
|
MODULE_VERSION(bcm_mips, 1);
|
||
|
MODULE_DEPEND(bcm_mips, bhnd, 1, 1, 1);
|