lkml.org 
[lkml]   [2006]   [May]   [30]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subject[patch 03/12] input: 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, effect timers, locking, etc.

As a result, support is added for gain and envelope for memoryless devices,
periodic => rumble conversion for memoryless devices and rumble => periodic
conversion for devices with periodic support instead of rumble support. Also
the effect memory of devices is not emptied if the root user opens and closes
the device while another user is using effects. This module also obsoletes
some flawed locking and timer code in few ff drivers.

The effects are now file descriptor specific instead of the previous strange
half-process half-fd specific behaviour. 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.

The code should be built as part of the input module, but unfortunately that
would require renaming input.c, which we don't want to do. So instead we make
INPUT_FF_EFFECTS a bool so that it cannot be built as a module.

Signed-off-by: Anssi Hannula <anssi.hannula@gmail.com>

---
drivers/input/Kconfig | 5
drivers/input/Makefile | 1
drivers/input/evdev.c | 30 -
drivers/input/ff-effects.c | 894 +++++++++++++++++++++++++++++++++++++++++++++
drivers/input/input.c | 12
include/linux/input.h | 81 ++++
6 files changed, 1006 insertions(+), 17 deletions(-)
Index: linux-2.6.17-rc4-git12/drivers/input/Kconfig
===================================================================
--- linux-2.6.17-rc4-git12.orig/drivers/input/Kconfig 2006-05-27 02:28:33.000000000 +0300
+++ linux-2.6.17-rc4-git12/drivers/input/Kconfig 2006-05-27 02:30:28.000000000 +0300
@@ -24,6 +24,11 @@ config INPUT

if INPUT

+config INPUT_FF_EFFECTS
+ bool "Force feedback effects"
+ help
+ Say Y here if you want to be able to play force feedback effects.
+
comment "Userland interfaces"

config INPUT_MOUSEDEV
Index: linux-2.6.17-rc4-git12/drivers/input/Makefile
===================================================================
--- linux-2.6.17-rc4-git12.orig/drivers/input/Makefile 2006-05-27 02:28:57.000000000 +0300
+++ linux-2.6.17-rc4-git12/drivers/input/Makefile 2006-05-27 02:48:34.000000000 +0300
@@ -5,6 +5,7 @@
# Each configuration option enables a list of files.

obj-$(CONFIG_INPUT) += input.o
+obj-$(CONFIG_INPUT_FF_EFFECTS) += ff-effects.o
obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o
obj-$(CONFIG_INPUT_JOYDEV) += joydev.o
obj-$(CONFIG_INPUT_EVDEV) += evdev.o
Index: linux-2.6.17-rc4-git12/include/linux/input.h
===================================================================
--- linux-2.6.17-rc4-git12.orig/include/linux/input.h 2006-05-27 02:28:33.000000000 +0300
+++ linux-2.6.17-rc4-git12/include/linux/input.h 2006-05-27 03:06:34.000000000 +0300
@@ -865,6 +865,9 @@ struct input_dev {
unsigned long sndbit[NBITS(SND_MAX)];
unsigned long ffbit[NBITS(FF_MAX)];
unsigned long swbit[NBITS(SW_MAX)];
+
+ struct ff_device *ff;
+ struct mutex ff_lock;
int ff_effects_max;

unsigned int keycodemax;
@@ -1000,6 +1003,84 @@ struct input_handle {
#define to_handle(n) container_of(n,struct input_handle,d_node)
#define to_handle_h(n) container_of(n,struct input_handle,h_node)

+#define FF_EFFECTS_MAX 64 /* maximum number of effects handled */
+
+struct ff_driver {
+ /* In case of devices with effect memory, upload() is called with the
+ effect to be uploaded into the device. old is the effect this
+ new effect replaces, otherwise NULL.
+ In case of memoryless devices, upload() is called with the effect
+ to be played back by the device. In case of memoryless devices,
+ upload() is called in atomic context. All other handlers are never
+ called in atomic context. */
+ int (*upload)(struct input_dev *dev, struct ff_effect *effect,
+ struct ff_effect *old);
+ int (*erase)(struct input_dev *dev, int effect_id);
+ /* Controls the playback on devices with effect memory */
+ 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);
+};
+
+#define FF_EFFECT_USED 0
+#define FF_EFFECT_STARTED 1
+#define FF_EFFECT_PLAYING 2
+
+struct ff_effect_status {
+ struct file *owner;
+ struct ff_effect effect;
+ unsigned long flags[1]; /* effect state (USED, STARTED, PLAYING) */
+ /* for memoryless devices: */
+ int count; /* loop count of the effect */
+ unsigned long play_at; /* start time */
+ unsigned long stop_at; /* stop time */
+ unsigned long adj_at; /* last time the effect was sent */
+};
+
+struct ff_device {
+ /* flags has the same values as ffbit, but the capabilities declared
+ here are really implemented by the driver, not by conversion etc */
+ unsigned long flags[NBITS(FF_MAX)];
+ u16 gain; /* the gain of memoryless device */
+ spinlock_t atomiclock; /* atomic lock for the memoryless devices */
+ struct ff_driver *driver;
+ struct timer_list timer;
+ struct ff_effect_status effects[FF_EFFECTS_MAX];
+};
+
+#ifdef CONFIG_INPUT_FF_EFFECTS
+int input_ff_allocate(struct input_dev *dev);
+int input_ff_register(struct input_dev *dev, struct ff_driver *driver);
+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);
+int input_ff_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int value);
+#else
+static inline int input_ff_allocate(struct input_dev *dev)
+{
+ return -EPERM;
+}
+static inline int input_ff_register(struct input_dev *dev,
+ struct ff_driver *driver)
+{
+ return -EPERM;
+}
+static inline int input_ff_upload(struct input_dev *dev,
+ struct ff_effect *effect, struct file *file)
+{
+ return -EINVAL;
+}
+static inline int input_ff_erase(struct input_dev *dev, int effect_id,
+ struct file *file)
+{
+ return -EINVAL;
+}
+static inline int input_ff_event(struct input_dev *dev, unsigned int type,
+ unsigned int code, int value) { }
+#endif
+
+
static inline void init_input_dev(struct input_dev *dev)
{
INIT_LIST_HEAD(&dev->h_list);
Index: linux-2.6.17-rc4-git12/drivers/input/input.c
===================================================================
--- linux-2.6.17-rc4-git12.orig/drivers/input/input.c 2006-05-27 02:28:57.000000000 +0300
+++ linux-2.6.17-rc4-git12/drivers/input/input.c 2006-05-27 02:38:35.000000000 +0300
@@ -733,6 +733,17 @@ static void input_dev_release(struct cla
{
struct input_dev *dev = to_input_dev(class_dev);

+ if (dev->ff) {
+ struct ff_device *ff = dev->ff;
+ clear_bit(EV_FF, dev->evbit);
+ mutex_lock(&dev->ff_lock);
+ del_timer_sync(&ff->timer);
+ dev->flush = NULL;
+ dev->ff = NULL;
+ mutex_unlock(&dev->ff_lock);
+ kfree(ff);
+ }
+
kfree(dev);
module_put(THIS_MODULE);
}
@@ -874,6 +885,7 @@ struct input_dev *input_allocate_device(
class_device_initialize(&dev->cdev);
INIT_LIST_HEAD(&dev->h_list);
INIT_LIST_HEAD(&dev->node);
+ mutex_init(&dev->ff_lock);
}

return dev;
Index: linux-2.6.17-rc4-git12/drivers/input/evdev.c
===================================================================
--- linux-2.6.17-rc4-git12.orig/drivers/input/evdev.c 2006-05-27 02:28:33.000000000 +0300
+++ linux-2.6.17-rc4-git12/drivers/input/evdev.c 2006-05-27 02:30:28.000000000 +0300
@@ -457,25 +457,21 @@ static long evdev_ioctl_handler(struct f

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;
+ case EVIOCSFF: {
+ struct ff_effect effect;
+ int err;

- case EVIOCRMFF:
- if (!dev->erase_effect)
- return -ENOSYS;
+ if (copy_from_user(&effect, p, sizeof(effect)))
+ return -EFAULT;
+ err = input_ff_upload(dev, &effect, file);
+ if (put_user(effect.id, &(((struct ff_effect __user *)p)->id)))
+ return -EFAULT;
+ return err;
+ }

- return dev->erase_effect(dev, (int)(unsigned long) p);
+ case EVIOCRMFF:
+ return input_ff_erase(dev, (int)(unsigned long) p,
+ file);

case EVIOCGEFFECTS:
if (put_user(dev->ff_effects_max, ip))
Index: linux-2.6.17-rc4-git12/drivers/input/ff-effects.c
===================================================================
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
+++ linux-2.6.17-rc4-git12/drivers/input/ff-effects.c 2006-05-27 03:06:56.000000000 +0300
@@ -0,0 +1,894 @@
+/*
+ * Force feedback support for Linux input subsystem
+ *
+ * Copyright (c) 2006 Anssi Hannula <anssi.hannula@gmail.com>
+ */
+
+/*
+ * 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-effects: " format "\n", ## arg)
+
+#include <linux/input.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/spinlock.h>
+#include <linux/sched.h>
+
+#include "fixp-arith.h"
+
+MODULE_LICENSE("GPL");
+MODULE_AUTHOR("Anssi Hannula <anssi.hannula@gmail.com>");
+MODULE_DESCRIPTION("Force feedback support for input subsystem");
+
+/* Number of effects handled with memoryless devices */
+#define FF_MEMLESS_EFFECTS 16
+
+/* Envelope update interval in ms */
+#define FF_ENVELOPE_INTERVAL 50
+
+EXPORT_SYMBOL_GPL(input_ff_allocate);
+EXPORT_SYMBOL_GPL(input_ff_register);
+EXPORT_SYMBOL_GPL(input_ff_upload);
+EXPORT_SYMBOL_GPL(input_ff_erase);
+EXPORT_SYMBOL_GPL(input_ff_event);
+
+/**
+ * Lock the mutex and check the device has not been deleted
+ */
+static inline int input_ff_safe_lock(struct input_dev *dev)
+{
+ mutex_lock(&dev->ff_lock);
+ if (dev->ff)
+ return 0;
+
+ mutex_unlock(&dev->ff_lock);
+ return 1;
+}
+
+/**
+ * Check that the effect_id is a valid effect and the user has access to it
+ */
+static inline int input_ff_effect_access(struct input_dev *dev, int effect_id,
+ struct file *file)
+{
+ struct ff_device *ff = dev->ff;
+
+ if (effect_id >= dev->ff_effects_max || effect_id < 0
+ || !test_bit(FF_EFFECT_USED, ff->effects[effect_id].flags))
+ return -EINVAL;
+
+ if (ff->effects[effect_id].owner != file)
+ return -EACCES;
+
+ return 0;
+}
+
+/**
+ * Check for the next time envelope requires an update on memoryless devices
+ * @event_time: Time of the next update
+ *
+ * If an event is found before @event_time, @event_time is changed and the
+ * functions returns 1. Otherwise it returns 0.
+ */
+static int input_ff_envelope_time(struct ff_effect_status *effect,
+ struct ff_envelope *envelope,
+ unsigned long *event_time)
+{
+ unsigned long fade_start;
+ if (!envelope)
+ return 0;
+
+ if (envelope->attack_length
+ && time_after(effect->play_at +
+ msecs_to_jiffies(envelope->attack_length),
+ effect->adj_at)) {
+ *event_time =
+ effect->adj_at + msecs_to_jiffies(FF_ENVELOPE_INTERVAL);
+ return 1;
+ }
+
+ if (!envelope->fade_length || !effect->effect.replay.length)
+ return 0;
+
+ fade_start = effect->stop_at - msecs_to_jiffies(envelope->fade_length);
+
+ if (time_after(fade_start, effect->adj_at)) {
+ *event_time = fade_start;
+ return 1;
+ }
+
+ *event_time = effect->adj_at + msecs_to_jiffies(FF_ENVELOPE_INTERVAL);
+ if (time_after(*event_time, effect->stop_at))
+ *event_time = effect->stop_at;
+
+ return 1;
+}
+
+/**
+ * Calculate the next time effect requires an update on memoryless devices
+ * @ff: The device
+ *
+ * Runs through the effects and updates the timer if necessary. This function
+ * should be called only when the spinlock is locked.
+ */
+static void input_ff_calc_timer(struct ff_device *ff)
+{
+ int i;
+ int events = 0;
+ unsigned long next_time = 0;
+ debug("calculating next timer");
+ for (i = 0; i < FF_MEMLESS_EFFECTS; ++i) {
+ unsigned long event_time;
+ struct ff_envelope *envelope = NULL;
+ int event_set = 0;
+
+ if (!test_bit(FF_EFFECT_STARTED, ff->effects[i].flags)) {
+ if (test_bit(FF_EFFECT_PLAYING, ff->effects[i].flags)) {
+ event_time = ff->effects[i].stop_at = jiffies;
+ event_set = 1;
+ } else
+ continue;
+ } else {
+ if (!test_bit(FF_EFFECT_PLAYING, ff->effects[i].flags)) {
+ event_time = ff->effects[i].play_at;
+ event_set = 1;
+ } else {
+ if (ff->effects[i].effect.type == FF_PERIODIC)
+ envelope =
+ &ff->effects[i].effect.u.periodic.
+ envelope;
+ else if (ff->effects[i].effect.type ==
+ FF_CONSTANT)
+ envelope =
+ &ff->effects[i].effect.u.constant.
+ envelope;
+
+ event_set =
+ input_ff_envelope_time(&ff->effects[i],
+ envelope,
+ &event_time);
+ if (!event_set
+ && ff->effects[i].effect.replay.length) {
+ event_time = ff->effects[i].stop_at;
+ event_set = 1;
+ }
+ }
+ }
+
+ if (!event_set)
+ continue;
+ events++;
+
+ if (time_after(jiffies, event_time)) {
+ event_time = jiffies;
+ break;
+ }
+ if (events == 1)
+ next_time = event_time;
+ else {
+ if (time_after(next_time, event_time))
+ next_time = event_time;
+ }
+ }
+ if (!events) {
+ debug("no actions");
+ del_timer(&ff->timer);
+ } else {
+ debug("timer set");
+ mod_timer(&ff->timer, next_time);
+ }
+}
+
+/**
+ * abs() with -0x8000 => 0x7fff exception
+ */
+static inline u16 input_ff_unsign(s16 value)
+{
+ if (value == -0x8000)
+ return 0x7fff;
+
+ return (value < 0 ? -value : value);
+}
+
+/**
+ * Apply an envelope to a value
+ */
+static int input_ff_envelope(struct ff_effect_status *effect, int value,
+ struct ff_envelope *envelope)
+{
+ unsigned long current_time = jiffies;
+ unsigned int time_from_level;
+ unsigned int time_of_envelope;
+ unsigned int envelope_level;
+ int difference;
+
+ if (envelope->attack_length
+ && time_after(effect->play_at +
+ msecs_to_jiffies(envelope->attack_length),
+ current_time)) {
+ debug("value = 0x%x, attack_level = 0x%x", value,
+ envelope->attack_level);
+ time_from_level = current_time - effect->play_at;
+ time_of_envelope = msecs_to_jiffies(envelope->attack_length);
+ envelope_level =
+ envelope->attack_level >
+ 0x7fff ? 0x7fff : envelope->attack_level;
+ } else if (envelope->fade_length && effect->effect.replay.length
+ && time_after(current_time,
+ effect->stop_at -
+ msecs_to_jiffies(envelope->fade_length))
+ && time_after(effect->stop_at, current_time)) {
+ time_from_level = effect->stop_at - current_time;
+ time_of_envelope = msecs_to_jiffies(envelope->fade_length);
+ envelope_level =
+ envelope->fade_level >
+ 0x7fff ? 0x7fff : envelope->fade_level;
+ } else {
+ return value;
+ }
+
+ difference = abs(value) - envelope_level;
+
+ debug("difference = %d", difference);
+ debug("time_from_level = 0x%x", time_from_level);
+ debug("time_of_envelope = 0x%x", time_of_envelope);
+ if (difference < 0)
+ difference =
+ -((-difference) * time_from_level / time_of_envelope);
+ else
+ difference = difference * time_from_level / time_of_envelope;
+
+ debug("difference = %d", difference);
+
+ if (value < 0)
+ return -(difference + envelope_level);
+
+ return difference + envelope_level;
+}
+
+/**
+ * Return the type the effect has to be converted into (memless devices)
+ */
+static int input_ff_emu_effect_type(struct input_dev *dev, int effect_type)
+{
+
+ if (test_bit(effect_type, dev->ff->flags))
+ return effect_type;
+
+ if (effect_type == FF_PERIODIC && test_bit(FF_RUMBLE, dev->ff->flags))
+ return FF_RUMBLE;
+
+ printk(KERN_ERR
+ "ff-effects: invalid type in input_ff_emu_effect_type()\n");
+ return 0;
+}
+
+/**
+ * Safe sum
+ * @a: Integer to sum
+ * @b: Integer to sum
+ * @limit: The sum limit
+ *
+ * If @a+@b is above @limit, return @limit
+ */
+static int input_ff_safe_sum(int a, int b, int limit)
+{
+ int c;
+ if (!a)
+ return b;
+ c = a + b;
+ if (c > limit)
+ return limit;
+ return c;
+}
+
+/**
+ * Signed safe sum with range -0x80-0x7f
+ */
+static s8 input_ff_s8_sum(int a, int b)
+{
+ int c;
+ c = input_ff_safe_sum(a, b, 0x7f);
+ if (c < -0x80)
+ return -0x80;
+ return c;
+}
+
+/**
+ * Convert an effect (for devices with memory)
+ */
+static void input_ff_convert_effect(struct input_dev *dev,
+ struct ff_effect *effect)
+{
+ int strong_magnitude, weak_magnitude;
+
+ if (effect->type == FF_RUMBLE && test_bit(FF_PERIODIC, dev->ff->flags)) {
+ /* Strong magnitude as 2/3 full and weak 1/3 full */
+ /* Also divide by 2 */
+ strong_magnitude = effect->u.rumble.strong_magnitude * 2 / 6;
+ weak_magnitude = 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 =
+ input_ff_safe_sum(strong_magnitude, weak_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;
+ }
+
+ printk(KERN_ERR
+ "ff-effects: invalid effect type in convert_effect()\n");
+}
+
+/**
+ * Sum two effects and apply gain
+ * @effect: The effect that is going to be sent to the device
+ * @new: The effect that is added to @effect
+ * @gain: The gain to be applied to @new before adding
+ *
+ * Memoryless devices have only one effect per effect type active at one time,
+ * so we have to convert multiple effects to a single effect.
+ */
+static void input_ff_sum_effect(struct ff_effect *effect,
+ struct ff_effect_status *new, unsigned int gain)
+{
+ unsigned int strong, weak, i;
+ int x, y;
+ fixp_t level;
+ switch (new->effect.type) {
+ case FF_CONSTANT:
+ if (effect->type != FF_CONSTANT)
+ break;
+ i = new->effect.direction * 360 / 0xffff;
+ level =
+ fixp_new16(input_ff_envelope
+ (new, new->effect.u.constant.level,
+ &new->effect.u.constant.envelope));
+ x = fixp_mult(fixp_sin(i), level) * gain / 0xffff;
+ y = fixp_mult(-fixp_cos(i), level) * gain / 0xffff;
+ /* here we abuse ff_ramp to hold x and y of constant force */
+ /* If in future any driver wants something else than
+ x and y in s8, this should be changed to something more generic */
+ effect->u.ramp.start_level =
+ input_ff_s8_sum(effect->u.rumble.strong_magnitude, x);
+ effect->u.ramp.end_level =
+ input_ff_s8_sum(effect->u.rumble.weak_magnitude, y);
+ return;
+ case FF_RUMBLE:
+ if (effect->type != FF_RUMBLE)
+ break;
+ strong = new->effect.u.rumble.strong_magnitude * gain / 0xffff;
+ weak = new->effect.u.rumble.weak_magnitude * gain / 0xffff;
+ effect->u.rumble.strong_magnitude =
+ input_ff_safe_sum(effect->u.rumble.strong_magnitude, strong,
+ 0xffff);
+ effect->u.rumble.weak_magnitude =
+ input_ff_safe_sum(effect->u.rumble.weak_magnitude, weak,
+ 0xffff);
+ return;
+ case FF_PERIODIC:
+ if (effect->type != FF_RUMBLE)
+ break;
+ /* very small period values should lessen the magnitude */
+ i = input_ff_unsign(new->effect.u.periodic.magnitude);
+ i = input_ff_envelope(new, i, &new->effect.u.periodic.envelope);
+ /* here we also scale it 0x7fff => 0xffff */
+ i = i * gain / 0x7fff;
+
+ effect->u.rumble.strong_magnitude =
+ input_ff_safe_sum(effect->u.rumble.strong_magnitude, i,
+ 0xffff);
+ effect->u.rumble.weak_magnitude =
+ input_ff_safe_sum(effect->u.rumble.weak_magnitude, i,
+ 0xffff);
+ return;
+ }
+
+ printk(KERN_ERR "ff-effects: invalid type in input_ff_sum_effect()\n");
+}
+
+/**
+ * Send the effects to the memoryless device
+ * @timer_data: struct input_dev
+ *
+ * All effects are converted and sent to the device. This function is atomic
+ * so it cannot lock the mutex. Therefore any data structures this function
+ * uses should not be used anywhere without locking the spinlock.
+ */
+static void input_ff_timer(unsigned long timer_data)
+{
+ struct input_dev *dev = (struct input_dev *)timer_data;
+ struct ff_device *ff = dev->ff;
+ struct ff_effect effect;
+ int i;
+ unsigned long flags;
+ int effects_pending = 1;
+ DECLARE_BITMAP(effect_handled, FF_MEMLESS_EFFECTS);
+ int effect_type;
+ int safety = 0;
+
+ debug("timer: updating effects");
+
+ memset(effect_handled, 0, sizeof(effect_handled));
+ spin_lock_irqsave(&ff->atomiclock, flags);
+
+ while (effects_pending) {
+ if (safety++ > 50) {
+ printk(KERN_ERR
+ "ff-effects: update aborted to avoid deadlock\n");
+ BUG();
+ break;
+ }
+ effects_pending = 0;
+ memset(&effect, 0, sizeof(effect));
+ for (i = 0; i < dev->ff_effects_max; i++) {
+
+ if (test_bit(i, effect_handled))
+ continue;
+
+ if (!test_bit(FF_EFFECT_STARTED, ff->effects[i].flags)) {
+ if (!test_bit
+ (FF_EFFECT_PLAYING, ff->effects[i].flags))
+ continue;
+ if (!effect.type) {
+ effect.type =
+ input_ff_emu_effect_type(dev,
+ ff->
+ effects[i].
+ effect.
+ type);
+ __clear_bit(FF_EFFECT_PLAYING,
+ ff->effects[i].flags);
+ __set_bit(i, effect_handled);
+ continue;
+ }
+ if (effect.type ==
+ input_ff_emu_effect_type(dev,
+ ff->effects[i].
+ effect.type)) {
+ __clear_bit(FF_EFFECT_PLAYING,
+ ff->effects[i].flags);
+ __set_bit(i, effect_handled);
+ continue;
+ }
+ debug("effects pending: case 1");
+ effects_pending = 1;
+ continue;
+ }
+
+ if (time_after(ff->effects[i].play_at, jiffies)) {
+ __set_bit(i, effect_handled);
+ continue;
+ }
+
+ effect_type =
+ input_ff_emu_effect_type(dev,
+ ff->effects[i].effect.
+ type);
+
+ if (effect.type != effect_type) {
+ if (effect.type) {
+ debug("effects pending: case 2");
+ effects_pending = 1;
+ continue;
+ }
+ effect.type = effect_type;
+ }
+
+ if (ff->effects[i].effect.replay.length
+ && time_after_eq(jiffies, ff->effects[i].stop_at)) {
+
+ __clear_bit(FF_EFFECT_PLAYING,
+ ff->effects[i].flags);
+
+ if (--ff->effects[i].count <= 0) {
+ __clear_bit(FF_EFFECT_STARTED,
+ ff->effects[i].flags);
+ __set_bit(i, effect_handled);
+ continue;
+ } else {
+ ff->effects[i].play_at =
+ jiffies +
+ msecs_to_jiffies(ff->effects[i].
+ effect.replay.
+ delay);
+ ff->effects[i].stop_at =
+ dev->ff->effects[i].play_at +
+ msecs_to_jiffies(ff->effects[i].
+ effect.replay.
+ length);
+ __set_bit(i, effect_handled);
+ continue;
+ }
+ }
+
+ __set_bit(FF_EFFECT_PLAYING, ff->effects[i].flags);
+
+ input_ff_sum_effect(&effect, &ff->effects[i], ff->gain);
+ ff->effects[i].adj_at = jiffies;
+ __set_bit(i, effect_handled);
+ }
+
+ ff->driver->upload(dev, &effect, NULL);
+ }
+
+ input_ff_calc_timer(ff);
+ spin_unlock_irqrestore(&ff->atomiclock, flags);
+
+}
+
+/**
+ * Erase an effect
+ * @file: The requester
+ *
+ * Erases the effect if the requester is also the effect owner. The mutex
+ * should already be locked before calling this function. If the device is a
+ * memoryless device, the spinlock will be locked.
+ */
+static int _input_ff_erase(struct input_dev *dev, int effect_id,
+ struct file *file)
+{
+ unsigned long flags;
+ int ret = input_ff_effect_access(dev, effect_id, file);
+ if (ret)
+ return ret;
+ if (dev->ff->driver->playback) {
+ dev->ff->driver->playback(dev, effect_id, 0);
+ ret = dev->ff->driver->erase(dev, effect_id);
+ if (!ret)
+ __clear_bit(FF_EFFECT_USED,
+ dev->ff->effects[effect_id].flags);
+ } else {
+ spin_lock_irqsave(&dev->ff->atomiclock, flags);
+ if (__test_and_clear_bit
+ (FF_EFFECT_STARTED, dev->ff->effects[effect_id].flags))
+ input_ff_calc_timer(dev->ff);
+ spin_unlock_irqrestore(&dev->ff->atomiclock, flags);
+ __clear_bit(FF_EFFECT_USED, dev->ff->effects[effect_id].flags);
+ }
+ return ret;
+}
+
+/**
+ * Lock the mutex and call _input_ff_erase
+ */
+int input_ff_erase(struct input_dev *dev, int effect_id, struct file *file)
+{
+ struct ff_device *ff;
+ int ret;
+ if (!test_bit(EV_FF, dev->evbit))
+ return -EINVAL;
+ if (input_ff_safe_lock(dev))
+ return -ENODEV;
+ ff = dev->ff;
+
+ ret = _input_ff_erase(dev, effect_id, file);
+
+ mutex_unlock(&dev->ff_lock);
+ return ret;
+}
+
+/**
+ * Lock the mutex and call _input_ff_erase for all effects
+ */
+static int input_ff_flush(struct input_dev *dev, struct file *file)
+{
+ struct ff_device *ff;
+ int i;
+ debug("flushing now");
+ if (input_ff_safe_lock(dev))
+ return -ENODEV;
+ ff = dev->ff;
+ for (i = 0; i < dev->ff_effects_max; i++)
+ _input_ff_erase(dev, i, file);
+ mutex_unlock(&dev->ff_lock);
+ return 0;
+}
+
+/**
+ * Handle the EV_FF input event
+ */
+int input_ff_event(struct input_dev *dev, unsigned int type, unsigned int code,
+ int value)
+{
+ struct ff_device *ff;
+ unsigned long flags;
+ int i;
+ if (type != EV_FF || value < 0)
+ return -EINVAL;
+
+ if (input_ff_safe_lock(dev))
+ return -ENODEV;
+ ff = dev->ff;
+
+ if (code == FF_GAIN) {
+ if (!test_bit(FF_GAIN, dev->ffbit) || value > 0xffff)
+ goto out;
+
+ if (ff->driver->playback) {
+ ff->driver->set_gain(dev, value);
+ } else {
+ spin_lock_irqsave(&ff->atomiclock, flags);
+ ff->gain = value;
+ for (i = 0; i < dev->ff_effects_max; i++)
+ __clear_bit(FF_EFFECT_PLAYING,
+ ff->effects[i].flags);
+ input_ff_calc_timer(ff);
+ spin_unlock_irqrestore(&ff->atomiclock, flags);
+ }
+
+ goto out;
+ }
+
+ if (code == FF_AUTOCENTER) {
+ if (!test_bit(FF_AUTOCENTER, dev->ffbit) || value > 0xffff)
+ goto out;
+
+ if (ff->driver->playback)
+ ff->driver->set_autocenter(dev, value);
+
+ goto out;
+ }
+
+ if (ff->driver->playback) {
+ ff->driver->playback(dev, code, value);
+ goto out;
+ }
+
+ spin_lock_irqsave(&ff->atomiclock, flags);
+ if (value > 0) {
+ debug("initiated play");
+ __set_bit(FF_EFFECT_STARTED, ff->effects[code].flags);
+ __clear_bit(FF_EFFECT_PLAYING, ff->effects[code].flags);
+ ff->effects[code].count = value;
+ ff->effects[code].play_at = jiffies +
+ msecs_to_jiffies(ff->effects[code].effect.replay.delay);
+ ff->effects[code].stop_at = ff->effects[code].play_at +
+ msecs_to_jiffies(ff->effects[code].effect.replay.length);
+ ff->effects[code].adj_at = ff->effects[code].play_at;
+ } else {
+ debug("initiated stop");
+ __clear_bit(FF_EFFECT_STARTED, ff->effects[code].flags);
+ }
+ input_ff_calc_timer(ff);
+ spin_unlock_irqrestore(&ff->atomiclock, flags);
+
+ out:
+ mutex_unlock(&dev->ff_lock);
+ return 0; /* Nobody cares about the return value of this function */
+}
+
+#define VALID_EFFECT_MIN FF_RUMBLE
+#define VALID_EFFECT_MAX FF_RAMP
+#define VALID_WFORM_MIN FF_SQUARE
+#define VALID_WFORM_MAX FF_CUSTOM
+/**
+ * Check that the effect type and waveform are valid
+ */
+static int input_ff_validate_effect(struct ff_effect *effect)
+{
+ if (effect->type < VALID_EFFECT_MIN || effect->type > VALID_EFFECT_MAX)
+ return -EINVAL;
+ if (effect->type == FF_PERIODIC &&
+ (effect->u.periodic.waveform < VALID_WFORM_MIN ||
+ effect->u.periodic.waveform > VALID_WFORM_MAX))
+ return -EINVAL;
+ return 0;
+}
+
+/**
+ * Handle effect upload
+ */
+int input_ff_upload(struct input_dev *dev, struct ff_effect *effect,
+ struct file *file)
+{
+ struct ff_device *ff;
+ unsigned long flags;
+ int ret = 0;
+ int id;
+
+ if (!test_bit(EV_FF, dev->evbit) || input_ff_validate_effect(effect) ||
+ !test_bit(effect->type, dev->ffbit) ||
+ (effect->type == FF_PERIODIC &&
+ !test_bit(effect->u.periodic.waveform, dev->ffbit))) {
+ debug("invalid upload");
+ return -EINVAL;
+ }
+
+ if (input_ff_safe_lock(dev))
+ return -ENODEV;
+ ff = dev->ff;
+
+ if (effect->id == -1) {
+ for (id = 0;
+ id < dev->ff_effects_max
+ && test_bit(FF_EFFECT_USED, ff->effects[id].flags); id++) ;
+
+ if (id >= dev->ff_effects_max) {
+ mutex_unlock(&dev->ff_lock);
+ return -ENOSPC;
+ }
+
+ effect->id = id;
+ ff->effects[id].owner = file;
+ ff->effects[id].flags[0] = 0;
+ ff->effects[id].effect = *effect;
+
+ if (ff->driver->playback) {
+ if (!test_bit(effect->type, ff->flags))
+ input_ff_convert_effect(dev, effect);
+ ret = ff->driver->upload(dev, effect, NULL);
+ if (!ret)
+ __set_bit(FF_EFFECT_USED,
+ ff->effects[id].flags);
+ } else
+ __set_bit(FF_EFFECT_USED, ff->effects[id].flags);
+
+ } else {
+ id = effect->id;
+
+ ret = input_ff_effect_access(dev, id, file);
+ if (ret) {
+ mutex_unlock(&dev->ff_lock);
+ return ret;
+ }
+
+ if (effect->type != ff->effects[id].effect.type ||
+ (effect->type == FF_PERIODIC
+ && effect->u.periodic.waveform !=
+ ff->effects[id].effect.u.periodic.waveform)) {
+ mutex_unlock(&dev->ff_lock);
+ return -EINVAL;
+ }
+
+ if (ff->driver->playback) {
+ if (!test_bit(effect->type, ff->flags))
+ input_ff_convert_effect(dev, effect);
+ ret =
+ ff->driver->upload(dev, effect,
+ &ff->effects[id].effect);
+ ff->effects[id].effect = *effect;
+ mutex_unlock(&dev->ff_lock);
+ return ret;
+ }
+ spin_lock_irqsave(&ff->atomiclock, flags);
+ ff->effects[id].effect = *effect;
+ __clear_bit(FF_EFFECT_PLAYING, ff->effects[id].flags);
+ spin_unlock_irqrestore(&ff->atomiclock, flags);
+
+ }
+
+ mutex_unlock(&dev->ff_lock);
+ return ret;
+}
+
+/**
+ * Allocate the ff_device in dev->ff
+ */
+int input_ff_allocate(struct input_dev *dev)
+{
+ debug("allocating device");
+ mutex_lock(&dev->ff_lock);
+ WARN_ON(dev->ff);
+ dev->ff = kzalloc(sizeof(*dev->ff), GFP_KERNEL);
+ if (!dev->ff) {
+ mutex_unlock(&dev->ff_lock);
+ return -ENOMEM;
+ }
+ spin_lock_init(&dev->ff->atomiclock);
+ setup_timer(&dev->ff->timer, input_ff_timer, (unsigned long)dev);
+ mutex_unlock(&dev->ff_lock);
+ debug("ff allocated");
+ return 0;
+}
+
+/**
+ * Set the bits and handlers
+ * @driver: The ff_driver struct
+ *
+ * If possible, this function should be called before the input device is
+ * registered. It can however be ran again if the capability bits have
+ * changed.
+ */
+int input_ff_register(struct input_dev *dev, struct ff_driver *driver)
+{
+ int i;
+ debug("registering device");
+ if (!dev->ff) {
+ printk(KERN_ERR
+ "ff-effects: tried to register before allocate\n");
+ return -EPERM;
+ }
+ if (driver->playback && !dev->ff_effects_max) {
+ printk(KERN_ERR
+ "ff-effects: cannot register a device with 0 effects\n");
+ return -EINVAL;
+ }
+ mutex_lock(&dev->ff_lock);
+
+ dev->flush = input_ff_flush;
+ dev->ff->driver = driver;
+
+ for (i = 0; i <= FF_MAX; i++)
+ clear_bit(i, dev->ffbit);
+
+ if (!dev->ff->driver->playback) {
+ dev->ff_effects_max = FF_MEMLESS_EFFECTS;
+ dev->ff->gain = 0xffff;
+ set_bit(FF_GAIN, dev->ffbit);
+ }
+
+ if (test_bit(FF_RUMBLE, dev->ff->flags)) {
+ debug("device has rumble");
+ set_bit(FF_RUMBLE, dev->ffbit);
+ if (!dev->ff->driver->playback) {
+ set_bit(FF_PERIODIC, dev->ffbit);
+ set_bit(FF_SINE, dev->ffbit);
+ set_bit(FF_TRIANGLE, dev->ffbit);
+ set_bit(FF_SQUARE, dev->ffbit);
+ }
+ }
+
+ if (test_bit(FF_PERIODIC, dev->ff->flags)) {
+ set_bit(FF_PERIODIC, dev->ffbit);
+ if (dev->ff->driver->playback)
+ set_bit(FF_RUMBLE, dev->ffbit);
+ }
+
+ if (test_bit(FF_CONSTANT, dev->ff->flags))
+ set_bit(FF_CONSTANT, dev->ffbit);
+ if (test_bit(FF_SPRING, dev->ff->flags))
+ set_bit(FF_SPRING, dev->ffbit);
+ if (test_bit(FF_FRICTION, dev->ff->flags))
+ set_bit(FF_FRICTION, dev->ffbit);
+ if (test_bit(FF_DAMPER, dev->ff->flags))
+ set_bit(FF_DAMPER, dev->ffbit);
+ if (test_bit(FF_INERTIA, dev->ff->flags))
+ set_bit(FF_INERTIA, dev->ffbit);
+ if (test_bit(FF_RAMP, dev->ff->flags))
+ set_bit(FF_RAMP, dev->ffbit);
+ if (test_bit(FF_SQUARE, dev->ff->flags))
+ set_bit(FF_SQUARE, dev->ffbit);
+ if (test_bit(FF_TRIANGLE, dev->ff->flags))
+ set_bit(FF_TRIANGLE, dev->ffbit);
+ if (test_bit(FF_SINE, dev->ff->flags))
+ set_bit(FF_SINE, dev->ffbit);
+ if (test_bit(FF_SAW_UP, dev->ff->flags))
+ set_bit(FF_SAW_UP, dev->ffbit);
+ if (test_bit(FF_SAW_DOWN, dev->ff->flags))
+ set_bit(FF_SAW_DOWN, dev->ffbit);
+ if (test_bit(FF_CUSTOM, dev->ff->flags))
+ set_bit(FF_CUSTOM, dev->ffbit);
+ if (test_bit(FF_GAIN, dev->ff->flags))
+ set_bit(FF_GAIN, dev->ffbit);
+ if (test_bit(FF_AUTOCENTER, dev->ff->flags))
+ set_bit(FF_AUTOCENTER, dev->ffbit);
+
+ set_bit(EV_FF, dev->evbit);
+ mutex_unlock(&dev->ff_lock);
+
+ return 0;
+}
--
Anssi Hannula
-
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@vger.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/

\
 
 \ /
  Last update: 2006-05-30 13:05    [from the cache]
©2003-2011 Jasper Spaans. Advertise on this site