root/sys/arch/m68k/m68k/m68k_intr.c
/*      $NetBSD: m68k_intr.c,v 1.13 2024/01/19 20:55:42 thorpej Exp $   */

/*-
 * Copyright (c) 1996, 2023, 2024 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by Adam Glass, Gordon W. Ross, and Jason R. Thorpe.
 *
 * 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.
 */     

/*
 * Common interrupt handling for m68k platforms.
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: m68k_intr.c,v 1.13 2024/01/19 20:55:42 thorpej Exp $");

#define _M68K_INTR_PRIVATE

#include <sys/param.h> 
#include <sys/systm.h>
#include <sys/kmem.h>
#include <sys/vmmeter.h>
#include <sys/device.h>
#include <sys/cpu.h>
#include <sys/bus.h>
#include <sys/once.h>
#include <sys/intr.h>

#include <machine/vectors.h>

#include <uvm/uvm_extern.h>

#ifdef __HAVE_M68K_INTR_VECTORED
#ifndef MACHINE_USERVEC_START
#define MACHINE_USERVEC_START   VECI_USRVEC_START
#endif

#define NVECHANDS       (NVECTORS - MACHINE_USERVEC_START)

#ifndef INTR_FREEVEC
#define INTR_FREEVEC    badtrap
#endif

extern char INTR_FREEVEC[];
extern char intrstub_vectored[];
#endif /* __HAVE_M68K_INTR_VECTORED */

/* A dummy event counter where interrupt stats go to die. */
static struct evcnt bitbucket;

volatile unsigned int intr_depth;       /* updated in assembly glue */

static struct m68k_intrhand_list m68k_intrhands_autovec[NAUTOVECTORS];
#ifdef __HAVE_M68K_INTR_VECTORED
static struct m68k_intrhand *m68k_intrhands_vectored[NVECHANDS];
#endif

#ifndef __HAVE_LEGACY_INTRCNT
#ifndef MACHINE_INTREVCNT_NAMES
#define MACHINE_INTREVCNT_NAMES                                         \
        { "spur", "lev1", "lev2", "lev3", "lev4", "lev5", "lev6", "nmi" }
#endif
static const char * const m68k_intr_evcnt_names[] = MACHINE_INTREVCNT_NAMES;
__CTASSERT(__arraycount(m68k_intr_evcnt_names) == NAUTOVECTORS);
static const char m68k_intr_evcnt_group[] = "cpu";

#define INTRCNT_INIT(x)                                                 \
        EVCNT_INITIALIZER(EVCNT_TYPE_INTR, NULL, m68k_intr_evcnt_group, \
                          m68k_intr_evcnt_names[(x)])

struct evcnt m68k_intr_evcnt[] = {
        INTRCNT_INIT(0), INTRCNT_INIT(1), INTRCNT_INIT(2), INTRCNT_INIT(3),
        INTRCNT_INIT(4), INTRCNT_INIT(5), INTRCNT_INIT(6), INTRCNT_INIT(7),
};
__CTASSERT(__arraycount(m68k_intr_evcnt) == NAUTOVECTORS);

#undef INTRCNT_INIT
#endif /* __HAVE_LEGACY_INTRCNT */

const uint16_t ipl2psl_table[NIPL] = {
        [IPL_NONE]       = PSL_S | PSL_IPL0,
        [IPL_SOFTBIO]    = PSL_S | MACHINE_PSL_IPL_SOFTBIO,
        [IPL_SOFTCLOCK]  = PSL_S | MACHINE_PSL_IPL_SOFTCLOCK,
        [IPL_SOFTNET]    = PSL_S | MACHINE_PSL_IPL_SOFTNET,
        [IPL_SOFTSERIAL] = PSL_S | MACHINE_PSL_IPL_SOFTSERIAL,
        [IPL_VM]         = PSL_S | MACHINE_PSL_IPL_VM,
        [IPL_SCHED]      = PSL_S | MACHINE_PSL_IPL_SCHED,
        [IPL_HIGH]       = PSL_S | PSL_IPL7,
};

/*
 * m68k_spurintr --
 *      Interrupt handler for the "spurious interrupt" that comes in
 *      on auto-vector level 0.  All we do is claim it; it gets counted
 *      during the normal course of auto-vector interrupt handling.
 */
static int
m68k_spurintr(void *arg)
{
        return 1;
}

static struct m68k_intrhand m68k_spurintr_ih = {
        .ih_func  = m68k_spurintr,
        .ih_arg   = m68k_spurintr,
        .ih_evcnt = &bitbucket,
};

static struct m68k_intrhand *
m68k_ih_stdalloc(int km_flag)
{
        return kmem_zalloc(sizeof(struct m68k_intrhand), km_flag);
}

static void
m68k_ih_stdfree(struct m68k_intrhand *ih)
{
        kmem_free(ih, sizeof(*ih));
}

static const struct m68k_ih_allocfuncs m68k_ih_stdallocfuncs = {
        .alloc = m68k_ih_stdalloc,
        .free  = m68k_ih_stdfree,
};

static const struct m68k_ih_allocfuncs *ih_allocfuncs;

static struct m68k_intrhand *
m68k_ih_alloc(void)
{
        KASSERT(ih_allocfuncs != NULL);
        return ih_allocfuncs->alloc(KM_SLEEP);
}

static void
m68k_ih_free(struct m68k_intrhand *ih)
{
        KASSERT(ih_allocfuncs != NULL);
        if (__predict_true(ih != &m68k_spurintr_ih)) {
                ih_allocfuncs->free(ih);
        }
}

#ifdef __HAVE_M68K_INTR_VECTORED

#define INTRVEC_SLOT(vec)       \
        (&m68k_intrhands_vectored[(vec) - MACHINE_USERVEC_START])

#define m68k_intrvec_handler(vec)       (*INTRVEC_SLOT(vec))

static bool
m68k_intrvec_add(struct m68k_intrhand *ih)
{
        if (ih->ih_vec < MACHINE_USERVEC_START || ih->ih_vec >= NVECTORS) {
                aprint_error("%s: vector=0x%x (invalid)\n", __func__,
                    ih->ih_vec);
                return false;
        }

        struct m68k_intrhand **slot =
            &m68k_intrhands_vectored[ih->ih_vec - MACHINE_USERVEC_START];

        if (*slot != NULL) {
                aprint_error("%s: vector=0x%x (in use)\n", __func__,
                    ih->ih_vec);
                return false;
        }

        if (vec_get_entry(ih->ih_vec) != INTR_FREEVEC) {
                aprint_error("%s: vector=0x%x (unavailable)\n", __func__,
                    ih->ih_vec);
                return false;
        }

        *slot = ih;
        vec_set_entry(ih->ih_vec, intrstub_vectored);
        return true;
}

static void
m68k_intrvec_remove(struct m68k_intrhand *ih)
{
        KASSERT(ih->ih_vec >= MACHINE_USERVEC_START);
        KASSERT(ih->ih_vec < NVECTORS);

        struct m68k_intrhand **slot =
            &m68k_intrhands_vectored[ih->ih_vec - MACHINE_USERVEC_START];

        KASSERT(*slot == ih);
        KASSERT(vec_get_entry(ih->ih_vec) == intrstub_vectored);

        vec_set_entry(ih->ih_vec, INTR_FREEVEC);
        *slot = NULL;
}

/* XXX This is horrible and should burn to the ground. */
void *
m68k_intrvec_intrhand(int vec)
{
        KASSERT(vec >= MACHINE_USERVEC_START);
        KASSERT(vec < NVECTORS);

        return m68k_intrhands_vectored[vec - MACHINE_USERVEC_START];
}

#endif /* __HAVE_M68K_INTR_VECTORED */

/*
 * m68k_intr_init --
 *      Initialize the interrupt subsystem.
 */
void
m68k_intr_init(const struct m68k_ih_allocfuncs *allocfuncs)
{
        int i;

        KASSERT(ih_allocfuncs == NULL);
        if (allocfuncs == NULL) {
                ih_allocfuncs = &m68k_ih_stdallocfuncs;
        } else {
                ih_allocfuncs = allocfuncs;
        }

        for (i = 0; i < NAUTOVECTORS; i++) {
                LIST_INIT(&m68k_intrhands_autovec[i]);
#ifndef __HAVE_LEGACY_INTRCNT
                evcnt_attach_static(&m68k_intr_evcnt[i]);
#endif
        }
        LIST_INSERT_HEAD(&m68k_intrhands_autovec[0],
            &m68k_spurintr_ih, ih_link);
}

/*
 * m68k_intr_establish --
 *      Establish an interrupt handler at the specified vector.
 *      XXX We don't do anything with the flags yet.
 */
void *
m68k_intr_establish(int (*func)(void *), void *arg, struct evcnt *ev,
    int vec, int ipl, int isrpri, int flags __unused)
{
        struct m68k_intrhand *ih;
        int s;

        /*
         * If a platform doesn't want special behavior, we don't
         * require them to call m68k_intr_init(); we just handle
         * it here.
         *
         * XXX m68k_intr_init() might be called really early, so
         * XXX can't use a once control.
         */
        if (__predict_false(ih_allocfuncs == NULL)) {
                m68k_intr_init(NULL);
        }

        /* These are m68k IPLs, not IPL_* values. */
        if (ipl < 0 || ipl > 7) {
                return NULL;
        }

#ifdef __HAVE_M68K_INTR_VECTORED
        KASSERT(vec >= 0);
        KASSERT(vec < NVECTORS);
#else
        KASSERT(vec == 0);
#endif /* __HAVE_M68K_INTR_VECTORED */

        ih = m68k_ih_alloc();
        ih->ih_func = func;
        ih->ih_arg = arg;
        ih->ih_vec = vec;
        ih->ih_ipl = ipl;
        ih->ih_isrpri = isrpri;
        if ((ih->ih_evcnt = ev) == NULL) {
                ih->ih_evcnt = &bitbucket;
        }

#ifdef __HAVE_M68K_INTR_VECTORED
        if (vec != 0) {
                if (vec_get_entry(vec) != INTR_FREEVEC) {
                        m68k_ih_free(ih);
                        return NULL;
                }
                if (! m68k_intrvec_add(ih)) {
                        m68k_ih_free(ih);
                        return NULL;
                }
                return ih;
        }
#endif

        /*
         * Some devices are particularly sensitive to interrupt
         * handling latency.  Unbuffered serial ports, for example,
         * can lose data if their interrupts aren't handled with
         * reasonable speed.  For this reason, we sort interrupt
         * handlers by an abstract "ISR" priority, inserting higher-
         * priority interrupts before lower-priority interrupts.
         */
        struct m68k_intrhand_list * const list = &m68k_intrhands_autovec[ipl];
        struct m68k_intrhand *curih;

        s = splhigh();
        if (LIST_EMPTY(list)) {
                LIST_INSERT_HEAD(list, ih, ih_link);
                goto done;
        }
        for (curih = LIST_FIRST(list);
             LIST_NEXT(curih, ih_link) != NULL;
             curih = LIST_NEXT(curih, ih_link)) {
                if (ih->ih_isrpri > curih->ih_isrpri) {
                        LIST_INSERT_BEFORE(curih, ih, ih_link);
                        goto done;
                }
        }
        LIST_INSERT_AFTER(curih, ih, ih_link);
 done:
        splx(s);

        return ih;
}

/*
 * m68k_intr_disestablish --
 *      Remove an interrupt handler.  Returns true if the handler
 *      list for this vector is now empty.
 */
bool
m68k_intr_disestablish(void *v)
{
        struct m68k_intrhand *ih = v;
        int s;
        bool empty;

#ifdef __HAVE_M68K_INTR_VECTORED
        if (ih->ih_vec != 0) {
                KASSERT(vec_get_entry(ih->ih_vec) == intrstub_vectored);
                m68k_intrvec_remove(ih);
                empty = true;
        } else
#endif
        {
                s = splhigh();
                LIST_REMOVE(ih, ih_link);
                empty = LIST_EMPTY(&m68k_intrhands_autovec[ih->ih_ipl]);
                splx(s);
        }

        m68k_ih_free(ih);

        return empty;
}

void    m68k_intr_autovec(struct clockframe);

#ifndef MACHINE_AUTOVEC_IGNORE_STRAY
#define MACHINE_AUTOVEC_IGNORE_STRAY(ipl)       0
#endif

/*
 * m68k_intr_autovec --
 *      Run the interrupt handlers for an auto-vectored interrupt.
 *      Called from the assembly glue in m68k_intr_stubs.s
 */
void
m68k_intr_autovec(struct clockframe frame)
{
        const int ipl = VECO_TO_VECI(frame.cf_vo) - VECI_INTRAV0;
        struct m68k_intrhand *ih;
        bool rv = false;

        m68k_count_intr(ipl);

        LIST_FOREACH(ih, &m68k_intrhands_autovec[ipl], ih_link) {
                void *arg = ih->ih_arg ? ih->ih_arg : &frame;
                if (ih->ih_func(arg)) {
                        ih->ih_evcnt->ev_count++;
                        rv = true;
                }
        }
        if (!rv && !MACHINE_AUTOVEC_IGNORE_STRAY(ipl)) {
                printf("Stray level %d interrupt\n", ipl);
        }

        ATOMIC_CAS_CHECK(&frame);
}

#ifdef __HAVE_M68K_INTR_VECTORED
void    m68k_intr_vectored(struct clockframe);

/*
 * m68k_intr_vectored --
 *      Run a vectored interrupt handler.
 *      Called from the assembly glue in m68k_intr_stubs.s
 */
void
m68k_intr_vectored(struct clockframe frame)
{
        const int vec = VECO_TO_VECI(frame.cf_vo);
        const int ipl = (getsr() >> 8) & 7;
        struct m68k_intrhand *ih;

        m68k_count_intr(ipl);

#ifdef DIAGNOSTIC
        if (vec < MACHINE_USERVEC_START || vec >= NVECTORS) {
                printf("%s: vector=0x%x (invalid)\n", __func__, vec);
                goto out;
        }
#endif
        ih = m68k_intrvec_handler(vec);
        if (ih == NULL) {
                printf("%s: vector=0x%x (no handler?)\n", __func__, vec);
                vec_set_entry(vec, INTR_FREEVEC);
        }

        if (__predict_true((*ih->ih_func)(ih->ih_arg ? ih->ih_arg
                                                     : &frame) != 0)) {
                ih->ih_evcnt->ev_count++;
        } else {
                printf("Stray level %d interrupt vector=0x%x\n",
                    ipl, vec);
        }
#ifdef DIAGNOSTIC
 out:
#endif
        ATOMIC_CAS_CHECK(&frame);
}
#endif /* __HAVE_M68K_INTR_VECTORED */