/* x86_vecreg.c -- logic to save and restore vector registers
 *                 on amd64 in FreeBSD kernel.
 *
 * Copyright (C) 2006-2026 wolfSSL Inc.
 *
 * This file is part of wolfSSL.
 *
 * wolfSSL is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * wolfSSL is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1335, USA
 */

/* included by bsdkm/wolfkmod.c */
#ifndef WC_SKIP_INCLUDED_C_FILES

#include <sys/proc.h>
#include <sys/smp.h>
#include <machine/fpu.h>
#include <machine/pcb.h>

struct wolfkmod_fpu_state_t {
    volatile lwpid_t td_tid;
    volatile u_int   nest;
};

typedef struct wolfkmod_fpu_state_t wolfkmod_fpu_state_t;

/* fpu_states array tracks thread id and nesting level of save/restore
 * and push/pop vector registers macro calls. It is indexed by raw cpu id,
 * and only accessed after the thread calls fpu_kern_enter(), and before
 * calling fpu_kern_leave(), and only indexed by the thread's PCPU_GET(cpuid).
 *
 * after calling fpu_kern_enter():
 *   - kernel fpu is enabled
 *   - migration is disabled
 *   - soft preempts are disabled
 * Hard irq are still possible , but hard irq are forbidden from using FPU
 * in FreeBSD kernel.
 * */
static wolfkmod_fpu_state_t * fpu_states = NULL;

/* check for active td_tid with atomic before proceeding.
 * technically not necessary because fpu_kern_enter() gives thread pinning
 * to cpu, but just to be safe...
 * */
#define wolfkmod_fpu_get_tid()                                               \
            atomic_load_acq_int(&fpu_states[PCPU_GET(cpuid)].td_tid)

int wolfkmod_vecreg_init(void)
{
    if (mp_ncpus <= 0) {
        printf("error: wolfkmod_vecreg_init: mp_ncpus = %d\n", mp_ncpus);
        return (EINVAL);
    }

    fpu_states = malloc(mp_ncpus * sizeof(wolfkmod_fpu_state_t),
                        M_WOLFSSL, M_WAITOK | M_ZERO);
    if (fpu_states == NULL) {
        printf("error: wolfkmod_vecreg_init: malloc(%lu) failed\n",
               mp_ncpus * sizeof(wolfkmod_fpu_state_t));
        return (ENOMEM);
    }

    return (0);
}

void wolfkmod_vecreg_exit(void)
{
    int i = 0;

    if (fpu_states == NULL) {
        return;
    }

    for (i = 0; i < mp_ncpus; ++i) {
        #if defined(WOLFSSL_BSDKM_FPU_DEBUG)
        printf("info: wolfkmod_vecreg_exit: fpu_states[%d] = %d, %d\n",
               i, fpu_states[i].nest, fpu_states[i].td_tid);
        #endif /* WOLFSSL_BSDKM_FPU_DEBUG */

        if (fpu_states[i].nest != 0 || fpu_states[i].td_tid != 0) {
            /* Check for orphaned fpu state. There's nothing we can do
             * but log the event and zero the nesting level. */
            printf("error: wolfkmod_vecreg_exit: fpu_states[%d] = %d, %d\n",
                   i, fpu_states[i].nest, fpu_states[i].td_tid);
            fpu_states[i].nest = 0;
        }
    }

    free(fpu_states, M_WOLFSSL);
    fpu_states = NULL;

    return;
}

/* fpu_kern_enter() and fpu_kern_leave() wrapper defines.
 * Build with WOLFSSL_BSDKM_FPU_DEBUG to see verbose FPU logging.
 */
#if defined(WOLFSSL_BSDKM_FPU_DEBUG)
    #define wolfkmod_print_curthread(what)                                   \
        printf("%s: cpuid = %d, curthread: td_tid = %d, pid = %d (%s), "     \
               "td_critnest = %d, kernfpu = %02x\n",                         \
               (what), PCPU_GET(cpuid), curthread->td_tid,                   \
               curthread->td_proc ? curthread->td_proc->p_pid : -1,          \
               curthread->td_proc ? curthread->td_proc->p_comm : "noproc",   \
               curthread->td_critnest,                                       \
               curthread->td_pcb->pcb_flags & PCB_KERNFPU);

    #define wolfkmod_fpu_kern_enter()                                        \
        wolfkmod_print_curthread("fpu_kern_enter");                          \
        fpu_kern_enter(curthread, NULL, FPU_KERN_NOCTX);

    #define wolfkmod_fpu_kern_leave()                                        \
        wolfkmod_print_curthread("fpu_kern_leave");                          \
        fpu_kern_leave(curthread, NULL);
#else
    #define wolfkmod_fpu_kern_enter()                                        \
        fpu_kern_enter(curthread, NULL, FPU_KERN_NOCTX);

    #define wolfkmod_fpu_kern_leave()                                        \
        fpu_kern_leave(curthread, NULL);
#endif /* WOLFSSL_BSDKM_FPU_DEBUG */

int wolfkmod_vecreg_save(int flags_unused)
{
    (void)flags_unused;

    #if defined(WOLFSSL_BSDKM_FPU_DEBUG)
    wolfkmod_print_curthread("wolfkmod_vecreg_save");
    #endif

    if (is_fpu_kern_thread(0)) {
        /* kernel fpu threads are special, do nothing. They own a
         * persistent, dedicated fpu context. */
        #if defined(WOLFSSL_BSDKM_FPU_DEBUG)
        printf("info: wolfkmod_vecreg_save: is fpu kern thread\n");
        #endif
        return (0);
    }

    if (curthread->td_pcb->pcb_flags & PCB_KERNFPU) {
        /* kern fpu is active for this thread. check td_tid and
         * increment nesting level. */
        lwpid_t td_tid = wolfkmod_fpu_get_tid();
        if (td_tid != curthread->td_tid) {
            printf("error: wolfkmod_vecreg_save: got tid = %d, expected %d\n",
                   td_tid, curthread->td_tid);
            return (EINVAL);
        }
        fpu_states[PCPU_GET(cpuid)].nest++;
    }
    else {
        /* kern fpu not active for this thread, call fpu_kern_enter().
         * after calling fpu_kern_enter():
         *   - kernel fpu is enabled
         *   - migration is disabled
         *   - soft preempts are disabled */
        lwpid_t td_tid = 0;
        wolfkmod_fpu_kern_enter();
        td_tid = wolfkmod_fpu_get_tid();

        if (fpu_states[PCPU_GET(cpuid)].nest != 0 || td_tid != 0) {
            printf("error: wolfkmod_fpu_kern_enter() with nest: %d, %d\n",
                   fpu_states[PCPU_GET(cpuid)].nest, td_tid);
            return (EINVAL);
        }

        /* increment nest and save td_tid. */
        fpu_states[PCPU_GET(cpuid)].nest++;
        fpu_states[PCPU_GET(cpuid)].td_tid = curthread->td_tid;
    }

    return (0);
}

void wolfkmod_vecreg_restore(void)
{
    #if defined(WOLFSSL_BSDKM_FPU_DEBUG)
    wolfkmod_print_curthread("wolfkmod_vecreg_restore");
    #endif

    if (is_fpu_kern_thread(0)) {
        /* kernel fpu threads are special, do nothing. They own a
         * persistent, dedicated fpu context. */
        #if defined(WOLFSSL_BSDKM_FPU_DEBUG)
        printf("info: wolfkmod_vecreg_restore: is fpu kern thread\n");
        #endif
        return;
    }

    if (curthread->td_pcb->pcb_flags & PCB_KERNFPU) {
        /* kern fpu is active for this thread. check tid and nesting level. */
        lwpid_t td_tid = wolfkmod_fpu_get_tid();
        if (td_tid != curthread->td_tid) {
            printf("error: wolfkmod_vecreg_restore: got tid = %d, "
                   "expected %d\n", td_tid, curthread->td_tid);
            return;
        }

        /* decrement the nesting level. */
        if (fpu_states[PCPU_GET(cpuid)].nest > 0) {
            fpu_states[PCPU_GET(cpuid)].nest--;
        }

        /* if last level, zero the thread id then call fpu_kern_leave */
        if (fpu_states[PCPU_GET(cpuid)].nest == 0) {
            fpu_states[PCPU_GET(cpuid)].td_tid = 0;
            wolfkmod_fpu_kern_leave();
        }
    }

    return;
}

#endif /* !WC_SKIP_INCLUDED_C_FILES */
