/*
 * PowerPC64 LPAR Configuration Information Driver
 *
 * Dave Engebretsen engebret@us.ibm.com
 *    Copyright (c) 2003 Dave Engebretsen
 * Will Schmidt willschm@us.ibm.com
 *    SPLPAR updates, Copyright (c) 2003 Will Schmidt IBM Corporation.
 * Nathan Lynch nathanl@austin.ibm.com
 *    Added lparcfg_write, Copyright (C) 2004 Nathan Lynch IBM Corporation.
 *
 *      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 driver creates a proc file at /proc/ppc64/lparcfg which contains
 * keyword - value pairs that specify the configuration of the partition.
 */

#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/errno.h>
#include <linux/proc_fs.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include <asm/iSeries/HvLpConfig.h>
#include <asm/iSeries/ItLpPaca.h>
#include <asm/hvcall.h>
#include <asm/cputable.h>

#define MODULE_VERS "1.0"
#define MODULE_NAME "lparcfg"

static struct proc_dir_entry *proc_ppc64_lparcfg;
#define LPARCFG_BUFF_SIZE 4096

#ifdef CONFIG_PPC_ISERIES

#define lparcfg_write NULL

static unsigned char e2a(unsigned char x)
{
        switch (x) {
        case 0xF0:
                return '0';
        case 0xF1:
                return '1';
        case 0xF2:
                return '2';
        case 0xF3:
                return '3';
        case 0xF4:
                return '4';
        case 0xF5:
                return '5';
        case 0xF6:
                return '6';
        case 0xF7:
                return '7';
        case 0xF8:
                return '8';
        case 0xF9:
                return '9';
        case 0xC1:
                return 'A';
        case 0xC2:
                return 'B';
        case 0xC3:
                return 'C';
        case 0xC4:
                return 'D';
        case 0xC5:
                return 'E';
        case 0xC6:
                return 'F';
        case 0xC7:
                return 'G';
        case 0xC8:
                return 'H';
        case 0xC9:
                return 'I';
        case 0xD1:
                return 'J';
        case 0xD2:
                return 'K';
        case 0xD3:
                return 'L';
        case 0xD4:
                return 'M';
        case 0xD5:
                return 'N';
        case 0xD6:
                return 'O';
        case 0xD7:
                return 'P';
        case 0xD8:
                return 'Q';
        case 0xD9:
                return 'R';
        case 0xE2:
                return 'S';
        case 0xE3:
                return 'T';
        case 0xE4:
                return 'U';
        case 0xE5:
                return 'V';
        case 0xE6:
                return 'W';
        case 0xE7:
                return 'X';
        case 0xE8:
                return 'Y';
        case 0xE9:
                return 'Z';
        }
        return ' ';
}

/* 
 * Methods used to fetch LPAR data when running on an iSeries platform.
 */
static int lparcfg_data(unsigned char *buf, unsigned long size)
{
	unsigned long n = 0, pool_id, lp_index; 
	int shared, entitled_capacity, max_entitled_capacity;
	int processors, max_processors;
	struct paca_struct *lpaca = get_paca();

	if((buf == NULL) || (size > LPARCFG_BUFF_SIZE)) {
		return -EFAULT;
	}
	memset(buf, 0, size); 

	shared = (int)(lpaca->xLpPacaPtr->xSharedProc);
	n += scnprintf(buf, LPARCFG_BUFF_SIZE - n,
		      "serial_number=%c%c%c%c%c%c%c\n", 
		      e2a(xItExtVpdPanel.mfgID[2]),
		      e2a(xItExtVpdPanel.mfgID[3]),
		      e2a(xItExtVpdPanel.systemSerial[1]),
		      e2a(xItExtVpdPanel.systemSerial[2]),
		      e2a(xItExtVpdPanel.systemSerial[3]),
		      e2a(xItExtVpdPanel.systemSerial[4]),
		      e2a(xItExtVpdPanel.systemSerial[5])); 

	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "system_type=%c%c%c%c\n",
		      e2a(xItExtVpdPanel.machineType[0]),
		      e2a(xItExtVpdPanel.machineType[1]),
		      e2a(xItExtVpdPanel.machineType[2]),
		      e2a(xItExtVpdPanel.machineType[3])); 

	lp_index = HvLpConfig_getLpIndex(); 
	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_id=%d\n", (int)lp_index); 

	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "system_active_processors=%d\n", 
		      (int)HvLpConfig_getSystemPhysicalProcessors()); 

	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "system_potential_processors=%d\n", 
		      (int)HvLpConfig_getSystemPhysicalProcessors()); 

	processors = (int)HvLpConfig_getPhysicalProcessors(); 
	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_active_processors=%d\n", processors);  

	max_processors = (int)HvLpConfig_getMaxPhysicalProcessors(); 
	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_potential_processors=%d\n", max_processors);  

	if(shared) {
		entitled_capacity = HvLpConfig_getSharedProcUnits(); 
		max_entitled_capacity = HvLpConfig_getMaxSharedProcUnits(); 
	} else {
		entitled_capacity = processors * 100; 
		max_entitled_capacity = max_processors * 100; 
	}
	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_entitled_capacity=%d\n", entitled_capacity);

	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_max_entitled_capacity=%d\n", 
		      max_entitled_capacity);

	if(shared) {
		pool_id = HvLpConfig_getSharedPoolIndex(); 
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n, "pool=%d\n",
			      (int)pool_id); 
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "pool_capacity=%d\n", (int)(HvLpConfig_getNumProcsInSharedPool(pool_id)*100)); 
	}

	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "shared_processor_mode=%d\n", shared);

	return 0;
}
#endif /* CONFIG_PPC_ISERIES */

#ifdef CONFIG_PPC_PSERIES
/* 
 * Methods used to fetch LPAR data when running on a pSeries platform.
 */

/*
 * H_GET_PPP hcall returns info in 4 parms.
 *  entitled_capacity,unallocated_capacity,
 *  aggregation, resource_capability).
 *
 *  R4 = Entitled Processor Capacity Percentage. 
 *  R5 = Unallocated Processor Capacity Percentage.
 *  R6 (AABBCCDDEEFFGGHH).
 *      XXXX - reserved (0)
 *          XXXX - reserved (0)
 *              XXXX - Group Number
 *                  XXXX - Pool Number.
 *  R7 (PPOONNMMLLKKJJII)
 *      XX - reserved. (0)
 *        XX - bit 0-6 reserved (0).   bit 7 is Capped indicator.
 *          XX - variable processor Capacity Weight
 *            XX - Unallocated Variable Processor Capacity Weight.
 *              XXXX - Active processors in Physical Processor Pool.
 *                  XXXX  - Processors active on platform. 
 */
unsigned int h_get_ppp(unsigned long *entitled,unsigned long  *unallocated,unsigned long *aggregation,unsigned long *resource)
{
	unsigned long rc;
	rc = plpar_hcall_4out(H_GET_PPP,0,0,0,0,entitled,unallocated,aggregation,resource);
	return 0;
}

/*
 * get_splpar_potential_characteristics().
 * Retrieve the potential_processors and max_entitled_capacity values
 * through the get-system-parameter rtas call.
 */
#define SPLPAR_CHARACTERISTICS_TOKEN 20
#define SPLPAR_MAXLENGTH 1026*(sizeof(char))
unsigned int get_splpar_potential_characteristics(void)
{
	/* return 0 for now.  Underlying rtas functionality is not yet complete. 12/01/2003*/
	return 0; 
#if 0 
	long call_status;
	unsigned long ret[2];

	char * buffer = kmalloc(SPLPAR_MAXLENGTH, GFP_KERNEL);

	printk("token for ibm,get-system-parameter (0x%x)\n",rtas_token("ibm,get-system-parameter"));

	call_status = rtas_call(rtas_token("ibm,get-system-parameter"), 3, 1,
				NULL,
				SPLPAR_CHARACTERISTICS_TOKEN,
				&buffer,
				SPLPAR_MAXLENGTH,
				(void *)&ret);

	if (call_status!=0) {
		printk("Error calling get-system-parameter (0x%lx)\n",call_status);
		kfree(buffer);
		return -1;
	} else {
		printk("get-system-parameter (%s)\n",buffer);
		kfree(buffer);
		/* TODO: Add code here to parse out value for system_potential_processors and partition_max_entitled_capacity */
		return 1;
	}
#endif
}

static int lparcfg_data(unsigned char *buf, unsigned long size)
{
	unsigned long n = 0;
	int shared, max_entitled_capacity;
	int processors, system_active_processors, system_potential_processors;
	struct device_node *root;
	const char *model = "";
	const char *system_id = "";
	unsigned int *lp_index_ptr, lp_index = 0;
	struct device_node *rtas_node;
	int *ip;
	unsigned long h_entitled,h_unallocated,h_aggregation,h_resource;

	if((buf == NULL) || (size > LPARCFG_BUFF_SIZE)) {
		return -EFAULT;
	}
	memset(buf, 0, size); 

	root = find_path_device("/");
	if (root) {
		model = get_property(root, "model", NULL);
		system_id = get_property(root, "system-id", NULL);
		lp_index_ptr = (unsigned int *)get_property(root, "ibm,partition-no", NULL);
		if(lp_index_ptr) lp_index = *lp_index_ptr;
	}

	n  = scnprintf(buf, LPARCFG_BUFF_SIZE - n,
		      "serial_number=%s\n", system_id); 

	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "system_type=%s\n", model); 

	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_id=%d\n", (int)lp_index); 

	rtas_node = find_path_device("/rtas");
	ip = (int *)get_property(rtas_node, "ibm,lrdr-capacity", NULL);
	if (ip == NULL) {
		system_active_processors = systemcfg->processorCount; 
	} else {
		system_active_processors = *(ip + 4);
	}

	if (cur_cpu_spec->firmware_features & FW_FEATURE_SPLPAR) {
		h_get_ppp(&h_entitled,&h_unallocated,&h_aggregation,&h_resource);
#ifdef DEBUG
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "R4=0x%lx\n", h_entitled);
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "R5=0x%lx\n", h_unallocated);
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "R6=0x%lx\n", h_aggregation);
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "R7=0x%lx\n", h_resource);
#endif /* DEBUG */
	}

	if (cur_cpu_spec->firmware_features & FW_FEATURE_SPLPAR) {
		system_potential_processors =  get_splpar_potential_characteristics();
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "system_active_processors=%ld\n", 
			      (h_resource >> 2*8) & 0xffff);
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "system_potential_processors=%d\n", 
			      system_potential_processors);
	} else {
		system_potential_processors = system_active_processors;
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "system_active_processors=%d\n", 
			      system_active_processors);
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "system_potential_processors=%d\n", 
			      system_potential_processors);
	}

	processors = systemcfg->processorCount;
	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_active_processors=%d\n", processors);  
	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_potential_processors=%d\n",
		      system_active_processors);

	/* max_entitled_capacity will come out of get_splpar_potential_characteristics() when that function is complete */
	max_entitled_capacity = system_active_processors * 100; 
	if (cur_cpu_spec->firmware_features & FW_FEATURE_SPLPAR) {
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "partition_entitled_capacity=%ld\n", h_entitled);
	} else {
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "partition_entitled_capacity=%d\n", system_active_processors*100);
	}

	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "partition_max_entitled_capacity=%d\n", 
		      max_entitled_capacity);

	shared = 0;
	n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
		      "shared_processor_mode=%d\n", shared);

	if (cur_cpu_spec->firmware_features & FW_FEATURE_SPLPAR) {
		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "pool=%ld\n", (h_aggregation >> 0*8)&0xffff);

		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "pool_capacity=%ld\n", (h_resource >> 3*8) &0xffff);

		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "group=%ld\n", (h_aggregation >> 2*8)&0xffff);

		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "capped=%ld\n", (h_resource >> 6*8)&0x40);

		n += scnprintf(buf+n, LPARCFG_BUFF_SIZE - n,
			      "capacity_weight=%d\n", (int)(h_resource>>5*8)&0xFF);
	}
	return 0;
}

/*
 * Interface for changing system parameters (variable capacity weight
 * and entitled capacity).  Format of input is "param_name=value";
 * anything after value is ignored.  Valid parameters at this time are
 * "partition_entitled_capacity" and "capacity_weight".  We use
 * H_SET_PPP to alter parameters.
 *
 * This function should be invoked only on systems with
 * FW_FEATURE_SPLPAR.
 */
static ssize_t lparcfg_write(struct file *file, const char __user *buf, size_t count, loff_t *off)
{
	char *kbuf;
	char *tmp;
	u64 new_entitled, *new_entitled_ptr = &new_entitled;
	u8 new_weight, *new_weight_ptr = &new_weight;

	unsigned long current_entitled;    /* parameters for h_get_ppp */
	unsigned long dummy;
	unsigned long resource;
	u8 current_weight;

	ssize_t retval = -ENOMEM;

	kbuf = kmalloc(count, GFP_KERNEL);
	if (!kbuf)
		goto out;

	retval = -EFAULT;
	if (copy_from_user(kbuf, buf, count))
		goto out;

	retval = -EINVAL;
	kbuf[count - 1] = '\0';
	tmp = strchr(kbuf, '=');
	if (!tmp)
		goto out;

	*tmp++ = '\0';

	if (!strcmp(kbuf, "partition_entitled_capacity")) {
		char *endp;
		*new_entitled_ptr = (u64)simple_strtoul(tmp, &endp, 10);
		if (endp == tmp)
			goto out;
		new_weight_ptr = &current_weight;
	} else if (!strcmp(kbuf, "capacity_weight")) {
		char *endp;
		*new_weight_ptr = (u8)simple_strtoul(tmp, &endp, 10);
		if (endp == tmp)
			goto out;
		new_entitled_ptr = &current_entitled;
	} else
		goto out;

	/* Get our current parameters */
	retval = h_get_ppp(&current_entitled, &dummy, &dummy, &resource);
	if (retval) {
		retval = -EIO;
		goto out;
	}

	current_weight = (resource>>5*8)&0xFF;

	pr_debug("%s: current_entitled = %lu, current_weight = %lu\n",
		 __FUNCTION__, current_entitled, current_weight);

	pr_debug("%s: new_entitled = %lu, new_weight = %lu\n",
		 __FUNCTION__, *new_entitled_ptr, *new_weight_ptr);

	retval = plpar_hcall_norets(H_SET_PPP, *new_entitled_ptr,
				    *new_weight_ptr);

	if (retval == H_Success || retval == H_Constrained) {
		retval = count;
	} else if (retval == H_Busy) {
		retval = -EBUSY;
	} else if (retval == H_Hardware) {
		retval = -EIO;
	} else if (retval == H_Parameter) {
		retval = -EINVAL;
	} else {
		printk(KERN_WARNING "%s: received unknown hv return code %ld",
		       __FUNCTION__, retval);
		retval = -EIO;
	}

out:
	kfree(kbuf);
	return retval;
}

#endif /* CONFIG_PPC_PSERIES */


static ssize_t lparcfg_read(struct file *file, char *buf,
			    size_t count, loff_t *ppos)
{
	struct proc_dir_entry *dp = PDE(file->f_dentry->d_inode);
	unsigned long *data = (unsigned long *)dp->data;
	unsigned long p;
	ssize_t read;
	char * pnt;

	if (!data) {
		printk(KERN_ERR "lparcfg: read failed no data\n");
		return -EIO;
	}

	if(ppos) {
		p = *ppos;
	} else {
		return -EFAULT;
	}

	if (p >= LPARCFG_BUFF_SIZE) return 0;

	lparcfg_data((unsigned char *)data, LPARCFG_BUFF_SIZE); 
	if (count > (strlen((char *)data) - p))
		count = (strlen((char *)data)) - p;
	read = 0;

	pnt = (char *)(data) + p;
	copy_to_user(buf, (void *)pnt, count);
	read += count;
	*ppos += read;
	return read;
}

static int lparcfg_open(struct inode * inode, struct file * file)
{
	struct proc_dir_entry *dp = PDE(file->f_dentry->d_inode);
	unsigned int *data = (unsigned int *)dp->data;

	if (!data) {
		printk(KERN_ERR "lparcfg: open failed no data\n");
		return -EIO;
	}

	return 0;
}

struct file_operations lparcfg_fops = {
	owner:		THIS_MODULE,
	read:		lparcfg_read,
	open:		lparcfg_open,
};

int __init lparcfg_init(void)
{
	struct proc_dir_entry *ent;
	mode_t mode = S_IRUSR;

	/* Allow writing if we have FW_FEATURE_SPLPAR */
	if (cur_cpu_spec->firmware_features & FW_FEATURE_SPLPAR) {
		lparcfg_fops.write = lparcfg_write;
		mode |= S_IWUSR;
	}

	ent = create_proc_entry("ppc64/lparcfg", mode, NULL);
	if (ent) {
		ent->proc_fops = &lparcfg_fops;
		ent->data = kmalloc(LPARCFG_BUFF_SIZE, GFP_KERNEL);
		if (!ent->data) {
			printk(KERN_ERR "Failed to allocate buffer for lparcfg\n");
			remove_proc_entry("lparcfg", ent->parent);
			return -ENOMEM;
		}
	} else {
		printk(KERN_ERR "Failed to create ppc64/lparcfg\n");
		return -EIO;
	}

	proc_ppc64_lparcfg = ent;
	return 0;
}

void __exit lparcfg_cleanup(void)
{
	if (proc_ppc64_lparcfg) {
		if (proc_ppc64_lparcfg->data) {
		    kfree(proc_ppc64_lparcfg->data);
		}
		remove_proc_entry("lparcfg", proc_ppc64_lparcfg->parent);
	}
}

module_init(lparcfg_init);
module_exit(lparcfg_cleanup);
MODULE_DESCRIPTION("Interface for LPAR configuration data");
MODULE_AUTHOR("Dave Engebretsen");
MODULE_LICENSE("GPL");
