2006-07-19 09:40:22 +04:00
/*
* Force feedback support for Linux input subsystem
*
* Copyright ( c ) 2006 Anssi Hannula < anssi . hannula @ gmail . com >
* Copyright ( c ) 2006 Dmitry Torokhov < dtor @ mail . ru >
*/
/*
* 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
*/
/* #define DEBUG */
# define debug(format, arg...) pr_debug("ff-core: " format "\n", ## arg)
# include <linux/input.h>
# include <linux/module.h>
# include <linux/mutex.h>
/*
* Check that the effect_id is a valid effect and whether the user
* is the owner
*/
static int check_effect_access ( struct ff_device * ff , int effect_id ,
struct file * file )
{
if ( effect_id < 0 | | effect_id > = ff - > max_effects | |
! ff - > effect_owners [ effect_id ] )
return - EINVAL ;
if ( file & & ff - > effect_owners [ effect_id ] ! = file )
return - EACCES ;
return 0 ;
}
/*
* Checks whether 2 effects can be combined together
*/
static inline int check_effects_compatible ( struct ff_effect * e1 ,
struct ff_effect * e2 )
{
return e1 - > type = = e2 - > type & &
( e1 - > type ! = FF_PERIODIC | |
e1 - > u . periodic . waveform = = e2 - > u . periodic . waveform ) ;
}
/*
* Convert an effect into compatible one
*/
static int compat_effect ( struct ff_device * ff , struct ff_effect * effect )
{
int magnitude ;
switch ( effect - > type ) {
case FF_RUMBLE :
if ( ! test_bit ( FF_PERIODIC , ff - > ffbit ) )
return - EINVAL ;
/*
* calculate manginude of sine wave as average of rumble ' s
* 2 / 3 of strong magnitude and 1 / 3 of weak magnitude
*/
magnitude = effect - > u . rumble . strong_magnitude / 3 +
effect - > u . rumble . weak_magnitude / 6 ;
effect - > type = FF_PERIODIC ;
effect - > u . periodic . waveform = FF_SINE ;
effect - > u . periodic . period = 50 ;
effect - > u . periodic . magnitude = max ( magnitude , 0x7fff ) ;
effect - > u . periodic . offset = 0 ;
effect - > u . periodic . phase = 0 ;
effect - > u . periodic . envelope . attack_length = 0 ;
effect - > u . periodic . envelope . attack_level = 0 ;
effect - > u . periodic . envelope . fade_length = 0 ;
effect - > u . periodic . envelope . fade_level = 0 ;
return 0 ;
default :
/* Let driver handle conversion */
return 0 ;
}
}
/**
* input_ff_upload ( ) - upload effect into force - feedback device
* @ dev : input device
* @ effect : effect to be uploaded
* @ file : owner of the effect
*/
int input_ff_upload ( struct input_dev * dev , struct ff_effect * effect ,
struct file * file )
{
struct ff_device * ff = dev - > ff ;
struct ff_effect * old ;
int ret = 0 ;
int id ;
if ( ! test_bit ( EV_FF , dev - > evbit ) )
return - ENOSYS ;
if ( effect - > type < FF_EFFECT_MIN | | effect - > type > FF_EFFECT_MAX | |
! test_bit ( effect - > type , dev - > ffbit ) ) {
debug ( " invalid or not supported effect type in upload " ) ;
return - EINVAL ;
}
if ( effect - > type = = FF_PERIODIC & &
( effect - > u . periodic . waveform < FF_WAVEFORM_MIN | |
effect - > u . periodic . waveform > FF_WAVEFORM_MAX | |
! test_bit ( effect - > u . periodic . waveform , dev - > ffbit ) ) ) {
debug ( " invalid or not supported wave form in upload " ) ;
return - EINVAL ;
}
if ( ! test_bit ( effect - > type , ff - > ffbit ) ) {
ret = compat_effect ( ff , effect ) ;
if ( ret )
return ret ;
}
mutex_lock ( & ff - > mutex ) ;
if ( effect - > id = = - 1 ) {
for ( id = 0 ; id < ff - > max_effects ; id + + )
if ( ! ff - > effect_owners [ id ] )
break ;
if ( id > = ff - > max_effects ) {
ret = - ENOSPC ;
goto out ;
}
effect - > id = id ;
old = NULL ;
} else {
id = effect - > id ;
ret = check_effect_access ( ff , id , file ) ;
if ( ret )
goto out ;
old = & ff - > effects [ id ] ;
if ( ! check_effects_compatible ( effect , old ) ) {
ret = - EINVAL ;
goto out ;
}
}
ret = ff - > upload ( dev , effect , old ) ;
if ( ret )
goto out ;
ff - > effects [ id ] = * effect ;
ff - > effect_owners [ id ] = file ;
out :
mutex_unlock ( & ff - > mutex ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( input_ff_upload ) ;
/*
* Erases the effect if the requester is also the effect owner . The mutex
* should already be locked before calling this function .
*/
static int erase_effect ( struct input_dev * dev , int effect_id ,
struct file * file )
{
struct ff_device * ff = dev - > ff ;
int error ;
error = check_effect_access ( ff , effect_id , file ) ;
if ( error )
return error ;
ff - > playback ( dev , effect_id , 0 ) ;
if ( ff - > erase ) {
error = ff - > erase ( dev , effect_id ) ;
if ( error )
return error ;
}
ff - > effect_owners [ effect_id ] = NULL ;
return 0 ;
}
/**
2006-11-24 08:43:09 +03:00
* input_ff_erase - erase a force - feedback effect from device
2006-07-19 09:40:22 +04:00
* @ dev : input device to erase effect from
* @ effect_id : id of the ffect to be erased
* @ file : purported owner of the request
*
* This function erases a force - feedback effect from specified device .
* The effect will only be erased if it was uploaded through the same
* file handle that is requesting erase .
*/
int input_ff_erase ( struct input_dev * dev , int effect_id , struct file * file )
{
struct ff_device * ff = dev - > ff ;
int ret ;
if ( ! test_bit ( EV_FF , dev - > evbit ) )
return - ENOSYS ;
mutex_lock ( & ff - > mutex ) ;
ret = erase_effect ( dev , effect_id , file ) ;
mutex_unlock ( & ff - > mutex ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( input_ff_erase ) ;
/*
* flush_effects - erase all effects owned by a file handle
*/
static int flush_effects ( struct input_dev * dev , struct file * file )
{
struct ff_device * ff = dev - > ff ;
int i ;
debug ( " flushing now " ) ;
mutex_lock ( & ff - > mutex ) ;
for ( i = 0 ; i < ff - > max_effects ; i + + )
erase_effect ( dev , i , file ) ;
mutex_unlock ( & ff - > mutex ) ;
return 0 ;
}
/**
* input_ff_event ( ) - generic handler for force - feedback events
* @ dev : input device to send the effect to
* @ type : event type ( anything but EV_FF is ignored )
* @ code : event code
* @ value : event value
*/
int input_ff_event ( struct input_dev * dev , unsigned int type ,
unsigned int code , int value )
{
struct ff_device * ff = dev - > ff ;
if ( type ! = EV_FF )
return 0 ;
mutex_lock ( & ff - > mutex ) ;
switch ( code ) {
case FF_GAIN :
if ( ! test_bit ( FF_GAIN , dev - > ffbit ) | | value > 0xffff )
break ;
ff - > set_gain ( dev , value ) ;
break ;
case FF_AUTOCENTER :
if ( ! test_bit ( FF_AUTOCENTER , dev - > ffbit ) | | value > 0xffff )
break ;
ff - > set_autocenter ( dev , value ) ;
break ;
default :
2007-05-03 08:51:10 +04:00
if ( check_effect_access ( ff , code , NULL ) = = 0 )
ff - > playback ( dev , code , value ) ;
2006-07-19 09:40:22 +04:00
break ;
}
mutex_unlock ( & ff - > mutex ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( input_ff_event ) ;
/**
* input_ff_create ( ) - create force - feedback device
* @ dev : input device supporting force - feedback
* @ max_effects : maximum number of effects supported by the device
*
* This function allocates all necessary memory for a force feedback
* portion of an input device and installs all default handlers .
* @ dev - > ffbit should be already set up before calling this function .
* Once ff device is created you need to setup its upload , erase ,
* playback and other handlers before registering input device
*/
int input_ff_create ( struct input_dev * dev , int max_effects )
{
struct ff_device * ff ;
int i ;
if ( ! max_effects ) {
printk ( KERN_ERR
" ff-core: cannot allocate device without any effects \n " ) ;
return - EINVAL ;
}
ff = kzalloc ( sizeof ( struct ff_device ) +
max_effects * sizeof ( struct file * ) , GFP_KERNEL ) ;
if ( ! ff )
return - ENOMEM ;
ff - > effects = kcalloc ( max_effects , sizeof ( struct ff_effect ) ,
GFP_KERNEL ) ;
if ( ! ff - > effects ) {
kfree ( ff ) ;
return - ENOMEM ;
}
ff - > max_effects = max_effects ;
mutex_init ( & ff - > mutex ) ;
dev - > ff = ff ;
dev - > flush = flush_effects ;
dev - > event = input_ff_event ;
set_bit ( EV_FF , dev - > evbit ) ;
/* Copy "true" bits into ff device bitmap */
for ( i = 0 ; i < = FF_MAX ; i + + )
if ( test_bit ( i , dev - > ffbit ) )
set_bit ( i , ff - > ffbit ) ;
/* we can emulate RUMBLE with periodic effects */
if ( test_bit ( FF_PERIODIC , ff - > ffbit ) )
set_bit ( FF_RUMBLE , dev - > ffbit ) ;
return 0 ;
}
EXPORT_SYMBOL_GPL ( input_ff_create ) ;
/**
* input_ff_free ( ) - frees force feedback portion of input device
2006-11-24 08:43:09 +03:00
* @ dev : input device supporting force feedback
2006-07-19 09:40:22 +04:00
*
* This function is only needed in error path as input core will
* automatically free force feedback structures when device is
* destroyed .
*/
void input_ff_destroy ( struct input_dev * dev )
{
clear_bit ( EV_FF , dev - > evbit ) ;
if ( dev - > ff ) {
if ( dev - > ff - > destroy )
dev - > ff - > destroy ( dev - > ff ) ;
kfree ( dev - > ff - > private ) ;
kfree ( dev - > ff ) ;
dev - > ff = NULL ;
}
}
EXPORT_SYMBOL_GPL ( input_ff_destroy ) ;