2012-06-10 17:16:13 +04:00
/*
* User - space I / O driver support for HID subsystem
* Copyright ( c ) 2012 David Herrmann
*/
/*
* 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 .
*/
# include <linux/atomic.h>
# include <linux/device.h>
# include <linux/fs.h>
# include <linux/hid.h>
# include <linux/input.h>
# include <linux/miscdevice.h>
# include <linux/module.h>
# include <linux/mutex.h>
# include <linux/poll.h>
# include <linux/sched.h>
# include <linux/spinlock.h>
# include <linux/uhid.h>
# include <linux/wait.h>
# define UHID_NAME "uhid"
2012-06-10 17:16:14 +04:00
# define UHID_BUFSIZE 32
struct uhid_device {
2012-06-10 17:16:16 +04:00
struct mutex devlock ;
2012-06-10 17:16:18 +04:00
bool running ;
__u8 * rd_data ;
uint rd_size ;
2012-06-10 17:16:14 +04:00
struct hid_device * hid ;
2012-06-10 17:16:17 +04:00
struct uhid_event input_buf ;
2012-06-10 17:16:14 +04:00
wait_queue_head_t waitq ;
spinlock_t qlock ;
__u8 head ;
__u8 tail ;
struct uhid_event * outq [ UHID_BUFSIZE ] ;
} ;
2012-06-10 17:16:13 +04:00
static struct miscdevice uhid_misc ;
2012-06-10 17:16:14 +04:00
static void uhid_queue ( struct uhid_device * uhid , struct uhid_event * ev )
{
__u8 newhead ;
newhead = ( uhid - > head + 1 ) % UHID_BUFSIZE ;
if ( newhead ! = uhid - > tail ) {
uhid - > outq [ uhid - > head ] = ev ;
uhid - > head = newhead ;
wake_up_interruptible ( & uhid - > waitq ) ;
} else {
hid_warn ( uhid - > hid , " Output queue is full \n " ) ;
kfree ( ev ) ;
}
}
static int uhid_queue_event ( struct uhid_device * uhid , __u32 event )
{
unsigned long flags ;
struct uhid_event * ev ;
ev = kzalloc ( sizeof ( * ev ) , GFP_KERNEL ) ;
if ( ! ev )
return - ENOMEM ;
ev - > type = event ;
spin_lock_irqsave ( & uhid - > qlock , flags ) ;
uhid_queue ( uhid , ev ) ;
spin_unlock_irqrestore ( & uhid - > qlock , flags ) ;
return 0 ;
}
2012-06-10 17:16:18 +04:00
static int uhid_hid_start ( struct hid_device * hid )
{
return 0 ;
}
static void uhid_hid_stop ( struct hid_device * hid )
{
}
static int uhid_hid_open ( struct hid_device * hid )
{
return 0 ;
}
static void uhid_hid_close ( struct hid_device * hid )
{
}
static int uhid_hid_input ( struct input_dev * input , unsigned int type ,
unsigned int code , int value )
{
return 0 ;
}
static int uhid_hid_parse ( struct hid_device * hid )
{
2012-06-10 17:16:20 +04:00
struct uhid_device * uhid = hid - > driver_data ;
return hid_parse_report ( hid , uhid - > rd_data , uhid - > rd_size ) ;
2012-06-10 17:16:18 +04:00
}
static int uhid_hid_get_raw ( struct hid_device * hid , unsigned char rnum ,
__u8 * buf , size_t count , unsigned char rtype )
{
return 0 ;
}
static int uhid_hid_output_raw ( struct hid_device * hid , __u8 * buf , size_t count ,
unsigned char report_type )
{
return 0 ;
}
static struct hid_ll_driver uhid_hid_driver = {
. start = uhid_hid_start ,
. stop = uhid_hid_stop ,
. open = uhid_hid_open ,
. close = uhid_hid_close ,
. hidinput_input_event = uhid_hid_input ,
. parse = uhid_hid_parse ,
} ;
static int uhid_dev_create ( struct uhid_device * uhid ,
const struct uhid_event * ev )
{
struct hid_device * hid ;
int ret ;
if ( uhid - > running )
return - EALREADY ;
uhid - > rd_size = ev - > u . create . rd_size ;
if ( uhid - > rd_size < = 0 | | uhid - > rd_size > HID_MAX_DESCRIPTOR_SIZE )
return - EINVAL ;
uhid - > rd_data = kmalloc ( uhid - > rd_size , GFP_KERNEL ) ;
if ( ! uhid - > rd_data )
return - ENOMEM ;
if ( copy_from_user ( uhid - > rd_data , ev - > u . create . rd_data ,
uhid - > rd_size ) ) {
ret = - EFAULT ;
goto err_free ;
}
hid = hid_allocate_device ( ) ;
if ( IS_ERR ( hid ) ) {
ret = PTR_ERR ( hid ) ;
goto err_free ;
}
strncpy ( hid - > name , ev - > u . create . name , 127 ) ;
hid - > name [ 127 ] = 0 ;
strncpy ( hid - > phys , ev - > u . create . phys , 63 ) ;
hid - > phys [ 63 ] = 0 ;
strncpy ( hid - > uniq , ev - > u . create . uniq , 63 ) ;
hid - > uniq [ 63 ] = 0 ;
hid - > ll_driver = & uhid_hid_driver ;
hid - > hid_get_raw_report = uhid_hid_get_raw ;
hid - > hid_output_raw_report = uhid_hid_output_raw ;
hid - > bus = ev - > u . create . bus ;
hid - > vendor = ev - > u . create . vendor ;
hid - > product = ev - > u . create . product ;
hid - > version = ev - > u . create . version ;
hid - > country = ev - > u . create . country ;
hid - > driver_data = uhid ;
hid - > dev . parent = uhid_misc . this_device ;
uhid - > hid = hid ;
uhid - > running = true ;
ret = hid_add_device ( hid ) ;
if ( ret ) {
hid_err ( hid , " Cannot register HID device \n " ) ;
goto err_hid ;
}
return 0 ;
err_hid :
hid_destroy_device ( hid ) ;
uhid - > hid = NULL ;
uhid - > running = false ;
err_free :
kfree ( uhid - > rd_data ) ;
return ret ;
}
static int uhid_dev_destroy ( struct uhid_device * uhid )
{
if ( ! uhid - > running )
return - EINVAL ;
uhid - > running = false ;
hid_destroy_device ( uhid - > hid ) ;
kfree ( uhid - > rd_data ) ;
return 0 ;
}
2012-06-10 17:16:19 +04:00
static int uhid_dev_input ( struct uhid_device * uhid , struct uhid_event * ev )
{
if ( ! uhid - > running )
return - EINVAL ;
hid_input_report ( uhid - > hid , HID_INPUT_REPORT , ev - > u . input . data ,
min_t ( size_t , ev - > u . input . size , UHID_DATA_MAX ) , 0 ) ;
return 0 ;
}
2012-06-10 17:16:13 +04:00
static int uhid_char_open ( struct inode * inode , struct file * file )
{
2012-06-10 17:16:14 +04:00
struct uhid_device * uhid ;
uhid = kzalloc ( sizeof ( * uhid ) , GFP_KERNEL ) ;
if ( ! uhid )
return - ENOMEM ;
2012-06-10 17:16:16 +04:00
mutex_init ( & uhid - > devlock ) ;
2012-06-10 17:16:14 +04:00
spin_lock_init ( & uhid - > qlock ) ;
init_waitqueue_head ( & uhid - > waitq ) ;
2012-06-10 17:16:18 +04:00
uhid - > running = false ;
2012-06-10 17:16:14 +04:00
file - > private_data = uhid ;
nonseekable_open ( inode , file ) ;
2012-06-10 17:16:13 +04:00
return 0 ;
}
static int uhid_char_release ( struct inode * inode , struct file * file )
{
2012-06-10 17:16:14 +04:00
struct uhid_device * uhid = file - > private_data ;
unsigned int i ;
2012-06-10 17:16:18 +04:00
uhid_dev_destroy ( uhid ) ;
2012-06-10 17:16:14 +04:00
for ( i = 0 ; i < UHID_BUFSIZE ; + + i )
kfree ( uhid - > outq [ i ] ) ;
kfree ( uhid ) ;
2012-06-10 17:16:13 +04:00
return 0 ;
}
static ssize_t uhid_char_read ( struct file * file , char __user * buffer ,
size_t count , loff_t * ppos )
{
2012-06-10 17:16:16 +04:00
struct uhid_device * uhid = file - > private_data ;
int ret ;
unsigned long flags ;
size_t len ;
/* they need at least the "type" member of uhid_event */
if ( count < sizeof ( __u32 ) )
return - EINVAL ;
try_again :
if ( file - > f_flags & O_NONBLOCK ) {
if ( uhid - > head = = uhid - > tail )
return - EAGAIN ;
} else {
ret = wait_event_interruptible ( uhid - > waitq ,
uhid - > head ! = uhid - > tail ) ;
if ( ret )
return ret ;
}
ret = mutex_lock_interruptible ( & uhid - > devlock ) ;
if ( ret )
return ret ;
if ( uhid - > head = = uhid - > tail ) {
mutex_unlock ( & uhid - > devlock ) ;
goto try_again ;
} else {
len = min ( count , sizeof ( * * uhid - > outq ) ) ;
if ( copy_to_user ( buffer , & uhid - > outq [ uhid - > tail ] , len ) ) {
ret = - EFAULT ;
} else {
kfree ( uhid - > outq [ uhid - > tail ] ) ;
uhid - > outq [ uhid - > tail ] = NULL ;
spin_lock_irqsave ( & uhid - > qlock , flags ) ;
uhid - > tail = ( uhid - > tail + 1 ) % UHID_BUFSIZE ;
spin_unlock_irqrestore ( & uhid - > qlock , flags ) ;
}
}
mutex_unlock ( & uhid - > devlock ) ;
return ret ? ret : len ;
2012-06-10 17:16:13 +04:00
}
static ssize_t uhid_char_write ( struct file * file , const char __user * buffer ,
size_t count , loff_t * ppos )
{
2012-06-10 17:16:17 +04:00
struct uhid_device * uhid = file - > private_data ;
int ret ;
size_t len ;
/* we need at least the "type" member of uhid_event */
if ( count < sizeof ( __u32 ) )
return - EINVAL ;
ret = mutex_lock_interruptible ( & uhid - > devlock ) ;
if ( ret )
return ret ;
memset ( & uhid - > input_buf , 0 , sizeof ( uhid - > input_buf ) ) ;
len = min ( count , sizeof ( uhid - > input_buf ) ) ;
if ( copy_from_user ( & uhid - > input_buf , buffer , len ) ) {
ret = - EFAULT ;
goto unlock ;
}
switch ( uhid - > input_buf . type ) {
2012-06-10 17:16:18 +04:00
case UHID_CREATE :
ret = uhid_dev_create ( uhid , & uhid - > input_buf ) ;
break ;
case UHID_DESTROY :
ret = uhid_dev_destroy ( uhid ) ;
break ;
2012-06-10 17:16:19 +04:00
case UHID_INPUT :
ret = uhid_dev_input ( uhid , & uhid - > input_buf ) ;
break ;
2012-06-10 17:16:17 +04:00
default :
ret = - EOPNOTSUPP ;
}
unlock :
mutex_unlock ( & uhid - > devlock ) ;
/* return "count" not "len" to not confuse the caller */
return ret ? ret : count ;
2012-06-10 17:16:13 +04:00
}
static unsigned int uhid_char_poll ( struct file * file , poll_table * wait )
{
2012-06-10 17:16:15 +04:00
struct uhid_device * uhid = file - > private_data ;
poll_wait ( file , & uhid - > waitq , wait ) ;
if ( uhid - > head ! = uhid - > tail )
return POLLIN | POLLRDNORM ;
2012-06-10 17:16:13 +04:00
return 0 ;
}
static const struct file_operations uhid_fops = {
. owner = THIS_MODULE ,
. open = uhid_char_open ,
. release = uhid_char_release ,
. read = uhid_char_read ,
. write = uhid_char_write ,
. poll = uhid_char_poll ,
. llseek = no_llseek ,
} ;
static struct miscdevice uhid_misc = {
. fops = & uhid_fops ,
. minor = MISC_DYNAMIC_MINOR ,
. name = UHID_NAME ,
} ;
static int __init uhid_init ( void )
{
return misc_register ( & uhid_misc ) ;
}
static void __exit uhid_exit ( void )
{
misc_deregister ( & uhid_misc ) ;
}
module_init ( uhid_init ) ;
module_exit ( uhid_exit ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_AUTHOR ( " David Herrmann <dh.herrmann@gmail.com> " ) ;
MODULE_DESCRIPTION ( " User-space I/O driver support for HID subsystem " ) ;