/*
 *  Copyright (C) 2000-2009, Parallels, Inc. All rights reserved.
 *
 *  This program 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 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program 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., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <stdlib.h>
#include <unistd.h>
#include <linux/vzcalluser.h>
#include <linux/fairsched.h>
#include <errno.h>

#include "types.h"
#include "bitmap.h"
#include "cpu.h"
#include "env.h"
#include "vzerror.h"
#include "logger.h"
#include "vzsyscalls.h"

static inline int fairsched_chwt(unsigned int id, unsigned wght)
{
	int ret;

	ret = syscall(__NR_fairsched_chwt, id, wght);
	if (ret && errno == ENOSYS)
		ret = 0;
	return ret;
}

static inline int fairsched_rate(unsigned int id, int op, unsigned rate)
{
	int ret;

	ret = syscall(__NR_fairsched_rate, id, op, rate);
	if (ret && errno == ENOSYS)
		ret = 0;
	return ret;
}

static inline int fairsched_vcpus(unsigned int id, unsigned vcpus)
{
	int ret;

	ret = syscall(__NR_fairsched_vcpus, id, vcpus);
	if (ret && errno == ENOSYS)
		ret = 0;
	return ret;
}

#if defined(__i386__) || defined(__x86_64__)
static inline int fairsched_cpumask(unsigned int id,
		unsigned int masksize, unsigned long *mask)
{
	int ret;

	ret = syscall(__NR_fairsched_cpumask, id, masksize, mask);
	if (ret && errno == ENOSYS)
		ret = 0;
	return ret;
}
#else
/*
 * fairsched_cpumask is available only in vz kernels based on linux 2.6.32
 * or later which do not support platforms different from x86.
 */
static inline int fairsched_cpumask(unsigned int id,
		unsigned int masksize, unsigned long *mask)
{
	return ENOTSUP;
}
#endif

static int set_cpulimit(envid_t veid, unsigned int cpulimit)
{
	unsigned cpulim1024 = (float)cpulimit * 1024 / 100;
	int op = cpulim1024 ? FAIRSCHED_SET_RATE : FAIRSCHED_DROP_RATE;

	logger(0, 0, "Setting CPU limit: %d", cpulimit);
	if (fairsched_rate(veid, op, cpulim1024) < 0) {
		logger(-1, errno, "fairsched_rate");
		return VZ_SETFSHD_ERROR;
	}
	return 0;
}

static int set_cpuweight(envid_t veid, unsigned int cpuweight)
{

	if (fairsched_chwt(veid, cpuweight)) {
		logger(-1, errno, "fairsched_chwt");
		return VZ_SETFSHD_ERROR;
	}
	return 0;
}

static int set_cpuunits(envid_t veid, unsigned int cpuunits)
{
	int cpuweight, ret;

	if (cpuunits < MINCPUUNITS || cpuunits > MAXCPUUNITS) {
		logger(-1, 0, "Invalid value for cpuunits: %d"
			" allowed range is %d-%d",
			cpuunits, MINCPUUNITS, MAXCPUUNITS);
		return VZ_SETFSHD_ERROR;
	}
	cpuweight = MAXCPUUNITS / cpuunits;
	logger(0, 0, "Setting CPU units: %d", cpuunits);
	ret = set_cpuweight(veid, cpuweight);
	return ret;
}

static int set_cpumask(envid_t veid, cpumask_t *mask)
{
	static char maskstr[CPUMASK_NBITS * 2];

	bitmap_snprintf(maskstr, CPUMASK_NBITS * 2,
			cpumask_bits(mask), CPUMASK_NBITS);
	logger(0, 0, "Setting CPU mask: %s", maskstr);
	if (fairsched_cpumask(veid, sizeof(cpumask_t), cpumask_bits(mask))) {
		logger(-1, errno, "fairsched_cpumask");
		return VZ_SETFSHD_ERROR;
	}
	return 0;
}

/** Change number of CPUs available in the running CT.
 *
 * @param veid		CT ID
 * @param vcpu		number of CPUs
 */
static int env_set_vcpus(envid_t veid, unsigned int vcpus)
{
	logger(0, 0, "Setting CPUs: %d", vcpus);
	if (fairsched_vcpus(veid, vcpus) != 0) {
		logger(-1, errno, "Unable to set cpus");
		return VZ_SETFSHD_ERROR;
	}

	return 0;
}


/**  Apply cpu parameters on Host system.
 *
 * @param cpu		cpu parameters.
 * @return		0 on success.
 */
int hn_set_cpu(cpu_param *cpu)
{
	if (cpu->units == NULL)
		return 0;
	return set_cpuunits(2147483647, *cpu->units);
}

/**  Apply cpu parameters on running CT.
 *
 * @param h		CT handler.
 * @param veid		CT ID.
 * @param cpu		cpu parameters.
 * @return		0 on success.
 */
int vps_set_cpu(vps_handler *h, envid_t veid, cpu_param *cpu)
{
	int ret = 0;

	if (cpu->limit == NULL &&
		cpu->units == NULL &&
		cpu->weight == NULL &&
		cpu->vcpus == NULL &&
		cpu->mask == NULL)
	{
		return 0;
	}
	if (!vps_is_run(h, veid)) {
		logger(-1, 0, "Unable to apply CPU parameters: "
			"container is not running");
		return VZ_VE_NOT_RUNNING;
	}
	if (cpu->limit != NULL) {
		ret = set_cpulimit(veid, *cpu->limit);
	}
	if (cpu->units != NULL) {
		ret = set_cpuunits(veid, *cpu->units);
	} else if (cpu->weight != NULL)
		ret = set_cpuweight(veid, *cpu->weight);
	if (cpu->vcpus != NULL) {
		ret = env_set_vcpus(veid, *cpu->vcpus);
	}
	if (cpu->mask != NULL) {
		ret = set_cpumask(veid, cpu->mask);
	}

	return ret;
}
