summary refs log tree commit diff
diff options
context:
space:
mode:
authorAnssi Hannula <anssi.hannula@gmail.com>2006-07-19 01:40:22 -0400
committerDmitry Torokhov <dtor@insightbb.com>2006-07-19 01:40:22 -0400
commit509ca1a9383601fdc5612d3d3ba5b981f6eb6c8b (patch)
tree4d7b63c2b108510c11a89dc0ea45efe788fed779
parent806d41b756fecc1b13584e2b806b76d8934b1679 (diff)
downloadlinux-509ca1a9383601fdc5612d3d3ba5b981f6eb6c8b.tar.gz
Input: implement new force feedback interface
Implement a new force feedback interface, in which all non-driver-specific
operations are separated to a common module. This includes handling effect
type validations, locking, etc.

The effects are now file descriptor specific instead of the previous strange
half-process half-fd specific behaviour. The effect memory of devices is not
emptied if the root user opens and closes the device while another user is
using effects. This is a minor change and most likely no force feedback
aware programs are affected by this negatively.

Otherwise the userspace interface is left unaltered.

Signed-off-by: Anssi Hannula <anssi.hannula@gmail.com>
Signed-off-by: Dmitry Torokhov <dtor@mail.ru>
-rw-r--r--drivers/input/Makefile2
-rw-r--r--drivers/input/evdev.c32
-rw-r--r--drivers/input/ff-core.c367
-rw-r--r--drivers/input/input.c6
-rw-r--r--include/linux/input.h61
5 files changed, 450 insertions, 18 deletions
diff --git a/drivers/input/Makefile b/drivers/input/Makefile
index e539a309df8a..abdc9d435705 100644
--- a/drivers/input/Makefile
+++ b/drivers/input/Makefile
@@ -5,7 +5,7 @@
 # Each configuration option enables a list of files.
 
 obj-$(CONFIG_INPUT)		+= input-core.o
-input-core-objs := input.o
+input-core-objs := input.o ff-core.o
 
 obj-$(CONFIG_INPUT_MOUSEDEV)	+= mousedev.o
 obj-$(CONFIG_INPUT_JOYDEV)	+= joydev.o
diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c
index 4bf48188cc91..12c7ab876c34 100644
--- a/drivers/input/evdev.c
+++ b/drivers/input/evdev.c
@@ -391,8 +391,10 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
 	struct evdev *evdev = list->evdev;
 	struct input_dev *dev = evdev->handle.dev;
 	struct input_absinfo abs;
+	struct ff_effect effect;
 	int __user *ip = (int __user *)p;
 	int i, t, u, v;
+	int error;
 
 	if (!evdev->exist)
 		return -ENODEV;
@@ -460,27 +462,22 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd,
 			return 0;
 
 		case EVIOCSFF:
-			if (dev->upload_effect) {
-				struct ff_effect effect;
-				int err;
-
-				if (copy_from_user(&effect, p, sizeof(effect)))
-					return -EFAULT;
-				err = dev->upload_effect(dev, &effect);
-				if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))
-					return -EFAULT;
-				return err;
-			} else
-				return -ENOSYS;
+			if (copy_from_user(&effect, p, sizeof(effect)))
+				return -EFAULT;
 
-		case EVIOCRMFF:
-			if (!dev->erase_effect)
-				return -ENOSYS;
+			error = input_ff_upload(dev, &effect, file);
 
-			return dev->erase_effect(dev, (int)(unsigned long) p);
+			if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))
+				return -EFAULT;
+
+			return error;
+
+		case EVIOCRMFF:
+			return input_ff_erase(dev, (int)(unsigned long) p, file);
 
 		case EVIOCGEFFECTS:
-			if (put_user(dev->ff_effects_max, ip))
+			i = test_bit(EV_FF, dev->evbit) ? dev->ff->max_effects : 0;
+			if (put_user(i, ip))
 				return -EFAULT;
 			return 0;
 
@@ -669,6 +666,7 @@ static void evdev_disconnect(struct input_handle *handle)
 	evdev->exist = 0;
 
 	if (evdev->open) {
+		input_flush_device(handle, NULL);
 		input_close_device(handle);
 		wake_up_interruptible(&evdev->wait);
 		list_for_each_entry(list, &evdev->list, node)
diff --git a/drivers/input/ff-core.c b/drivers/input/ff-core.c
new file mode 100644
index 000000000000..35656cadc914
--- /dev/null
+++ b/drivers/input/ff-core.c
@@ -0,0 +1,367 @@
+/*
+ *  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;
+}
+
+/**
+ * input_ff_erase - erase an effect from device
+ * @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:
+		ff->playback(dev, code, value);
+		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
+ * @dev: input device supporintg force feedback
+ *
+ * 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);
diff --git a/drivers/input/input.c b/drivers/input/input.c
index 9cb4b9a54f01..1fc0517e9428 100644
--- a/drivers/input/input.c
+++ b/drivers/input/input.c
@@ -176,6 +176,10 @@ void input_event(struct input_dev *dev, unsigned int type, unsigned int code, in
 			break;
 
 		case EV_FF:
+
+			if (value < 0)
+				return;
+
 			if (dev->event)
 				dev->event(dev, type, code, value);
 			break;
@@ -762,7 +766,9 @@ static void input_dev_release(struct class_device *class_dev)
 {
 	struct input_dev *dev = to_input_dev(class_dev);
 
+	input_ff_destroy(dev);
 	kfree(dev);
+
 	module_put(THIS_MODULE);
 }
 
diff --git a/include/linux/input.h b/include/linux/input.h
index b3253ab72ff7..81c6ea5afedb 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -784,6 +784,9 @@ struct ff_effect {
 #define FF_INERTIA	0x56
 #define FF_RAMP		0x57
 
+#define FF_EFFECT_MIN	FF_RUMBLE
+#define FF_EFFECT_MAX	FF_RAMP
+
 /*
  * Force feedback periodic effect types
  */
@@ -795,6 +798,9 @@ struct ff_effect {
 #define FF_SAW_DOWN	0x5c
 #define FF_CUSTOM	0x5d
 
+#define FF_WAVEFORM_MIN	FF_SQUARE
+#define FF_WAVEFORM_MAX	FF_CUSTOM
+
 /*
  * Set ff device properties
  */
@@ -870,6 +876,8 @@ struct input_dev {
 	unsigned int keycodesize;
 	void *keycode;
 
+	struct ff_device *ff;
+
 	unsigned int repeat_key;
 	struct timer_list timer;
 
@@ -1108,5 +1116,58 @@ static inline void input_set_abs_params(struct input_dev *dev, int axis, int min
 
 extern struct class input_class;
 
+/**
+ * struct ff_device - force-feedback part of an input device
+ * @upload: Called to upload an new effect into device
+ * @erase: Called to erase an effect from device
+ * @playback: Called to request device to start playing specified effect
+ * @set_gain: Called to set specified gain
+ * @set_autocenter: Called to auto-center device
+ * @destroy: called by input core when parent input device is being
+ *	destroyed
+ * @private: driver-specific data, will be freed automatically
+ * @ffbit: bitmap of force feedback capabilities truly supported by
+ *	device (not emulated like ones in input_dev->ffbit)
+ * @mutex: mutex for serializing access to the device
+ * @max_effects: maximum number of effects supported by device
+ * @effects: pointer to an array of effects currently loaded into device
+ * @effect_owners: array of effect owners; when file handle owning
+ *	an effect gets closed the effcet is automatically erased
+ *
+ * Every force-feedback device must implement upload() and playback()
+ * methods; erase() is optional. set_gain() and set_autocenter() need
+ * only be implemented if driver sets up FF_GAIN and FF_AUTOCENTER
+ * bits.
+ */
+struct ff_device {
+	int (*upload)(struct input_dev *dev, struct ff_effect *effect,
+		      struct ff_effect *old);
+	int (*erase)(struct input_dev *dev, int effect_id);
+
+	int (*playback)(struct input_dev *dev, int effect_id, int value);
+	void (*set_gain)(struct input_dev *dev, u16 gain);
+	void (*set_autocenter)(struct input_dev *dev, u16 magnitude);
+
+	void (*destroy)(struct ff_device *);
+
+	void *private;
+
+	unsigned long ffbit[NBITS(FF_MAX)];
+
+	struct mutex mutex;
+
+	int max_effects;
+	struct ff_effect *effects;
+	struct file *effect_owners[];
+};
+
+int input_ff_create(struct input_dev *dev, int max_effects);
+void input_ff_destroy(struct input_dev *dev);
+
+int input_ff_event(struct input_dev *dev, unsigned int type, unsigned int code, int value);
+
+int input_ff_upload(struct input_dev *dev, struct ff_effect *effect, struct file *file);
+int input_ff_erase(struct input_dev *dev, int effect_id, struct file *file);
+
 #endif
 #endif