root/sys/arch/powerpc/powerpc/fpu.c
/*      $NetBSD: fpu.c,v 1.42 2020/07/15 09:19:49 rin Exp $     */

/*
 * Copyright (C) 1996 Wolfgang Solfrank.
 * Copyright (C) 1996 TooLs GmbH.
 * All rights reserved.
 *
 * 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 TooLs GmbH.
 * 4. The name of TooLs GmbH may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY TOOLS GMBH ``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 TOOLS GMBH 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: fpu.c,v 1.42 2020/07/15 09:19:49 rin Exp $");

#include <sys/param.h>
#include <sys/proc.h>
#include <sys/systm.h>
#include <sys/atomic.h>
#include <sys/siginfo.h>
#include <sys/pcu.h>

#include <machine/pcb.h>
#include <machine/fpu.h>
#include <machine/psl.h>

static void fpu_state_load(lwp_t *, u_int);
static void fpu_state_save(lwp_t *);
static void fpu_state_release(lwp_t *);

const pcu_ops_t fpu_ops = {
        .pcu_id = PCU_FPU,
        .pcu_state_load = fpu_state_load,
        .pcu_state_save = fpu_state_save,
        .pcu_state_release = fpu_state_release,
};

bool
fpu_used_p(lwp_t *l)
{
        return pcu_valid_p(&fpu_ops, l);
}

void
fpu_mark_used(lwp_t *l)
{
        pcu_discard(&fpu_ops, l, true);
}

void
fpu_state_load(lwp_t *l, u_int flags)
{
#ifdef PPC_HAVE_FPU
        struct pcb * const pcb = lwp_getpcb(l);

        if ((flags & PCU_VALID) == 0) {
                memset(&pcb->pcb_fpu, 0, sizeof(pcb->pcb_fpu));
        }

        if ((flags & PCU_REENABLE) == 0) {
                const register_t msr = mfmsr();
                mtmsr((msr & ~PSL_EE) | PSL_FP);
                __asm volatile ("isync");

                fpu_load_from_fpreg(&pcb->pcb_fpu);
                __asm volatile ("sync");

                mtmsr(msr);
                __asm volatile ("isync");
        }

        curcpu()->ci_ev_fpusw.ev_count++;
        l->l_md.md_utf->tf_srr1 |= PSL_FP|(pcb->pcb_flags & (PCB_FE0|PCB_FE1));
#endif
}

/*
 * Save the contents of the current CPU's FPU to its PCB.
 */
void
fpu_state_save(lwp_t *l)
{
#ifdef PPC_HAVE_FPU
        struct pcb * const pcb = lwp_getpcb(l);

        const register_t msr = mfmsr();
        mtmsr((msr & ~PSL_EE) | PSL_FP);
        __asm volatile ("isync");

        fpu_unload_to_fpreg(&pcb->pcb_fpu);
        __asm volatile ("sync");

        mtmsr(msr);
        __asm volatile ("isync");
#endif
}

void
fpu_state_release(lwp_t *l)
{
#ifdef PPC_HAVE_FPU
        l->l_md.md_utf->tf_srr1 &= ~PSL_FP;
#endif
}

#define STICKYBITS      (FPSCR_VX|FPSCR_OX|FPSCR_UX|FPSCR_ZX|FPSCR_XX)
#define STICKYSHIFT     25
#define MASKBITS        (FPSCR_VE|FPSCR_OE|FPSCR_UE|FPSCR_ZE|FPSCR_XE)
#define MASKSHIFT       3

int
fpu_get_fault_code(void)
{
        lwp_t * const l = curlwp;
        struct pcb * const pcb = lwp_getpcb(l);
        uint64_t fpscr64;
        uint32_t fpscr, ofpscr;
        int code;

#ifdef PPC_HAVE_FPU
        kpreempt_disable();

        struct cpu_info * const ci = curcpu();

        /*
         * If we got preempted, we may be running on a different CPU.  So we
         * need to check for that.
         */
        KASSERT(fpu_used_p(l));
        if (__predict_true(l->l_pcu_cpu[PCU_FPU] == ci)) {
                uint64_t tmp;
                const register_t msr = mfmsr();
                mtmsr((msr & ~PSL_EE) | PSL_FP);
                __asm volatile ("isync");
                __asm volatile (
                        "stfd   0,0(%[tmp])\n"          /* save f0 */
                        "mffs   0\n"                    /* get FPSCR */
                        "stfd   0,0(%[fpscr64])\n"      /* store a temp copy */
                        "mtfsb0 0\n"                    /* clear FPSCR_FX */
                        "mtfsb0 24\n"                   /* clear FPSCR_VE */
                        "mtfsb0 25\n"                   /* clear FPSCR_OE */
                        "mtfsb0 26\n"                   /* clear FPSCR_UE */
                        "mtfsb0 27\n"                   /* clear FPSCR_ZE */
                        "mtfsb0 28\n"                   /* clear FPSCR_XE */
                        "mffs   0\n"                    /* get FPSCR */
                        "stfd   0,0(%[fpscr])\n"        /* store it */
                        "lfd    0,0(%[tmp])\n"          /* restore f0 */
                    ::  [tmp] "b"(&tmp),
                        [fpscr] "b"(&pcb->pcb_fpu.fpscr),
                        [fpscr64] "b"(&fpscr64));
                mtmsr(msr);
                __asm volatile ("isync");
        } else {
                /*
                 * We got preempted to a different CPU so we need to save
                 * our FPU state.
                 */
                fpu_save(l);
                fpscr64 = *(uint64_t *)&pcb->pcb_fpu.fpscr;
                ((uint32_t *)&pcb->pcb_fpu.fpscr)[_QUAD_LOWWORD] &= ~MASKBITS;
        }

        kpreempt_enable();
#else /* !PPC_HAVE_FPU */
        fpscr64 = *(uint64_t *)&pcb->pcb_fpu.fpscr;
        ((uint32_t *)&pcb->pcb_fpu.fpscr)[_QUAD_LOWWORD] &= ~MASKBITS;
#endif

        /*
         * Now determine the fault type.  First we test to see if any of sticky
         * bits correspond to the enabled exceptions.  If so, we only test
         * those bits.  If not, we look at all the bits.  (In reality, we only
         * could get an exception if FPSCR_FEX changed state.  So we should
         * have at least one bit that corresponds).
         */
        ofpscr = (uint32_t)fpscr64;
        ofpscr &= ofpscr << (STICKYSHIFT - MASKSHIFT);
        fpscr = ((uint32_t *)&pcb->pcb_fpu.fpscr)[_QUAD_LOWWORD];
        if (fpscr & ofpscr & STICKYBITS)
                fpscr &= ofpscr;

        /*
         * Let's determine what the appropriate code is.
         */
        if (fpscr & FPSCR_VX)           code = FPE_FLTINV;
        else if (fpscr & FPSCR_OX)      code = FPE_FLTOVF;
        else if (fpscr & FPSCR_UX)      code = FPE_FLTUND;
        else if (fpscr & FPSCR_ZX)      code = FPE_FLTDIV;
        else if (fpscr & FPSCR_XX)      code = FPE_FLTRES;
        else                            code = 0;
        return code;
}

bool
fpu_save_to_mcontext(lwp_t *l, mcontext_t *mcp, unsigned int *flagp)
{
        KASSERT(l == curlwp);

        if (!pcu_valid_p(&fpu_ops, l))
                return false;

        struct pcb * const pcb = lwp_getpcb(l);

#ifdef PPC_HAVE_FPU
        /* If we're the FPU owner, dump its context to the PCB first. */
        pcu_save(&fpu_ops, l);
#endif
        (void)memcpy(mcp->__fpregs.__fpu_regs, pcb->pcb_fpu.fpreg,
            sizeof (mcp->__fpregs.__fpu_regs));
        mcp->__fpregs.__fpu_fpscr =
            ((int *)&pcb->pcb_fpu.fpscr)[_QUAD_LOWWORD];
        mcp->__fpregs.__fpu_valid = 1;
        *flagp |= _UC_FPU;
        return true;
}

void
fpu_restore_from_mcontext(lwp_t *l, const mcontext_t *mcp)
{
        if (!mcp->__fpregs.__fpu_valid)
                return;

        struct pcb * const pcb = lwp_getpcb(l);

#ifdef PPC_HAVE_FPU
        /* we don't need to save the state, just drop it */
        pcu_discard(&fpu_ops, l, true);
#endif
        (void)memcpy(&pcb->pcb_fpu.fpreg, &mcp->__fpregs.__fpu_regs,
            sizeof (pcb->pcb_fpu.fpreg));
        ((int *)&pcb->pcb_fpu.fpscr)[_QUAD_LOWWORD] = mcp->__fpregs.__fpu_fpscr;
}