headers: remove sched.h from poll.h
[safe/jmp/linux-2.6] / drivers / input / misc / uinput.c
index 546ed9b..d3f5724 100644 (file)
@@ -20,6 +20,9 @@
  * Author: Aristeu Sergio Rozanski Filho <aris@cathedrallabs.org>
  *
  * Changes/Revisions:
+ *     0.3     09/04/2006 (Anssi Hannula <anssi.hannula@gmail.com>)
+ *             - updated ff support for the changes in kernel interface
+ *             - added MODULE_VERSION
  *     0.2     16/10/2004 (Micah Dowty <micah@navi.cx>)
  *             - added force feedback support
  *              - added UI_SET_PHYS
  *             - first public version
  */
 #include <linux/poll.h>
+#include <linux/sched.h>
 #include <linux/slab.h>
 #include <linux/module.h>
 #include <linux/init.h>
-#include <linux/input.h>
 #include <linux/smp_lock.h>
 #include <linux/fs.h>
 #include <linux/miscdevice.h>
 #include <linux/uinput.h>
+#include "../input-compat.h"
 
 static int uinput_dev_event(struct input_dev *dev, unsigned int type, unsigned int code, int value)
 {
-       struct uinput_device    *udev;
-
-       udev = dev->private;
+       struct uinput_device    *udev = input_get_drvdata(dev);
 
        udev->buff[udev->head].type = type;
        udev->buff[udev->head].code = code;
@@ -53,31 +55,33 @@ static int uinput_dev_event(struct input_dev *dev, unsigned int type, unsigned i
        return 0;
 }
 
+/* Atomically allocate an ID for the given request. Returns 0 on success. */
 static int uinput_request_alloc_id(struct uinput_device *udev, struct uinput_request *request)
 {
-       /* Atomically allocate an ID for the given request. Returns 0 on success. */
        int id;
        int err = -1;
 
        spin_lock(&udev->requests_lock);
 
-       for (id = 0; id < UINPUT_NUM_REQUESTS; id++)
+       for (id = 0; id < UINPUT_NUM_REQUESTS; id++) {
                if (!udev->requests[id]) {
                        request->id = id;
                        udev->requests[id] = request;
                        err = 0;
                        break;
                }
+       }
 
        spin_unlock(&udev->requests_lock);
        return err;
 }
 
-static struct uinput_requestuinput_request_find(struct uinput_device *udev, int id)
+static struct uinput_request *uinput_request_find(struct uinput_device *udev, int id)
 {
        /* Find an input request, by ID. Returns NULL if the ID isn't valid. */
        if (id >= UINPUT_NUM_REQUESTS || id < 0)
                return NULL;
+
        return udev->requests[id];
 }
 
@@ -97,38 +101,103 @@ static void uinput_request_done(struct uinput_device *udev, struct uinput_reques
        complete(&request->done);
 }
 
-static int uinput_request_submit(struct input_dev *dev, struct uinput_request *request)
+static int uinput_request_submit(struct uinput_device *udev, struct uinput_request *request)
 {
+       int retval;
+
+       retval = uinput_request_reserve_slot(udev, request);
+       if (retval)
+               return retval;
+
+       retval = mutex_lock_interruptible(&udev->mutex);
+       if (retval)
+               return retval;
+
+       if (udev->state != UIST_CREATED) {
+               retval = -ENODEV;
+               goto out;
+       }
+
        /* Tell our userspace app about this new request by queueing an input event */
-       uinput_dev_event(dev, EV_UINPUT, request->code, request->id);
+       uinput_dev_event(udev->dev, EV_UINPUT, request->code, request->id);
 
-       /* Wait for the request to complete */
-       wait_for_completion(&request->done);
-       return request->retval;
+ out:
+       mutex_unlock(&udev->mutex);
+       return retval;
 }
 
-static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect)
+/*
+ * Fail all ouitstanding requests so handlers don't wait for the userspace
+ * to finish processing them.
+ */
+static void uinput_flush_requests(struct uinput_device *udev)
 {
+       struct uinput_request *request;
+       int i;
+
+       spin_lock(&udev->requests_lock);
+
+       for (i = 0; i < UINPUT_NUM_REQUESTS; i++) {
+               request = udev->requests[i];
+               if (request) {
+                       request->retval = -ENODEV;
+                       uinput_request_done(udev, request);
+               }
+       }
+
+       spin_unlock(&udev->requests_lock);
+}
+
+static void uinput_dev_set_gain(struct input_dev *dev, u16 gain)
+{
+       uinput_dev_event(dev, EV_FF, FF_GAIN, gain);
+}
+
+static void uinput_dev_set_autocenter(struct input_dev *dev, u16 magnitude)
+{
+       uinput_dev_event(dev, EV_FF, FF_AUTOCENTER, magnitude);
+}
+
+static int uinput_dev_playback(struct input_dev *dev, int effect_id, int value)
+{
+       return uinput_dev_event(dev, EV_FF, effect_id, value);
+}
+
+static int uinput_dev_upload_effect(struct input_dev *dev, struct ff_effect *effect, struct ff_effect *old)
+{
+       struct uinput_device *udev = input_get_drvdata(dev);
        struct uinput_request request;
        int retval;
 
-       if (!test_bit(EV_FF, dev->evbit))
-               return -ENOSYS;
+       /*
+        * uinput driver does not currently support periodic effects with
+        * custom waveform since it does not have a way to pass buffer of
+        * samples (custom_data) to userspace. If ever there is a device
+        * supporting custom waveforms we would need to define an additional
+        * ioctl (UI_UPLOAD_SAMPLES) but for now we just bail out.
+        */
+       if (effect->type == FF_PERIODIC &&
+                       effect->u.periodic.waveform == FF_CUSTOM)
+               return -EINVAL;
 
        request.id = -1;
        init_completion(&request.done);
        request.code = UI_FF_UPLOAD;
-       request.u.effect = effect;
+       request.u.upload.effect = effect;
+       request.u.upload.old = old;
 
-       retval = uinput_request_reserve_slot(dev->private, &request);
-       if (!retval)
-               retval = uinput_request_submit(dev, &request);
+       retval = uinput_request_submit(udev, &request);
+       if (!retval) {
+               wait_for_completion(&request.done);
+               retval = request.retval;
+       }
 
        return retval;
 }
 
 static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id)
 {
+       struct uinput_device *udev = input_get_drvdata(dev);
        struct uinput_request request;
        int retval;
 
@@ -140,9 +209,11 @@ static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id)
        request.code = UI_FF_ERASE;
        request.u.effect_id = effect_id;
 
-       retval = uinput_request_reserve_slot(dev->private, &request);
-       if (!retval)
-               retval = uinput_request_submit(dev, &request);
+       retval = uinput_request_submit(udev, &request);
+       if (!retval) {
+               wait_for_completion(&request.done);
+               retval = request.retval;
+       }
 
        return retval;
 }
@@ -150,24 +221,29 @@ static int uinput_dev_erase_effect(struct input_dev *dev, int effect_id)
 static void uinput_destroy_device(struct uinput_device *udev)
 {
        const char *name, *phys;
+       struct input_dev *dev = udev->dev;
+       enum uinput_state old_state = udev->state;
+
+       udev->state = UIST_NEW_DEVICE;
 
-       if (udev->dev) {
-               name = udev->dev->name;
-               phys = udev->dev->phys;
-               if (udev->state == UIST_CREATED)
-                       input_unregister_device(udev->dev);
-               else
-                       input_free_device(udev->dev);
+       if (dev) {
+               name = dev->name;
+               phys = dev->phys;
+               if (old_state == UIST_CREATED) {
+                       uinput_flush_requests(udev);
+                       input_unregister_device(dev);
+               } else {
+                       input_free_device(dev);
+               }
                kfree(name);
                kfree(phys);
                udev->dev = NULL;
        }
-
-       udev->state = UIST_NEW_DEVICE;
 }
 
 static int uinput_create_device(struct uinput_device *udev)
 {
+       struct input_dev *dev = udev->dev;
        int error;
 
        if (udev->state != UIST_SETUP_COMPLETE) {
@@ -175,15 +251,29 @@ static int uinput_create_device(struct uinput_device *udev)
                return -EINVAL;
        }
 
-       error = input_register_device(udev->dev);
-       if (error) {
-               uinput_destroy_device(udev);
-               return error;
+       if (udev->ff_effects_max) {
+               error = input_ff_create(dev, udev->ff_effects_max);
+               if (error)
+                       goto fail1;
+
+               dev->ff->upload = uinput_dev_upload_effect;
+               dev->ff->erase = uinput_dev_erase_effect;
+               dev->ff->playback = uinput_dev_playback;
+               dev->ff->set_gain = uinput_dev_set_gain;
+               dev->ff->set_autocenter = uinput_dev_set_autocenter;
        }
 
+       error = input_register_device(udev->dev);
+       if (error)
+               goto fail2;
+
        udev->state = UIST_CREATED;
 
        return 0;
+
+ fail2:        input_ff_destroy(dev);
+ fail1: uinput_destroy_device(udev);
+       return error;
 }
 
 static int uinput_open(struct inode *inode, struct file *file)
@@ -194,13 +284,15 @@ static int uinput_open(struct inode *inode, struct file *file)
        if (!newdev)
                return -ENOMEM;
 
-       init_MUTEX(&newdev->sem);
+       lock_kernel();
+       mutex_init(&newdev->mutex);
        spin_lock_init(&newdev->requests_lock);
        init_waitqueue_head(&newdev->requests_waitq);
        init_waitqueue_head(&newdev->waitq);
        newdev->state = UIST_NEW_DEVICE;
 
        file->private_data = newdev;
+       unlock_kernel();
 
        return 0;
 }
@@ -243,9 +335,7 @@ static int uinput_allocate_device(struct uinput_device *udev)
                return -ENOMEM;
 
        udev->dev->event = uinput_dev_event;
-       udev->dev->upload_effect = uinput_dev_upload_effect;
-       udev->dev->erase_effect = uinput_dev_erase_effect;
-       udev->dev->private = udev;
+       input_set_drvdata(udev->dev, udev);
 
        return 0;
 }
@@ -278,6 +368,8 @@ static int uinput_setup_device(struct uinput_device *udev, const char __user *bu
                goto exit;
        }
 
+       udev->ff_effects_max = user_dev->ff_effects_max;
+
        size = strnlen(user_dev->name, UINPUT_MAX_NAME_SIZE) + 1;
        if (!size) {
                retval = -EINVAL;
@@ -296,7 +388,6 @@ static int uinput_setup_device(struct uinput_device *udev, const char __user *bu
        dev->id.vendor  = user_dev->id.vendor;
        dev->id.product = user_dev->id.product;
        dev->id.version = user_dev->id.version;
-       dev->ff_effects_max = user_dev->ff_effects_max;
 
        size = sizeof(int) * (ABS_MAX + 1);
        memcpy(dev->absmax, user_dev->absmax, size);
@@ -324,15 +415,15 @@ static inline ssize_t uinput_inject_event(struct uinput_device *udev, const char
 {
        struct input_event ev;
 
-       if (count != sizeof(struct input_event))
+       if (count < input_event_size())
                return -EINVAL;
 
-       if (copy_from_user(&ev, buffer, sizeof(struct input_event)))
+       if (input_event_from_user(buffer, &ev))
                return -EFAULT;
 
        input_event(udev->dev, ev.type, ev.code, ev.value);
 
-       return sizeof(struct input_event);
+       return input_event_size();
 }
 
 static ssize_t uinput_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos)
@@ -340,7 +431,7 @@ static ssize_t uinput_write(struct file *file, const char __user *buffer, size_t
        struct uinput_device *udev = file->private_data;
        int retval;
 
-       retval = down_interruptible(&udev->sem);
+       retval = mutex_lock_interruptible(&udev->mutex);
        if (retval)
                return retval;
 
@@ -348,7 +439,7 @@ static ssize_t uinput_write(struct file *file, const char __user *buffer, size_t
                        uinput_inject_event(udev, buffer, count) :
                        uinput_setup_device(udev, buffer, count);
 
-       up(&udev->sem);
+       mutex_unlock(&udev->mutex);
 
        return retval;
 }
@@ -369,7 +460,7 @@ static ssize_t uinput_read(struct file *file, char __user *buffer, size_t count,
        if (retval)
                return retval;
 
-       retval = down_interruptible(&udev->sem);
+       retval = mutex_lock_interruptible(&udev->mutex);
        if (retval)
                return retval;
 
@@ -378,17 +469,17 @@ static ssize_t uinput_read(struct file *file, char __user *buffer, size_t count,
                goto out;
        }
 
-       while (udev->head != udev->tail && retval + sizeof(struct input_event) <= count) {
-               if (copy_to_user(buffer + retval, &udev->buff[udev->tail], sizeof(struct input_event))) {
+       while (udev->head != udev->tail && retval + input_event_size() <= count) {
+               if (input_event_to_user(buffer + retval, &udev->buff[udev->tail])) {
                        retval = -EFAULT;
                        goto out;
                }
                udev->tail = (udev->tail + 1) % UINPUT_BUFFER_SIZE;
-               retval += sizeof(struct input_event);
+               retval += input_event_size();
        }
 
  out:
-       up(&udev->sem);
+       mutex_unlock(&udev->mutex);
 
        return retval;
 }
@@ -415,6 +506,93 @@ static int uinput_release(struct inode *inode, struct file *file)
        return 0;
 }
 
+#ifdef CONFIG_COMPAT
+struct uinput_ff_upload_compat {
+       int                     request_id;
+       int                     retval;
+       struct ff_effect_compat effect;
+       struct ff_effect_compat old;
+};
+
+static int uinput_ff_upload_to_user(char __user *buffer,
+                                   const struct uinput_ff_upload *ff_up)
+{
+       if (INPUT_COMPAT_TEST) {
+               struct uinput_ff_upload_compat ff_up_compat;
+
+               ff_up_compat.request_id = ff_up->request_id;
+               ff_up_compat.retval = ff_up->retval;
+               /*
+                * It so happens that the pointer that gives us the trouble
+                * is the last field in the structure. Since we don't support
+                * custom waveforms in uinput anyway we can just copy the whole
+                * thing (to the compat size) and ignore the pointer.
+                */
+               memcpy(&ff_up_compat.effect, &ff_up->effect,
+                       sizeof(struct ff_effect_compat));
+               memcpy(&ff_up_compat.old, &ff_up->old,
+                       sizeof(struct ff_effect_compat));
+
+               if (copy_to_user(buffer, &ff_up_compat,
+                                sizeof(struct uinput_ff_upload_compat)))
+                       return -EFAULT;
+       } else {
+               if (copy_to_user(buffer, ff_up,
+                                sizeof(struct uinput_ff_upload)))
+                       return -EFAULT;
+       }
+
+       return 0;
+}
+
+static int uinput_ff_upload_from_user(const char __user *buffer,
+                                     struct uinput_ff_upload *ff_up)
+{
+       if (INPUT_COMPAT_TEST) {
+               struct uinput_ff_upload_compat ff_up_compat;
+
+               if (copy_from_user(&ff_up_compat, buffer,
+                                  sizeof(struct uinput_ff_upload_compat)))
+                       return -EFAULT;
+
+               ff_up->request_id = ff_up_compat.request_id;
+               ff_up->retval = ff_up_compat.retval;
+               memcpy(&ff_up->effect, &ff_up_compat.effect,
+                       sizeof(struct ff_effect_compat));
+               memcpy(&ff_up->old, &ff_up_compat.old,
+                       sizeof(struct ff_effect_compat));
+
+       } else {
+               if (copy_from_user(ff_up, buffer,
+                                  sizeof(struct uinput_ff_upload)))
+                       return -EFAULT;
+       }
+
+       return 0;
+}
+
+#else
+
+static int uinput_ff_upload_to_user(char __user *buffer,
+                                   const struct uinput_ff_upload *ff_up)
+{
+       if (copy_to_user(buffer, ff_up, sizeof(struct uinput_ff_upload)))
+               return -EFAULT;
+
+       return 0;
+}
+
+static int uinput_ff_upload_from_user(const char __user *buffer,
+                                     struct uinput_ff_upload *ff_up)
+{
+       if (copy_from_user(ff_up, buffer, sizeof(struct uinput_ff_upload)))
+               return -EFAULT;
+
+       return 0;
+}
+
+#endif
+
 #define uinput_set_bit(_arg, _bit, _max)               \
 ({                                                     \
        int __ret = 0;                                  \
@@ -426,20 +604,18 @@ static int uinput_release(struct inode *inode, struct file *file)
        __ret;                                          \
 })
 
-static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+static long uinput_ioctl_handler(struct file *file, unsigned int cmd,
+                                unsigned long arg, void __user *p)
 {
        int                     retval;
-       struct uinput_device    *udev;
-       void __user             *p = (void __user *)arg;
+       struct uinput_device    *udev = file->private_data;
        struct uinput_ff_upload ff_up;
        struct uinput_ff_erase  ff_erase;
        struct uinput_request   *req;
        int                     length;
        char                    *phys;
 
-       udev = file->private_data;
-
-       retval = down_interruptible(&udev->sem);
+       retval = mutex_lock_interruptible(&udev->mutex);
        if (retval)
                return retval;
 
@@ -520,21 +696,24 @@ static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
                        break;
 
                case UI_BEGIN_FF_UPLOAD:
-                       if (copy_from_user(&ff_up, p, sizeof(ff_up))) {
-                               retval = -EFAULT;
+                       retval = uinput_ff_upload_from_user(p, &ff_up);
+                       if (retval)
                                break;
-                       }
+
                        req = uinput_request_find(udev, ff_up.request_id);
-                       if (!(req && req->code == UI_FF_UPLOAD && req->u.effect)) {
+                       if (!req || req->code != UI_FF_UPLOAD || !req->u.upload.effect) {
                                retval = -EINVAL;
                                break;
                        }
+
                        ff_up.retval = 0;
-                       memcpy(&ff_up.effect, req->u.effect, sizeof(struct ff_effect));
-                       if (copy_to_user(p, &ff_up, sizeof(ff_up))) {
-                               retval = -EFAULT;
-                               break;
-                       }
+                       ff_up.effect = *req->u.upload.effect;
+                       if (req->u.upload.old)
+                               ff_up.old = *req->u.upload.old;
+                       else
+                               memset(&ff_up.old, 0, sizeof(struct ff_effect));
+
+                       retval = uinput_ff_upload_to_user(p, &ff_up);
                        break;
 
                case UI_BEGIN_FF_ERASE:
@@ -542,31 +721,35 @@ static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
                                retval = -EFAULT;
                                break;
                        }
+
                        req = uinput_request_find(udev, ff_erase.request_id);
-                       if (!(req && req->code == UI_FF_ERASE)) {
+                       if (!req || req->code != UI_FF_ERASE) {
                                retval = -EINVAL;
                                break;
                        }
+
                        ff_erase.retval = 0;
                        ff_erase.effect_id = req->u.effect_id;
                        if (copy_to_user(p, &ff_erase, sizeof(ff_erase))) {
                                retval = -EFAULT;
                                break;
                        }
+
                        break;
 
                case UI_END_FF_UPLOAD:
-                       if (copy_from_user(&ff_up, p, sizeof(ff_up))) {
-                               retval = -EFAULT;
+                       retval = uinput_ff_upload_from_user(p, &ff_up);
+                       if (retval)
                                break;
-                       }
+
                        req = uinput_request_find(udev, ff_up.request_id);
-                       if (!(req && req->code == UI_FF_UPLOAD && req->u.effect)) {
+                       if (!req || req->code != UI_FF_UPLOAD ||
+                           !req->u.upload.effect) {
                                retval = -EINVAL;
                                break;
                        }
+
                        req->retval = ff_up.retval;
-                       memcpy(req->u.effect, &ff_up.effect, sizeof(struct ff_effect));
                        uinput_request_done(udev, req);
                        break;
 
@@ -575,11 +758,13 @@ static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
                                retval = -EFAULT;
                                break;
                        }
+
                        req = uinput_request_find(udev, ff_erase.request_id);
-                       if (!(req && req->code == UI_FF_ERASE)) {
+                       if (!req || req->code != UI_FF_ERASE) {
                                retval = -EINVAL;
                                break;
                        }
+
                        req->retval = ff_erase.retval;
                        uinput_request_done(udev, req);
                        break;
@@ -589,11 +774,23 @@ static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
        }
 
  out:
-       up(&udev->sem);
+       mutex_unlock(&udev->mutex);
        return retval;
 }
 
-static struct file_operations uinput_fops = {
+static long uinput_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       return uinput_ioctl_handler(file, cmd, arg, (void __user *)arg);
+}
+
+#ifdef CONFIG_COMPAT
+static long uinput_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
+{
+       return uinput_ioctl_handler(file, cmd, arg, compat_ptr(arg));
+}
+#endif
+
+static const struct file_operations uinput_fops = {
        .owner          = THIS_MODULE,
        .open           = uinput_open,
        .release        = uinput_release,
@@ -601,6 +798,9 @@ static struct file_operations uinput_fops = {
        .write          = uinput_write,
        .poll           = uinput_poll,
        .unlocked_ioctl = uinput_ioctl,
+#ifdef CONFIG_COMPAT
+       .compat_ioctl   = uinput_compat_ioctl,
+#endif
 };
 
 static struct miscdevice uinput_misc = {
@@ -622,6 +822,7 @@ static void __exit uinput_exit(void)
 MODULE_AUTHOR("Aristeu Sergio Rozanski Filho");
 MODULE_DESCRIPTION("User level driver support for input subsystem");
 MODULE_LICENSE("GPL");
+MODULE_VERSION("0.3");
 
 module_init(uinput_init);
 module_exit(uinput_exit);