root/usr/src/uts/common/disp/class.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (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 2008 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 * Copyright 2026 Oxide Computer Company
 */

#include <sys/types.h>
#include <sys/systm.h>
#include <sys/cmn_err.h>
#include <sys/class.h>
#include <sys/kmem.h>
#include <sys/cred.h>
#include <sys/proc.h>
#include <sys/procset.h>
#include <sys/modctl.h>
#include <sys/disp.h>
#include <sys/sysmacros.h>
#include <sys/schedctl.h>

static int getcidbyname_locked(char *, id_t *);

/*
 * Allocate a cid given a class name if one is not already allocated.
 * Returns 0 if the cid was already exists or if the allocation of a new
 * cid was successful. Nonzero return indicates error.
 */
int
alloc_cid(char *clname, id_t *cidp)
{
        sclass_t *clp;

        ASSERT(MUTEX_HELD(&class_lock));

        /*
         * If the clname doesn't already have a cid, allocate one.
         */
        if (getcidbyname_locked(clname, cidp) != 0) {
                /*
                 * Allocate a class entry and a lock for it.
                 */
                for (clp = sclass; clp < &sclass[nclass]; clp++)
                        if (clp->cl_name[0] == '\0' && clp->cl_lock == NULL)
                                break;

                if (clp == &sclass[nclass]) {
                        return (ENOSPC);
                }
                *cidp = clp - &sclass[0];
                clp->cl_lock = kmem_alloc(sizeof (krwlock_t), KM_SLEEP);
                clp->cl_name = kmem_alloc(strlen(clname) + 1, KM_SLEEP);
                (void) strcpy(clp->cl_name, clname);
                rw_init(clp->cl_lock, NULL, RW_DEFAULT, NULL);
        }

        /*
         * At this point, *cidp will contain the index into the class
         * array for the given class name.
         */
        return (0);
}

int
scheduler_load(char *clname, sclass_t *clp)
{
        int rv = 0;
        char *tmp = clname + 1;

        /* Check if class name is  "",  ".",  ".."  or  "`"  */
        if (*clname == '\0' || *clname == '`' ||
            (*clname == '.' && *tmp == '\0') ||
            (*clname == '.' && *tmp == '.' && *(++tmp) == '\0')) {
                return (EINVAL);
        }

        if (LOADABLE_SCHED(clp)) {
                rw_enter(clp->cl_lock, RW_READER);
                if (!SCHED_INSTALLED(clp)) {
                        rw_exit(clp->cl_lock);
                        if (modload("sched", clname) == -1)
                                return (EINVAL);
                        rw_enter(clp->cl_lock, RW_READER);
                        /* did we really load a scheduling class? */
                        if (!SCHED_INSTALLED(clp))
                                rv = EINVAL;
                }
                rw_exit(clp->cl_lock);
        }
        return (rv);
}

/*
 * Get class ID given class name.
 */
int
getcid(char *clname, id_t *cidp)
{
        sclass_t *clp;
        int retval;

        mutex_enter(&class_lock);
        if ((retval = alloc_cid(clname, cidp)) == 0) {
                clp = &sclass[*cidp];
                clp->cl_count++;

                /*
                 * If it returns zero, it's loaded & locked
                 * or we found a statically installed scheduler
                 * module.
                 * If it returns EINVAL, modload() failed when
                 * it tried to load the module.
                 */
                mutex_exit(&class_lock);
                retval = scheduler_load(clname, clp);
                mutex_enter(&class_lock);

                clp->cl_count--;
                if (retval != 0 && clp->cl_count == 0) {
                        /* last guy out of scheduler_load frees the storage */
                        kmem_free(clp->cl_name, strlen(clname) + 1);
                        kmem_free(clp->cl_lock, sizeof (krwlock_t));
                        clp->cl_name = "";
                        clp->cl_lock = (krwlock_t *)NULL;
                }
        }
        mutex_exit(&class_lock);
        return (retval);

}

static int
getcidbyname_locked(char *clname, id_t *cidp)
{
        sclass_t *clp;

        ASSERT(MUTEX_HELD(&class_lock));

        if (*clname == '\0')
                return (EINVAL);

        for (clp = &sclass[0]; clp < &sclass[nclass]; clp++) {
                if (strcmp(clp->cl_name, clname) == 0) {
                        *cidp = clp - &sclass[0];
                        return (0);
                }
        }
        return (EINVAL);
}

/*
 * Lookup a module by name.
 */
int
getcidbyname(char *clname, id_t *cidp)
{
        int retval;

        mutex_enter(&class_lock);
        retval = getcidbyname_locked(clname, cidp);
        mutex_exit(&class_lock);

        return (retval);
}

/*
 * Get the scheduling parameters of the thread pointed to by
 * tp into the buffer pointed to by parmsp.
 */
void
parmsget(kthread_t *tp, pcparms_t *parmsp)
{
        parmsp->pc_cid = tp->t_cid;
        CL_PARMSGET(tp, parmsp->pc_clparms);
}


/*
 * Check the validity of the scheduling parameters in the buffer
 * pointed to by parmsp.
 * Note that the format of the parameters may be changed by class
 * specific code which we call.
 */
int
parmsin(pcparms_t *parmsp, pc_vaparms_t *vaparmsp)
{
        if (parmsp->pc_cid >= loaded_classes || parmsp->pc_cid < 1)
                return (EINVAL);

        /*
         * Call the class specific routine to validate class
         * specific parameters.
         * The input parameters are either in a pcparms structure (PC_SETPARMS)
         * or in a variable parameter structure (PC_SETXPARMS). In the
         * 'PC_SETPARMS' case vaparmsp is a NULL pointer and a CL_PARMSIN()
         * routine gets the parameter. Otherwise vaparmsp points to a variable
         * parameter structure and a CL_VAPARMSIN() routine gets the parameter.
         */
        if (vaparmsp != NULL)
                return (CL_VAPARMSIN(&sclass[parmsp->pc_cid],
                    parmsp->pc_clparms, vaparmsp));
        else
                return (CL_PARMSIN(&sclass[parmsp->pc_cid],
                    parmsp->pc_clparms));
}


/*
 * Call the class specific code to do the required processing
 * before the scheduling parameters are copied out to the user.
 * Note that the format of the parameters may be changed by the
 * class specific code.
 */
int
parmsout(pcparms_t *parmsp, pc_vaparms_t *vaparmsp)
{
        return (CL_PARMSOUT(&sclass[parmsp->pc_cid], parmsp->pc_clparms,
            vaparmsp));
}


/*
 * Set the scheduling parameters of the thread pointed to by
 * targtp to those specified in the pcparms structure pointed
 * to by parmsp.  If reqtp is non-NULL it points to the thread
 * that initiated the request for the parameter change and indicates
 * that our caller wants us to verify that the requesting thread
 * has the appropriate permissions.
 */
int
parmsset(pcparms_t *parmsp, kthread_t *targtp)
{
        caddr_t clprocp;
        int     error;
        cred_t  *reqpcredp;
        proc_t  *reqpp = ttoproc(curthread);
        proc_t  *targpp = ttoproc(targtp);
        id_t    oldcid;

        ASSERT(MUTEX_HELD(&pidlock));
        ASSERT(MUTEX_HELD(&targpp->p_lock));
        if (reqpp != NULL) {
                mutex_enter(&reqpp->p_crlock);
                crhold(reqpcredp = reqpp->p_cred);
                mutex_exit(&reqpp->p_crlock);

                /*
                 * Check basic permissions.
                 */
                if (!prochasprocperm(targpp, reqpp, reqpcredp)) {
                        crfree(reqpcredp);
                        return (EPERM);
                }
        } else {
                reqpcredp = NULL;
        }

        if (parmsp->pc_cid != targtp->t_cid) {
                void    *bufp = NULL;
                /*
                 * Target thread must change to new class.
                 */
                clprocp = (caddr_t)targtp->t_cldata;
                oldcid  = targtp->t_cid;

                /*
                 * Purpose: allow scheduling class to veto moves
                 * to other classes. All the classes, except FSS,
                 * do nothing except returning 0.
                 */
                error = CL_CANEXIT(targtp, reqpcredp);
                if (error) {
                        /*
                         * Not allowed to leave the class, so return error.
                         */
                        crfree(reqpcredp);
                        return (error);
                } else {
                        /*
                         * Pre-allocate scheduling class data.
                         */
                        if (CL_ALLOC(&bufp, parmsp->pc_cid, KM_NOSLEEP) != 0) {
                                error = ENOMEM; /* no memory available */
                                crfree(reqpcredp);
                                return (error);
                        } else {
                                error = CL_ENTERCLASS(targtp, parmsp->pc_cid,
                                    parmsp->pc_clparms, reqpcredp, bufp);
                                crfree(reqpcredp);
                                if (error) {
                                        CL_FREE(parmsp->pc_cid, bufp);
                                        return (error);
                                }
                        }
                }
                CL_EXITCLASS(oldcid, clprocp);
        } else {

                /*
                 * Not changing class
                 */
                error = CL_PARMSSET(targtp, parmsp->pc_clparms,
                    curthread->t_cid, reqpcredp);
                crfree(reqpcredp);
                if (error)
                        return (error);
        }
        schedctl_set_cidpri(targtp);
        return (0);
}


/*
 * Copy all selected class parameters to the user.
 * The parameters are specified by a key.
 */
int
vaparmsout(char *classp, pcparms_t *prmsp, pc_vaparms_t *vaparmsp,
    uio_seg_t seg)
{
        char    *clname;

        ASSERT(MUTEX_NOT_HELD(&curproc->p_lock));

        if (classp != NULL)
                return (CL_VAPARMSOUT(&sclass[prmsp->pc_cid],
                    prmsp->pc_clparms, vaparmsp));

        switch (vaparmsp->pc_vaparmscnt) {
        case 0:
                return (0);
        case 1:
                break;
        default:
                return (EINVAL);
        }

        if (vaparmsp->pc_parms[0].pc_key != PC_KY_CLNAME)
                return (EINVAL);

        clname = sclass[prmsp->pc_cid].cl_name;
        if (uio_copyout(clname,
            (void *)(uintptr_t)vaparmsp->pc_parms[0].pc_parm,
            MIN(strlen(clname) + 1, PC_CLNMSZ), seg) != 0)
                return (EFAULT);

        return (0);
}