root/sys/arch/sh3/sh3/exception.c
/*      $NetBSD: exception.c,v 1.75 2023/10/05 19:41:05 ad Exp $        */

/*-
 * Copyright (c) 2002, 2019 The NetBSD Foundation, Inc. All rights reserved.
 * Copyright (c) 1990 The Regents of the University of California.
 * All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the University of Utah, and William Jolitz.
 *
 * 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.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 *      @(#)trap.c      7.4 (Berkeley) 5/13/91
 */

/*-
 * Copyright (c) 1995 Charles M. Hannum.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * the University of Utah, and William Jolitz.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS 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 REGENTS 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.
 *
 *      @(#)trap.c      7.4 (Berkeley) 5/13/91
 */

/*
 * SH3 Trap and System call handling
 *
 * T.Horiuchi 1998.06.8
 */

#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: exception.c,v 1.75 2023/10/05 19:41:05 ad Exp $");

#include "opt_ddb.h"
#include "opt_kgdb.h"

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/signal.h>
#include <sys/intr.h>

#ifdef DDB
#include <sh3/db_machdep.h>
#endif
#ifdef KGDB
#include <sys/kgdb.h>
#endif

#include <uvm/uvm_extern.h>

#include <sh3/cpu.h>
#include <sh3/mmu.h>
#include <sh3/pcb.h>
#include <sh3/exception.h>
#include <sh3/userret.h>

const char * const exp_type[] = {
        "--",                                   /* 0x000 (reset vector) */
        "--",                                   /* 0x020 (reset vector) */
        "TLB miss/invalid (load)",              /* 0x040 EXPEVT_TLB_MISS_LD */
        "TLB miss/invalid (store)",             /* 0x060 EXPEVT_TLB_MISS_ST */
        "initial page write",                   /* 0x080 EXPEVT_TLB_MOD */
        "TLB protection violation (load)",      /* 0x0a0 EXPEVT_TLB_PROT_LD */
        "TLB protection violation (store)",     /* 0x0c0 EXPEVT_TLB_PROT_ST */
        "address error (load)",                 /* 0x0e0 EXPEVT_ADDR_ERR_LD */
        "address error (store)",                /* 0x100 EXPEVT_ADDR_ERR_ST */
        "FPU",                                  /* 0x120 EXPEVT_FPU */
        "--",                                   /* 0x140 (reset vector) */
        "unconditional trap (TRAPA)",           /* 0x160 EXPEVT_TRAPA */
        "reserved instruction code exception",  /* 0x180 EXPEVT_RES_INST */
        "illegal slot instruction exception",   /* 0x1a0 EXPEVT_SLOT_INST */
        "--",                                   /* 0x1c0 (external interrupt) */
        "user break point trap",                /* 0x1e0 EXPEVT_BREAK */
};
const int exp_types = __arraycount(exp_type);

void general_exception(struct lwp *, struct trapframe *, uint32_t);
void tlb_exception(struct lwp *, struct trapframe *, uint32_t);
void ast(struct lwp *, struct trapframe *);

/*
 * void general_exception(struct lwp *l, struct trapframe *tf):
 *      l  ... curlwp when exception occur.
 *      tf ... full user context.
 *      va ... fault va for user mode EXPEVT_ADDR_ERR_{LD,ST}
 */
void
general_exception(struct lwp *l, struct trapframe *tf, uint32_t va)
{
        int expevt = tf->tf_expevt;
        bool usermode = !KERNELMODE(tf->tf_ssr);
        struct pcb *pcb;
        ksiginfo_t ksi;
        uint32_t trapcode;
#ifdef DDB
        uint32_t code;
#endif

        curcpu()->ci_data.cpu_ntrap++;

        /*
         * Read trap code from TRA before enabling interrupts,
         * otherwise it can be clobbered by a ddb breakpoint in an
         * interrupt handler.
         */
        trapcode = _reg_read_4(SH_(TRA)) >> 2;

        splx(tf->tf_ssr & PSL_IMASK);

        if (l == NULL)
                goto do_panic;

        if (usermode) {
                KDASSERT(l->l_md.md_regs == tf); /* check exception depth */
                expevt |= EXP_USER;
        }

        switch (expevt) {
        case EXPEVT_TRAPA | EXP_USER:
                /* Check for debugger break */
                if (trapcode == _SH_TRA_BREAK) {
                        tf->tf_spc -= 2; /* back to the breakpoint address */
                        KSI_INIT_TRAP(&ksi);
                        ksi.ksi_signo = SIGTRAP;
                        ksi.ksi_code = TRAP_BRKPT;
                        ksi.ksi_addr = (void *)tf->tf_spc;
                        goto trapsignal;
                } else {
                        /* XXX: we shouldn't treat *any* TRAPA as a syscall */
                        (*l->l_proc->p_md.md_syscall)(l, tf);
                        return;
                }
                break;

        case EXPEVT_BREAK | EXP_USER:
                l->l_md.md_flags &= ~MDL_SSTEP;
                KSI_INIT_TRAP(&ksi);
                ksi.ksi_signo = SIGTRAP;
                ksi.ksi_code = TRAP_TRACE;
                ksi.ksi_addr = (void *)tf->tf_spc;
                goto trapsignal;

        case EXPEVT_ADDR_ERR_LD: /* FALLTHROUGH */
        case EXPEVT_ADDR_ERR_ST:
                pcb = lwp_getpcb(l);
                if (__predict_false(pcb->pcb_onfault == NULL))
                        goto do_panic;
                tf->tf_spc = (int)pcb->pcb_onfault;
                tf->tf_r0 = EFAULT;
                break;

        case EXPEVT_ADDR_ERR_LD | EXP_USER: /* FALLTHROUGH */
        case EXPEVT_ADDR_ERR_ST | EXP_USER:
                KSI_INIT_TRAP(&ksi);
                if (((int)va) < 0) {
                    ksi.ksi_signo = SIGSEGV;
                    ksi.ksi_code = SEGV_ACCERR;
                } else {
                    ksi.ksi_signo = SIGBUS;
                    ksi.ksi_code = BUS_ADRALN;
                }
                ksi.ksi_addr = (void *)va;
                goto trapsignal;

        case EXPEVT_RES_INST | EXP_USER: /* FALLTHROUGH */
        case EXPEVT_SLOT_INST | EXP_USER:
                KSI_INIT_TRAP(&ksi);
                ksi.ksi_signo = SIGILL;
                ksi.ksi_code = ILL_ILLOPC; /* XXX: could be ILL_PRVOPC */
                ksi.ksi_addr = (void *)tf->tf_spc;
                goto trapsignal;

        default:
                goto do_panic;
        }

        if (usermode)
                userret(l);
        return;

 trapsignal:
        KASSERT(usermode);
        ksi.ksi_trap = tf->tf_expevt;
        trapsignal(l, &ksi);
        userret(l);
        return;

 do_panic:
#ifdef DDB
        switch (expevt & ~EXP_USER) {
        case EXPEVT_TRAPA:
                code = trapcode;
                break;
        default:
                code = 0;
                break;
        }
        if (kdb_trap(expevt, code, tf))
                return;
#endif
#ifdef KGDB
        if (kgdb_trap(EXPEVT_BREAK, tf))
                return;
#endif
        if (expevt >> 5 < exp_types)
                printf("fatal %s", exp_type[expevt >> 5]);
        else
                printf("EXPEVT 0x%03x", expevt);
        printf(" in %s mode\n", usermode ? "user" : "kernel");
        printf(" spc %x ssr %x \n", tf->tf_spc, tf->tf_ssr);

        panic("general_exception");
        /* NOTREACHED */
}


/*
 * void tlb_exception(struct lwp *l, struct trapframe *tf, uint32_t va):
 *      l  ... curlwp when exception occur.
 *      tf ... full user context.
 *      va ... fault address.
 */
void
tlb_exception(struct lwp *l, struct trapframe *tf, uint32_t va)
{
        struct vm_map *map;
        struct pcb *pcb;
        pmap_t pmap;
        void *onfault;
        ksiginfo_t ksi;
        bool usermode;
        int err, track, ftype;
        const char *panic_msg;

        pcb = lwp_getpcb(l);
        onfault = pcb->pcb_onfault;

#define TLB_ASSERT(assert, msg)                         \
                do {                                    \
                        if (!(assert)) {                \
                                panic_msg =  msg;       \
                                goto tlb_panic;         \
                        }                               \
                } while(/*CONSTCOND*/0)

        usermode = !KERNELMODE(tf->tf_ssr);
        if (usermode) {
                KDASSERT(l->l_md.md_regs == tf);
        } else {
#if 0 /* FIXME: probably wrong for yamt-idlelwp */
                KDASSERT(l == NULL ||           /* idle */
                    l == &lwp0 ||               /* kthread */
                    l->l_md.md_regs != tf);     /* other */
#endif
        }

        switch (tf->tf_expevt) {
        case EXPEVT_TLB_MISS_LD:
                track = PVH_REFERENCED;
                ftype = VM_PROT_READ;
                break;
        case EXPEVT_TLB_MISS_ST:
                track = PVH_REFERENCED;
                ftype = VM_PROT_WRITE;
                break;
        case EXPEVT_TLB_MOD:
                track = PVH_REFERENCED | PVH_MODIFIED;
                ftype = VM_PROT_WRITE;
                break;
        case EXPEVT_TLB_PROT_LD:
                TLB_ASSERT((int)va > 0,
                    "kernel virtual protection fault (load)");
                if (usermode) {
                        KSI_INIT_TRAP(&ksi);
                        ksi.ksi_signo = SIGSEGV;
                        ksi.ksi_code = SEGV_ACCERR;
                        ksi.ksi_addr = (void *)va;
                        splx(tf->tf_ssr & PSL_IMASK);
                        goto user_fault;
                } else {
                        TLB_ASSERT(l && onfault != NULL,
                            "no copyin/out fault handler (load protection)");
                        tf->tf_spc = (int)onfault;
                        tf->tf_r0 = EFAULT;
                }
                return;

        case EXPEVT_TLB_PROT_ST:
                track = 0;      /* call uvm_fault first. (COW) */
                ftype = VM_PROT_WRITE;
                break;

        default:
                TLB_ASSERT(0, "impossible expevt");
        }

        /* Select address space */
        if (usermode) {
                TLB_ASSERT(l != NULL, "no curlwp");
                map = &l->l_proc->p_vmspace->vm_map;
                pmap = map->pmap;
        } else {
                if ((int)va < 0) {
                        map = kernel_map;
                        pmap = pmap_kernel();
                } else {
                        TLB_ASSERT(l != NULL && onfault != NULL,
                            "invalid user-space access from kernel mode");
                        if (va == 0) {
                                tf->tf_spc = (int)onfault;
                                tf->tf_r0 = EFAULT;
                                return;
                        }
                        map = &l->l_proc->p_vmspace->vm_map;
                        pmap = map->pmap;
                }
        }

        /* Lookup page table. if entry found, load it. */
        if (track && __pmap_pte_load(pmap, va, track)) {
                return;
        }

        /* Page not found. call fault handler */
        splx(tf->tf_ssr & PSL_IMASK);
        pcb->pcb_onfault = NULL;
        err = uvm_fault(map, va, ftype);
        pcb->pcb_onfault = onfault;

        /* User stack extension */
        if (map != kernel_map &&
            (va >= (vaddr_t)l->l_proc->p_vmspace->vm_maxsaddr) &&
            (va <  (vaddr_t)l->l_proc->p_vmspace->vm_minsaddr)) {
                if (err == 0) {
                        struct vmspace *vm = l->l_proc->p_vmspace;
                        uint32_t nss;
                        nss = btoc((vaddr_t)vm->vm_minsaddr - va);
                        if (nss > vm->vm_ssize)
                                vm->vm_ssize = nss;
                } else if (err == EACCES) {
                        err = EFAULT;
                }
        }

        /* Page in. load PTE to TLB. */
        if (err == 0) {
                bool loaded;
                if (usermode)
                        userret(l);
                loaded = __pmap_pte_load(pmap, va, track);
#if 0
                /*
                 * XXXAD I don't think you should do this - consider
                 * a multithreaded program where another thread got
                 * switched to during UVM fault and it unmapped the
                 * page. I think you should just let the fault happen
                 * again.
                 */
                TLB_ASSERT(loaded, "page table entry not found");
#else
                __USE(loaded);
#endif
                return;
        }

        /* Page not found. */
        if (usermode) {
                KSI_INIT_TRAP(&ksi);
                ksi.ksi_addr = (void *)va;

                switch (err) {
                case ENOMEM:
                        ksi.ksi_signo = SIGKILL;
                        break;
                case EINVAL:
                        ksi.ksi_signo = SIGBUS;
                        ksi.ksi_code = BUS_ADRERR;
                        break;
                case EACCES:
                        ksi.ksi_signo = SIGSEGV;
                        ksi.ksi_code = SEGV_ACCERR;
                        break;
                default:
                        ksi.ksi_signo = SIGSEGV;
                        ksi.ksi_code = SEGV_MAPERR;
                        break;
                }
                goto user_fault;
        } else {
                TLB_ASSERT(onfault,
                    "no copyin/out fault handler (page not found)");
                tf->tf_spc = (int)onfault;
                tf->tf_r0 = err;
        }
        return;

 user_fault:
        ksi.ksi_trap = tf->tf_expevt;
        trapsignal(l, &ksi);
        userret(l);
        return;

 tlb_panic:
        panic("tlb_exception: %s\n"
              "expevt=%x va=%08x ssr=%08x spc=%08x lwp=%p onfault=%p",
              panic_msg, tf->tf_expevt, va, tf->tf_ssr, tf->tf_spc,
              l, pcb->pcb_onfault);
#undef  TLB_ASSERT
}


/*
 * void ast(struct lwp *l, struct trapframe *tf):
 *      l  ... curlwp when exception occur.
 *      tf ... full user context.
 *      This is called when exception return. if return from kernel to user,
 *      handle asynchronous software traps and context switch if needed.
 *      Interrupts are blocked on entry.
 */
void
ast(struct lwp *l, struct trapframe *tf)
{
        int s;

        if (__predict_true(l->l_md.md_astpending == 0)) {
                return;
        }
        if (__predict_false(KERNELMODE(tf->tf_ssr))) {
                /* should not occur but leave it here to be safe */
                return;
        }

        KDASSERT(l != NULL);
        KDASSERT(l->l_md.md_regs == tf);

        s = tf->tf_ssr & PSL_IMASK;
        do {
                splx(s);
                /* userret() clears l_md.md_astpending */
                userret(l);
                s = splhigh();
        } while (__predict_false(l->l_md.md_astpending));
}