
/*********************************************************************
 *                
 * Filename:      thinkpad.c
 * Description:   loadable kernel module that serves as a router of ioctl
 *                requests to other modules that control various hardware
 *                features of IBM ThinkPads
 * Author:        Thomas Hood
 * Created:       19 July 1999 
 *
 * Notes:  This driver exists so that several thinkpad devices can be
 *         driven with only a single permanent misc device minor number
 *         allocated.  With the advent of devfs, such tricks are no longer
 *         necessary.  The elegant thing to do, under devfs, would be
 *         for each driver to have its own device file under /dev/thinkpad.
 *         However, the present scheme continues to work under devfs and
 *         will not be changed unless there is a pressing need, since it
 *         would involve substantial changes both to the drivers and to
 *         the tpctl program that uses them.
 *
 * Please report bugs to the author ASAP.
 * 
 *     Copyright (c) 1999 J.D. Thomas Hood, 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.
 * 
 *     To receive a copy of the GNU General Public License, please write
 *     to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 *     Boston, MA 02111-1307 USA
 *     
 ********************************************************************/

#include "thinkpad_driver.h"

#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/proc_fs.h>
#include <asm/uaccess.h>
#include <linux/wrapper.h>
#include "thinkpad_common.h"
#include "thinkpad.h"
#include "smapi.h"
#include "superio.h"
#include "rtcmosram.h"
#include "thinkpadpm.h"

/****** definitions ******/

#define MINOR_THINKPAD 170

/****** forward declarations ******/
static int thinkpad_open(
	struct inode * pinodeThe,
	struct file * pfileThe
);
static int thinkpad_release(
	struct inode * pinodeThe,
	struct file * pfileThe 
);
static int thinkpad_ioctl(
	struct inode * pinodeThe,
	struct file * pfileThe,
	unsigned int uintIoctlNum,
	unsigned long ulongIoctlArg
);

typedef enum _mod_t {
	MOD_SMAPI,
	MOD_SUPERIO,
	MOD_RTCMOSRAM,
	MOD_THINKPADPM
} mod_t;

/* pointer to an executable "do" function returning an integer */
typedef int (* pxint_do_t)( unsigned long, flag_t );

/****** variables ******/

/*** global ***/
struct proc_dir_entry *thinkpad_ppde;

/*** module parameters ***/
static int enable_smapi = 1;
static int enable_superio = 1;
static int enable_rtcmosram = 1;
static int enable_thinkpadpm = 1;
MODULE_PARM( enable_smapi, "i" );
MODULE_PARM( enable_superio, "i" );
MODULE_PARM( enable_rtcmosram, "i" );
MODULE_PARM( enable_thinkpadpm, "i" );
MODULE_AUTHOR( "Thomas Hood" );
MODULE_DESCRIPTION( "Metadriver for IBM ThinkPad hardware drivers" );
#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,4,10)
MODULE_LICENSE( "GPL" );
#endif

/*** local ***/
static const char _szMyName[] = "thinkpad";
static const char _szMyVersion[] = "4.0";
static const char _szSmapiName[] = "smapi";
static const char _szSuperioName[] = "superio";
static const char _szRtcmosramName[] = "rtcmosram";
static const char _szThinkpadpmName[] = "thinkpadpm";
static flag_t _fInUse = 1;

static struct file_operations _fileopsThinkpad = {
	.ioctl   = thinkpad_ioctl,
	.open    = thinkpad_open,
	.release = thinkpad_release,
	.owner   = THIS_MODULE
};


/****** functions ******/

static int thinkpad_read_proc(
	char *pchBuf, 
	char **ppchStart, 
	off_t off,
	int intCount, 
	int *pintEof, 
	void *pdata
) {
	return snprintf(
		pchBuf, intCount,
		"%s version %s enabled to access modules: %s %s %s %s.\n",
		_szMyName, _szMyVersion,
		enable_smapi ? _szSmapiName : "",
		enable_superio ? _szSuperioName : "",
		enable_rtcmosram ? _szRtcmosramName : "",
		enable_thinkpadpm ? _szThinkpadpmName : ""
	);
}

static int thinkpad_open(
	struct inode * pinodeThe,
	struct file * pfileThe
) {

	if ( _fInUse ) return -EBUSY;

	_fInUse = 1;

	return 0;
}


static int thinkpad_release(
	struct inode * pinodeThe,
	struct file * pfileThe
) {

	_fInUse = 0;

	return 0;
}


static flag_t caller_has_w( struct file * pfileThe )
{
	return ((pfileThe->f_mode) & FMODE_WRITE) ? 1 : 0;
}


static int thinkpad_ioctl(
	struct inode * pinodeThe,
	struct file * pfileThe,
	unsigned int uintIoctlNum,
	unsigned long ulongIoctlArg
) {
	unsigned long ulRtnCopy;

#ifdef DEBUG_VERBOSE
	printk( "%s: Doing ioctl number 0x%x\n", _szMyName, uintIoctlNum );
#endif

	switch ( uintIoctlNum ) {
		/* should  return  at the end of each case block */

		case IOCTL_THINKPAD_GETVER: 
			ulRtnCopy = copy_to_user(
				(byte *)ulongIoctlArg,
				_szMyVersion,
				strlen( _szMyVersion ) + 1
			);
			if ( ulRtnCopy ) return -EFAULT;
			return 0;

		case IOCTL_THINKPAD_ENABLE:
		case IOCTL_THINKPAD_DISABLE: {
			long lRes;
			char szBuf[LEN_NAME_MAX+1];
			flag_t fEnable;

			fEnable = ( uintIoctlNum == IOCTL_THINKPAD_ENABLE ) ? 1 : 0;

			if ( !caller_has_w( pfileThe ) ) return -EACCES;

			lRes = strncpy_from_user(
				szBuf,
				(char *)ulongIoctlArg,
				LEN_NAME_MAX
			);
			szBuf[LEN_NAME_MAX] = '\0'; /* ensure termination */

			printk(KERN_INFO
				"%s: %sabling %s\n", _szMyName, fEnable?"En":"Dis", szBuf
			);

			if ( strnicmp( szBuf, _szSmapiName, LEN_NAME_MAX ) == 0 ) {
				enable_smapi = fEnable; return 0;
			} else if ( strnicmp( szBuf, _szSuperioName, LEN_NAME_MAX ) == 0 ) {
				enable_superio = fEnable; return 0;
			} else if ( strnicmp( szBuf, _szRtcmosramName, LEN_NAME_MAX ) == 0 ) {
				enable_rtcmosram = fEnable; return 0; 
			} else if ( strnicmp( szBuf, _szThinkpadpmName, LEN_NAME_MAX ) == 0 ) {
				enable_thinkpadpm = fEnable; return 0; 
			}
			return -EINVAL;
		}

		case IOCTL_SMAPI_REQUEST: {
			pxint_do_t pxint_doSmapi;
			int intRet;
			if ( ! enable_smapi ) return -ETHINKPAD_MODULE_DISABLED;
			pxint_doSmapi = (pxint_do_t)inter_module_get_request( "smapi_do" , _szSmapiName );
			if ( pxint_doSmapi == NULL ) return -ETHINKPAD_MODULE_NOT_FOUND;
			intRet = (*pxint_doSmapi)(
				ulongIoctlArg, 
				caller_has_w( pfileThe )
			);
			inter_module_put("smapi_do");
			if ( intRet > 0 ) intRet = -ETHINKPAD_PROGRAMMING;
			return intRet;
		}

		case IOCTL_SUPERIO_REQUEST:  {
			pxint_do_t pxint_doSuperio;
			int intRet;
			if ( ! enable_superio ) return -ETHINKPAD_MODULE_DISABLED;
			pxint_doSuperio = (pxint_do_t)inter_module_get_request( "superio_do" , _szSuperioName );
			if ( pxint_doSuperio == NULL ) return -ETHINKPAD_MODULE_NOT_FOUND;
			intRet = (*pxint_doSuperio)(
				ulongIoctlArg, 
				caller_has_w( pfileThe )
			);
			inter_module_put("superio_do");
			if ( intRet > 0 ) intRet = -ETHINKPAD_PROGRAMMING;
			return intRet;
		}

		case IOCTL_RTCMOSRAM_REQUEST: {
			pxint_do_t pxint_doRtcmosram;
			int intRet;
			if ( ! enable_rtcmosram ) return -ETHINKPAD_MODULE_DISABLED;
			pxint_doRtcmosram = (pxint_do_t)inter_module_get_request( "rtcmosram_do" , _szRtcmosramName );
			if ( pxint_doRtcmosram == NULL ) return -ETHINKPAD_MODULE_NOT_FOUND;
			intRet = (*pxint_doRtcmosram)(
				ulongIoctlArg, 
				caller_has_w( pfileThe )
			);
			inter_module_put("rtcmosram_do");
			if ( intRet > 0 ) intRet = -ETHINKPAD_PROGRAMMING;
			return intRet;
		}

		case IOCTL_THINKPADPM_REQUEST: {
			pxint_do_t pxint_doThinkpadpm;
			int intRet;
			if ( ! enable_thinkpadpm ) return -ETHINKPAD_MODULE_DISABLED;
			pxint_doThinkpadpm = (pxint_do_t)inter_module_get_request( "thinkpadpm_do" , _szThinkpadpmName );
			if ( pxint_doThinkpadpm == NULL ) return -ETHINKPAD_MODULE_NOT_FOUND;
			intRet = (*pxint_doThinkpadpm)(
				ulongIoctlArg, 
				caller_has_w( pfileThe )
			);
			inter_module_put("thinkpadpm_do");
			if ( intRet > 0 ) intRet = -ETHINKPAD_PROGRAMMING;
			return intRet;
		}

		default:
			/* ioctl number not recognized -- do nothing */
			return -ENOTTY;

	} /* switch */
			
	return -ETHINKPAD_PROGRAMMING; /* We should never arrive here */
}


static struct miscdevice _structmiscdeviceThinkpad = {
	.minor = MINOR_THINKPAD,
	.name  = "thinkpad/thinkpad",
	.fops  = &_fileopsThinkpad
};

static int __init thinkpad_init( void )
{
	int intRtnMiscRegister;

	intRtnMiscRegister = misc_register( &_structmiscdeviceThinkpad );
	if ( intRtnMiscRegister ) {
		if ( intRtnMiscRegister == -EBUSY ) {
			printk(KERN_ERR
				"%s: Error %d was returned by misc_register(): device is busy\n",
				_szMyName, intRtnMiscRegister
			);
		} else {
			printk(KERN_ERR
				"%s: Error %d was returned by misc_register()\n",
				_szMyName, intRtnMiscRegister
			);
		}
		return intRtnMiscRegister;
	}

	/* this module has been registered successfully */

	printk(KERN_INFO
		"%s: I have registered to handle major: %d minor: %d.\n",
		_szMyName, 10, MINOR_THINKPAD
	);

 	if ( (thinkpad_ppde = proc_mkdir( "driver/thinkpad", NULL )) == NULL ) {
		printk(KERN_ERR "%s: Could not proc_mkdir() /proc/driver/thinkpad/\n", _szMyName );
	} else { /* /proc/thinkpad was created. */
		if ( !create_proc_read_entry( "driver/thinkpad/thinkpad", S_IFREG | S_IRUGO, NULL, thinkpad_read_proc, NULL )) {
			printk(KERN_ERR "%s: Could not create_proc_read_entry() /proc/driver/thinkpad/thinkpad\n", _szMyName );
		}
	}
	/* proc entries created */

	_fInUse = 0;

	return 0;
}
		

static void __exit thinkpad_exit( void )
{
	int intRtn;

 	remove_proc_entry( "driver/thinkpad/thinkpad", NULL );
 	remove_proc_entry( "driver/thinkpad", NULL );

	intRtn = misc_deregister( &_structmiscdeviceThinkpad );
	if ( intRtn != 0 ) printk(KERN_ERR "%s: Error reported by misc_deregister: %d\n", _szMyName, intRtn );

	printk(KERN_INFO
		"%s: I have cleaned up.\n",
		_szMyName
	);

	return;
}

module_init(thinkpad_init);
module_exit(thinkpad_exit);

