#include <sys/cdefs.h>
__KERNEL_RCSID(1, "$NetBSD: fpu.c,v 1.13 2022/08/20 11:34:08 riastradh Exp $");
#include <sys/param.h>
#include <sys/types.h>
#include <sys/cpu.h>
#include <sys/kernel.h>
#include <sys/kthread.h>
#include <sys/lwp.h>
#include <sys/evcnt.h>
#include <arm/cpufunc.h>
#include <arm/fpu.h>
#include <arm/cpufunc.h>
#include <aarch64/locore.h>
#include <aarch64/reg.h>
#include <aarch64/pcb.h>
#include <aarch64/armreg.h>
#include <aarch64/machdep.h>
static void fpu_state_load(lwp_t *, unsigned int);
static void fpu_state_save(lwp_t *);
static void fpu_state_release(lwp_t *);
const pcu_ops_t pcu_fpu_ops = {
.pcu_id = PCU_FPU,
.pcu_state_load = fpu_state_load,
.pcu_state_save = fpu_state_save,
.pcu_state_release = fpu_state_release
};
void
fpu_attach(struct cpu_info *ci)
{
evcnt_attach_dynamic(&ci->ci_vfp_use, EVCNT_TYPE_MISC, NULL,
ci->ci_cpuname, "vfp use");
evcnt_attach_dynamic(&ci->ci_vfp_reuse, EVCNT_TYPE_MISC, NULL,
ci->ci_cpuname, "vfp reuse");
evcnt_attach_dynamic(&ci->ci_vfp_save, EVCNT_TYPE_MISC, NULL,
ci->ci_cpuname, "vfp save");
evcnt_attach_dynamic(&ci->ci_vfp_release, EVCNT_TYPE_MISC, NULL,
ci->ci_cpuname, "vfp release");
}
static void
fpu_state_load(lwp_t *l, unsigned int flags)
{
struct pcb * const pcb = lwp_getpcb(l);
KASSERT(l == curlwp);
if (__predict_false((flags & PCU_VALID) == 0)) {
uint64_t mvfr1 = reg_mvfr1_el1_read();
bool fp16 = false;
uint32_t fpcr = 0;
switch (__SHIFTOUT(mvfr1, MVFR1_FPHP)) {
case MVFR1_FPHP_HALF_ARITH:
fp16 = true;
break;
}
fpcr |= __SHIFTIN(FPCR_RN, FPCR_RMODE);
switch (__SHIFTOUT(mvfr1, MVFR1_FPDNAN)) {
case MVFR1_FPDNAN_NAN:
break;
default:
fpcr |= FPCR_DN;
}
switch (__SHIFTOUT(mvfr1, MVFR1_FPFTZ)) {
case MVFR1_FPFTZ_DENORMAL:
break;
default:
fpcr |= FPCR_FZ;
if (fp16)
fpcr |= FPCR_FZ16;
}
memset(&pcb->pcb_fpregs, 0, sizeof(pcb->pcb_fpregs));
pcb->pcb_fpregs.fpcr = fpcr;
curcpu()->ci_vfp_use.ev_count++;
} else {
curcpu()->ci_vfp_reuse.ev_count++;
}
l->l_md.md_cpacr = CPACR_FPEN_ALL;
reg_cpacr_el1_write(CPACR_FPEN_ALL);
isb();
if ((flags & PCU_REENABLE) == 0)
load_fpregs(&pcb->pcb_fpregs);
}
static void
fpu_state_save(lwp_t *l)
{
struct pcb * const pcb = lwp_getpcb(l);
curcpu()->ci_vfp_save.ev_count++;
reg_cpacr_el1_write(CPACR_FPEN_EL1);
isb();
save_fpregs(&pcb->pcb_fpregs);
reg_cpacr_el1_write(CPACR_FPEN_NONE);
isb();
}
static void
fpu_state_release(lwp_t *l)
{
curcpu()->ci_vfp_release.ev_count++;
l->l_md.md_cpacr = CPACR_FPEN_NONE;
reg_cpacr_el1_write(CPACR_FPEN_NONE);
isb();
}
static const struct fpreg zero_fpreg;
static inline bool
lwp_system_fpu_p(struct lwp *l)
{
return (l->l_flag & (LW_SYSTEM|LW_SYSTEM_FPU)) ==
(LW_SYSTEM|LW_SYSTEM_FPU);
}
void
fpu_kern_enter(void)
{
struct cpu_info *ci;
int s;
if (lwp_system_fpu_p(curlwp) && !cpu_intr_p()) {
KASSERT(!cpu_softintr_p());
return;
}
s = splvm();
ci = curcpu();
#if 0
KASSERTMSG(ci->ci_cpl <= IPL_VM || cold, "cpl=%d", ci->ci_cpl);
#endif
KASSERT(ci->ci_kfpu_spl == -1);
ci->ci_kfpu_spl = s;
pcu_save_all_on_cpu();
reg_cpacr_el1_write(CPACR_FPEN_ALL);
isb();
}
void
fpu_kern_leave(void)
{
struct cpu_info *ci;
int s;
if (lwp_system_fpu_p(curlwp) && !cpu_intr_p()) {
KASSERT(!cpu_softintr_p());
return;
}
ci = curcpu();
#if 0
KASSERT(ci->ci_cpl == IPL_VM || cold);
#endif
KASSERT(ci->ci_kfpu_spl != -1);
load_fpregs(&zero_fpreg);
reg_cpacr_el1_write(CPACR_FPEN_NONE);
isb();
s = ci->ci_kfpu_spl;
ci->ci_kfpu_spl = -1;
splx(s);
}
void
kthread_fpu_enter_md(void)
{
fpu_load(curlwp);
}
void
kthread_fpu_exit_md(void)
{
load_fpregs(&zero_fpreg);
fpu_discard(curlwp, 0);
}