#include <sys/cdefs.h>
#ifdef VFP
#include <sys/param.h>
#include <sys/systm.h>
#include <sys/elf.h>
#include <sys/eventhandler.h>
#include <sys/limits.h>
#include <sys/kernel.h>
#include <sys/malloc.h>
#include <sys/pcpu.h>
#include <sys/proc.h>
#include <sys/reg.h>
#include <sys/smp.h>
#include <vm/uma.h>
#include <machine/armreg.h>
#include <machine/md_var.h>
#include <machine/pcb.h>
#include <machine/vfp.h>
CTASSERT(sizeof(((struct pcb *)0)->pcb_fpustate.vfp_regs) == 16 * 32);
static MALLOC_DEFINE(M_FPUKERN_CTX, "fpukern_ctx",
"Kernel contexts for VFP state");
struct fpu_kern_ctx {
struct vfpstate *prev;
#define FPU_KERN_CTX_DUMMY 0x01
#define FPU_KERN_CTX_INUSE 0x02
uint32_t flags;
struct vfpstate state;
};
static uma_zone_t fpu_save_area_zone;
static struct vfpstate *fpu_initialstate;
static u_int sve_max_vector_len;
static size_t
_sve_buf_size(u_int sve_len)
{
size_t len;
len = (size_t)sve_len * 32;
len += ((size_t)sve_len * 17) / 8;
len += sizeof(uint64_t) * 2;
return (len);
}
size_t
sve_max_buf_size(void)
{
MPASS(sve_max_vector_len > 0);
return (_sve_buf_size(sve_max_vector_len));
}
size_t
sve_buf_size(struct thread *td)
{
struct pcb *pcb;
pcb = td->td_pcb;
MPASS(pcb->pcb_svesaved != NULL);
MPASS(pcb->pcb_sve_len > 0);
return (_sve_buf_size(pcb->pcb_sve_len));
}
static void *
sve_alloc(void)
{
void *buf;
buf = malloc(sve_max_buf_size(), M_FPUKERN_CTX, M_WAITOK | M_ZERO);
return (buf);
}
static void
sve_free(void *buf)
{
free(buf, M_FPUKERN_CTX);
}
void
vfp_enable(void)
{
uint32_t cpacr;
cpacr = READ_SPECIALREG(cpacr_el1);
cpacr = (cpacr & ~CPACR_FPEN_MASK) | CPACR_FPEN_TRAP_NONE;
WRITE_SPECIALREG(cpacr_el1, cpacr);
isb();
}
static void
sve_enable(void)
{
uint32_t cpacr;
cpacr = READ_SPECIALREG(cpacr_el1);
cpacr = (cpacr & ~CPACR_FPEN_MASK) | CPACR_FPEN_TRAP_NONE;
cpacr = (cpacr & ~CPACR_ZEN_MASK) | CPACR_ZEN_TRAP_NONE;
WRITE_SPECIALREG(cpacr_el1, cpacr);
isb();
}
void
vfp_disable(void)
{
uint32_t cpacr;
cpacr = READ_SPECIALREG(cpacr_el1);
cpacr = (cpacr & ~CPACR_FPEN_MASK) | CPACR_FPEN_TRAP_ALL1;
cpacr = (cpacr & ~CPACR_ZEN_MASK) | CPACR_ZEN_TRAP_ALL1;
WRITE_SPECIALREG(cpacr_el1, cpacr);
isb();
}
void
vfp_discard(struct thread *td)
{
#ifdef INVARIANTS
if (td != NULL)
CRITICAL_ASSERT(td);
#endif
if (PCPU_GET(fpcurthread) == td)
PCPU_SET(fpcurthread, NULL);
vfp_disable();
}
void
vfp_store(struct vfpstate *state)
{
__uint128_t *vfp_state;
uint64_t fpcr, fpsr;
vfp_state = state->vfp_regs;
__asm __volatile(
".arch_extension fp\n"
"mrs %0, fpcr \n"
"mrs %1, fpsr \n"
"stp q0, q1, [%2, #16 * 0]\n"
"stp q2, q3, [%2, #16 * 2]\n"
"stp q4, q5, [%2, #16 * 4]\n"
"stp q6, q7, [%2, #16 * 6]\n"
"stp q8, q9, [%2, #16 * 8]\n"
"stp q10, q11, [%2, #16 * 10]\n"
"stp q12, q13, [%2, #16 * 12]\n"
"stp q14, q15, [%2, #16 * 14]\n"
"stp q16, q17, [%2, #16 * 16]\n"
"stp q18, q19, [%2, #16 * 18]\n"
"stp q20, q21, [%2, #16 * 20]\n"
"stp q22, q23, [%2, #16 * 22]\n"
"stp q24, q25, [%2, #16 * 24]\n"
"stp q26, q27, [%2, #16 * 26]\n"
"stp q28, q29, [%2, #16 * 28]\n"
"stp q30, q31, [%2, #16 * 30]\n"
".arch_extension nofp\n"
: "=&r"(fpcr), "=&r"(fpsr) : "r"(vfp_state));
state->vfp_fpcr = fpcr;
state->vfp_fpsr = fpsr;
}
void
vfp_restore(struct vfpstate *state)
{
__uint128_t *vfp_state;
uint64_t fpcr, fpsr;
vfp_state = state->vfp_regs;
fpcr = state->vfp_fpcr;
fpsr = state->vfp_fpsr;
__asm __volatile(
".arch_extension fp\n"
"ldp q0, q1, [%2, #16 * 0]\n"
"ldp q2, q3, [%2, #16 * 2]\n"
"ldp q4, q5, [%2, #16 * 4]\n"
"ldp q6, q7, [%2, #16 * 6]\n"
"ldp q8, q9, [%2, #16 * 8]\n"
"ldp q10, q11, [%2, #16 * 10]\n"
"ldp q12, q13, [%2, #16 * 12]\n"
"ldp q14, q15, [%2, #16 * 14]\n"
"ldp q16, q17, [%2, #16 * 16]\n"
"ldp q18, q19, [%2, #16 * 18]\n"
"ldp q20, q21, [%2, #16 * 20]\n"
"ldp q22, q23, [%2, #16 * 22]\n"
"ldp q24, q25, [%2, #16 * 24]\n"
"ldp q26, q27, [%2, #16 * 26]\n"
"ldp q28, q29, [%2, #16 * 28]\n"
"ldp q30, q31, [%2, #16 * 30]\n"
"msr fpcr, %0 \n"
"msr fpsr, %1 \n"
".arch_extension nofp\n"
: : "r"(fpcr), "r"(fpsr), "r"(vfp_state));
}
static void
sve_store(void *state, u_int sve_len)
{
vm_offset_t f_start, p_start, z_start;
uint64_t fpcr, fpsr;
z_start = (vm_offset_t)state;
p_start = z_start + sve_len * 32;
f_start = p_start + (sve_len / 8) * 17;
__asm __volatile(
".arch_extension sve \n"
"str z0, [%0, #0, MUL VL] \n"
"str z1, [%0, #1, MUL VL] \n"
"str z2, [%0, #2, MUL VL] \n"
"str z3, [%0, #3, MUL VL] \n"
"str z4, [%0, #4, MUL VL] \n"
"str z5, [%0, #5, MUL VL] \n"
"str z6, [%0, #6, MUL VL] \n"
"str z7, [%0, #7, MUL VL] \n"
"str z8, [%0, #8, MUL VL] \n"
"str z9, [%0, #9, MUL VL] \n"
"str z10, [%0, #10, MUL VL] \n"
"str z11, [%0, #11, MUL VL] \n"
"str z12, [%0, #12, MUL VL] \n"
"str z13, [%0, #13, MUL VL] \n"
"str z14, [%0, #14, MUL VL] \n"
"str z15, [%0, #15, MUL VL] \n"
"str z16, [%0, #16, MUL VL] \n"
"str z17, [%0, #17, MUL VL] \n"
"str z18, [%0, #18, MUL VL] \n"
"str z19, [%0, #19, MUL VL] \n"
"str z20, [%0, #20, MUL VL] \n"
"str z21, [%0, #21, MUL VL] \n"
"str z22, [%0, #22, MUL VL] \n"
"str z23, [%0, #23, MUL VL] \n"
"str z24, [%0, #24, MUL VL] \n"
"str z25, [%0, #25, MUL VL] \n"
"str z26, [%0, #26, MUL VL] \n"
"str z27, [%0, #27, MUL VL] \n"
"str z28, [%0, #28, MUL VL] \n"
"str z29, [%0, #29, MUL VL] \n"
"str z30, [%0, #30, MUL VL] \n"
"str z31, [%0, #31, MUL VL] \n"
"str p0, [%1, #0, MUL VL] \n"
"str p1, [%1, #1, MUL VL] \n"
"str p2, [%1, #2, MUL VL] \n"
"str p3, [%1, #3, MUL VL] \n"
"str p4, [%1, #4, MUL VL] \n"
"str p5, [%1, #5, MUL VL] \n"
"str p6, [%1, #6, MUL VL] \n"
"str p7, [%1, #7, MUL VL] \n"
"str p8, [%1, #8, MUL VL] \n"
"str p9, [%1, #9, MUL VL] \n"
"str p10, [%1, #10, MUL VL] \n"
"str p11, [%1, #11, MUL VL] \n"
"str p12, [%1, #12, MUL VL] \n"
"str p13, [%1, #13, MUL VL] \n"
"str p14, [%1, #14, MUL VL] \n"
"str p15, [%1, #15, MUL VL] \n"
".arch_extension nosve \n"
: : "r"(z_start), "r"(p_start));
__asm __volatile(
".arch_extension sve \n"
"rdffr p0.b \n"
"str p0, [%0, #16, MUL VL] \n"
"ldr p0, [%0, #0, MUL VL] \n"
".arch_extension nosve \n"
: : "r"(p_start));
__asm __volatile(
".arch_extension fp \n"
"mrs %0, fpsr \n"
"mrs %1, fpcr \n"
"stp %w0, %w1, [%2] \n"
".arch_extension nofp \n"
: "=&r"(fpsr), "=&r"(fpcr) : "r"(f_start));
}
static void
sve_restore(void *state, u_int sve_len)
{
vm_offset_t f_start, p_start, z_start;
uint64_t fpcr, fpsr;
z_start = (vm_offset_t)state;
p_start = z_start + sve_len * 32;
f_start = p_start + (sve_len / 8) * 17;
__asm __volatile(
".arch_extension sve \n"
"ldr p0, [%0, #16, MUL VL] \n"
"wrffr p0.b \n"
".arch_extension nosve \n"
: : "r"(p_start));
__asm __volatile(
".arch_extension sve \n"
"ldr z0, [%0, #0, MUL VL] \n"
"ldr z1, [%0, #1, MUL VL] \n"
"ldr z2, [%0, #2, MUL VL] \n"
"ldr z3, [%0, #3, MUL VL] \n"
"ldr z4, [%0, #4, MUL VL] \n"
"ldr z5, [%0, #5, MUL VL] \n"
"ldr z6, [%0, #6, MUL VL] \n"
"ldr z7, [%0, #7, MUL VL] \n"
"ldr z8, [%0, #8, MUL VL] \n"
"ldr z9, [%0, #9, MUL VL] \n"
"ldr z10, [%0, #10, MUL VL] \n"
"ldr z11, [%0, #11, MUL VL] \n"
"ldr z12, [%0, #12, MUL VL] \n"
"ldr z13, [%0, #13, MUL VL] \n"
"ldr z14, [%0, #14, MUL VL] \n"
"ldr z15, [%0, #15, MUL VL] \n"
"ldr z16, [%0, #16, MUL VL] \n"
"ldr z17, [%0, #17, MUL VL] \n"
"ldr z18, [%0, #18, MUL VL] \n"
"ldr z19, [%0, #19, MUL VL] \n"
"ldr z20, [%0, #20, MUL VL] \n"
"ldr z21, [%0, #21, MUL VL] \n"
"ldr z22, [%0, #22, MUL VL] \n"
"ldr z23, [%0, #23, MUL VL] \n"
"ldr z24, [%0, #24, MUL VL] \n"
"ldr z25, [%0, #25, MUL VL] \n"
"ldr z26, [%0, #26, MUL VL] \n"
"ldr z27, [%0, #27, MUL VL] \n"
"ldr z28, [%0, #28, MUL VL] \n"
"ldr z29, [%0, #29, MUL VL] \n"
"ldr z30, [%0, #30, MUL VL] \n"
"ldr z31, [%0, #31, MUL VL] \n"
"ldr p0, [%1, #0, MUL VL] \n"
"ldr p1, [%1, #1, MUL VL] \n"
"ldr p2, [%1, #2, MUL VL] \n"
"ldr p3, [%1, #3, MUL VL] \n"
"ldr p4, [%1, #4, MUL VL] \n"
"ldr p5, [%1, #5, MUL VL] \n"
"ldr p6, [%1, #6, MUL VL] \n"
"ldr p7, [%1, #7, MUL VL] \n"
"ldr p8, [%1, #8, MUL VL] \n"
"ldr p9, [%1, #9, MUL VL] \n"
"ldr p10, [%1, #10, MUL VL] \n"
"ldr p11, [%1, #11, MUL VL] \n"
"ldr p12, [%1, #12, MUL VL] \n"
"ldr p13, [%1, #13, MUL VL] \n"
"ldr p14, [%1, #14, MUL VL] \n"
"ldr p15, [%1, #15, MUL VL] \n"
".arch_extension nosve \n"
: : "r"(z_start), "r"(p_start));
__asm __volatile(
".arch_extension fp \n"
"ldp %w0, %w1, [%2] \n"
"msr fpsr, %0 \n"
"msr fpcr, %1 \n"
".arch_extension nofp \n"
: "=&r"(fpsr), "=&r"(fpcr) : "r"(f_start));
}
void
vfp_to_sve_sync(struct thread *td)
{
struct pcb *pcb;
uint32_t *fpxr;
pcb = td->td_pcb;
if (pcb->pcb_svesaved == NULL)
return;
MPASS(pcb->pcb_fpusaved != NULL);
for (int i = 0; i < nitems(pcb->pcb_fpusaved->vfp_regs); i++) {
__uint128_t *sve_reg;
sve_reg = (__uint128_t *)((uintptr_t)pcb->pcb_svesaved +
i * pcb->pcb_sve_len);
*sve_reg = pcb->pcb_fpusaved->vfp_regs[i];
}
fpxr = (uint32_t *)((uintptr_t)pcb->pcb_svesaved +
(32 * pcb->pcb_sve_len) + (17 * pcb->pcb_sve_len / 8));
fpxr[0] = pcb->pcb_fpusaved->vfp_fpsr;
fpxr[1] = pcb->pcb_fpusaved->vfp_fpcr;
}
void
sve_to_vfp_sync(struct thread *td)
{
struct pcb *pcb;
uint32_t *fpxr;
pcb = td->td_pcb;
if (pcb->pcb_svesaved == NULL)
return;
MPASS(pcb->pcb_fpusaved == &pcb->pcb_fpustate);
for (int i = 0; i < nitems(pcb->pcb_fpusaved->vfp_regs); i++) {
__uint128_t *sve_reg;
sve_reg = (__uint128_t *)((uintptr_t)pcb->pcb_svesaved +
i * pcb->pcb_sve_len);
pcb->pcb_fpusaved->vfp_regs[i] = *sve_reg;
}
fpxr = (uint32_t *)((uintptr_t)pcb->pcb_svesaved +
(32 * pcb->pcb_sve_len) + (17 * pcb->pcb_sve_len / 8));
pcb->pcb_fpusaved->vfp_fpsr = fpxr[0];
pcb->pcb_fpusaved->vfp_fpcr = fpxr[1];
}
static void
vfp_save_state_common(struct thread *td, struct pcb *pcb, bool full_save)
{
uint32_t cpacr;
bool save_sve;
save_sve = false;
critical_enter();
cpacr = READ_SPECIALREG(cpacr_el1);
if ((cpacr & CPACR_FPEN_MASK) != CPACR_FPEN_TRAP_NONE)
goto done;
KASSERT(PCPU_GET(fpcurthread) == td,
("Storing an invalid VFP state"));
if ((cpacr & CPACR_ZEN_MASK) == CPACR_ZEN_TRAP_NONE) {
MPASS((pcb->pcb_fpflags & PCB_FP_SVEVALID) != 0);
if (td->td_frame == NULL ||
(ESR_ELx_EXCEPTION(td->td_frame->tf_esr) != EXCP_SVC64 &&
td->td_sa.code != (u_int)-1))
save_sve = true;
}
if (save_sve) {
KASSERT(pcb->pcb_svesaved != NULL,
("Storing to a NULL SVE state"));
sve_store(pcb->pcb_svesaved, pcb->pcb_sve_len);
if (full_save)
sve_to_vfp_sync(td);
} else {
pcb->pcb_fpflags &= ~PCB_FP_SVEVALID;
vfp_store(pcb->pcb_fpusaved);
}
dsb(ish);
vfp_disable();
done:
critical_exit();
}
void
vfp_save_state(struct thread *td, struct pcb *pcb)
{
KASSERT(td != NULL, ("NULL vfp thread"));
KASSERT(pcb != NULL, ("NULL vfp pcb"));
KASSERT(td->td_pcb == pcb, ("Invalid vfp pcb"));
vfp_save_state_common(td, pcb, true);
}
void
vfp_save_state_savectx(struct pcb *pcb)
{
MPASS(pcb->pcb_fpusaved == NULL ||
pcb->pcb_fpusaved == &pcb->pcb_fpustate);
pcb->pcb_fpusaved = &pcb->pcb_fpustate;
vfp_save_state_common(curthread, pcb, true);
}
void
vfp_save_state_switch(struct thread *td)
{
KASSERT(td != NULL, ("NULL vfp thread"));
vfp_save_state_common(td, td->td_pcb, false);
}
void
vfp_new_thread(struct thread *newtd, struct thread *oldtd, bool fork)
{
struct pcb *newpcb, *oldpcb;
newpcb = newtd->td_pcb;
oldpcb = oldtd->td_pcb;
if ((oldtd->td_pflags & TDP_KTHREAD) != 0) {
newpcb->pcb_fpflags &=
~(PCB_FP_STARTED | PCB_FP_SVEVALID | PCB_FP_KERN |
PCB_FP_NOSAVE);
} else {
MPASS((newpcb->pcb_fpflags & (PCB_FP_KERN|PCB_FP_NOSAVE)) == 0);
newpcb->pcb_fpflags &= ~PCB_FP_SVEVALID;
if (!fork) {
newpcb->pcb_fpflags &= ~PCB_FP_STARTED;
}
}
newpcb->pcb_svesaved = NULL;
if (oldpcb->pcb_svesaved == NULL)
newpcb->pcb_sve_len = sve_max_vector_len;
else
KASSERT(newpcb->pcb_sve_len == oldpcb->pcb_sve_len,
("%s: pcb sve vector length differs: %x != %x", __func__,
newpcb->pcb_sve_len, oldpcb->pcb_sve_len));
newpcb->pcb_fpusaved = &newpcb->pcb_fpustate;
newpcb->pcb_vfpcpu = UINT_MAX;
}
void
vfp_reset_state(struct thread *td, struct pcb *pcb)
{
critical_enter();
vfp_discard(td);
critical_exit();
bzero(&pcb->pcb_fpustate.vfp_regs, sizeof(pcb->pcb_fpustate.vfp_regs));
KASSERT(pcb->pcb_fpusaved == &pcb->pcb_fpustate,
("pcb_fpusaved should point to pcb_fpustate."));
pcb->pcb_fpustate.vfp_fpcr = VFPCR_INIT;
pcb->pcb_fpustate.vfp_fpsr = 0;
pcb->pcb_svesaved = NULL;
pcb->pcb_vfpcpu = UINT_MAX;
pcb->pcb_fpflags = 0;
}
static void
vfp_restore_state_common(struct thread *td, int flags)
{
struct pcb *curpcb;
u_int cpu;
bool restore_sve;
KASSERT(td == curthread, ("%s: Called with non-current thread",
__func__));
critical_enter();
cpu = PCPU_GET(cpuid);
curpcb = td->td_pcb;
restore_sve = false;
if ((curpcb->pcb_fpflags & PCB_FP_SVEVALID) != 0 &&
curpcb->pcb_fpusaved == &curpcb->pcb_fpustate) {
MPASS(curpcb->pcb_svesaved != NULL);
MPASS((flags & PCB_FP_KERN) == 0);
restore_sve = true;
}
if (restore_sve) {
MPASS((curpcb->pcb_fpflags & PCB_FP_SVEVALID) != 0);
sve_enable();
} else {
curpcb->pcb_fpflags |= PCB_FP_STARTED;
vfp_enable();
}
if (PCPU_GET(fpcurthread) != curthread || cpu != curpcb->pcb_vfpcpu) {
if (restore_sve) {
MPASS(td->td_pcb->pcb_svesaved != NULL);
sve_restore(td->td_pcb->pcb_svesaved,
td->td_pcb->pcb_sve_len);
} else {
vfp_restore(td->td_pcb->pcb_fpusaved);
}
PCPU_SET(fpcurthread, td);
curpcb->pcb_vfpcpu = cpu;
}
critical_exit();
}
void
vfp_restore_state(void)
{
struct thread *td;
td = curthread;
vfp_restore_state_common(td, td->td_pcb->pcb_fpflags);
}
bool
sve_restore_state(struct thread *td)
{
struct pcb *curpcb;
void *svesaved;
uint64_t cpacr;
KASSERT(td == curthread, ("%s: Called with non-current thread",
__func__));
curpcb = td->td_pcb;
MPASS(curpcb->pcb_fpusaved == &curpcb->pcb_fpustate);
if (curpcb->pcb_sve_len == 0) {
if (sve_max_vector_len == 0)
return (false);
MPASS(curpcb->pcb_svesaved == NULL);
curpcb->pcb_sve_len = sve_max_vector_len;
}
if (curpcb->pcb_svesaved == NULL) {
MPASS((curpcb->pcb_fpflags & PCB_FP_SVEVALID) == 0);
svesaved = sve_alloc();
critical_enter();
cpacr = READ_SPECIALREG(cpacr_el1);
if ((cpacr & CPACR_FPEN_MASK) != CPACR_FPEN_TRAP_NONE) {
vfp_restore_state_common(td, curpcb->pcb_fpflags);
}
curpcb->pcb_svesaved = svesaved;
curpcb->pcb_fpflags |= PCB_FP_SVEVALID;
sve_enable();
critical_exit();
} else {
vfp_restore_state_common(td, curpcb->pcb_fpflags);
if ((curpcb->pcb_fpflags & PCB_FP_SVEVALID) == 0) {
critical_enter();
sve_enable();
curpcb->pcb_fpflags |= PCB_FP_SVEVALID;
critical_exit();
}
}
return (true);
}
void
vfp_init_secondary(void)
{
uint64_t pfr;
pfr = READ_SPECIALREG(id_aa64pfr0_el1);
if ((pfr & ID_AA64PFR0_FP_MASK) == ID_AA64PFR0_FP_NONE)
return;
vfp_disable();
}
static void
vfp_init(const void *dummy __unused)
{
uint64_t pfr;
pfr = READ_SPECIALREG(id_aa64pfr0_el1);
if ((pfr & ID_AA64PFR0_FP_MASK) == ID_AA64PFR0_FP_NONE)
return;
fpu_save_area_zone = uma_zcreate("VFP_save_area",
sizeof(struct vfpstate), NULL, NULL, NULL, NULL,
_Alignof(struct vfpstate) - 1, 0);
fpu_initialstate = uma_zalloc(fpu_save_area_zone, M_WAITOK | M_ZERO);
vfp_enable();
vfp_store(fpu_initialstate);
vfp_disable();
bzero(fpu_initialstate->vfp_regs, sizeof(fpu_initialstate->vfp_regs));
thread0.td_pcb->pcb_fpusaved->vfp_fpcr = VFPCR_INIT;
}
SYSINIT(vfp, SI_SUB_CPU, SI_ORDER_ANY, vfp_init, NULL);
static void
sve_thread_dtor(void *arg __unused, struct thread *td)
{
sve_free(td->td_pcb->pcb_svesaved);
}
static void
sve_pcpu_read(void *arg)
{
u_int *len;
uint64_t vl;
len = arg;
sve_enable();
WRITE_SPECIALREG(ZCR_EL1_REG, ZCR_LEN_MASK);
isb();
__asm __volatile(
".arch_extension sve \n"
"rdvl %0, #1 \n"
".arch_extension nosve \n"
: "=&r"(vl));
vfp_disable();
len[PCPU_GET(cpuid)] = vl;
}
static void
sve_init(const void *dummy __unused)
{
u_int *len_list;
uint64_t reg;
int i;
get_kernel_reg(ID_AA64PFR0_EL1, ®);
if (ID_AA64PFR0_SVE_VAL(reg) == ID_AA64PFR0_SVE_NONE)
return;
len_list = malloc(sizeof(*len_list) * (mp_maxid + 1), M_TEMP,
M_WAITOK | M_ZERO);
smp_rendezvous(NULL, sve_pcpu_read, NULL, len_list);
sve_max_vector_len = ZCR_LEN_BYTES(ZCR_LEN_MASK);
CPU_FOREACH(i) {
if (bootverbose)
printf("CPU%d SVE vector length: %u\n", i, len_list[i]);
sve_max_vector_len = MIN(sve_max_vector_len, len_list[i]);
}
free(len_list, M_TEMP);
if (bootverbose)
printf("SVE with %u byte vectors\n", sve_max_vector_len);
if (sve_max_vector_len > 0) {
EVENTHANDLER_REGISTER(thread_dtor, sve_thread_dtor, NULL,
EVENTHANDLER_PRI_ANY);
}
}
SYSINIT(sve, SI_SUB_SMP, SI_ORDER_ANY, sve_init, NULL);
static bool
get_arm64_sve(struct regset *rs, struct thread *td, void *buf,
size_t *sizep)
{
struct svereg_header *header;
struct pcb *pcb;
size_t buf_size;
uint16_t sve_flags;
pcb = td->td_pcb;
if (td == curthread && (pcb->pcb_fpflags & PCB_FP_STARTED) != 0)
vfp_save_state(td, pcb);
if (pcb->pcb_sve_len == 0)
return (false);
sve_flags = 0;
if ((pcb->pcb_fpflags & PCB_FP_SVEVALID) == 0) {
buf_size = sizeof(struct fpreg);
sve_flags |= SVEREG_FLAG_FP;
} else {
buf_size = sve_buf_size(td);
sve_flags |= SVEREG_FLAG_SVE;
KASSERT(pcb->pcb_svesaved != NULL, ("%s: no saved sve",
__func__));
}
if (buf != NULL) {
KASSERT(*sizep == sizeof(struct svereg_header) + buf_size,
("%s: invalid size", __func__));
header = buf;
memset(header, 0, sizeof(*header));
header->sve_size = sizeof(struct svereg_header) + buf_size;
header->sve_maxsize = sizeof(struct svereg_header) +
sve_max_buf_size();
header->sve_vec_len = pcb->pcb_sve_len;
header->sve_max_vec_len = sve_max_vector_len;
header->sve_flags = sve_flags;
if ((sve_flags & SVEREG_FLAG_REGS_MASK) == SVEREG_FLAG_FP) {
struct fpreg *fpregs;
fpregs = (void *)(&header[1]);
memcpy(fpregs->fp_q, pcb->pcb_fpustate.vfp_regs,
sizeof(fpregs->fp_q));
fpregs->fp_cr = pcb->pcb_fpustate.vfp_fpcr;
fpregs->fp_sr = pcb->pcb_fpustate.vfp_fpsr;
} else {
memcpy((void *)(&header[1]), pcb->pcb_svesaved,
buf_size);
}
}
*sizep = sizeof(struct svereg_header) + buf_size;
return (true);
}
static bool
set_arm64_sve(struct regset *rs, struct thread *td, void *buf, size_t size)
{
struct svereg_header *header;
struct pcb *pcb;
size_t buf_size;
uint16_t sve_flags;
pcb = td->td_pcb;
if (pcb->pcb_sve_len == 0)
return (false);
sve_flags = 0;
if ((pcb->pcb_fpflags & PCB_FP_SVEVALID) == 0) {
buf_size = sizeof(struct fpreg);
sve_flags |= SVEREG_FLAG_FP;
} else {
MPASS(pcb->pcb_svesaved != NULL);
buf_size = sve_buf_size(td);
sve_flags |= SVEREG_FLAG_SVE;
KASSERT(pcb->pcb_svesaved != NULL, ("%s: no saved sve",
__func__));
}
if (size != sizeof(struct svereg_header) + buf_size)
return (false);
header = buf;
if (header->sve_size != sizeof(struct svereg_header) + buf_size)
return (false);
if (header->sve_maxsize != sizeof(struct svereg_header) +
sve_max_buf_size())
return (false);
if (header->sve_vec_len != pcb->pcb_sve_len)
return (false);
if (header->sve_max_vec_len != sve_max_vector_len)
return (false);
if (header->sve_flags != sve_flags)
return (false);
if ((sve_flags & SVEREG_FLAG_REGS_MASK) == SVEREG_FLAG_FP) {
struct fpreg *fpregs;
fpregs = (void *)(&header[1]);
memcpy(pcb->pcb_fpustate.vfp_regs, fpregs->fp_q,
sizeof(fpregs->fp_q));
pcb->pcb_fpustate.vfp_fpcr = fpregs->fp_cr;
pcb->pcb_fpustate.vfp_fpsr = fpregs->fp_sr;
} else {
memcpy(pcb->pcb_svesaved, (void *)(&header[1]), buf_size);
}
return (true);
}
static struct regset regset_arm64_sve = {
.note = NT_ARM_SVE,
.get = get_arm64_sve,
.set = set_arm64_sve,
};
ELF_REGSET(regset_arm64_sve);
struct fpu_kern_ctx *
fpu_kern_alloc_ctx(u_int flags)
{
struct fpu_kern_ctx *res;
size_t sz;
sz = sizeof(struct fpu_kern_ctx);
res = malloc(sz, M_FPUKERN_CTX, ((flags & FPU_KERN_NOWAIT) ?
M_NOWAIT : M_WAITOK) | M_ZERO);
return (res);
}
void
fpu_kern_free_ctx(struct fpu_kern_ctx *ctx)
{
KASSERT((ctx->flags & FPU_KERN_CTX_INUSE) == 0, ("free'ing inuse ctx"));
free(ctx, M_FPUKERN_CTX);
}
void
fpu_kern_enter(struct thread *td, struct fpu_kern_ctx *ctx, u_int flags)
{
struct pcb *pcb;
pcb = td->td_pcb;
KASSERT((flags & FPU_KERN_NOCTX) != 0 || ctx != NULL,
("ctx is required when !FPU_KERN_NOCTX"));
KASSERT(ctx == NULL || (ctx->flags & FPU_KERN_CTX_INUSE) == 0,
("using inuse ctx"));
KASSERT((pcb->pcb_fpflags & PCB_FP_NOSAVE) == 0,
("recursive fpu_kern_enter while in PCB_FP_NOSAVE state"));
if ((flags & FPU_KERN_NOCTX) != 0) {
critical_enter();
if (curthread == PCPU_GET(fpcurthread)) {
vfp_save_state(curthread, pcb);
}
PCPU_SET(fpcurthread, NULL);
vfp_enable();
pcb->pcb_fpflags |= PCB_FP_KERN | PCB_FP_NOSAVE |
PCB_FP_STARTED;
return;
}
if ((flags & FPU_KERN_KTHR) != 0 && is_fpu_kern_thread(0)) {
ctx->flags = FPU_KERN_CTX_DUMMY | FPU_KERN_CTX_INUSE;
return;
}
KASSERT((pcb->pcb_fpflags & PCB_FP_KERN) != 0 ||
pcb->pcb_fpusaved == &pcb->pcb_fpustate,
("Mangled pcb_fpusaved %x %p %p", pcb->pcb_fpflags, pcb->pcb_fpusaved, &pcb->pcb_fpustate));
ctx->flags = FPU_KERN_CTX_INUSE;
vfp_save_state(curthread, pcb);
ctx->prev = pcb->pcb_fpusaved;
pcb->pcb_fpusaved = &ctx->state;
pcb->pcb_fpflags |= PCB_FP_KERN;
pcb->pcb_fpflags &= ~PCB_FP_STARTED;
return;
}
int
fpu_kern_leave(struct thread *td, struct fpu_kern_ctx *ctx)
{
struct pcb *pcb;
pcb = td->td_pcb;
if ((pcb->pcb_fpflags & PCB_FP_NOSAVE) != 0) {
KASSERT(ctx == NULL, ("non-null ctx after FPU_KERN_NOCTX"));
KASSERT(PCPU_GET(fpcurthread) == NULL,
("non-NULL fpcurthread for PCB_FP_NOSAVE"));
CRITICAL_ASSERT(td);
vfp_disable();
pcb->pcb_fpflags &= ~(PCB_FP_NOSAVE | PCB_FP_STARTED);
critical_exit();
} else {
KASSERT((ctx->flags & FPU_KERN_CTX_INUSE) != 0,
("FPU context not inuse"));
ctx->flags &= ~FPU_KERN_CTX_INUSE;
if (is_fpu_kern_thread(0) &&
(ctx->flags & FPU_KERN_CTX_DUMMY) != 0)
return (0);
KASSERT((ctx->flags & FPU_KERN_CTX_DUMMY) == 0, ("dummy ctx"));
critical_enter();
vfp_discard(td);
critical_exit();
pcb->pcb_fpflags &= ~PCB_FP_STARTED;
pcb->pcb_fpusaved = ctx->prev;
}
if (pcb->pcb_fpusaved == &pcb->pcb_fpustate) {
pcb->pcb_fpflags &= ~PCB_FP_KERN;
} else {
KASSERT((pcb->pcb_fpflags & PCB_FP_KERN) != 0,
("unpaired fpu_kern_leave"));
}
return (0);
}
int
fpu_kern_thread(u_int flags __unused)
{
struct pcb *pcb = curthread->td_pcb;
KASSERT((curthread->td_pflags & TDP_KTHREAD) != 0,
("Only kthread may use fpu_kern_thread"));
KASSERT(pcb->pcb_fpusaved == &pcb->pcb_fpustate,
("Mangled pcb_fpusaved"));
KASSERT((pcb->pcb_fpflags & PCB_FP_KERN) == 0,
("Thread already setup for the VFP"));
pcb->pcb_fpflags |= PCB_FP_KERN;
return (0);
}
int
is_fpu_kern_thread(u_int flags __unused)
{
struct pcb *curpcb;
if ((curthread->td_pflags & TDP_KTHREAD) == 0)
return (0);
curpcb = curthread->td_pcb;
return ((curpcb->pcb_fpflags & PCB_FP_KERN) != 0);
}
struct vfpstate *
fpu_save_area_alloc(void)
{
return (uma_zalloc(fpu_save_area_zone, M_WAITOK));
}
void
fpu_save_area_free(struct vfpstate *fsa)
{
uma_zfree(fpu_save_area_zone, fsa);
}
void
fpu_save_area_reset(struct vfpstate *fsa)
{
memcpy(fsa, fpu_initialstate, sizeof(*fsa));
}
#endif