/*
 * 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 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/var.h>
#include <sys/thread.h>
#include <sys/cpuvar.h>
#include <sys/kstat.h>
#include <sys/uadmin.h>
#include <sys/systm.h>
#include <sys/errno.h>
#include <sys/cmn_err.h>
#include <sys/procset.h>
#include <sys/processor.h>
#include <sys/debug.h>
#include <sys/task.h>
#include <sys/project.h>
#include <sys/zone.h>
#include <sys/contract_impl.h>
#include <sys/contract/process_impl.h>

/*
 * Bind all the threads of a process to a CPU.
 */
static int
cpu_bind_process(proc_t *pp, processorid_t bind, processorid_t *obind,
    int *error)
{
	kthread_t	*tp;
	kthread_t	*fp;
	int		err = 0;
	int		i;

	ASSERT(MUTEX_HELD(&pidlock));

	/* skip kernel processes */
	if (pp->p_flag & SSYS) {
		*obind = PBIND_NONE;
		*error = ENOTSUP;
		return (0);
	}

	mutex_enter(&pp->p_lock);
	tp = pp->p_tlist;
	if (tp != NULL) {
		fp = tp;
		do {
			i = cpu_bind_thread(tp, bind, obind, error);
			if (err == 0)
				err = i;
		} while ((tp = tp->t_forw) != fp);
	}

	mutex_exit(&pp->p_lock);
	return (err);
}

/*
 * Bind all the processes of a task to a CPU.
 */
static int
cpu_bind_task(task_t *tk, processorid_t bind, processorid_t *obind,
    int *error)
{
	proc_t	*p;
	int	err = 0;
	int	i;

	ASSERT(MUTEX_HELD(&pidlock));

	if ((p = tk->tk_memb_list) == NULL)
		return (ESRCH);

	do {
		if (!(p->p_flag & SSYS)) {
			i = cpu_bind_process(p, bind, obind, error);
			if (err == 0)
				err = i;
		}
	} while ((p = p->p_tasknext) != tk->tk_memb_list);

	return (err);
}

/*
 * Bind all the processes in a project to a CPU.
 */
static int
cpu_bind_project(kproject_t *kpj, processorid_t bind, processorid_t *obind,
    int *error)
{
	proc_t *p;
	int err = 0;
	int i;

	ASSERT(MUTEX_HELD(&pidlock));

	for (p = practive; p != NULL; p = p->p_next) {
		if (p->p_tlist == NULL)
			continue;
		if (p->p_task->tk_proj == kpj && !(p->p_flag & SSYS)) {
			i = cpu_bind_process(p, bind, obind, error);
			if (err == 0)
				err = i;
		}
	}
	return (err);
}

/*
 * Bind all the processes in a zone to a CPU.
 */
int
cpu_bind_zone(zone_t *zptr, processorid_t bind, processorid_t *obind,
    int *error)
{
	proc_t *p;
	int err = 0;
	int i;

	ASSERT(MUTEX_HELD(&pidlock));

	for (p = practive; p != NULL; p = p->p_next) {
		if (p->p_tlist == NULL)
			continue;
		if (p->p_zone == zptr && !(p->p_flag & SSYS)) {
			i = cpu_bind_process(p, bind, obind, error);
			if (err == 0)
				err = i;
		}
	}
	return (err);
}

/*
 * Bind all the processes in a process contract to a CPU.
 */
int
cpu_bind_contract(cont_process_t *ctp, processorid_t bind, processorid_t *obind,
    int *error)
{
	proc_t *p;
	int err = 0;
	int i;

	ASSERT(MUTEX_HELD(&pidlock));

	for (p = practive; p != NULL; p = p->p_next) {
		if (p->p_tlist == NULL)
			continue;
		if (p->p_ct_process == ctp) {
			i = cpu_bind_process(p, bind, obind, error);
			if (err == 0)
				err = i;
		}
	}
	return (err);
}

/*
 * processor_bind(2) - Processor binding interfaces.
 */
int
processor_bind(idtype_t idtype, id_t id, processorid_t bind,
    processorid_t *obindp)
{
	processorid_t	obind = PBIND_NONE;
	int		ret = 0;
	int		err = 0;
	cpu_t		*cp;
	kthread_id_t	tp;
	proc_t		*pp;
	task_t		*tk;
	kproject_t	*kpj;
	zone_t		*zptr;
	contract_t	*ct;

	/*
	 * Since we might be making a binding to a processor, hold the
	 * cpu_lock so that the processor cannot be taken offline while
	 * we do this.
	 */
	mutex_enter(&cpu_lock);

	/*
	 * Check to be sure binding processor ID is valid.
	 */
	switch (bind) {
	default:
		if ((cp = cpu_get(bind)) == NULL ||
		    (cp->cpu_flags & (CPU_QUIESCED | CPU_OFFLINE)))
			ret = EINVAL;
		else if ((cp->cpu_flags & CPU_READY) == 0)
			ret = EIO;
		break;

	case PBIND_NONE:
	case PBIND_QUERY:
	case PBIND_HARD:
	case PBIND_SOFT:
	case PBIND_QUERY_TYPE:
		break;
	}

	if (ret) {
		mutex_exit(&cpu_lock);
		return (set_errno(ret));
	}

	switch (idtype) {
	case P_LWPID:
		pp = curproc;
		mutex_enter(&pp->p_lock);
		if (id == P_MYID) {
			ret = cpu_bind_thread(curthread, bind, &obind, &err);
		} else {
			int	found = 0;

			tp = pp->p_tlist;
			do {
				if (tp->t_tid == id) {
					ret = cpu_bind_thread(tp,
					    bind, &obind, &err);
					found = 1;
					break;
				}
			} while ((tp = tp->t_forw) != pp->p_tlist);
			if (!found)
				ret = ESRCH;
		}
		mutex_exit(&pp->p_lock);
		break;

	case P_PID:
		/*
		 * Note.  Cannot use dotoprocs here because it doesn't find
		 * system class processes, which are legal to query.
		 */
		mutex_enter(&pidlock);
		if (id == P_MYID) {
			ret = cpu_bind_process(curproc, bind, &obind, &err);
		} else if ((pp = prfind(id)) != NULL) {
			ret = cpu_bind_process(pp, bind, &obind, &err);
		} else {
			ret = ESRCH;
		}
		mutex_exit(&pidlock);
		break;

	case P_TASKID:
		mutex_enter(&pidlock);
		if (id == P_MYID) {
			proc_t *p = curproc;
			id = p->p_task->tk_tkid;
		}

		if ((tk = task_hold_by_id(id)) != NULL) {
			ret = cpu_bind_task(tk, bind, &obind, &err);
			mutex_exit(&pidlock);
			task_rele(tk);
		} else {
			mutex_exit(&pidlock);
			ret = ESRCH;
		}
		break;

	case P_PROJID:
		pp = curproc;
		if (id == P_MYID)
			id = curprojid();
		if ((kpj = project_hold_by_id(id, pp->p_zone,
		    PROJECT_HOLD_FIND)) == NULL) {
			ret = ESRCH;
		} else {
			mutex_enter(&pidlock);
			ret = cpu_bind_project(kpj, bind, &obind, &err);
			mutex_exit(&pidlock);
			project_rele(kpj);
		}
		break;

	case P_ZONEID:
		if (id == P_MYID)
			id = getzoneid();

		if ((zptr = zone_find_by_id(id)) == NULL) {
			ret = ESRCH;
		} else {
			mutex_enter(&pidlock);
			ret = cpu_bind_zone(zptr, bind, &obind, &err);
			mutex_exit(&pidlock);
			zone_rele(zptr);
		}
		break;

	case P_CTID:
		if (id == P_MYID)
			id = PRCTID(curproc);

		if ((ct = contract_type_ptr(process_type, id,
		    curproc->p_zone->zone_uniqid)) == NULL) {
			ret = ESRCH;
		} else {
			mutex_enter(&pidlock);
			ret = cpu_bind_contract(ct->ct_data,
			    bind, &obind, &err);
			mutex_exit(&pidlock);
			contract_rele(ct);
		}
		break;

	case P_CPUID:
		if (id == P_MYID || bind != PBIND_NONE || cpu_get(id) == NULL)
			ret = EINVAL;
		else
			ret = cpu_unbind(id, B_TRUE);
		break;

	case P_ALL:
		if (id == P_MYID || bind != PBIND_NONE) {
			ret = EINVAL;
		} else {
			int i;
			cpu_t *cp = cpu_list;
			do {
				if ((cp->cpu_flags & CPU_EXISTS) == 0)
					continue;
				i = cpu_unbind(cp->cpu_id, B_TRUE);
				if (ret == 0)
					ret = i;
			} while ((cp = cp->cpu_next) != cpu_list);
		}
		break;

	default:
		/*
		 * Spec says this is invalid, even though we could
		 * handle other idtypes.
		 */
		ret = EINVAL;
		break;
	}
	mutex_exit(&cpu_lock);

	/*
	 * If no search error occurred, see if any permissions errors did.
	 */
	if (ret == 0)
		ret = err;

	if (ret == 0 && obindp != NULL)
		if (copyout((caddr_t)&obind, (caddr_t)obindp,
		    sizeof (obind)) == -1)
			ret = EFAULT;
	return (ret ? set_errno(ret) : 0);	/* return success or failure */
}