root/usr/src/uts/sparc/v9/os/xregs.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 1994-1998,2003 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#pragma ident   "%Z%%M% %I%     %E% SMI"

#include <sys/types.h>
#include <sys/t_lock.h>
#include <sys/klwp.h>
#include <sys/ucontext.h>
#include <sys/procfs.h>
#include <sys/privregs.h>
#include <sys/cpuvar.h>
#include <sys/cmn_err.h>
#include <sys/systm.h>
#include <sys/archsystm.h>
#include <sys/machsystm.h>
#include <sys/fpu/fpusystm.h>

/*
 * Association of extra register state with a struct ucontext is
 * done by placing an xrs_t within the uc_mcontext filler area.
 *
 * The following routines provide an interface for this association.
 */

/*
 * clear the struct ucontext extra register state pointer
 */
/* ARGSUSED */
void
xregs_clrptr(klwp_id_t lwp, ucontext_t *uc)
{
        uc->uc_mcontext.xrs.xrs_id = 0;
        uc->uc_mcontext.xrs.xrs_ptr = NULL;
}

/*
 * indicate whether or not an extra register state
 * pointer is associated with a struct ucontext
 */
/* ARGSUSED */
int
xregs_hasptr(klwp_id_t lwp, ucontext_t *uc)
{
        return (uc->uc_mcontext.xrs.xrs_id == XRS_ID);
}

/*
 * get the struct ucontext extra register state pointer field
 */
/* ARGSUSED */
caddr_t
xregs_getptr(klwp_id_t lwp, ucontext_t *uc)
{
        if (uc->uc_mcontext.xrs.xrs_id == XRS_ID)
                return (uc->uc_mcontext.xrs.xrs_ptr);
        return (NULL);
}

/*
 * set the struct ucontext extra register state pointer field
 */
/* ARGSUSED */
void
xregs_setptr(klwp_id_t lwp, ucontext_t *uc, caddr_t xrp)
{
        uc->uc_mcontext.xrs.xrs_id = XRS_ID;
        uc->uc_mcontext.xrs.xrs_ptr = xrp;
}

#ifdef _SYSCALL32_IMPL

/* ARGSUSED */
void
xregs_clrptr32(klwp_id_t lwp, ucontext32_t *uc)
{
        uc->uc_mcontext.xrs.xrs_id = 0;
        uc->uc_mcontext.xrs.xrs_ptr = 0;
}

/* ARGSUSED */
int
xregs_hasptr32(klwp_id_t lwp, ucontext32_t *uc)
{
        return (uc->uc_mcontext.xrs.xrs_id == XRS_ID);
}

/* ARGSUSED */
caddr32_t
xregs_getptr32(klwp_id_t lwp, ucontext32_t *uc)
{
        if (uc->uc_mcontext.xrs.xrs_id == XRS_ID)
                return (uc->uc_mcontext.xrs.xrs_ptr);
        return (0);
}

/* ARGSUSED */
void
xregs_setptr32(klwp_id_t lwp, ucontext32_t *uc, caddr32_t xrp)
{
        uc->uc_mcontext.xrs.xrs_id = XRS_ID;
        uc->uc_mcontext.xrs.xrs_ptr = xrp;
}

#endif /* _SYSCALL32_IMPL */

/*
 * Extra register state manipulation routines.
 * NOTE:  'lwp' might not correspond to 'curthread' in any of the
 * functions below since they are called from code in /proc to get
 * or set the extra registers of another lwp.
 */

int xregs_exists = 1;

#define GET_UPPER_32(all)               (uint32_t)((uint64_t)(all) >> 32)
#define SET_ALL_64(upper, lower)        \
                (((uint64_t)(upper) << 32) | (uint32_t)(lower))


/*
 * fill in the extra register state area specified with the
 * specified lwp's non-floating-point extra register state
 * information
 */
void
xregs_getgregs(klwp_id_t lwp, caddr_t xrp)
{
        prxregset_t *xregs = (prxregset_t *)xrp;
        struct regs *rp = lwptoregs(lwp);

        if (xregs == NULL)
                return;

        xregs->pr_type = XR_TYPE_V8P;

        xregs->pr_un.pr_v8p.pr_xg[XR_G0] = 0;
        xregs->pr_un.pr_v8p.pr_xg[XR_G1] = GET_UPPER_32(rp->r_g1);
        xregs->pr_un.pr_v8p.pr_xg[XR_G2] = GET_UPPER_32(rp->r_g2);
        xregs->pr_un.pr_v8p.pr_xg[XR_G3] = GET_UPPER_32(rp->r_g3);
        xregs->pr_un.pr_v8p.pr_xg[XR_G4] = GET_UPPER_32(rp->r_g4);
        xregs->pr_un.pr_v8p.pr_xg[XR_G5] = GET_UPPER_32(rp->r_g5);
        xregs->pr_un.pr_v8p.pr_xg[XR_G6] = GET_UPPER_32(rp->r_g6);
        xregs->pr_un.pr_v8p.pr_xg[XR_G7] = GET_UPPER_32(rp->r_g7);

        xregs->pr_un.pr_v8p.pr_xo[XR_O0] = GET_UPPER_32(rp->r_o0);
        xregs->pr_un.pr_v8p.pr_xo[XR_O1] = GET_UPPER_32(rp->r_o1);
        xregs->pr_un.pr_v8p.pr_xo[XR_O2] = GET_UPPER_32(rp->r_o2);
        xregs->pr_un.pr_v8p.pr_xo[XR_O3] = GET_UPPER_32(rp->r_o3);
        xregs->pr_un.pr_v8p.pr_xo[XR_O4] = GET_UPPER_32(rp->r_o4);
        xregs->pr_un.pr_v8p.pr_xo[XR_O5] = GET_UPPER_32(rp->r_o5);
        xregs->pr_un.pr_v8p.pr_xo[XR_O6] = GET_UPPER_32(rp->r_o6);
        xregs->pr_un.pr_v8p.pr_xo[XR_O7] = GET_UPPER_32(rp->r_o7);

        xregs->pr_un.pr_v8p.pr_tstate = rp->r_tstate;

        xregs_getgfiller(lwp, xrp);
}

/*
 * fill in the extra register state area specified with the
 * specified lwp's floating-point extra register state information
 */
void
xregs_getfpregs(klwp_id_t lwp, caddr_t xrp)
{
        prxregset_t *xregs = (prxregset_t *)xrp;
        kfpu_t *fp = lwptofpu(lwp);

        if (xregs == NULL)
                return;

        kpreempt_disable();

        xregs->pr_type = XR_TYPE_V8P;

        if (ttolwp(curthread) == lwp)
                fp->fpu_fprs = _fp_read_fprs();
        if ((fp->fpu_en) || (fp->fpu_fprs & FPRS_FEF)) {
                /*
                 * If we have an fpu and the current thread owns the fp
                 * context, flush fp registers into the pcb.
                 */
                if (fpu_exists && (ttolwp(curthread) == lwp)) {
                        if ((fp->fpu_fprs & FPRS_FEF) != FPRS_FEF) {
                                uint32_t fprs = (FPRS_FEF|FPRS_DU|FPRS_DL);

                                _fp_write_fprs(fprs);
                                fp->fpu_fprs = fprs;
#ifdef DEBUG
                                if (fpdispr) {
                                        cmn_err(CE_NOTE, "xregs_getfpregs "
                                            "with fp disabled!");
                                }
#endif /* DEBUG */
                        }
                        fp_v8p_fksave(fp);
                }
                (void) kcopy(&fp->fpu_fr.fpu_dregs[16],
                    &xregs->pr_un.pr_v8p.pr_xfr,
                    sizeof (xregs->pr_un.pr_v8p.pr_xfr));
                xregs->pr_un.pr_v8p.pr_xfsr = GET_UPPER_32(fp->fpu_fsr);
                xregs->pr_un.pr_v8p.pr_fprs = fp->fpu_fprs;

                xregs_getfpfiller(lwp, xrp);
        } else {
                int i;
                for (i = 0; i < 32; i++)                        /* Nan */
                        xregs->pr_un.pr_v8p.pr_xfr.pr_regs[i] = (uint32_t)-1;
        }

        kpreempt_enable();
}

/*
 * fill in the extra register state area specified with
 * the specified lwp's extra register state information
 */
void
xregs_get(klwp_id_t lwp, caddr_t xrp)
{
        if (xrp != NULL) {
                bzero(xrp, sizeof (prxregset_t));
                xregs_getgregs(lwp, xrp);
                xregs_getfpregs(lwp, xrp);
        }
}

/*
 * set the specified lwp's non-floating-point extra
 * register state based on the specified input
 */
void
xregs_setgregs(klwp_id_t lwp, caddr_t xrp)
{
        prxregset_t *xregs = (prxregset_t *)xrp;
        struct regs *rp = lwptoregs(lwp);
        int current = (lwp == curthread->t_lwp);

        if (xregs == NULL)
                return;

#ifdef DEBUG
        if (xregs->pr_type != XR_TYPE_V8P) {
                cmn_err(CE_WARN,
                    "xregs_setgregs: pr_type is %d and should be %d",
                    xregs->pr_type, XR_TYPE_V8P);
        }
#endif /* DEBUG */

        if (current) {
                /*
                 * copy the args from the regs first
                 */
                (void) save_syscall_args();
        }

        rp->r_g1 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xg[XR_G1], rp->r_g1);
        rp->r_g2 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xg[XR_G2], rp->r_g2);
        rp->r_g3 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xg[XR_G3], rp->r_g3);
        rp->r_g4 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xg[XR_G4], rp->r_g4);
        rp->r_g5 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xg[XR_G5], rp->r_g5);
        rp->r_g6 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xg[XR_G6], rp->r_g6);
        rp->r_g7 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xg[XR_G7], rp->r_g7);

        rp->r_o0 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xo[XR_O0], rp->r_o0);
        rp->r_o1 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xo[XR_O1], rp->r_o1);
        rp->r_o2 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xo[XR_O2], rp->r_o2);
        rp->r_o3 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xo[XR_O3], rp->r_o3);
        rp->r_o4 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xo[XR_O4], rp->r_o4);
        rp->r_o5 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xo[XR_O5], rp->r_o5);
        rp->r_o6 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xo[XR_O6], rp->r_o6);
        rp->r_o7 = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xo[XR_O7], rp->r_o7);

        rp->r_tstate &= ~((uint64_t)CCR_XCC << TSTATE_CCR_SHIFT);
        rp->r_tstate |= xregs->pr_un.pr_v8p.pr_tstate &
            ((uint64_t)CCR_XCC << TSTATE_CCR_SHIFT);
        rp->r_tstate &= ~((uint64_t)TSTATE_ASI_MASK << TSTATE_ASI_SHIFT);
        rp->r_tstate |= xregs->pr_un.pr_v8p.pr_tstate &
            ((uint64_t)TSTATE_ASI_MASK << TSTATE_ASI_SHIFT);

        xregs_setgfiller(lwp, xrp);

        if (current) {
                /*
                 * This was called from a system call, but we
                 * do not want to return via the shared window;
                 * restoring the CPU context changes everything.
                 */
                lwp->lwp_eosys = JUSTRETURN;
                curthread->t_post_sys = 1;
        }
}

/*
 * set the specified lwp's floating-point extra
 * register state based on the specified input
 */
void
xregs_setfpregs(klwp_id_t lwp, caddr_t xrp)
{
        prxregset_t *xregs = (prxregset_t *)xrp;
        kfpu_t *fp = lwptofpu(lwp);

        if (xregs == NULL)
                return;

#ifdef DEBUG
        if (xregs->pr_type != XR_TYPE_V8P) {
                cmn_err(CE_WARN,
                    "xregs_setfpregs: pr_type is %d and should be %d",
                    xregs->pr_type, XR_TYPE_V8P);
        }
#endif /* DEBUG */
        if ((fp->fpu_en) || (xregs->pr_un.pr_v8p.pr_fprs & FPRS_FEF)) {
                kpreempt_disable();
                (void) kcopy(&xregs->pr_un.pr_v8p.pr_xfr,
                    &fp->fpu_fr.fpu_dregs[16],
                    sizeof (xregs->pr_un.pr_v8p.pr_xfr));
                fp->fpu_fprs = xregs->pr_un.pr_v8p.pr_fprs;
                fp->fpu_fsr = SET_ALL_64(xregs->pr_un.pr_v8p.pr_xfsr,
                    fp->fpu_fsr);

                xregs_setfpfiller(lwp, xrp);

                /*
                 * If not the current lwp then resume() will handle it
                 */
                if (lwp != ttolwp(curthread)) {
                        /* force resume to reload fp regs */
                        kpreempt_enable();
                        return;
                }

                if (fpu_exists) {
                        fp->fpu_fprs = _fp_read_fprs();
                        if ((fp->fpu_fprs & FPRS_FEF) != FPRS_FEF) {
                                uint32_t fprs = (FPRS_FEF|FPRS_DU|FPRS_DL);

                                _fp_write_fprs(fprs);
                                fp->fpu_fprs = (V9_FPU_FPRS_TYPE)fprs;
#ifdef DEBUG
                                if (fpdispr) {
                                        cmn_err(CE_NOTE, "xregs_setfpregs "
                                            "with fp disabled!");
                                }
#endif /* DEBUG */
                        }
                        fp_v8p_load(fp);
                }

                kpreempt_enable();
        }
}

/*
 * set the specified lwp's extra register
 * state based on the specified input
 */
void
xregs_set(klwp_id_t lwp, caddr_t xrp)
{
        if (xrp != NULL) {
                xregs_setgregs(lwp, xrp);
                xregs_setfpregs(lwp, xrp);
        }
}

/*
 * return the size of the extra register state
 */
int
xregs_getsize(proc_t *p)
{
        if (!xregs_exists || p->p_model == DATAMODEL_LP64)
                return (0);
        return (sizeof (prxregset_t));
}