11da177e4SLinus Torvalds /* 21da177e4SLinus Torvalds * User level driver support for input subsystem 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * Heavily based on evdev.c by Vojtech Pavlik 51da177e4SLinus Torvalds * 61da177e4SLinus Torvalds * This program is free software; you can redistribute it and/or modify 71da177e4SLinus Torvalds * it under the terms of the GNU General Public License as published by 81da177e4SLinus Torvalds * the Free Software Foundation; either version 2 of the License, or 91da177e4SLinus Torvalds * (at your option) any later version. 101da177e4SLinus Torvalds * 111da177e4SLinus Torvalds * This program is distributed in the hope that it will be useful, 121da177e4SLinus Torvalds * but WITHOUT ANY WARRANTY; without even the implied warranty of 131da177e4SLinus Torvalds * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 141da177e4SLinus Torvalds * GNU General Public License for more details. 151da177e4SLinus Torvalds * 161da177e4SLinus Torvalds * You should have received a copy of the GNU General Public License 171da177e4SLinus Torvalds * along with this program; if not, write to the Free Software 181da177e4SLinus Torvalds * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 191da177e4SLinus Torvalds * 201da177e4SLinus Torvalds * Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org> 211da177e4SLinus Torvalds * 221da177e4SLinus Torvalds * Changes/Revisions: 23ff462551SAnssi Hannula * 0.3 09/04/2006 (Anssi Hannula <anssi.hannula@gmail.com>) 24ff462551SAnssi Hannula * - updated ff support for the changes in kernel interface 25ff462551SAnssi Hannula * - added MODULE_VERSION 261da177e4SLinus Torvalds * 0.2 16/10/2004 (Micah Dowty <micah@navi.cx>) 271da177e4SLinus Torvalds * - added force feedback support 281da177e4SLinus Torvalds * - added UI_SET_PHYS 291da177e4SLinus Torvalds * 0.1 20/06/2002 301da177e4SLinus Torvalds * - first public version 311da177e4SLinus Torvalds */ 321da177e4SLinus Torvalds #include <linux/poll.h> 331da177e4SLinus Torvalds #include <linux/slab.h> 341da177e4SLinus Torvalds #include <linux/module.h> 351da177e4SLinus Torvalds #include <linux/init.h> 361da177e4SLinus Torvalds #include <linux/smp_lock.h> 371da177e4SLinus Torvalds #include <linux/fs.h> 381da177e4SLinus Torvalds #include <linux/miscdevice.h> 391da177e4SLinus Torvalds #include <linux/uinput.h> 40*2d56f3a3SPhilip Langdale #include "../input-compat.h" 411da177e4SLinus Torvalds 421da177e4SLinus Torvalds static int uinput_dev_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) 431da177e4SLinus Torvalds { 44373f9713SDmitry Torokhov struct uinput_device *udev = input_get_drvdata(dev); 451da177e4SLinus Torvalds 461da177e4SLinus Torvalds udev->buff[udev->head].type = type; 471da177e4SLinus Torvalds udev->buff[udev->head].code = code; 481da177e4SLinus Torvalds udev->buff[udev->head].value = value; 491da177e4SLinus Torvalds do_gettimeofday(&udev->buff[udev->head].time); 501da177e4SLinus Torvalds udev->head = (udev->head + 1) % UINPUT_BUFFER_SIZE; 511da177e4SLinus Torvalds 521da177e4SLinus Torvalds wake_up_interruptible(&udev->waitq); 531da177e4SLinus Torvalds 541da177e4SLinus Torvalds return 0; 551da177e4SLinus Torvalds } 561da177e4SLinus Torvalds 570048e603SDmitry Torokhov static int uinput_request_alloc_id(struct uinput_device *udev, struct uinput_request *request) 581da177e4SLinus Torvalds { 591da177e4SLinus Torvalds /* Atomically allocate an ID for the given request. Returns 0 on success. */ 601da177e4SLinus Torvalds int id; 61152c12f5SDmitry Torokhov int err = -1; 621da177e4SLinus Torvalds 630048e603SDmitry Torokhov spin_lock(&udev->requests_lock); 64152c12f5SDmitry Torokhov 651da177e4SLinus Torvalds for (id = 0; id < UINPUT_NUM_REQUESTS; id++) 661da177e4SLinus Torvalds if (!udev->requests[id]) { 671da177e4SLinus Torvalds request->id = id; 680048e603SDmitry Torokhov udev->requests[id] = request; 69152c12f5SDmitry Torokhov err = 0; 70152c12f5SDmitry Torokhov break; 711da177e4SLinus Torvalds } 72152c12f5SDmitry Torokhov 730048e603SDmitry Torokhov spin_unlock(&udev->requests_lock); 74152c12f5SDmitry Torokhov return err; 751da177e4SLinus Torvalds } 761da177e4SLinus Torvalds 771da177e4SLinus Torvalds static struct uinput_request* uinput_request_find(struct uinput_device *udev, int id) 781da177e4SLinus Torvalds { 791da177e4SLinus Torvalds /* Find an input request, by ID. Returns NULL if the ID isn't valid. */ 801da177e4SLinus Torvalds if (id >= UINPUT_NUM_REQUESTS || id < 0) 811da177e4SLinus Torvalds return NULL; 82*2d56f3a3SPhilip Langdale 831da177e4SLinus Torvalds return udev->requests[id]; 841da177e4SLinus Torvalds } 851da177e4SLinus Torvalds 860048e603SDmitry Torokhov static inline int uinput_request_reserve_slot(struct uinput_device *udev, struct uinput_request *request) 871da177e4SLinus Torvalds { 880048e603SDmitry Torokhov /* Allocate slot. If none are available right away, wait. */ 890048e603SDmitry Torokhov return wait_event_interruptible(udev->requests_waitq, 900048e603SDmitry Torokhov !uinput_request_alloc_id(udev, request)); 911da177e4SLinus Torvalds } 921da177e4SLinus Torvalds 930048e603SDmitry Torokhov static void uinput_request_done(struct uinput_device *udev, struct uinput_request *request) 941da177e4SLinus Torvalds { 950048e603SDmitry Torokhov /* Mark slot as available */ 960048e603SDmitry Torokhov udev->requests[request->id] = NULL; 97e597f0c8SDmitry Torokhov wake_up(&udev->requests_waitq); 98e7507ed9SDmitry Torokhov 99e7507ed9SDmitry Torokhov complete(&request->done); 1000048e603SDmitry Torokhov } 1010048e603SDmitry Torokhov 1020048e603SDmitry Torokhov static int uinput_request_submit(struct input_dev *dev, struct uinput_request *request) 1030048e603SDmitry Torokhov { 1041da177e4SLinus Torvalds /* Tell our userspace app about this new request by queueing an input event */ 1051da177e4SLinus Torvalds uinput_dev_event(dev, EV_UINPUT, request->code, request->id); 1061da177e4SLinus Torvalds 1071da177e4SLinus Torvalds /* Wait for the request to complete */ 108e597f0c8SDmitry Torokhov wait_for_completion(&request->done); 109e597f0c8SDmitry Torokhov return request->retval; 1101da177e4SLinus Torvalds } 1111da177e4SLinus Torvalds 112ff462551SAnssi Hannula static void uinput_dev_set_gain(struct input_dev *dev, u16 gain) 113ff462551SAnssi Hannula { 114ff462551SAnssi Hannula uinput_dev_event(dev, EV_FF, FF_GAIN, gain); 115ff462551SAnssi Hannula } 116ff462551SAnssi Hannula 117ff462551SAnssi Hannula static void uinput_dev_set_autocenter(struct input_dev *dev, u16 magnitude) 118ff462551SAnssi Hannula { 119ff462551SAnssi Hannula uinput_dev_event(dev, EV_FF, FF_AUTOCENTER, magnitude); 120ff462551SAnssi Hannula } 121ff462551SAnssi Hannula 122ff462551SAnssi Hannula static int uinput_dev_playback(struct input_dev *dev, int effect_id, int value) 123ff462551SAnssi Hannula { 124ff462551SAnssi Hannula return uinput_dev_event(dev, EV_FF, effect_id, value); 125ff462551SAnssi Hannula } 126ff462551SAnssi Hannula 127ff462551SAnssi Hannula static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old) 1281da177e4SLinus Torvalds { 1291da177e4SLinus Torvalds struct uinput_request request; 1300048e603SDmitry Torokhov int retval; 1311da177e4SLinus Torvalds 132*2d56f3a3SPhilip Langdale /* 133*2d56f3a3SPhilip Langdale * uinput driver does not currently support periodic effects with 134*2d56f3a3SPhilip Langdale * custom waveform since it does not have a way to pass buffer of 135*2d56f3a3SPhilip Langdale * samples (custom_data) to userspace. If ever there is a device 136*2d56f3a3SPhilip Langdale * supporting custom waveforms we would need to define an additional 137*2d56f3a3SPhilip Langdale * ioctl (UI_UPLOAD_SAMPLES) but for now we just bail out. 138*2d56f3a3SPhilip Langdale */ 139*2d56f3a3SPhilip Langdale if (effect->type == FF_PERIODIC && 140*2d56f3a3SPhilip Langdale effect->u.periodic.waveform == FF_CUSTOM) 141*2d56f3a3SPhilip Langdale return -EINVAL; 142*2d56f3a3SPhilip Langdale 1430048e603SDmitry Torokhov request.id = -1; 1440048e603SDmitry Torokhov init_completion(&request.done); 1450048e603SDmitry Torokhov request.code = UI_FF_UPLOAD; 146ff462551SAnssi Hannula request.u.upload.effect = effect; 147ff462551SAnssi Hannula request.u.upload.old = old; 1480048e603SDmitry Torokhov 149373f9713SDmitry Torokhov retval = uinput_request_reserve_slot(input_get_drvdata(dev), &request); 1500048e603SDmitry Torokhov if (!retval) 1510048e603SDmitry Torokhov retval = uinput_request_submit(dev, &request); 1520048e603SDmitry Torokhov 1530048e603SDmitry Torokhov return retval; 1541da177e4SLinus Torvalds } 1551da177e4SLinus Torvalds 1561da177e4SLinus Torvalds static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id) 1571da177e4SLinus Torvalds { 1581da177e4SLinus Torvalds struct uinput_request request; 1590048e603SDmitry Torokhov int retval; 1601da177e4SLinus Torvalds 1611da177e4SLinus Torvalds if (!test_bit(EV_FF, dev->evbit)) 1621da177e4SLinus Torvalds return -ENOSYS; 1631da177e4SLinus Torvalds 1640048e603SDmitry Torokhov request.id = -1; 1650048e603SDmitry Torokhov init_completion(&request.done); 1660048e603SDmitry Torokhov request.code = UI_FF_ERASE; 1671da177e4SLinus Torvalds request.u.effect_id = effect_id; 1680048e603SDmitry Torokhov 169373f9713SDmitry Torokhov retval = uinput_request_reserve_slot(input_get_drvdata(dev), &request); 1700048e603SDmitry Torokhov if (!retval) 1710048e603SDmitry Torokhov retval = uinput_request_submit(dev, &request); 1720048e603SDmitry Torokhov 1730048e603SDmitry Torokhov return retval; 1741da177e4SLinus Torvalds } 1751da177e4SLinus Torvalds 17629506415SDmitry Torokhov static void uinput_destroy_device(struct uinput_device *udev) 17729506415SDmitry Torokhov { 17829506415SDmitry Torokhov const char *name, *phys; 17929506415SDmitry Torokhov 18029506415SDmitry Torokhov if (udev->dev) { 18129506415SDmitry Torokhov name = udev->dev->name; 18229506415SDmitry Torokhov phys = udev->dev->phys; 18329506415SDmitry Torokhov if (udev->state == UIST_CREATED) 18429506415SDmitry Torokhov input_unregister_device(udev->dev); 18529506415SDmitry Torokhov else 18629506415SDmitry Torokhov input_free_device(udev->dev); 18729506415SDmitry Torokhov kfree(name); 18829506415SDmitry Torokhov kfree(phys); 18929506415SDmitry Torokhov udev->dev = NULL; 19029506415SDmitry Torokhov } 19129506415SDmitry Torokhov 19229506415SDmitry Torokhov udev->state = UIST_NEW_DEVICE; 19329506415SDmitry Torokhov } 19429506415SDmitry Torokhov 1951da177e4SLinus Torvalds static int uinput_create_device(struct uinput_device *udev) 1961da177e4SLinus Torvalds { 197ff462551SAnssi Hannula struct input_dev *dev = udev->dev; 19829506415SDmitry Torokhov int error; 19929506415SDmitry Torokhov 20029506415SDmitry Torokhov if (udev->state != UIST_SETUP_COMPLETE) { 2011da177e4SLinus Torvalds printk(KERN_DEBUG "%s: write device info first\n", UINPUT_NAME); 2021da177e4SLinus Torvalds return -EINVAL; 2031da177e4SLinus Torvalds } 2041da177e4SLinus Torvalds 205ff462551SAnssi Hannula if (udev->ff_effects_max) { 206ff462551SAnssi Hannula error = input_ff_create(dev, udev->ff_effects_max); 207ff462551SAnssi Hannula if (error) 208ff462551SAnssi Hannula goto fail1; 209ff462551SAnssi Hannula 210ff462551SAnssi Hannula dev->ff->upload = uinput_dev_upload_effect; 211ff462551SAnssi Hannula dev->ff->erase = uinput_dev_erase_effect; 212ff462551SAnssi Hannula dev->ff->playback = uinput_dev_playback; 213ff462551SAnssi Hannula dev->ff->set_gain = uinput_dev_set_gain; 214ff462551SAnssi Hannula dev->ff->set_autocenter = uinput_dev_set_autocenter; 2151da177e4SLinus Torvalds } 2161da177e4SLinus Torvalds 217ff462551SAnssi Hannula error = input_register_device(udev->dev); 218ff462551SAnssi Hannula if (error) 219ff462551SAnssi Hannula goto fail2; 220ff462551SAnssi Hannula 22129506415SDmitry Torokhov udev->state = UIST_CREATED; 2221da177e4SLinus Torvalds 2231da177e4SLinus Torvalds return 0; 224ff462551SAnssi Hannula 225ff462551SAnssi Hannula fail2: input_ff_destroy(dev); 226ff462551SAnssi Hannula fail1: uinput_destroy_device(udev); 227ff462551SAnssi Hannula return error; 2281da177e4SLinus Torvalds } 2291da177e4SLinus Torvalds 2301da177e4SLinus Torvalds static int uinput_open(struct inode *inode, struct file *file) 2311da177e4SLinus Torvalds { 2321da177e4SLinus Torvalds struct uinput_device *newdev; 2331da177e4SLinus Torvalds 23429506415SDmitry Torokhov newdev = kzalloc(sizeof(struct uinput_device), GFP_KERNEL); 2351da177e4SLinus Torvalds if (!newdev) 23629506415SDmitry Torokhov return -ENOMEM; 23729506415SDmitry Torokhov 23887029658SArnd Bergmann lock_kernel(); 239221979aaSDmitry Torokhov mutex_init(&newdev->mutex); 2400048e603SDmitry Torokhov spin_lock_init(&newdev->requests_lock); 2411da177e4SLinus Torvalds init_waitqueue_head(&newdev->requests_waitq); 24229506415SDmitry Torokhov init_waitqueue_head(&newdev->waitq); 24329506415SDmitry Torokhov newdev->state = UIST_NEW_DEVICE; 2441da177e4SLinus Torvalds 2451da177e4SLinus Torvalds file->private_data = newdev; 24687029658SArnd Bergmann unlock_kernel(); 2471da177e4SLinus Torvalds 2481da177e4SLinus Torvalds return 0; 2491da177e4SLinus Torvalds } 2501da177e4SLinus Torvalds 2511da177e4SLinus Torvalds static int uinput_validate_absbits(struct input_dev *dev) 2521da177e4SLinus Torvalds { 2531da177e4SLinus Torvalds unsigned int cnt; 2541da177e4SLinus Torvalds int retval = 0; 2551da177e4SLinus Torvalds 2561da177e4SLinus Torvalds for (cnt = 0; cnt < ABS_MAX + 1; cnt++) { 2571da177e4SLinus Torvalds if (!test_bit(cnt, dev->absbit)) 2581da177e4SLinus Torvalds continue; 2591da177e4SLinus Torvalds 2601da177e4SLinus Torvalds if ((dev->absmax[cnt] <= dev->absmin[cnt])) { 2611da177e4SLinus Torvalds printk(KERN_DEBUG 2621da177e4SLinus Torvalds "%s: invalid abs[%02x] min:%d max:%d\n", 2631da177e4SLinus Torvalds UINPUT_NAME, cnt, 2641da177e4SLinus Torvalds dev->absmin[cnt], dev->absmax[cnt]); 2651da177e4SLinus Torvalds retval = -EINVAL; 2661da177e4SLinus Torvalds break; 2671da177e4SLinus Torvalds } 2681da177e4SLinus Torvalds 2691da177e4SLinus Torvalds if (dev->absflat[cnt] > (dev->absmax[cnt] - dev->absmin[cnt])) { 2701da177e4SLinus Torvalds printk(KERN_DEBUG 2711da177e4SLinus Torvalds "%s: absflat[%02x] out of range: %d " 2721da177e4SLinus Torvalds "(min:%d/max:%d)\n", 2731da177e4SLinus Torvalds UINPUT_NAME, cnt, dev->absflat[cnt], 2741da177e4SLinus Torvalds dev->absmin[cnt], dev->absmax[cnt]); 2751da177e4SLinus Torvalds retval = -EINVAL; 2761da177e4SLinus Torvalds break; 2771da177e4SLinus Torvalds } 2781da177e4SLinus Torvalds } 2791da177e4SLinus Torvalds return retval; 2801da177e4SLinus Torvalds } 2811da177e4SLinus Torvalds 28229506415SDmitry Torokhov static int uinput_allocate_device(struct uinput_device *udev) 28329506415SDmitry Torokhov { 28429506415SDmitry Torokhov udev->dev = input_allocate_device(); 28529506415SDmitry Torokhov if (!udev->dev) 28629506415SDmitry Torokhov return -ENOMEM; 28729506415SDmitry Torokhov 28829506415SDmitry Torokhov udev->dev->event = uinput_dev_event; 289373f9713SDmitry Torokhov input_set_drvdata(udev->dev, udev); 29029506415SDmitry Torokhov 29129506415SDmitry Torokhov return 0; 29229506415SDmitry Torokhov } 29329506415SDmitry Torokhov 29429506415SDmitry Torokhov static int uinput_setup_device(struct uinput_device *udev, const char __user *buffer, size_t count) 2951da177e4SLinus Torvalds { 2961da177e4SLinus Torvalds struct uinput_user_dev *user_dev; 2971da177e4SLinus Torvalds struct input_dev *dev; 2985b6271bdSDmitry Torokhov char *name; 299152c12f5SDmitry Torokhov int size; 300152c12f5SDmitry Torokhov int retval; 3011da177e4SLinus Torvalds 30229506415SDmitry Torokhov if (count != sizeof(struct uinput_user_dev)) 30329506415SDmitry Torokhov return -EINVAL; 3041da177e4SLinus Torvalds 30529506415SDmitry Torokhov if (!udev->dev) { 30629506415SDmitry Torokhov retval = uinput_allocate_device(udev); 30729506415SDmitry Torokhov if (retval) 30829506415SDmitry Torokhov return retval; 30929506415SDmitry Torokhov } 31029506415SDmitry Torokhov 3111da177e4SLinus Torvalds dev = udev->dev; 3121da177e4SLinus Torvalds 313152c12f5SDmitry Torokhov user_dev = kmalloc(sizeof(struct uinput_user_dev), GFP_KERNEL); 31429506415SDmitry Torokhov if (!user_dev) 31529506415SDmitry Torokhov return -ENOMEM; 3161da177e4SLinus Torvalds 3171da177e4SLinus Torvalds if (copy_from_user(user_dev, buffer, sizeof(struct uinput_user_dev))) { 3181da177e4SLinus Torvalds retval = -EFAULT; 3191da177e4SLinus Torvalds goto exit; 3201da177e4SLinus Torvalds } 3211da177e4SLinus Torvalds 322ff462551SAnssi Hannula udev->ff_effects_max = user_dev->ff_effects_max; 323ff462551SAnssi Hannula 3241da177e4SLinus Torvalds size = strnlen(user_dev->name, UINPUT_MAX_NAME_SIZE) + 1; 32529506415SDmitry Torokhov if (!size) { 32629506415SDmitry Torokhov retval = -EINVAL; 32729506415SDmitry Torokhov goto exit; 32829506415SDmitry Torokhov } 32929506415SDmitry Torokhov 33029506415SDmitry Torokhov kfree(dev->name); 3315b6271bdSDmitry Torokhov dev->name = name = kmalloc(size, GFP_KERNEL); 3325b6271bdSDmitry Torokhov if (!name) { 3331da177e4SLinus Torvalds retval = -ENOMEM; 3341da177e4SLinus Torvalds goto exit; 3351da177e4SLinus Torvalds } 3365b6271bdSDmitry Torokhov strlcpy(name, user_dev->name, size); 3371da177e4SLinus Torvalds 3381da177e4SLinus Torvalds dev->id.bustype = user_dev->id.bustype; 3391da177e4SLinus Torvalds dev->id.vendor = user_dev->id.vendor; 3401da177e4SLinus Torvalds dev->id.product = user_dev->id.product; 3411da177e4SLinus Torvalds dev->id.version = user_dev->id.version; 3421da177e4SLinus Torvalds 3431da177e4SLinus Torvalds size = sizeof(int) * (ABS_MAX + 1); 3441da177e4SLinus Torvalds memcpy(dev->absmax, user_dev->absmax, size); 3451da177e4SLinus Torvalds memcpy(dev->absmin, user_dev->absmin, size); 3461da177e4SLinus Torvalds memcpy(dev->absfuzz, user_dev->absfuzz, size); 3471da177e4SLinus Torvalds memcpy(dev->absflat, user_dev->absflat, size); 3481da177e4SLinus Torvalds 3491da177e4SLinus Torvalds /* check if absmin/absmax/absfuzz/absflat are filled as 3501da177e4SLinus Torvalds * told in Documentation/input/input-programming.txt */ 3511da177e4SLinus Torvalds if (test_bit(EV_ABS, dev->evbit)) { 35229506415SDmitry Torokhov retval = uinput_validate_absbits(dev); 35329506415SDmitry Torokhov if (retval < 0) 35429506415SDmitry Torokhov goto exit; 3551da177e4SLinus Torvalds } 35629506415SDmitry Torokhov 35729506415SDmitry Torokhov udev->state = UIST_SETUP_COMPLETE; 35829506415SDmitry Torokhov retval = count; 3591da177e4SLinus Torvalds 3601da177e4SLinus Torvalds exit: 3611da177e4SLinus Torvalds kfree(user_dev); 3621da177e4SLinus Torvalds return retval; 3631da177e4SLinus Torvalds } 3641da177e4SLinus Torvalds 36529506415SDmitry Torokhov static inline ssize_t uinput_inject_event(struct uinput_device *udev, const char __user *buffer, size_t count) 3661da177e4SLinus Torvalds { 3671da177e4SLinus Torvalds struct input_event ev; 3681da177e4SLinus Torvalds 369*2d56f3a3SPhilip Langdale if (count < input_event_size()) 37029506415SDmitry Torokhov return -EINVAL; 37129506415SDmitry Torokhov 372*2d56f3a3SPhilip Langdale if (input_event_from_user(buffer, &ev)) 3731da177e4SLinus Torvalds return -EFAULT; 3741da177e4SLinus Torvalds 37529506415SDmitry Torokhov input_event(udev->dev, ev.type, ev.code, ev.value); 37629506415SDmitry Torokhov 377*2d56f3a3SPhilip Langdale return input_event_size(); 37829506415SDmitry Torokhov } 37929506415SDmitry Torokhov 38029506415SDmitry Torokhov static ssize_t uinput_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) 38129506415SDmitry Torokhov { 38229506415SDmitry Torokhov struct uinput_device *udev = file->private_data; 38329506415SDmitry Torokhov int retval; 38429506415SDmitry Torokhov 385221979aaSDmitry Torokhov retval = mutex_lock_interruptible(&udev->mutex); 38629506415SDmitry Torokhov if (retval) 38729506415SDmitry Torokhov return retval; 38829506415SDmitry Torokhov 38929506415SDmitry Torokhov retval = udev->state == UIST_CREATED ? 39029506415SDmitry Torokhov uinput_inject_event(udev, buffer, count) : 39129506415SDmitry Torokhov uinput_setup_device(udev, buffer, count); 39229506415SDmitry Torokhov 393221979aaSDmitry Torokhov mutex_unlock(&udev->mutex); 39429506415SDmitry Torokhov 39529506415SDmitry Torokhov return retval; 3961da177e4SLinus Torvalds } 3971da177e4SLinus Torvalds 3981da177e4SLinus Torvalds static ssize_t uinput_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) 3991da177e4SLinus Torvalds { 4001da177e4SLinus Torvalds struct uinput_device *udev = file->private_data; 4011da177e4SLinus Torvalds int retval = 0; 4021da177e4SLinus Torvalds 40329506415SDmitry Torokhov if (udev->state != UIST_CREATED) 4041da177e4SLinus Torvalds return -ENODEV; 4051da177e4SLinus Torvalds 406152c12f5SDmitry Torokhov if (udev->head == udev->tail && (file->f_flags & O_NONBLOCK)) 4071da177e4SLinus Torvalds return -EAGAIN; 4081da177e4SLinus Torvalds 4091da177e4SLinus Torvalds retval = wait_event_interruptible(udev->waitq, 41029506415SDmitry Torokhov udev->head != udev->tail || udev->state != UIST_CREATED); 4111da177e4SLinus Torvalds if (retval) 4121da177e4SLinus Torvalds return retval; 4131da177e4SLinus Torvalds 414221979aaSDmitry Torokhov retval = mutex_lock_interruptible(&udev->mutex); 41529506415SDmitry Torokhov if (retval) 41629506415SDmitry Torokhov return retval; 4171da177e4SLinus Torvalds 41829506415SDmitry Torokhov if (udev->state != UIST_CREATED) { 41929506415SDmitry Torokhov retval = -ENODEV; 42029506415SDmitry Torokhov goto out; 42129506415SDmitry Torokhov } 42229506415SDmitry Torokhov 423*2d56f3a3SPhilip Langdale while (udev->head != udev->tail && retval + input_event_size() <= count) { 424*2d56f3a3SPhilip Langdale if (input_event_to_user(buffer + retval, &udev->buff[udev->tail])) { 42529506415SDmitry Torokhov retval = -EFAULT; 42629506415SDmitry Torokhov goto out; 42729506415SDmitry Torokhov } 4281da177e4SLinus Torvalds udev->tail = (udev->tail + 1) % UINPUT_BUFFER_SIZE; 429*2d56f3a3SPhilip Langdale retval += input_event_size(); 4301da177e4SLinus Torvalds } 4311da177e4SLinus Torvalds 43229506415SDmitry Torokhov out: 433221979aaSDmitry Torokhov mutex_unlock(&udev->mutex); 43429506415SDmitry Torokhov 4351da177e4SLinus Torvalds return retval; 4361da177e4SLinus Torvalds } 4371da177e4SLinus Torvalds 4381da177e4SLinus Torvalds static unsigned int uinput_poll(struct file *file, poll_table *wait) 4391da177e4SLinus Torvalds { 4401da177e4SLinus Torvalds struct uinput_device *udev = file->private_data; 4411da177e4SLinus Torvalds 4421da177e4SLinus Torvalds poll_wait(file, &udev->waitq, wait); 4431da177e4SLinus Torvalds 4441da177e4SLinus Torvalds if (udev->head != udev->tail) 4451da177e4SLinus Torvalds return POLLIN | POLLRDNORM; 4461da177e4SLinus Torvalds 4471da177e4SLinus Torvalds return 0; 4481da177e4SLinus Torvalds } 4491da177e4SLinus Torvalds 45029506415SDmitry Torokhov static int uinput_release(struct inode *inode, struct file *file) 4511da177e4SLinus Torvalds { 45229506415SDmitry Torokhov struct uinput_device *udev = file->private_data; 4531da177e4SLinus Torvalds 45429506415SDmitry Torokhov uinput_destroy_device(udev); 4551da177e4SLinus Torvalds kfree(udev); 4561da177e4SLinus Torvalds 4571da177e4SLinus Torvalds return 0; 4581da177e4SLinus Torvalds } 4591da177e4SLinus Torvalds 460*2d56f3a3SPhilip Langdale #ifdef CONFIG_COMPAT 461*2d56f3a3SPhilip Langdale struct uinput_ff_upload_compat { 462*2d56f3a3SPhilip Langdale int request_id; 463*2d56f3a3SPhilip Langdale int retval; 464*2d56f3a3SPhilip Langdale struct ff_effect_compat effect; 465*2d56f3a3SPhilip Langdale struct ff_effect_compat old; 466*2d56f3a3SPhilip Langdale }; 467*2d56f3a3SPhilip Langdale 468*2d56f3a3SPhilip Langdale static int uinput_ff_upload_to_user(char __user *buffer, 469*2d56f3a3SPhilip Langdale const struct uinput_ff_upload *ff_up) 470*2d56f3a3SPhilip Langdale { 471*2d56f3a3SPhilip Langdale if (INPUT_COMPAT_TEST) { 472*2d56f3a3SPhilip Langdale struct uinput_ff_upload_compat ff_up_compat; 473*2d56f3a3SPhilip Langdale 474*2d56f3a3SPhilip Langdale ff_up_compat.request_id = ff_up->request_id; 475*2d56f3a3SPhilip Langdale ff_up_compat.retval = ff_up->retval; 476*2d56f3a3SPhilip Langdale /* 477*2d56f3a3SPhilip Langdale * It so happens that the pointer that gives us the trouble 478*2d56f3a3SPhilip Langdale * is the last field in the structure. Since we don't support 479*2d56f3a3SPhilip Langdale * custom waveforms in uinput anyway we can just copy the whole 480*2d56f3a3SPhilip Langdale * thing (to the compat size) and ignore the pointer. 481*2d56f3a3SPhilip Langdale */ 482*2d56f3a3SPhilip Langdale memcpy(&ff_up_compat.effect, &ff_up->effect, 483*2d56f3a3SPhilip Langdale sizeof(struct ff_effect_compat)); 484*2d56f3a3SPhilip Langdale memcpy(&ff_up_compat.old, &ff_up->old, 485*2d56f3a3SPhilip Langdale sizeof(struct ff_effect_compat)); 486*2d56f3a3SPhilip Langdale 487*2d56f3a3SPhilip Langdale if (copy_to_user(buffer, &ff_up_compat, 488*2d56f3a3SPhilip Langdale sizeof(struct uinput_ff_upload_compat))) 489*2d56f3a3SPhilip Langdale return -EFAULT; 490*2d56f3a3SPhilip Langdale } else { 491*2d56f3a3SPhilip Langdale if (copy_to_user(buffer, ff_up, 492*2d56f3a3SPhilip Langdale sizeof(struct uinput_ff_upload))) 493*2d56f3a3SPhilip Langdale return -EFAULT; 494*2d56f3a3SPhilip Langdale } 495*2d56f3a3SPhilip Langdale 496*2d56f3a3SPhilip Langdale return 0; 497*2d56f3a3SPhilip Langdale } 498*2d56f3a3SPhilip Langdale 499*2d56f3a3SPhilip Langdale static int uinput_ff_upload_from_user(const char __user *buffer, 500*2d56f3a3SPhilip Langdale struct uinput_ff_upload *ff_up) 501*2d56f3a3SPhilip Langdale { 502*2d56f3a3SPhilip Langdale if (INPUT_COMPAT_TEST) { 503*2d56f3a3SPhilip Langdale struct uinput_ff_upload_compat ff_up_compat; 504*2d56f3a3SPhilip Langdale 505*2d56f3a3SPhilip Langdale if (copy_from_user(&ff_up_compat, buffer, 506*2d56f3a3SPhilip Langdale sizeof(struct uinput_ff_upload_compat))) 507*2d56f3a3SPhilip Langdale return -EFAULT; 508*2d56f3a3SPhilip Langdale 509*2d56f3a3SPhilip Langdale ff_up->request_id = ff_up_compat.request_id; 510*2d56f3a3SPhilip Langdale ff_up->retval = ff_up_compat.retval; 511*2d56f3a3SPhilip Langdale memcpy(&ff_up->effect, &ff_up_compat.effect, 512*2d56f3a3SPhilip Langdale sizeof(struct ff_effect_compat)); 513*2d56f3a3SPhilip Langdale memcpy(&ff_up->old, &ff_up_compat.old, 514*2d56f3a3SPhilip Langdale sizeof(struct ff_effect_compat)); 515*2d56f3a3SPhilip Langdale 516*2d56f3a3SPhilip Langdale } else { 517*2d56f3a3SPhilip Langdale if (copy_from_user(ff_up, buffer, 518*2d56f3a3SPhilip Langdale sizeof(struct uinput_ff_upload))) 519*2d56f3a3SPhilip Langdale return -EFAULT; 520*2d56f3a3SPhilip Langdale } 521*2d56f3a3SPhilip Langdale 522*2d56f3a3SPhilip Langdale return 0; 523*2d56f3a3SPhilip Langdale } 524*2d56f3a3SPhilip Langdale 525*2d56f3a3SPhilip Langdale #else 526*2d56f3a3SPhilip Langdale 527*2d56f3a3SPhilip Langdale static int uinput_ff_upload_to_user(char __user *buffer, 528*2d56f3a3SPhilip Langdale const struct uinput_ff_upload *ff_up) 529*2d56f3a3SPhilip Langdale { 530*2d56f3a3SPhilip Langdale if (copy_to_user(buffer, ff_up, sizeof(struct uinput_ff_upload))) 531*2d56f3a3SPhilip Langdale return -EFAULT; 532*2d56f3a3SPhilip Langdale 533*2d56f3a3SPhilip Langdale return 0; 534*2d56f3a3SPhilip Langdale } 535*2d56f3a3SPhilip Langdale 536*2d56f3a3SPhilip Langdale static int uinput_ff_upload_from_user(const char __user *buffer, 537*2d56f3a3SPhilip Langdale struct uinput_ff_upload *ff_up) 538*2d56f3a3SPhilip Langdale { 539*2d56f3a3SPhilip Langdale if (copy_from_user(ff_up, buffer, sizeof(struct uinput_ff_upload))) 540*2d56f3a3SPhilip Langdale return -EFAULT; 541*2d56f3a3SPhilip Langdale 542*2d56f3a3SPhilip Langdale return 0; 543*2d56f3a3SPhilip Langdale } 544*2d56f3a3SPhilip Langdale 545*2d56f3a3SPhilip Langdale #endif 546*2d56f3a3SPhilip Langdale 54729506415SDmitry Torokhov #define uinput_set_bit(_arg, _bit, _max) \ 54829506415SDmitry Torokhov ({ \ 54929506415SDmitry Torokhov int __ret = 0; \ 55029506415SDmitry Torokhov if (udev->state == UIST_CREATED) \ 55129506415SDmitry Torokhov __ret = -EINVAL; \ 55229506415SDmitry Torokhov else if ((_arg) > (_max)) \ 55329506415SDmitry Torokhov __ret = -EINVAL; \ 55429506415SDmitry Torokhov else set_bit((_arg), udev->dev->_bit); \ 55529506415SDmitry Torokhov __ret; \ 55629506415SDmitry Torokhov }) 5571da177e4SLinus Torvalds 558*2d56f3a3SPhilip Langdale static long uinput_ioctl_handler(struct file *file, unsigned int cmd, 559*2d56f3a3SPhilip Langdale unsigned long arg, void __user *p) 5601da177e4SLinus Torvalds { 56129506415SDmitry Torokhov int retval; 562*2d56f3a3SPhilip Langdale struct uinput_device *udev = file->private_data; 5631da177e4SLinus Torvalds struct uinput_ff_upload ff_up; 5641da177e4SLinus Torvalds struct uinput_ff_erase ff_erase; 5651da177e4SLinus Torvalds struct uinput_request *req; 5661da177e4SLinus Torvalds int length; 5675b6271bdSDmitry Torokhov char *phys; 5681da177e4SLinus Torvalds 569221979aaSDmitry Torokhov retval = mutex_lock_interruptible(&udev->mutex); 57029506415SDmitry Torokhov if (retval) 57129506415SDmitry Torokhov return retval; 57229506415SDmitry Torokhov 57329506415SDmitry Torokhov if (!udev->dev) { 57429506415SDmitry Torokhov retval = uinput_allocate_device(udev); 57529506415SDmitry Torokhov if (retval) 57629506415SDmitry Torokhov goto out; 5771da177e4SLinus Torvalds } 5781da177e4SLinus Torvalds 5791da177e4SLinus Torvalds switch (cmd) { 5801da177e4SLinus Torvalds case UI_DEV_CREATE: 5811da177e4SLinus Torvalds retval = uinput_create_device(udev); 5821da177e4SLinus Torvalds break; 5831da177e4SLinus Torvalds 5841da177e4SLinus Torvalds case UI_DEV_DESTROY: 58529506415SDmitry Torokhov uinput_destroy_device(udev); 5861da177e4SLinus Torvalds break; 5871da177e4SLinus Torvalds 5881da177e4SLinus Torvalds case UI_SET_EVBIT: 58929506415SDmitry Torokhov retval = uinput_set_bit(arg, evbit, EV_MAX); 5901da177e4SLinus Torvalds break; 5911da177e4SLinus Torvalds 5921da177e4SLinus Torvalds case UI_SET_KEYBIT: 59329506415SDmitry Torokhov retval = uinput_set_bit(arg, keybit, KEY_MAX); 5941da177e4SLinus Torvalds break; 5951da177e4SLinus Torvalds 5961da177e4SLinus Torvalds case UI_SET_RELBIT: 59729506415SDmitry Torokhov retval = uinput_set_bit(arg, relbit, REL_MAX); 5981da177e4SLinus Torvalds break; 5991da177e4SLinus Torvalds 6001da177e4SLinus Torvalds case UI_SET_ABSBIT: 60129506415SDmitry Torokhov retval = uinput_set_bit(arg, absbit, ABS_MAX); 6021da177e4SLinus Torvalds break; 6031da177e4SLinus Torvalds 6041da177e4SLinus Torvalds case UI_SET_MSCBIT: 60529506415SDmitry Torokhov retval = uinput_set_bit(arg, mscbit, MSC_MAX); 6061da177e4SLinus Torvalds break; 6071da177e4SLinus Torvalds 6081da177e4SLinus Torvalds case UI_SET_LEDBIT: 60929506415SDmitry Torokhov retval = uinput_set_bit(arg, ledbit, LED_MAX); 6101da177e4SLinus Torvalds break; 6111da177e4SLinus Torvalds 6121da177e4SLinus Torvalds case UI_SET_SNDBIT: 61329506415SDmitry Torokhov retval = uinput_set_bit(arg, sndbit, SND_MAX); 6141da177e4SLinus Torvalds break; 6151da177e4SLinus Torvalds 6161da177e4SLinus Torvalds case UI_SET_FFBIT: 61729506415SDmitry Torokhov retval = uinput_set_bit(arg, ffbit, FF_MAX); 6181da177e4SLinus Torvalds break; 6191da177e4SLinus Torvalds 62059c7c037SDmitry Torokhov case UI_SET_SWBIT: 62159c7c037SDmitry Torokhov retval = uinput_set_bit(arg, swbit, SW_MAX); 62259c7c037SDmitry Torokhov break; 62359c7c037SDmitry Torokhov 6241da177e4SLinus Torvalds case UI_SET_PHYS: 62529506415SDmitry Torokhov if (udev->state == UIST_CREATED) { 62629506415SDmitry Torokhov retval = -EINVAL; 62729506415SDmitry Torokhov goto out; 62829506415SDmitry Torokhov } 6291da177e4SLinus Torvalds length = strnlen_user(p, 1024); 6301da177e4SLinus Torvalds if (length <= 0) { 6311da177e4SLinus Torvalds retval = -EFAULT; 6321da177e4SLinus Torvalds break; 6331da177e4SLinus Torvalds } 6341da177e4SLinus Torvalds kfree(udev->dev->phys); 6355b6271bdSDmitry Torokhov udev->dev->phys = phys = kmalloc(length, GFP_KERNEL); 6365b6271bdSDmitry Torokhov if (!phys) { 6371da177e4SLinus Torvalds retval = -ENOMEM; 6381da177e4SLinus Torvalds break; 6391da177e4SLinus Torvalds } 6405b6271bdSDmitry Torokhov if (copy_from_user(phys, p, length)) { 6411da177e4SLinus Torvalds udev->dev->phys = NULL; 6425b6271bdSDmitry Torokhov kfree(phys); 6435b6271bdSDmitry Torokhov retval = -EFAULT; 6441da177e4SLinus Torvalds break; 6451da177e4SLinus Torvalds } 6465b6271bdSDmitry Torokhov phys[length - 1] = '\0'; 6471da177e4SLinus Torvalds break; 6481da177e4SLinus Torvalds 6491da177e4SLinus Torvalds case UI_BEGIN_FF_UPLOAD: 650*2d56f3a3SPhilip Langdale retval = uinput_ff_upload_from_user(p, &ff_up); 651*2d56f3a3SPhilip Langdale if (retval) 6521da177e4SLinus Torvalds break; 653*2d56f3a3SPhilip Langdale 6541da177e4SLinus Torvalds req = uinput_request_find(udev, ff_up.request_id); 655*2d56f3a3SPhilip Langdale if (!req || req->code != UI_FF_UPLOAD || !req->u.upload.effect) { 6561da177e4SLinus Torvalds retval = -EINVAL; 6571da177e4SLinus Torvalds break; 6581da177e4SLinus Torvalds } 659*2d56f3a3SPhilip Langdale 6601da177e4SLinus Torvalds ff_up.retval = 0; 661*2d56f3a3SPhilip Langdale ff_up.effect = *req->u.upload.effect; 662ff462551SAnssi Hannula if (req->u.upload.old) 663*2d56f3a3SPhilip Langdale ff_up.old = *req->u.upload.old; 664ff462551SAnssi Hannula else 665ff462551SAnssi Hannula memset(&ff_up.old, 0, sizeof(struct ff_effect)); 666ff462551SAnssi Hannula 667*2d56f3a3SPhilip Langdale retval = uinput_ff_upload_to_user(p, &ff_up); 6681da177e4SLinus Torvalds break; 6691da177e4SLinus Torvalds 6701da177e4SLinus Torvalds case UI_BEGIN_FF_ERASE: 6711da177e4SLinus Torvalds if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) { 6721da177e4SLinus Torvalds retval = -EFAULT; 6731da177e4SLinus Torvalds break; 6741da177e4SLinus Torvalds } 675*2d56f3a3SPhilip Langdale 6761da177e4SLinus Torvalds req = uinput_request_find(udev, ff_erase.request_id); 677*2d56f3a3SPhilip Langdale if (!req || req->code != UI_FF_ERASE) { 6781da177e4SLinus Torvalds retval = -EINVAL; 6791da177e4SLinus Torvalds break; 6801da177e4SLinus Torvalds } 681*2d56f3a3SPhilip Langdale 6821da177e4SLinus Torvalds ff_erase.retval = 0; 6831da177e4SLinus Torvalds ff_erase.effect_id = req->u.effect_id; 6841da177e4SLinus Torvalds if (copy_to_user(p, &ff_erase, sizeof(ff_erase))) { 6851da177e4SLinus Torvalds retval = -EFAULT; 6861da177e4SLinus Torvalds break; 6871da177e4SLinus Torvalds } 688*2d56f3a3SPhilip Langdale 6891da177e4SLinus Torvalds break; 6901da177e4SLinus Torvalds 6911da177e4SLinus Torvalds case UI_END_FF_UPLOAD: 692*2d56f3a3SPhilip Langdale retval = uinput_ff_upload_from_user(p, &ff_up); 693*2d56f3a3SPhilip Langdale if (retval) 6941da177e4SLinus Torvalds break; 695*2d56f3a3SPhilip Langdale 6961da177e4SLinus Torvalds req = uinput_request_find(udev, ff_up.request_id); 697*2d56f3a3SPhilip Langdale if (!req || req->code != UI_FF_UPLOAD || 698*2d56f3a3SPhilip Langdale !req->u.upload.effect) { 6991da177e4SLinus Torvalds retval = -EINVAL; 7001da177e4SLinus Torvalds break; 7011da177e4SLinus Torvalds } 702*2d56f3a3SPhilip Langdale 7031da177e4SLinus Torvalds req->retval = ff_up.retval; 7040048e603SDmitry Torokhov uinput_request_done(udev, req); 7051da177e4SLinus Torvalds break; 7061da177e4SLinus Torvalds 7071da177e4SLinus Torvalds case UI_END_FF_ERASE: 7081da177e4SLinus Torvalds if (copy_from_user(&ff_erase, p, sizeof(ff_erase))) { 7091da177e4SLinus Torvalds retval = -EFAULT; 7101da177e4SLinus Torvalds break; 7111da177e4SLinus Torvalds } 712*2d56f3a3SPhilip Langdale 7131da177e4SLinus Torvalds req = uinput_request_find(udev, ff_erase.request_id); 714*2d56f3a3SPhilip Langdale if (!req || req->code != UI_FF_ERASE) { 7151da177e4SLinus Torvalds retval = -EINVAL; 7161da177e4SLinus Torvalds break; 7171da177e4SLinus Torvalds } 718*2d56f3a3SPhilip Langdale 7191da177e4SLinus Torvalds req->retval = ff_erase.retval; 7200048e603SDmitry Torokhov uinput_request_done(udev, req); 7211da177e4SLinus Torvalds break; 7221da177e4SLinus Torvalds 7231da177e4SLinus Torvalds default: 7241da177e4SLinus Torvalds retval = -EINVAL; 7251da177e4SLinus Torvalds } 72629506415SDmitry Torokhov 72729506415SDmitry Torokhov out: 728221979aaSDmitry Torokhov mutex_unlock(&udev->mutex); 7291da177e4SLinus Torvalds return retval; 7301da177e4SLinus Torvalds } 7311da177e4SLinus Torvalds 732*2d56f3a3SPhilip Langdale static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 733*2d56f3a3SPhilip Langdale { 734*2d56f3a3SPhilip Langdale return uinput_ioctl_handler(file, cmd, arg, (void __user *)arg); 735*2d56f3a3SPhilip Langdale } 736*2d56f3a3SPhilip Langdale 737*2d56f3a3SPhilip Langdale #ifdef CONFIG_COMPAT 738*2d56f3a3SPhilip Langdale static long uinput_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) 739*2d56f3a3SPhilip Langdale { 740*2d56f3a3SPhilip Langdale return uinput_ioctl_handler(file, cmd, arg, compat_ptr(arg)); 741*2d56f3a3SPhilip Langdale } 742*2d56f3a3SPhilip Langdale #endif 743*2d56f3a3SPhilip Langdale 7442b8693c0SArjan van de Ven static const struct file_operations uinput_fops = { 7451da177e4SLinus Torvalds .owner = THIS_MODULE, 7461da177e4SLinus Torvalds .open = uinput_open, 74729506415SDmitry Torokhov .release = uinput_release, 7481da177e4SLinus Torvalds .read = uinput_read, 7491da177e4SLinus Torvalds .write = uinput_write, 7501da177e4SLinus Torvalds .poll = uinput_poll, 75129506415SDmitry Torokhov .unlocked_ioctl = uinput_ioctl, 752*2d56f3a3SPhilip Langdale #ifdef CONFIG_COMPAT 753*2d56f3a3SPhilip Langdale .compat_ioctl = uinput_compat_ioctl, 754*2d56f3a3SPhilip Langdale #endif 7551da177e4SLinus Torvalds }; 7561da177e4SLinus Torvalds 7571da177e4SLinus Torvalds static struct miscdevice uinput_misc = { 7581da177e4SLinus Torvalds .fops = &uinput_fops, 7591da177e4SLinus Torvalds .minor = UINPUT_MINOR, 7601da177e4SLinus Torvalds .name = UINPUT_NAME, 7611da177e4SLinus Torvalds }; 7621da177e4SLinus Torvalds 7631da177e4SLinus Torvalds static int __init uinput_init(void) 7641da177e4SLinus Torvalds { 7651da177e4SLinus Torvalds return misc_register(&uinput_misc); 7661da177e4SLinus Torvalds } 7671da177e4SLinus Torvalds 7681da177e4SLinus Torvalds static void __exit uinput_exit(void) 7691da177e4SLinus Torvalds { 7701da177e4SLinus Torvalds misc_deregister(&uinput_misc); 7711da177e4SLinus Torvalds } 7721da177e4SLinus Torvalds 7731da177e4SLinus Torvalds MODULE_AUTHOR("Aristeu Sergio Rozanski Filho"); 7741da177e4SLinus Torvalds MODULE_DESCRIPTION("User level driver support for input subsystem"); 7751da177e4SLinus Torvalds MODULE_LICENSE("GPL"); 776ff462551SAnssi Hannula MODULE_VERSION("0.3"); 7771da177e4SLinus Torvalds 7781da177e4SLinus Torvalds module_init(uinput_init); 7791da177e4SLinus Torvalds module_exit(uinput_exit); 7801da177e4SLinus Torvalds 781