root/sys/arch/riscv/fdt/intc_fdt.c
/*      $NetBSD: intc_fdt.c,v 1.9 2026/01/09 16:11:10 skrll Exp $       */

/*-
 * Copyright (c) 2023 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Nick Hudson
 *
 * 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 NETBSD FOUNDATION, INC. 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 FOUNDATION 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 "opt_multiprocessor.h"

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: intc_fdt.c,v 1.9 2026/01/09 16:11:10 skrll Exp $");

#include <sys/param.h>

#include <sys/bus.h>
#include <sys/cpu.h>
#include <sys/device.h>
#include <sys/evcnt.h>
#include <sys/kmem.h>
#include <sys/intr.h>

#include <dev/fdt/fdtvar.h>

#include <machine/frame.h>
#include <machine/machdep.h>
#include <machine/sysreg.h>

static const struct device_compatible_entry compat_data[] = {
        { .compat = "riscv,cpu-intc" },
        DEVICE_COMPAT_EOL
};


struct intc_irqhandler;
struct intc_irq;

typedef int (*intcih_t)(void *);

struct intc_irqhandler {
        struct intc_irq          *ih_irq;
        intcih_t                  ih_fn;
        void                     *ih_arg;
        TAILQ_ENTRY(intc_irqhandler)
                                  ih_next;
};

struct intc_irq {
        struct intc_fdt_softc   *intr_sc;
        void                    *intr_ih;
        void                    *intr_arg;
        int                      intr_refcnt;
        int                      intr_ipl;
        int                      intr_source;
        int                      intr_istflags;
        struct evcnt *pcpu_evs;
        TAILQ_HEAD(, intc_irqhandler)
                                 intr_handlers;
};


struct intc_fdt_softc {
        device_t                 sc_dev;
        bus_space_tag_t          sc_bst;
        bus_space_handle_t       sc_bsh;

        struct intc_irq         *sc_irq[IRQ_NSOURCES];

        struct evcnt             sc_evs[IRQ_NSOURCES];

        struct cpu_info         *sc_ci;
        cpuid_t                  sc_hartid;
};

static const char * const intc_sources[IRQ_NSOURCES] = {
        /* Software interrupts */
        "(reserved)",
        "Supervisor software",
        "Virtual Supervisor software",
        "Machine software",

        /* Timer interrupts */
        "(reserved)",
        "Supervisor timer",
        "Virtual Supervisor timer",
        "Machine timer",

        /* External interrupts */
        "(reserved)",
        "Supervisor external",
        "Virtual Supervisor external",
        "Machine external",

        "(reserved)",
        "Supervisor guest external.",
        "(reserved)",
        "(reserved)"
};

#ifndef MULTIPROCESSOR
struct intc_fdt_softc *intc_sc;
#endif


static inline struct intc_fdt_softc *
intc_getsc(struct cpu_info *ci)
{
#ifdef MULTIPROCESSOR
        return ci->ci_intcsoftc;
#else
        return intc_sc;
#endif
}

static void *
intc_intr_establish(struct intc_fdt_softc *sc, u_int source, u_int ipl,
    u_int istflags, int (*func)(void *), void *arg, const char *xname)
{
        if (source > IRQ_NSOURCES)
                return NULL;

        const device_t dev = sc->sc_dev;
        struct intc_irq *irq = sc->sc_irq[source];
        if (irq == NULL) {
                irq = kmem_alloc(sizeof(*irq), KM_SLEEP);
                irq->intr_sc = sc;
                irq->intr_refcnt = 0;
                irq->intr_arg = arg;
                irq->intr_ipl = ipl;
                irq->intr_istflags = istflags;
                irq->intr_source = source;
                TAILQ_INIT(&irq->intr_handlers);
                sc->sc_irq[source] = irq;

                evcnt_attach_dynamic(&sc->sc_evs[source], EVCNT_TYPE_INTR, NULL,
                    device_xname(sc->sc_dev), intc_sources[source]);
        } else {
                if (irq->intr_arg == NULL || arg == NULL) {
                        device_printf(dev,
                            "cannot share irq with NULL-arg handler\n");
                        return NULL;
                }
                if (irq->intr_ipl != ipl) {
                        device_printf(dev,
                            "cannot share irq with different ipl\n");
                        return NULL;
                }
                if (irq->intr_istflags != istflags) {
                        device_printf(dev,
                            "cannot share irq between mpsafe/non-mpsafe\n");
                        return NULL;
                }
        }

        struct intc_irqhandler *irqh = kmem_alloc(sizeof(*irqh), KM_SLEEP);
        irqh->ih_irq = irq;
        irqh->ih_fn = func;
        irqh->ih_arg = arg;

        irq->intr_refcnt++;
        TAILQ_INSERT_TAIL(&irq->intr_handlers, irqh, ih_next);

        /*
         * XXX interrupt_distribute(9) assumes that any interrupt
         * handle can be used as an input to the MD interrupt_distribute
         * implementation, so we are forced to return the handle
         * we got back from intr_establish().  Upshot is that the
         * input to bcm2835_icu_fdt_disestablish() is ambiguous for
         * shared IRQs, rendering them un-disestablishable.
         */

        return irqh;
}


static void *
intc_fdt_establish(device_t dev, u_int *specifier, int ipl, int flags,
    int (*func)(void *), void *arg, const char *xname)
{
        struct intc_fdt_softc * const sc = device_private(dev);

        /*
         * 1st (and only) cell is the interrupt source, e.g.
         *  1  IRQ_SUPERVISOR_SOFTWARE
         *  5  IRQ_SUPERVISOR_TIMER
         *  9  IRQ_SUPERVISOR_EXTERNAL
         */

        const u_int source = be32toh(specifier[0]);
        const u_int mpsafe = (flags & FDT_INTR_MPSAFE) ? IST_MPSAFE : 0;

        return intc_intr_establish(sc, source, ipl, mpsafe, func, arg, xname);
}

static void
intc_fdt_disestablish(device_t dev, void *ih)
{
#if 0
        struct intc_fdt_softc * const sc = device_private(dev);
#endif
}

static bool
intc_fdt_intrstr(device_t dev, u_int *specifier, char *buf, size_t buflen)
{
        if (!specifier)
                return false;

        struct intc_fdt_softc * const sc = device_private(dev);
        if (sc->sc_ci == NULL)
                return false;

        const u_int source = be32toh(specifier[0]);
        snprintf(buf, buflen, "cpu%u (hart %lu)/%s #%u", cpu_index(sc->sc_ci),
            sc->sc_hartid, intc_sources[source], source);

        return true;
}


struct fdtbus_interrupt_controller_func intc_fdt_funcs = {
        .establish = intc_fdt_establish,
        .disestablish = intc_fdt_disestablish,
        .intrstr = intc_fdt_intrstr
};


static void
intc_intr_handler(struct trapframe *tf, register_t epc, register_t status,
    register_t cause)
{
        const int ppl = splhigh();
        struct cpu_info * const ci = curcpu();
        unsigned long pending;
        int ipl;

        KASSERT(CAUSE_INTERRUPT_P(cause));

        struct intc_fdt_softc * const sc = intc_getsc(ci);

        ci->ci_intr_depth++;
        ci->ci_data.cpu_nintr++;

        while (ppl < (ipl = splintr(&pending))) {
                if (pending == 0)
                        continue;

                splx(ipl);

                int source = ffs(pending) - 1;
                struct intc_irq *irq = sc->sc_irq[source];
                sc->sc_evs[source].ev_count++;

                KASSERTMSG(irq != NULL, "source %d\n", source);

                if (irq) {
                        struct intc_irqhandler *iih;

                        bool mpsafe =
                            source != IRQ_SUPERVISOR_EXTERNAL ||
                            (irq->intr_istflags & IST_MPSAFE) != 0;
                        struct clockframe cf = {
                                .cf_epc = epc,
                                .cf_status = status,
                                .cf_intr_depth = ci->ci_intr_depth
                        };

                        if (!mpsafe) {
                                KERNEL_LOCK(1, NULL);
                        }

                        TAILQ_FOREACH(iih, &irq->intr_handlers, ih_next) {
                                int handled =
                                    iih->ih_fn(iih->ih_arg ? iih->ih_arg : &cf);
                                if (handled)
                                        break;
                        }

                        if (!mpsafe) {
                                KERNEL_UNLOCK_ONE(NULL);
                        }
                }
                splhigh();
        }
        ci->ci_intr_depth--;
        splx(ppl);
}



static int
intc_match(device_t parent, cfdata_t cf, void *aux)
{
        struct fdt_attach_args * const faa = aux;
        return of_compatible_match(faa->faa_phandle, compat_data);
}

static void
intc_attach(device_t parent, device_t self, void *aux)
{
        const struct fdt_attach_args * const faa = aux;
        const int phandle = faa->faa_phandle;

        int error = fdtbus_register_interrupt_controller(self, phandle,
            &intc_fdt_funcs);
        if (error) {
                aprint_error(": couldn't register with fdtbus: %d\n", error);
                return;
        }

        struct cpu_info * const ci = device_private(parent);
        if (ci == NULL) {
                aprint_naive(": disabled\n");
                aprint_normal(": disabled\n");
                return;
        }
        aprint_naive("\n");
        aprint_normal(": local interrupt controller\n");

        struct intc_fdt_softc * const sc = device_private(self);

        riscv_intr_set_handler(intc_intr_handler);

        sc->sc_dev = self;
        sc->sc_ci = ci;
        sc->sc_hartid = ci->ci_cpuid;

        intc_intr_establish(sc, IRQ_SUPERVISOR_TIMER, IPL_SCHED, IST_MPSAFE,
            riscv_timer_intr, NULL, "clock");
#ifdef MULTIPROCESSOR
        ci->ci_intcsoftc = sc;
        intc_intr_establish(sc, IRQ_SUPERVISOR_SOFTWARE, IPL_HIGH, IST_MPSAFE,
            riscv_ipi_intr, NULL, "ipi");
#else
        intc_sc = sc;
#endif
}

CFATTACH_DECL_NEW(intc_fdt, sizeof(struct intc_fdt_softc),
        intc_match, intc_attach, NULL, NULL);