2007-05-14 11:57:40 +04:00
/*
* HID raw devices , giving access to raw HID events .
*
* In comparison to hiddev , this device does not process the
* hid events at all ( no parsing , no lookups ) . This lets applications
* to work on raw hid events as they want to , and avoids a need to
* use a transport - specific userspace libhid / libusb libraries .
*
* Copyright ( c ) 2007 Jiri Kosina
*/
/*
* This program is free software ; you can redistribute it and / or modify it
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
* 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 . ,
* 51 Franklin St - Fifth Floor , Boston , MA 02110 - 1301 USA .
*/
# include <linux/fs.h>
# include <linux/module.h>
# include <linux/errno.h>
# include <linux/kernel.h>
# include <linux/init.h>
# include <linux/cdev.h>
# include <linux/poll.h>
# include <linux/device.h>
# include <linux/major.h>
# include <linux/hid.h>
# include <linux/mutex.h>
2008-05-15 20:25:44 +04:00
# include <linux/smp_lock.h>
2007-05-14 11:57:40 +04:00
# include <linux/hidraw.h>
static int hidraw_major ;
static struct cdev hidraw_cdev ;
static struct class * hidraw_class ;
static struct hidraw * hidraw_table [ HIDRAW_MAX_DEVICES ] ;
2008-10-31 02:07:23 +03:00
static DEFINE_MUTEX ( minors_lock ) ;
2007-05-14 11:57:40 +04:00
static ssize_t hidraw_read ( struct file * file , char __user * buffer , size_t count , loff_t * ppos )
{
struct hidraw_list * list = file - > private_data ;
int ret = 0 , len ;
char * report ;
DECLARE_WAITQUEUE ( wait , current ) ;
while ( ret = = 0 ) {
mutex_lock ( & list - > read_mutex ) ;
if ( list - > head = = list - > tail ) {
add_wait_queue ( & list - > hidraw - > wait , & wait ) ;
set_current_state ( TASK_INTERRUPTIBLE ) ;
while ( list - > head = = list - > tail ) {
if ( file - > f_flags & O_NONBLOCK ) {
ret = - EAGAIN ;
break ;
}
if ( signal_pending ( current ) ) {
ret = - ERESTARTSYS ;
break ;
}
if ( ! list - > hidraw - > exist ) {
ret = - EIO ;
break ;
}
/* allow O_NONBLOCK to work well from other threads */
mutex_unlock ( & list - > read_mutex ) ;
schedule ( ) ;
mutex_lock ( & list - > read_mutex ) ;
set_current_state ( TASK_INTERRUPTIBLE ) ;
}
set_current_state ( TASK_RUNNING ) ;
remove_wait_queue ( & list - > hidraw - > wait , & wait ) ;
}
if ( ret )
goto out ;
report = list - > buffer [ list - > tail ] . value ;
len = list - > buffer [ list - > tail ] . len > count ?
count : list - > buffer [ list - > tail ] . len ;
if ( copy_to_user ( buffer , list - > buffer [ list - > tail ] . value , len ) ) {
ret = - EFAULT ;
goto out ;
}
ret + = len ;
kfree ( list - > buffer [ list - > tail ] . value ) ;
list - > tail = ( list - > tail + 1 ) & ( HIDRAW_BUFFER_SIZE - 1 ) ;
}
out :
mutex_unlock ( & list - > read_mutex ) ;
return ret ;
}
/* the first byte is expected to be a report number */
static ssize_t hidraw_write ( struct file * file , const char __user * buffer , size_t count , loff_t * ppos )
{
unsigned int minor = iminor ( file - > f_path . dentry - > d_inode ) ;
2008-05-26 13:25:26 +04:00
/* FIXME: What stops hidraw_table going NULL */
2007-05-14 11:57:40 +04:00
struct hid_device * dev = hidraw_table [ minor ] - > hid ;
__u8 * buf ;
int ret = 0 ;
if ( ! dev - > hid_output_raw_report )
return - ENODEV ;
2008-09-17 21:41:58 +04:00
if ( count > HID_MAX_BUFFER_SIZE ) {
2007-05-14 11:57:40 +04:00
printk ( KERN_WARNING " hidraw: pid %d passed too large report \n " ,
2007-10-19 10:40:40 +04:00
task_pid_nr ( current ) ) ;
2007-05-14 11:57:40 +04:00
return - EINVAL ;
}
if ( count < 2 ) {
printk ( KERN_WARNING " hidraw: pid %d passed too short report \n " ,
2007-10-19 10:40:40 +04:00
task_pid_nr ( current ) ) ;
2007-05-14 11:57:40 +04:00
return - EINVAL ;
}
buf = kmalloc ( count * sizeof ( __u8 ) , GFP_KERNEL ) ;
if ( ! buf )
return - ENOMEM ;
if ( copy_from_user ( buf , buffer , count ) ) {
ret = - EFAULT ;
goto out ;
}
ret = dev - > hid_output_raw_report ( dev , buf , count ) ;
out :
kfree ( buf ) ;
return ret ;
}
static unsigned int hidraw_poll ( struct file * file , poll_table * wait )
{
struct hidraw_list * list = file - > private_data ;
poll_wait ( file , & list - > hidraw - > wait , wait ) ;
if ( list - > head ! = list - > tail )
return POLLIN | POLLRDNORM ;
if ( ! list - > hidraw - > exist )
return POLLERR | POLLHUP ;
return 0 ;
}
static int hidraw_open ( struct inode * inode , struct file * file )
{
unsigned int minor = iminor ( inode ) ;
struct hidraw * dev ;
struct hidraw_list * list ;
int err = 0 ;
if ( ! ( list = kzalloc ( sizeof ( struct hidraw_list ) , GFP_KERNEL ) ) ) {
err = - ENOMEM ;
goto out ;
}
2008-10-31 02:07:23 +03:00
lock_kernel ( ) ;
mutex_lock ( & minors_lock ) ;
2007-05-14 11:57:40 +04:00
if ( ! hidraw_table [ minor ] ) {
printk ( KERN_EMERG " hidraw device with minor %d doesn't exist \n " ,
minor ) ;
kfree ( list ) ;
err = - ENODEV ;
goto out_unlock ;
}
list - > hidraw = hidraw_table [ minor ] ;
mutex_init ( & list - > read_mutex ) ;
list_add_tail ( & list - > node , & hidraw_table [ minor ] - > list ) ;
file - > private_data = list ;
dev = hidraw_table [ minor ] ;
2008-10-31 02:07:23 +03:00
if ( ! dev - > open + + ) {
2008-12-17 17:38:03 +03:00
if ( dev - > hid - > ll_driver - > power ) {
err = dev - > hid - > ll_driver - > power ( dev - > hid , PM_HINT_FULLON ) ;
if ( err < 0 )
goto out_unlock ;
}
2008-10-31 02:07:23 +03:00
err = dev - > hid - > ll_driver - > open ( dev - > hid ) ;
2008-12-17 17:38:03 +03:00
if ( err < 0 ) {
if ( dev - > hid - > ll_driver - > power )
dev - > hid - > ll_driver - > power ( dev - > hid , PM_HINT_NORMAL ) ;
2008-10-31 02:07:23 +03:00
dev - > open - - ;
2008-12-17 17:38:03 +03:00
}
2008-10-31 02:07:23 +03:00
}
2007-05-14 11:57:40 +04:00
out_unlock :
2008-10-31 02:07:23 +03:00
mutex_unlock ( & minors_lock ) ;
2008-05-15 20:25:44 +04:00
unlock_kernel ( ) ;
2008-10-31 02:07:23 +03:00
out :
2007-05-14 11:57:40 +04:00
return err ;
}
static int hidraw_release ( struct inode * inode , struct file * file )
{
unsigned int minor = iminor ( inode ) ;
struct hidraw * dev ;
struct hidraw_list * list = file - > private_data ;
if ( ! hidraw_table [ minor ] ) {
printk ( KERN_EMERG " hidraw device with minor %d doesn't exist \n " ,
minor ) ;
return - ENODEV ;
}
list_del ( & list - > node ) ;
dev = hidraw_table [ minor ] ;
2008-12-15 15:12:08 +03:00
if ( ! - - dev - > open ) {
2008-12-17 17:38:03 +03:00
if ( list - > hidraw - > exist ) {
if ( dev - > hid - > ll_driver - > power )
dev - > hid - > ll_driver - > power ( dev - > hid , PM_HINT_NORMAL ) ;
2008-05-16 13:49:16 +04:00
dev - > hid - > ll_driver - > close ( dev - > hid ) ;
2008-12-17 17:38:03 +03:00
} else {
2007-05-14 11:57:40 +04:00
kfree ( list - > hidraw ) ;
2008-12-17 17:38:03 +03:00
}
2007-05-14 11:57:40 +04:00
}
2008-06-24 16:45:27 +04:00
kfree ( list ) ;
2007-05-14 11:57:40 +04:00
return 0 ;
}
2008-05-26 13:25:26 +04:00
static long hidraw_ioctl ( struct file * file , unsigned int cmd ,
unsigned long arg )
2007-05-14 11:57:40 +04:00
{
2008-05-26 13:25:26 +04:00
struct inode * inode = file - > f_path . dentry - > d_inode ;
2007-05-14 11:57:40 +04:00
unsigned int minor = iminor ( inode ) ;
2008-05-26 13:25:26 +04:00
long ret = 0 ;
/* FIXME: What stops hidraw_table going NULL */
2007-05-14 11:57:40 +04:00
struct hidraw * dev = hidraw_table [ minor ] ;
void __user * user_arg = ( void __user * ) arg ;
2008-05-26 13:25:26 +04:00
lock_kernel ( ) ;
2007-05-14 11:57:40 +04:00
switch ( cmd ) {
case HIDIOCGRDESCSIZE :
if ( put_user ( dev - > hid - > rsize , ( int __user * ) arg ) )
2008-05-26 13:25:26 +04:00
ret = - EFAULT ;
break ;
2007-05-14 11:57:40 +04:00
case HIDIOCGRDESC :
{
__u32 len ;
if ( get_user ( len , ( int __user * ) arg ) )
2008-05-26 13:25:26 +04:00
ret = - EFAULT ;
else if ( len > HID_MAX_DESCRIPTOR_SIZE - 1 )
ret = - EINVAL ;
else if ( copy_to_user ( user_arg + offsetof (
struct hidraw_report_descriptor ,
value [ 0 ] ) ,
dev - > hid - > rdesc ,
min ( dev - > hid - > rsize , len ) ) )
ret = - EFAULT ;
break ;
2007-05-14 11:57:40 +04:00
}
case HIDIOCGRAWINFO :
{
struct hidraw_devinfo dinfo ;
dinfo . bustype = dev - > hid - > bus ;
dinfo . vendor = dev - > hid - > vendor ;
dinfo . product = dev - > hid - > product ;
if ( copy_to_user ( user_arg , & dinfo , sizeof ( dinfo ) ) )
2008-05-26 13:25:26 +04:00
ret = - EFAULT ;
break ;
2007-05-14 11:57:40 +04:00
}
default :
2008-11-12 18:14:08 +03:00
{
struct hid_device * hid = dev - > hid ;
2009-02-03 16:35:17 +03:00
if ( _IOC_TYPE ( cmd ) ! = ' H ' | | _IOC_DIR ( cmd ) ! = _IOC_READ ) {
ret = - EINVAL ;
break ;
}
2008-11-12 18:14:08 +03:00
if ( _IOC_NR ( cmd ) = = _IOC_NR ( HIDIOCGRAWNAME ( 0 ) ) ) {
int len ;
if ( ! hid - > name )
return 0 ;
len = strlen ( hid - > name ) + 1 ;
if ( len > _IOC_SIZE ( cmd ) )
len = _IOC_SIZE ( cmd ) ;
2009-02-03 16:35:17 +03:00
ret = copy_to_user ( user_arg , hid - > name , len ) ?
2008-11-12 18:14:08 +03:00
- EFAULT : len ;
2009-02-03 16:35:17 +03:00
break ;
2008-11-12 18:14:08 +03:00
}
if ( _IOC_NR ( cmd ) = = _IOC_NR ( HIDIOCGRAWPHYS ( 0 ) ) ) {
int len ;
if ( ! hid - > phys )
return 0 ;
len = strlen ( hid - > phys ) + 1 ;
if ( len > _IOC_SIZE ( cmd ) )
len = _IOC_SIZE ( cmd ) ;
2009-02-03 16:35:17 +03:00
ret = copy_to_user ( user_arg , hid - > phys , len ) ?
2008-11-12 18:14:08 +03:00
- EFAULT : len ;
2009-02-03 16:35:17 +03:00
break ;
2008-11-12 18:14:08 +03:00
}
}
2009-02-03 16:35:17 +03:00
ret = - ENOTTY ;
2007-05-14 11:57:40 +04:00
}
2008-10-23 03:47:30 +04:00
unlock_kernel ( ) ;
2008-05-26 13:25:26 +04:00
return ret ;
2007-05-14 11:57:40 +04:00
}
static const struct file_operations hidraw_ops = {
. owner = THIS_MODULE ,
. read = hidraw_read ,
. write = hidraw_write ,
. poll = hidraw_poll ,
. open = hidraw_open ,
. release = hidraw_release ,
2008-05-26 13:25:26 +04:00
. unlocked_ioctl = hidraw_ioctl ,
2007-05-14 11:57:40 +04:00
} ;
void hidraw_report_event ( struct hid_device * hid , u8 * data , int len )
{
struct hidraw * dev = hid - > hidraw ;
struct hidraw_list * list ;
list_for_each_entry ( list , & dev - > list , node ) {
list - > buffer [ list - > head ] . value = kmemdup ( data , len , GFP_ATOMIC ) ;
list - > buffer [ list - > head ] . len = len ;
list - > head = ( list - > head + 1 ) & ( HIDRAW_BUFFER_SIZE - 1 ) ;
kill_fasync ( & list - > fasync , SIGIO , POLL_IN ) ;
}
wake_up_interruptible ( & dev - > wait ) ;
}
EXPORT_SYMBOL_GPL ( hidraw_report_event ) ;
int hidraw_connect ( struct hid_device * hid )
{
2007-09-27 13:24:55 +04:00
int minor , result ;
2007-05-14 11:57:40 +04:00
struct hidraw * dev ;
/* TODO currently we accept any HID device. This should later
* probably be fixed to accept only those devices which provide
* non - input applications
*/
2007-09-27 13:24:55 +04:00
dev = kzalloc ( sizeof ( struct hidraw ) , GFP_KERNEL ) ;
if ( ! dev )
return - ENOMEM ;
result = - EINVAL ;
2007-05-14 11:57:40 +04:00
2008-10-31 02:07:23 +03:00
mutex_lock ( & minors_lock ) ;
2007-05-14 11:57:40 +04:00
for ( minor = 0 ; minor < HIDRAW_MAX_DEVICES ; minor + + ) {
if ( hidraw_table [ minor ] )
continue ;
hidraw_table [ minor ] = dev ;
result = 0 ;
break ;
}
2007-09-27 13:24:55 +04:00
if ( result ) {
2008-10-31 02:07:23 +03:00
mutex_unlock ( & minors_lock ) ;
2007-09-27 13:24:55 +04:00
kfree ( dev ) ;
2007-05-14 11:57:40 +04:00
goto out ;
2007-09-27 13:24:55 +04:00
}
2007-05-14 11:57:40 +04:00
2008-12-04 18:16:46 +03:00
dev - > dev = device_create ( hidraw_class , & hid - > dev , MKDEV ( hidraw_major , minor ) ,
2008-07-22 07:03:34 +04:00
NULL , " %s%d " , " hidraw " , minor ) ;
2007-05-14 11:57:40 +04:00
if ( IS_ERR ( dev - > dev ) ) {
hidraw_table [ minor ] = NULL ;
2008-10-31 02:07:23 +03:00
mutex_unlock ( & minors_lock ) ;
2007-05-14 11:57:40 +04:00
result = PTR_ERR ( dev - > dev ) ;
2007-09-27 13:24:55 +04:00
kfree ( dev ) ;
2007-05-14 11:57:40 +04:00
goto out ;
}
2008-10-31 02:07:23 +03:00
mutex_unlock ( & minors_lock ) ;
2007-05-14 11:57:40 +04:00
init_waitqueue_head ( & dev - > wait ) ;
INIT_LIST_HEAD ( & dev - > list ) ;
dev - > hid = hid ;
dev - > minor = minor ;
dev - > exist = 1 ;
hid - > hidraw = dev ;
out :
return result ;
}
EXPORT_SYMBOL_GPL ( hidraw_connect ) ;
void hidraw_disconnect ( struct hid_device * hid )
{
struct hidraw * hidraw = hid - > hidraw ;
hidraw - > exist = 0 ;
2008-10-31 02:07:23 +03:00
mutex_lock ( & minors_lock ) ;
2007-05-14 11:57:40 +04:00
hidraw_table [ hidraw - > minor ] = NULL ;
2008-10-31 02:07:23 +03:00
mutex_unlock ( & minors_lock ) ;
2007-05-14 11:57:40 +04:00
device_destroy ( hidraw_class , MKDEV ( hidraw_major , hidraw - > minor ) ) ;
if ( hidraw - > open ) {
2008-05-16 13:49:16 +04:00
hid - > ll_driver - > close ( hid ) ;
2007-05-14 11:57:40 +04:00
wake_up_interruptible ( & hidraw - > wait ) ;
} else {
kfree ( hidraw ) ;
}
}
EXPORT_SYMBOL_GPL ( hidraw_disconnect ) ;
int __init hidraw_init ( void )
{
int result ;
dev_t dev_id ;
result = alloc_chrdev_region ( & dev_id , HIDRAW_FIRST_MINOR ,
HIDRAW_MAX_DEVICES , " hidraw " ) ;
hidraw_major = MAJOR ( dev_id ) ;
if ( result < 0 ) {
printk ( KERN_WARNING " hidraw: can't get major number \n " ) ;
result = 0 ;
goto out ;
}
hidraw_class = class_create ( THIS_MODULE , " hidraw " ) ;
if ( IS_ERR ( hidraw_class ) ) {
result = PTR_ERR ( hidraw_class ) ;
unregister_chrdev ( hidraw_major , " hidraw " ) ;
goto out ;
}
cdev_init ( & hidraw_cdev , & hidraw_ops ) ;
cdev_add ( & hidraw_cdev , dev_id , HIDRAW_MAX_DEVICES ) ;
out :
return result ;
}
2008-10-17 20:04:48 +04:00
void hidraw_exit ( void )
2007-05-14 11:57:40 +04:00
{
dev_t dev_id = MKDEV ( hidraw_major , 0 ) ;
cdev_del ( & hidraw_cdev ) ;
class_destroy ( hidraw_class ) ;
unregister_chrdev_region ( dev_id , HIDRAW_MAX_DEVICES ) ;
}