root/sys/arch/hpcarm/hpcarm/softintr.c
/*      $NetBSD: softintr.c,v 1.18 2020/11/21 21:27:09 thorpej Exp $    */

/*-
 * Copyright (c) 2001 The NetBSD Foundation, Inc.
 * All rights reserved.
 *
 * This code is derived from software contributed to The NetBSD Foundation
 * by IWAMOTO Toshihiro.
 *
 * 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: softintr.c,v 1.18 2020/11/21 21:27:09 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/syslog.h>
#include <sys/kmem.h>

#include <machine/cpu.h>
#include <arm/cpufunc.h>
#include <machine/intr.h>

void softintr_free(void *);
void softintr_dispatch(int);

struct softintr_handler {
        struct softintr_handler *sh_vlink;      /* vertical link */
        struct softintr_handler *sh_hlink;      /* horizontal link */

        void (*sh_fun)(void *);
        void *sh_arg;
        int sh_level;

        int sh_pending;
};

struct softintr_handler *softintrs;
struct softintr_handler *softintr_pending;

void *
softintr_establish(int level, void (*fun)(void *), void *arg)
{
        struct softintr_handler *sh;

        sh = kmem_alloc(sizeof(*sh), KM_SLEEP);
        sh->sh_fun = fun;
        sh->sh_level = ipl_to_spl(level);
        sh->sh_arg = arg;
        sh->sh_pending = 0;

        return sh;
}

void
softintr_disestablish(void *cookie)
{
        struct softintr_handler *sh = cookie;
        u_int saved_cpsr;

        saved_cpsr = SetCPSR(I32_bit, I32_bit);
        if (sh->sh_pending) {
                sh->sh_fun = softintr_free;
                sh->sh_arg = sh;
                SetCPSR(I32_bit, I32_bit & saved_cpsr);
        } else {
                SetCPSR(I32_bit, I32_bit & saved_cpsr);
                kmem_free(sh, sizeof(*sh));
        }
}

void
softintr_free(void *arg)
{
        struct softintr_handler *sh = arg;

        kmem_free(sh, sizeof(*sh));
}

void
softintr_schedule(void *cookie)
{
        struct softintr_handler **p, *sh = cookie;
        register int pending, saved_cpsr;

        pending = 1;
        __asm("swp %0, %0, [%1]" : "+r" (pending) : "r" (&sh->sh_pending));

        if (pending)
                return;

        sh->sh_vlink = NULL;
        sh->sh_hlink = NULL;

#ifdef __GNUC__
        __asm volatile("mrs %0, cpsr\n orr r1, %0, %1\n msr cpsr_a;;, r1" :
            "=r" (saved_cpsr) : "i" (I32_bit) : "r1");
#else
        saved_cpsr = SetCPSR(I32_bit, I32_bit);
#endif
        p = &softintr_pending;

        for (;; p = &(*p)->sh_vlink) {
                if (*p == NULL)
                        goto set_and_exit;

                if ((*p)->sh_level <= sh->sh_level)
                        break;
        }

        if ((*p)->sh_level == sh->sh_level) {
                sh->sh_hlink = *p;
                sh->sh_vlink = (*p)->sh_vlink;
                goto set_and_exit;
        }

        sh->sh_vlink = *p;
set_and_exit:
        *p = sh;
#ifdef __GNUC__
        __asm volatile("msr cpsr_c, %0" : : "r" (saved_cpsr));
#else
        SetCPSR(I32_bit, I32_bit & saved_cpsr);
#endif
        return;
}

void
softintr_dispatch(int s)
{
        struct softintr_handler *sh, *sh1;
        register int saved_cpsr;

        while (1) {
                /* Protect list operation from interrupts */
#ifdef __GNUC__
                __asm volatile("mrs %0, cpsr\n orr r1, %0, %1\n"
                    " msr cpsr_all, r1" : "=r" (saved_cpsr) :
                    "i" (I32_bit) : "r1");
#else
                saved_cpsr = SetCPSR(I32_bit, I32_bit);
#endif

                if (softintr_pending == NULL ||
                    softintr_pending->sh_level <= s) {
#ifdef __GNUC__
                        __asm("msr cpsr_c, %0" : : "r" (saved_cpsr));
#else
                        SetCPSR(I32_bit, I32_bit & saved_cpsr);
#endif
                        splx(s);
                        return;
                }
                sh = softintr_pending;
                softintr_pending = softintr_pending->sh_vlink;

                if (sh->sh_level > current_spl_level)
                        raisespl(sh->sh_level);
#ifdef __GNUC__
                __asm volatile("msr cpsr_c, %0" : : "r" (saved_cpsr));
#else
                SetCPSR(I32_bit, I32_bit & saved_cpsr);
#endif
                if (sh->sh_level < current_spl_level)
                        lowerspl(sh->sh_level);

                while (1) {
                        /* The order is important */
                        sh1 = sh->sh_hlink;
                        sh->sh_pending = 0;

                        (sh->sh_fun)(sh->sh_arg);
                        if (sh1 == NULL)
                                break;
                        sh = sh1;
                }
        }
        splx(s);
}