From 20b3cdd6773be09f7bf52113de0d0c37da287f29 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 18 Jul 2007 01:20:34 -0400 Subject: Input: xpad - use le16_to_cpup when parsing data stream Use avaliable functions instead of doing it all manually. Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/xpad.c | 55 ++++++++++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 19 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c index 28080395899..623629a69b0 100644 --- a/drivers/input/joystick/xpad.c +++ b/drivers/input/joystick/xpad.c @@ -223,12 +223,16 @@ static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *d struct input_dev *dev = xpad->dev; /* left stick */ - input_report_abs(dev, ABS_X, (__s16) (((__s16)data[13] << 8) | data[12])); - input_report_abs(dev, ABS_Y, (__s16) (((__s16)data[15] << 8) | data[14])); + input_report_abs(dev, ABS_X, + (__s16) le16_to_cpup((__le16 *)(data + 12))); + input_report_abs(dev, ABS_Y, + (__s16) le16_to_cpup((__le16 *)(data + 14))); /* right stick */ - input_report_abs(dev, ABS_RX, (__s16) (((__s16)data[17] << 8) | data[16])); - input_report_abs(dev, ABS_RY, (__s16) (((__s16)data[19] << 8) | data[18])); + input_report_abs(dev, ABS_RX, + (__s16) le16_to_cpup((__le16 *)(data + 16))); + input_report_abs(dev, ABS_RY, + (__s16) le16_to_cpup((__le16 *)(data + 18))); /* triggers left/right */ input_report_abs(dev, ABS_Z, data[10]); @@ -236,8 +240,10 @@ static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *d /* digital pad */ if (xpad->dpad_mapping == MAP_DPAD_TO_AXES) { - input_report_abs(dev, ABS_HAT0X, !!(data[2] & 0x08) - !!(data[2] & 0x04)); - input_report_abs(dev, ABS_HAT0Y, !!(data[2] & 0x02) - !!(data[2] & 0x01)); + input_report_abs(dev, ABS_HAT0X, + !!(data[2] & 0x08) - !!(data[2] & 0x04)); + input_report_abs(dev, ABS_HAT0Y, + !!(data[2] & 0x02) - !!(data[2] & 0x01)); } else /* xpad->dpad_mapping == MAP_DPAD_TO_BUTTONS */ { input_report_key(dev, BTN_LEFT, data[2] & 0x04); input_report_key(dev, BTN_RIGHT, data[2] & 0x08); @@ -274,14 +280,17 @@ static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *d * http://www.free60.org/wiki/Gamepad */ -static void xpad360_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data) +static void xpad360_process_packet(struct usb_xpad *xpad, + u16 cmd, unsigned char *data) { struct input_dev *dev = xpad->dev; /* digital pad */ if (xpad->dpad_mapping == MAP_DPAD_TO_AXES) { - input_report_abs(dev, ABS_HAT0X, !!(data[2] & 0x08) - !!(data[2] & 0x04)); - input_report_abs(dev, ABS_HAT0Y, !!(data[2] & 0x02) - !!(data[2] & 0x01)); + input_report_abs(dev, ABS_HAT0X, + !!(data[2] & 0x08) - !!(data[2] & 0x04)); + input_report_abs(dev, ABS_HAT0Y, + !!(data[2] & 0x02) - !!(data[2] & 0x01)); } else if (xpad->dpad_mapping == MAP_DPAD_TO_BUTTONS) { /* dpad as buttons (right, left, down, up) */ input_report_key(dev, BTN_LEFT, data[2] & 0x04); @@ -308,12 +317,16 @@ static void xpad360_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char input_report_key(dev, BTN_MODE, data[3] & 0x04); /* left stick */ - input_report_abs(dev, ABS_X, (__s16) (((__s16)data[7] << 8) | (__s16)data[6])); - input_report_abs(dev, ABS_Y, (__s16) (((__s16)data[9] << 8) | (__s16)data[8])); + input_report_abs(dev, ABS_X, + (__s16) le16_to_cpup((__le16 *)(data + 6))); + input_report_abs(dev, ABS_Y, + (__s16) le16_to_cpup((__le16 *)(data + 8))); /* right stick */ - input_report_abs(dev, ABS_RX, (__s16) (((__s16)data[11] << 8) | (__s16)data[10])); - input_report_abs(dev, ABS_RY, (__s16) (((__s16)data[13] << 8) | (__s16)data[12])); + input_report_abs(dev, ABS_RX, + (__s16) le16_to_cpup((__le16 *)(data + 10))); + input_report_abs(dev, ABS_RY, + (__s16) le16_to_cpup((__le16 *)(data + 12))); /* triggers left/right */ input_report_abs(dev, ABS_Z, data[4]); @@ -335,10 +348,12 @@ static void xpad_irq_in(struct urb *urb) case -ENOENT: case -ESHUTDOWN: /* this urb is terminated, clean up */ - dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + dbg("%s - urb shutting down with status: %d", + __FUNCTION__, urb->status); return; default: - dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + dbg("%s - nonzero urb status received: %d", + __FUNCTION__, urb->status); goto exit; } @@ -367,10 +382,12 @@ static void xpad_irq_out(struct urb *urb) case -ENOENT: case -ESHUTDOWN: /* this urb is terminated, clean up */ - dbg("%s - urb shutting down with status: %d", __FUNCTION__, urb->status); + dbg("%s - urb shutting down with status: %d", + __FUNCTION__, urb->status); return; default: - dbg("%s - nonzero urb status received: %d", __FUNCTION__, urb->status); + dbg("%s - nonzero urb status received: %d", + __FUNCTION__, urb->status); goto exit; } @@ -378,7 +395,7 @@ exit: retval = usb_submit_urb(urb, GFP_ATOMIC); if (retval) err("%s - usb_submit_urb failed with result %d", - __FUNCTION__, retval); + __FUNCTION__, retval); } static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad) @@ -595,7 +612,7 @@ static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs) static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id) { - struct usb_device *udev = interface_to_usbdev (intf); + struct usb_device *udev = interface_to_usbdev(intf); struct usb_xpad *xpad; struct input_dev *input_dev; struct usb_endpoint_descriptor *ep_irq_in; -- cgit v1.2.3 From 018db6bb0d726c3683a5e1eb4621e728965db94c Mon Sep 17 00:00:00 2001 From: Markus Armbruster Date: Wed, 18 Jul 2007 01:20:41 -0400 Subject: Input: i8042 - restore control register when enabling port fails When enabling interrupts for a port fails, the interrupt enable and port enable bits remain set in i8042_ctr. Later writes of i8042_ctr to the hardware could accidentally retry enabling interrupts. Clear the bits on failure. Signed-off-by: Markus Armbruster Signed-off-by: Dmitry Torokhov --- drivers/input/serio/i8042.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'drivers/input') diff --git a/drivers/input/serio/i8042.c b/drivers/input/serio/i8042.c index 3888dc307e0..42aa4ec7266 100644 --- a/drivers/input/serio/i8042.c +++ b/drivers/input/serio/i8042.c @@ -385,6 +385,8 @@ static int i8042_enable_kbd_port(void) i8042_ctr |= I8042_CTR_KBDINT; if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + i8042_ctr &= ~I8042_CTR_KBDINT; + i8042_ctr |= I8042_CTR_KBDDIS; printk(KERN_ERR "i8042.c: Failed to enable KBD port.\n"); return -EIO; } @@ -402,6 +404,8 @@ static int i8042_enable_aux_port(void) i8042_ctr |= I8042_CTR_AUXINT; if (i8042_command(&i8042_ctr, I8042_CMD_CTL_WCTR)) { + i8042_ctr &= ~I8042_CTR_AUXINT; + i8042_ctr |= I8042_CTR_AUXDIS; printk(KERN_ERR "i8042.c: Failed to enable AUX port.\n"); return -EIO; } -- cgit v1.2.3 From 8006479c9b75fb6594a7b746af3d7f1fbb68f18f Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 30 Aug 2007 00:22:11 -0400 Subject: Input: implement proper locking in input core Also add some kerneldoc documentation to input.h Signed-off-by: Dmitry Torokhov --- drivers/input/input.c | 666 +++++++++++++++++++++++++++++++++++++------------- 1 file changed, 491 insertions(+), 175 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/input.c b/drivers/input/input.c index 5fe75558662..c59544f7e23 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -17,10 +17,10 @@ #include #include #include -#include #include #include #include +#include MODULE_AUTHOR("Vojtech Pavlik "); MODULE_DESCRIPTION("Input core"); @@ -31,167 +31,244 @@ MODULE_LICENSE("GPL"); static LIST_HEAD(input_dev_list); static LIST_HEAD(input_handler_list); +/* + * input_mutex protects access to both input_dev_list and input_handler_list. + * This also causes input_[un]register_device and input_[un]register_handler + * be mutually exclusive which simplifies locking in drivers implementing + * input handlers. + */ +static DEFINE_MUTEX(input_mutex); + static struct input_handler *input_table[8]; -/** - * input_event() - report new input event - * @dev: device that generated the event - * @type: type of the event - * @code: event code - * @value: value of the event - * - * This function should be used by drivers implementing various input devices - * See also input_inject_event() - */ -void input_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) +static inline int is_event_supported(unsigned int code, + unsigned long *bm, unsigned int max) { - struct input_handle *handle; + return code <= max && test_bit(code, bm); +} - if (type > EV_MAX || !test_bit(type, dev->evbit)) - return; +static int input_defuzz_abs_event(int value, int old_val, int fuzz) +{ + if (fuzz) { + if (value > old_val - fuzz / 2 && value < old_val + fuzz / 2) + return old_val; - add_input_randomness(type, code, value); + if (value > old_val - fuzz && value < old_val + fuzz) + return (old_val * 3 + value) / 4; - switch (type) { + if (value > old_val - fuzz * 2 && value < old_val + fuzz * 2) + return (old_val + value) / 2; + } - case EV_SYN: - switch (code) { - case SYN_CONFIG: - if (dev->event) - dev->event(dev, type, code, value); - break; - - case SYN_REPORT: - if (dev->sync) - return; - dev->sync = 1; - break; - } - break; + return value; +} - case EV_KEY: +/* + * Pass event through all open handles. This function is called with + * dev->event_lock held and interrupts disabled. Because of that we + * do not need to use rcu_read_lock() here although we are using RCU + * to access handle list. Note that because of that write-side uses + * synchronize_sched() instead of synchronize_ru(). + */ +static void input_pass_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + struct input_handle *handle = rcu_dereference(dev->grab); - if (code > KEY_MAX || !test_bit(code, dev->keybit) || !!test_bit(code, dev->key) == value) - return; + if (handle) + handle->handler->event(handle, type, code, value); + else + list_for_each_entry_rcu(handle, &dev->h_list, d_node) + if (handle->open) + handle->handler->event(handle, + type, code, value); +} - if (value == 2) - break; +/* + * Generate software autorepeat event. Note that we take + * dev->event_lock here to avoid racing with input_event + * which may cause keys get "stuck". + */ +static void input_repeat_key(unsigned long data) +{ + struct input_dev *dev = (void *) data; + unsigned long flags; - change_bit(code, dev->key); + spin_lock_irqsave(&dev->event_lock, flags); - if (test_bit(EV_REP, dev->evbit) && dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && dev->timer.data && value) { - dev->repeat_key = code; - mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_DELAY])); - } + if (test_bit(dev->repeat_key, dev->key) && + is_event_supported(dev->repeat_key, dev->keybit, KEY_MAX)) { - break; + input_pass_event(dev, EV_KEY, dev->repeat_key, 2); - case EV_SW: + if (dev->sync) { + /* + * Only send SYN_REPORT if we are not in a middle + * of driver parsing a new hardware packet. + * Otherwise assume that the driver will send + * SYN_REPORT once it's done. + */ + input_pass_event(dev, EV_SYN, SYN_REPORT, 1); + } - if (code > SW_MAX || !test_bit(code, dev->swbit) || !!test_bit(code, dev->sw) == value) - return; + if (dev->rep[REP_PERIOD]) + mod_timer(&dev->timer, jiffies + + msecs_to_jiffies(dev->rep[REP_PERIOD])); + } - change_bit(code, dev->sw); + spin_unlock_irqrestore(&dev->event_lock, flags); +} - break; +static void input_start_autorepeat(struct input_dev *dev, int code) +{ + if (test_bit(EV_REP, dev->evbit) && + dev->rep[REP_PERIOD] && dev->rep[REP_DELAY] && + dev->timer.data) { + dev->repeat_key = code; + mod_timer(&dev->timer, + jiffies + msecs_to_jiffies(dev->rep[REP_DELAY])); + } +} - case EV_ABS: +#define INPUT_IGNORE_EVENT 0 +#define INPUT_PASS_TO_HANDLERS 1 +#define INPUT_PASS_TO_DEVICE 2 +#define INPUT_PASS_TO_ALL (INPUT_PASS_TO_HANDLERS | INPUT_PASS_TO_DEVICE) - if (code > ABS_MAX || !test_bit(code, dev->absbit)) - return; +static void input_handle_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + int disposition = INPUT_IGNORE_EVENT; - if (dev->absfuzz[code]) { - if ((value > dev->abs[code] - (dev->absfuzz[code] >> 1)) && - (value < dev->abs[code] + (dev->absfuzz[code] >> 1))) - return; + switch (type) { - if ((value > dev->abs[code] - dev->absfuzz[code]) && - (value < dev->abs[code] + dev->absfuzz[code])) - value = (dev->abs[code] * 3 + value) >> 2; + case EV_SYN: + switch (code) { + case SYN_CONFIG: + disposition = INPUT_PASS_TO_ALL; + break; - if ((value > dev->abs[code] - (dev->absfuzz[code] << 1)) && - (value < dev->abs[code] + (dev->absfuzz[code] << 1))) - value = (dev->abs[code] + value) >> 1; + case SYN_REPORT: + if (!dev->sync) { + dev->sync = 1; + disposition = INPUT_PASS_TO_HANDLERS; } - - if (dev->abs[code] == value) - return; - - dev->abs[code] = value; break; + } + break; - case EV_REL: + case EV_KEY: + if (is_event_supported(code, dev->keybit, KEY_MAX) && + !!test_bit(code, dev->key) != value) { - if (code > REL_MAX || !test_bit(code, dev->relbit) || (value == 0)) - return; + if (value != 2) { + __change_bit(code, dev->key); + if (value) + input_start_autorepeat(dev, code); + } - break; + disposition = INPUT_PASS_TO_HANDLERS; + } + break; - case EV_MSC: + case EV_SW: + if (is_event_supported(code, dev->swbit, SW_MAX) && + !!test_bit(code, dev->sw) != value) { - if (code > MSC_MAX || !test_bit(code, dev->mscbit)) - return; + __change_bit(code, dev->sw); + disposition = INPUT_PASS_TO_HANDLERS; + } + break; - if (dev->event) - dev->event(dev, type, code, value); + case EV_ABS: + if (is_event_supported(code, dev->absbit, ABS_MAX)) { - break; + value = input_defuzz_abs_event(value, + dev->abs[code], dev->absfuzz[code]); - case EV_LED: + if (dev->abs[code] != value) { + dev->abs[code] = value; + disposition = INPUT_PASS_TO_HANDLERS; + } + } + break; - if (code > LED_MAX || !test_bit(code, dev->ledbit) || !!test_bit(code, dev->led) == value) - return; + case EV_REL: + if (is_event_supported(code, dev->relbit, REL_MAX) && value) + disposition = INPUT_PASS_TO_HANDLERS; - change_bit(code, dev->led); + break; - if (dev->event) - dev->event(dev, type, code, value); + case EV_MSC: + if (is_event_supported(code, dev->mscbit, MSC_MAX)) + disposition = INPUT_PASS_TO_ALL; - break; + break; + + case EV_LED: + if (is_event_supported(code, dev->ledbit, LED_MAX) && + !!test_bit(code, dev->led) != value) { - case EV_SND: + __change_bit(code, dev->led); + disposition = INPUT_PASS_TO_ALL; + } + break; - if (code > SND_MAX || !test_bit(code, dev->sndbit)) - return; + case EV_SND: + if (is_event_supported(code, dev->sndbit, SND_MAX)) { if (!!test_bit(code, dev->snd) != !!value) - change_bit(code, dev->snd); + __change_bit(code, dev->snd); + disposition = INPUT_PASS_TO_ALL; + } + break; - if (dev->event) - dev->event(dev, type, code, value); + case EV_REP: + if (code <= REP_MAX && value >= 0 && dev->rep[code] != value) { + dev->rep[code] = value; + disposition = INPUT_PASS_TO_ALL; + } + break; - break; + case EV_FF: + if (value >= 0) + disposition = INPUT_PASS_TO_ALL; + break; + } - case EV_REP: + if (type != EV_SYN) + dev->sync = 0; - if (code > REP_MAX || value < 0 || dev->rep[code] == value) - return; + if ((disposition & INPUT_PASS_TO_DEVICE) && dev->event) + dev->event(dev, type, code, value); - dev->rep[code] = value; - if (dev->event) - dev->event(dev, type, code, value); + if (disposition & INPUT_PASS_TO_HANDLERS) + input_pass_event(dev, type, code, value); +} - break; +/** + * input_event() - report new input event + * @dev: device that generated the event + * @type: type of the event + * @code: event code + * @value: value of the event + * + * This function should be used by drivers implementing various input + * devices. See also input_inject_event(). + */ - case EV_FF: +void input_event(struct input_dev *dev, + unsigned int type, unsigned int code, int value) +{ + unsigned long flags; - if (value < 0) - return; + if (is_event_supported(type, dev->evbit, EV_MAX)) { - if (dev->event) - dev->event(dev, type, code, value); - break; + spin_lock_irqsave(&dev->event_lock, flags); + add_input_randomness(type, code, value); + input_handle_event(dev, type, code, value); + spin_unlock_irqrestore(&dev->event_lock, flags); } - - if (type != EV_SYN) - dev->sync = 0; - - if (dev->grab) - dev->grab->handler->event(dev->grab, type, code, value); - else - list_for_each_entry(handle, &dev->h_list, d_node) - if (handle->open) - handle->handler->event(handle, type, code, value); } EXPORT_SYMBOL(input_event); @@ -202,102 +279,230 @@ EXPORT_SYMBOL(input_event); * @code: event code * @value: value of the event * - * Similar to input_event() but will ignore event if device is "grabbed" and handle - * injecting event is not the one that owns the device. + * Similar to input_event() but will ignore event if device is + * "grabbed" and handle injecting event is not the one that owns + * the device. */ -void input_inject_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) +void input_inject_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) { - if (!handle->dev->grab || handle->dev->grab == handle) - input_event(handle->dev, type, code, value); -} -EXPORT_SYMBOL(input_inject_event); - -static void input_repeat_key(unsigned long data) -{ - struct input_dev *dev = (void *) data; + struct input_dev *dev = handle->dev; + struct input_handle *grab; + unsigned long flags; - if (!test_bit(dev->repeat_key, dev->key)) - return; + if (is_event_supported(type, dev->evbit, EV_MAX)) { + spin_lock_irqsave(&dev->event_lock, flags); - input_event(dev, EV_KEY, dev->repeat_key, 2); - input_sync(dev); + grab = rcu_dereference(dev->grab); + if (!grab || grab == handle) + input_handle_event(dev, type, code, value); - if (dev->rep[REP_PERIOD]) - mod_timer(&dev->timer, jiffies + msecs_to_jiffies(dev->rep[REP_PERIOD])); + spin_unlock_irqrestore(&dev->event_lock, flags); + } } +EXPORT_SYMBOL(input_inject_event); +/** + * input_grab_device - grabs device for exclusive use + * @handle: input handle that wants to own the device + * + * When a device is grabbed by an input handle all events generated by + * the device are delivered only to this handle. Also events injected + * by other input handles are ignored while device is grabbed. + */ int input_grab_device(struct input_handle *handle) { - if (handle->dev->grab) - return -EBUSY; + struct input_dev *dev = handle->dev; + int retval; - handle->dev->grab = handle; - return 0; + retval = mutex_lock_interruptible(&dev->mutex); + if (retval) + return retval; + + if (dev->grab) { + retval = -EBUSY; + goto out; + } + + rcu_assign_pointer(dev->grab, handle); + /* + * Not using synchronize_rcu() because read-side is protected + * by a spinlock with interrupts off instead of rcu_read_lock(). + */ + synchronize_sched(); + + out: + mutex_unlock(&dev->mutex); + return retval; } EXPORT_SYMBOL(input_grab_device); -void input_release_device(struct input_handle *handle) +static void __input_release_device(struct input_handle *handle) { struct input_dev *dev = handle->dev; if (dev->grab == handle) { - dev->grab = NULL; + rcu_assign_pointer(dev->grab, NULL); + /* Make sure input_pass_event() notices that grab is gone */ + synchronize_sched(); list_for_each_entry(handle, &dev->h_list, d_node) - if (handle->handler->start) + if (handle->open && handle->handler->start) handle->handler->start(handle); } } + +/** + * input_release_device - release previously grabbed device + * @handle: input handle that owns the device + * + * Releases previously grabbed device so that other input handles can + * start receiving input events. Upon release all handlers attached + * to the device have their start() method called so they have a change + * to synchronize device state with the rest of the system. + */ +void input_release_device(struct input_handle *handle) +{ + struct input_dev *dev = handle->dev; + + mutex_lock(&dev->mutex); + __input_release_device(handle); + mutex_unlock(&dev->mutex); +} EXPORT_SYMBOL(input_release_device); +/** + * input_open_device - open input device + * @handle: handle through which device is being accessed + * + * This function should be called by input handlers when they + * want to start receive events from given input device. + */ int input_open_device(struct input_handle *handle) { struct input_dev *dev = handle->dev; - int err; + int retval; - err = mutex_lock_interruptible(&dev->mutex); - if (err) - return err; + retval = mutex_lock_interruptible(&dev->mutex); + if (retval) + return retval; + + if (dev->going_away) { + retval = -ENODEV; + goto out; + } handle->open++; if (!dev->users++ && dev->open) - err = dev->open(dev); - - if (err) - handle->open--; + retval = dev->open(dev); + + if (retval) { + dev->users--; + if (!--handle->open) { + /* + * Make sure we are not delivering any more events + * through this handle + */ + synchronize_sched(); + } + } + out: mutex_unlock(&dev->mutex); - - return err; + return retval; } EXPORT_SYMBOL(input_open_device); -int input_flush_device(struct input_handle* handle, struct file* file) +int input_flush_device(struct input_handle *handle, struct file *file) { - if (handle->dev->flush) - return handle->dev->flush(handle->dev, file); + struct input_dev *dev = handle->dev; + int retval; - return 0; + retval = mutex_lock_interruptible(&dev->mutex); + if (retval) + return retval; + + if (dev->flush) + retval = dev->flush(dev, file); + + mutex_unlock(&dev->mutex); + return retval; } EXPORT_SYMBOL(input_flush_device); +/** + * input_close_device - close input device + * @handle: handle through which device is being accessed + * + * This function should be called by input handlers when they + * want to stop receive events from given input device. + */ void input_close_device(struct input_handle *handle) { struct input_dev *dev = handle->dev; - input_release_device(handle); - mutex_lock(&dev->mutex); + __input_release_device(handle); + if (!--dev->users && dev->close) dev->close(dev); - handle->open--; + + if (!--handle->open) { + /* + * synchronize_sched() makes sure that input_pass_event() + * completed and that no more input events are delivered + * through this handle + */ + synchronize_sched(); + } mutex_unlock(&dev->mutex); } EXPORT_SYMBOL(input_close_device); +/* + * Prepare device for unregistering + */ +static void input_disconnect_device(struct input_dev *dev) +{ + struct input_handle *handle; + int code; + + /* + * Mark device as going away. Note that we take dev->mutex here + * not to protect access to dev->going_away but rather to ensure + * that there are no threads in the middle of input_open_device() + */ + mutex_lock(&dev->mutex); + dev->going_away = 1; + mutex_unlock(&dev->mutex); + + spin_lock_irq(&dev->event_lock); + + /* + * Simulate keyup events for all pressed keys so that handlers + * are not left with "stuck" keys. The driver may continue + * generate events even after we done here but they will not + * reach any handlers. + */ + if (is_event_supported(EV_KEY, dev->evbit, EV_MAX)) { + for (code = 0; code <= KEY_MAX; code++) { + if (is_event_supported(code, dev->keybit, KEY_MAX) && + test_bit(code, dev->key)) { + input_pass_event(dev, EV_KEY, code, 0); + } + } + input_pass_event(dev, EV_SYN, SYN_REPORT, 1); + } + + list_for_each_entry(handle, &dev->h_list, d_node) + handle->open = 0; + + spin_unlock_irq(&dev->event_lock); +} + static int input_fetch_keycode(struct input_dev *dev, int scancode) { switch (dev->keycodesize) { @@ -473,7 +678,8 @@ static unsigned int input_proc_devices_poll(struct file *file, poll_table *wait) static void *input_devices_seq_start(struct seq_file *seq, loff_t *pos) { - /* acquire lock here ... Yes, we do need locking, I knowi, I know... */ + if (mutex_lock_interruptible(&input_mutex)) + return NULL; return seq_list_start(&input_dev_list, *pos); } @@ -485,7 +691,7 @@ static void *input_devices_seq_next(struct seq_file *seq, void *v, loff_t *pos) static void input_devices_seq_stop(struct seq_file *seq, void *v) { - /* release lock here */ + mutex_unlock(&input_mutex); } static void input_seq_print_bitmap(struct seq_file *seq, const char *name, @@ -569,7 +775,9 @@ static const struct file_operations input_devices_fileops = { static void *input_handlers_seq_start(struct seq_file *seq, loff_t *pos) { - /* acquire lock here ... Yes, we do need locking, I knowi, I know... */ + if (mutex_lock_interruptible(&input_mutex)) + return NULL; + seq->private = (void *)(unsigned long)*pos; return seq_list_start(&input_handler_list, *pos); } @@ -582,7 +790,7 @@ static void *input_handlers_seq_next(struct seq_file *seq, void *v, loff_t *pos) static void input_handlers_seq_stop(struct seq_file *seq, void *v) { - /* release lock here */ + mutex_unlock(&input_mutex); } static int input_handlers_seq_show(struct seq_file *seq, void *v) @@ -1005,6 +1213,7 @@ struct input_dev *input_allocate_device(void) dev->dev.class = &input_class; device_initialize(&dev->dev); mutex_init(&dev->mutex); + spin_lock_init(&dev->event_lock); INIT_LIST_HEAD(&dev->h_list); INIT_LIST_HEAD(&dev->node); @@ -1022,7 +1231,7 @@ EXPORT_SYMBOL(input_allocate_device); * This function should only be used if input_register_device() * was not called yet or if it failed. Once device was registered * use input_unregister_device() and memory will be freed once last - * refrence to the device is dropped. + * reference to the device is dropped. * * Device should be allocated by input_allocate_device(). * @@ -1092,6 +1301,18 @@ void input_set_capability(struct input_dev *dev, unsigned int type, unsigned int } EXPORT_SYMBOL(input_set_capability); +/** + * input_register_device - register device with input core + * @dev: device to be registered + * + * This function registers device with input core. The device must be + * allocated with input_allocate_device() and all it's capabilities + * set up before registering. + * If function fails the device must be freed with input_free_device(). + * Once device has been successfully registered it can be unregistered + * with input_unregister_device(); input_free_device() should not be + * called in this case. + */ int input_register_device(struct input_dev *dev) { static atomic_t input_no = ATOMIC_INIT(0); @@ -1099,7 +1320,7 @@ int input_register_device(struct input_dev *dev) const char *path; int error; - set_bit(EV_SYN, dev->evbit); + __set_bit(EV_SYN, dev->evbit); /* * If delay and period are pre-set by the driver, then autorepeating @@ -1120,8 +1341,6 @@ int input_register_device(struct input_dev *dev) if (!dev->setkeycode) dev->setkeycode = input_default_setkeycode; - list_add_tail(&dev->node, &input_dev_list); - snprintf(dev->dev.bus_id, sizeof(dev->dev.bus_id), "input%ld", (unsigned long) atomic_inc_return(&input_no) - 1); @@ -1137,49 +1356,79 @@ int input_register_device(struct input_dev *dev) dev->name ? dev->name : "Unspecified device", path ? path : "N/A"); kfree(path); + error = mutex_lock_interruptible(&input_mutex); + if (error) { + device_del(&dev->dev); + return error; + } + + list_add_tail(&dev->node, &input_dev_list); + list_for_each_entry(handler, &input_handler_list, node) input_attach_handler(dev, handler); input_wakeup_procfs_readers(); + mutex_unlock(&input_mutex); + return 0; } EXPORT_SYMBOL(input_register_device); +/** + * input_unregister_device - unregister previously registered device + * @dev: device to be unregistered + * + * This function unregisters an input device. Once device is unregistered + * the caller should not try to access it as it may get freed at any moment. + */ void input_unregister_device(struct input_dev *dev) { struct input_handle *handle, *next; - int code; - for (code = 0; code <= KEY_MAX; code++) - if (test_bit(code, dev->key)) - input_report_key(dev, code, 0); - input_sync(dev); + input_disconnect_device(dev); - del_timer_sync(&dev->timer); + mutex_lock(&input_mutex); list_for_each_entry_safe(handle, next, &dev->h_list, d_node) handle->handler->disconnect(handle); WARN_ON(!list_empty(&dev->h_list)); + del_timer_sync(&dev->timer); list_del_init(&dev->node); - device_unregister(&dev->dev); - input_wakeup_procfs_readers(); + + mutex_unlock(&input_mutex); + + device_unregister(&dev->dev); } EXPORT_SYMBOL(input_unregister_device); +/** + * input_register_handler - register a new input handler + * @handler: handler to be registered + * + * This function registers a new input handler (interface) for input + * devices in the system and attaches it to all input devices that + * are compatible with the handler. + */ int input_register_handler(struct input_handler *handler) { struct input_dev *dev; + int retval; + + retval = mutex_lock_interruptible(&input_mutex); + if (retval) + return retval; INIT_LIST_HEAD(&handler->h_list); if (handler->fops != NULL) { - if (input_table[handler->minor >> 5]) - return -EBUSY; - + if (input_table[handler->minor >> 5]) { + retval = -EBUSY; + goto out; + } input_table[handler->minor >> 5] = handler; } @@ -1189,14 +1438,26 @@ int input_register_handler(struct input_handler *handler) input_attach_handler(dev, handler); input_wakeup_procfs_readers(); - return 0; + + out: + mutex_unlock(&input_mutex); + return retval; } EXPORT_SYMBOL(input_register_handler); +/** + * input_unregister_handler - unregisters an input handler + * @handler: handler to be unregistered + * + * This function disconnects a handler from its input devices and + * removes it from lists of known handlers. + */ void input_unregister_handler(struct input_handler *handler) { struct input_handle *handle, *next; + mutex_lock(&input_mutex); + list_for_each_entry_safe(handle, next, &handler->h_list, h_node) handler->disconnect(handle); WARN_ON(!list_empty(&handler->h_list)); @@ -1207,14 +1468,50 @@ void input_unregister_handler(struct input_handler *handler) input_table[handler->minor >> 5] = NULL; input_wakeup_procfs_readers(); + + mutex_unlock(&input_mutex); } EXPORT_SYMBOL(input_unregister_handler); +/** + * input_register_handle - register a new input handle + * @handle: handle to register + * + * This function puts a new input handle onto device's + * and handler's lists so that events can flow through + * it once it is opened using input_open_device(). + * + * This function is supposed to be called from handler's + * connect() method. + */ int input_register_handle(struct input_handle *handle) { struct input_handler *handler = handle->handler; + struct input_dev *dev = handle->dev; + int error; + + /* + * We take dev->mutex here to prevent race with + * input_release_device(). + */ + error = mutex_lock_interruptible(&dev->mutex); + if (error) + return error; + list_add_tail_rcu(&handle->d_node, &dev->h_list); + mutex_unlock(&dev->mutex); + /* + * We don't use synchronize_rcu() here because we rely + * on dev->event_lock to protect read-side critical + * section in input_pass_event(). + */ + synchronize_sched(); - list_add_tail(&handle->d_node, &handle->dev->h_list); + /* + * Since we are supposed to be called from ->connect() + * which is mutually exclusive with ->disconnect() + * we can't be racing with input_unregister_handle() + * and so separate lock is not needed here. + */ list_add_tail(&handle->h_node, &handler->h_list); if (handler->start) @@ -1224,10 +1521,29 @@ int input_register_handle(struct input_handle *handle) } EXPORT_SYMBOL(input_register_handle); +/** + * input_unregister_handle - unregister an input handle + * @handle: handle to unregister + * + * This function removes input handle from device's + * and handler's lists. + * + * This function is supposed to be called from handler's + * disconnect() method. + */ void input_unregister_handle(struct input_handle *handle) { + struct input_dev *dev = handle->dev; + list_del_init(&handle->h_node); - list_del_init(&handle->d_node); + + /* + * Take dev->mutex to prevent race with input_release_device(). + */ + mutex_lock(&dev->mutex); + list_del_rcu(&handle->d_node); + mutex_unlock(&dev->mutex); + synchronize_sched(); } EXPORT_SYMBOL(input_unregister_handle); -- cgit v1.2.3 From 6addb1d6de1968b84852f54561cc9a999909b5a9 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 30 Aug 2007 00:22:18 -0400 Subject: Input: evdev - implement proper locking Signed-off-by: Dmitry Torokhov --- drivers/input/evdev.c | 713 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 473 insertions(+), 240 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index ab4b2d9b532..bfee4f9a771 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -30,6 +30,8 @@ struct evdev { wait_queue_head_t wait; struct evdev_client *grab; struct list_head client_list; + spinlock_t client_lock; /* protects client_list */ + struct mutex mutex; struct device dev; }; @@ -37,39 +39,53 @@ struct evdev_client { struct input_event buffer[EVDEV_BUFFER_SIZE]; int head; int tail; + spinlock_t buffer_lock; /* protects access to buffer, head and tail */ struct fasync_struct *fasync; struct evdev *evdev; struct list_head node; }; static struct evdev *evdev_table[EVDEV_MINORS]; +static DEFINE_MUTEX(evdev_table_mutex); -static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) +static void evdev_pass_event(struct evdev_client *client, + struct input_event *event) +{ + /* + * Interrupts are disabled, just acquire the lock + */ + spin_lock(&client->buffer_lock); + client->buffer[client->head++] = *event; + client->head &= EVDEV_BUFFER_SIZE - 1; + spin_unlock(&client->buffer_lock); + + kill_fasync(&client->fasync, SIGIO, POLL_IN); +} + +/* + * Pass incoming event to all connected clients. Note that we are + * caleld under a spinlock with interrupts off so we don't need + * to use rcu_read_lock() here. Writers will be using syncronize_sched() + * instead of synchrnoize_rcu(). + */ +static void evdev_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) { struct evdev *evdev = handle->private; struct evdev_client *client; + struct input_event event; - if (evdev->grab) { - client = evdev->grab; - - do_gettimeofday(&client->buffer[client->head].time); - client->buffer[client->head].type = type; - client->buffer[client->head].code = code; - client->buffer[client->head].value = value; - client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1); - - kill_fasync(&client->fasync, SIGIO, POLL_IN); - } else - list_for_each_entry(client, &evdev->client_list, node) { - - do_gettimeofday(&client->buffer[client->head].time); - client->buffer[client->head].type = type; - client->buffer[client->head].code = code; - client->buffer[client->head].value = value; - client->head = (client->head + 1) & (EVDEV_BUFFER_SIZE - 1); + do_gettimeofday(&event.time); + event.type = type; + event.code = code; + event.value = value; - kill_fasync(&client->fasync, SIGIO, POLL_IN); - } + client = rcu_dereference(evdev->grab); + if (client) + evdev_pass_event(client, &event); + else + list_for_each_entry_rcu(client, &evdev->client_list, node) + evdev_pass_event(client, &event); wake_up_interruptible(&evdev->wait); } @@ -88,38 +104,142 @@ static int evdev_flush(struct file *file, fl_owner_t id) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; + int retval; + + retval = mutex_lock_interruptible(&evdev->mutex); + if (retval) + return retval; if (!evdev->exist) - return -ENODEV; + retval = -ENODEV; + else + retval = input_flush_device(&evdev->handle, file); - return input_flush_device(&evdev->handle, file); + mutex_unlock(&evdev->mutex); + return retval; } static void evdev_free(struct device *dev) { struct evdev *evdev = container_of(dev, struct evdev, dev); - evdev_table[evdev->minor] = NULL; kfree(evdev); } +/* + * Grabs an event device (along with underlying input device). + * This function is called with evdev->mutex taken. + */ +static int evdev_grab(struct evdev *evdev, struct evdev_client *client) +{ + int error; + + if (evdev->grab) + return -EBUSY; + + error = input_grab_device(&evdev->handle); + if (error) + return error; + + rcu_assign_pointer(evdev->grab, client); + /* + * We don't use synchronize_rcu() here because read-side + * critical section is protected by a spinlock instead + * of rcu_read_lock(). + */ + synchronize_sched(); + + return 0; +} + +static int evdev_ungrab(struct evdev *evdev, struct evdev_client *client) +{ + if (evdev->grab != client) + return -EINVAL; + + rcu_assign_pointer(evdev->grab, NULL); + synchronize_sched(); + input_release_device(&evdev->handle); + + return 0; +} + +static void evdev_attach_client(struct evdev *evdev, + struct evdev_client *client) +{ + spin_lock(&evdev->client_lock); + list_add_tail_rcu(&client->node, &evdev->client_list); + spin_unlock(&evdev->client_lock); + synchronize_sched(); +} + +static void evdev_detach_client(struct evdev *evdev, + struct evdev_client *client) +{ + spin_lock(&evdev->client_lock); + list_del_rcu(&client->node); + spin_unlock(&evdev->client_lock); + synchronize_sched(); +} + +static int evdev_open_device(struct evdev *evdev) +{ + int retval; + + retval = mutex_lock_interruptible(&evdev->mutex); + if (retval) + return retval; + + if (!evdev->exist) + retval = -ENODEV; + else if (!evdev->open++) + retval = input_open_device(&evdev->handle); + + mutex_unlock(&evdev->mutex); + return retval; +} + +static void evdev_close_device(struct evdev *evdev) +{ + mutex_lock(&evdev->mutex); + + if (evdev->exist && !--evdev->open) + input_close_device(&evdev->handle); + + mutex_unlock(&evdev->mutex); +} + +/* + * Wake up users waiting for IO so they can disconnect from + * dead device. + */ +static void evdev_hangup(struct evdev *evdev) +{ + struct evdev_client *client; + + spin_lock(&evdev->client_lock); + list_for_each_entry(client, &evdev->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + spin_unlock(&evdev->client_lock); + + wake_up_interruptible(&evdev->wait); +} + static int evdev_release(struct inode *inode, struct file *file) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; - if (evdev->grab == client) { - input_release_device(&evdev->handle); - evdev->grab = NULL; - } + mutex_lock(&evdev->mutex); + if (evdev->grab == client) + evdev_ungrab(evdev, client); + mutex_unlock(&evdev->mutex); evdev_fasync(-1, file, 0); - list_del(&client->node); + evdev_detach_client(evdev, client); kfree(client); - if (!--evdev->open && evdev->exist) - input_close_device(&evdev->handle); - + evdev_close_device(evdev); put_device(&evdev->dev); return 0; @@ -127,41 +247,44 @@ static int evdev_release(struct inode *inode, struct file *file) static int evdev_open(struct inode *inode, struct file *file) { - struct evdev_client *client; struct evdev *evdev; + struct evdev_client *client; int i = iminor(inode) - EVDEV_MINOR_BASE; int error; if (i >= EVDEV_MINORS) return -ENODEV; + error = mutex_lock_interruptible(&evdev_table_mutex); + if (error) + return error; evdev = evdev_table[i]; + if (evdev) + get_device(&evdev->dev); + mutex_unlock(&evdev_table_mutex); - if (!evdev || !evdev->exist) + if (!evdev) return -ENODEV; - get_device(&evdev->dev); - client = kzalloc(sizeof(struct evdev_client), GFP_KERNEL); if (!client) { error = -ENOMEM; goto err_put_evdev; } + spin_lock_init(&client->buffer_lock); client->evdev = evdev; - list_add_tail(&client->node, &evdev->client_list); + evdev_attach_client(evdev, client); - if (!evdev->open++ && evdev->exist) { - error = input_open_device(&evdev->handle); - if (error) - goto err_free_client; - } + error = evdev_open_device(evdev); + if (error) + goto err_free_client; file->private_data = client; return 0; err_free_client: - list_del(&client->node); + evdev_detach_client(evdev, client); kfree(client); err_put_evdev: put_device(&evdev->dev); @@ -197,12 +320,14 @@ static inline size_t evdev_event_size(void) sizeof(struct input_event_compat) : sizeof(struct input_event); } -static int evdev_event_from_user(const char __user *buffer, struct input_event *event) +static int evdev_event_from_user(const char __user *buffer, + struct input_event *event) { if (COMPAT_TEST) { struct input_event_compat compat_event; - if (copy_from_user(&compat_event, buffer, sizeof(struct input_event_compat))) + if (copy_from_user(&compat_event, buffer, + sizeof(struct input_event_compat))) return -EFAULT; event->time.tv_sec = compat_event.time.tv_sec; @@ -219,7 +344,8 @@ static int evdev_event_from_user(const char __user *buffer, struct input_event * return 0; } -static int evdev_event_to_user(char __user *buffer, const struct input_event *event) +static int evdev_event_to_user(char __user *buffer, + const struct input_event *event) { if (COMPAT_TEST) { struct input_event_compat compat_event; @@ -230,7 +356,8 @@ static int evdev_event_to_user(char __user *buffer, const struct input_event *ev compat_event.code = event->code; compat_event.value = event->value; - if (copy_to_user(buffer, &compat_event, sizeof(struct input_event_compat))) + if (copy_to_user(buffer, &compat_event, + sizeof(struct input_event_compat))) return -EFAULT; } else { @@ -248,7 +375,8 @@ static inline size_t evdev_event_size(void) return sizeof(struct input_event); } -static int evdev_event_from_user(const char __user *buffer, struct input_event *event) +static int evdev_event_from_user(const char __user *buffer, + struct input_event *event) { if (copy_from_user(event, buffer, sizeof(struct input_event))) return -EFAULT; @@ -256,7 +384,8 @@ static int evdev_event_from_user(const char __user *buffer, struct input_event * return 0; } -static int evdev_event_to_user(char __user *buffer, const struct input_event *event) +static int evdev_event_to_user(char __user *buffer, + const struct input_event *event) { if (copy_to_user(buffer, event, sizeof(struct input_event))) return -EFAULT; @@ -266,37 +395,71 @@ static int evdev_event_to_user(char __user *buffer, const struct input_event *ev #endif /* CONFIG_COMPAT */ -static ssize_t evdev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +static ssize_t evdev_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; struct input_event event; - int retval = 0; + int retval; - if (!evdev->exist) - return -ENODEV; + retval = mutex_lock_interruptible(&evdev->mutex); + if (retval) + return retval; + + if (!evdev->exist) { + retval = -ENODEV; + goto out; + } while (retval < count) { - if (evdev_event_from_user(buffer + retval, &event)) - return -EFAULT; - input_inject_event(&evdev->handle, event.type, event.code, event.value); + if (evdev_event_from_user(buffer + retval, &event)) { + retval = -EFAULT; + goto out; + } + + input_inject_event(&evdev->handle, + event.type, event.code, event.value); retval += evdev_event_size(); } + out: + mutex_unlock(&evdev->mutex); return retval; } -static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +static int evdev_fetch_next_event(struct evdev_client *client, + struct input_event *event) +{ + int have_event; + + spin_lock_irq(&client->buffer_lock); + + have_event = client->head != client->tail; + if (have_event) { + *event = client->buffer[client->tail++]; + client->tail &= EVDEV_BUFFER_SIZE - 1; + } + + spin_unlock_irq(&client->buffer_lock); + + return have_event; +} + +static ssize_t evdev_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; + struct input_event event; int retval; if (count < evdev_event_size()) return -EINVAL; - if (client->head == client->tail && evdev->exist && (file->f_flags & O_NONBLOCK)) + if (client->head == client->tail && evdev->exist && + (file->f_flags & O_NONBLOCK)) return -EAGAIN; retval = wait_event_interruptible(evdev->wait, @@ -307,14 +470,12 @@ static ssize_t evdev_read(struct file *file, char __user *buffer, size_t count, if (!evdev->exist) return -ENODEV; - while (client->head != client->tail && retval + evdev_event_size() <= count) { - - struct input_event *event = (struct input_event *) client->buffer + client->tail; + while (retval + evdev_event_size() <= count && + evdev_fetch_next_event(client, &event)) { - if (evdev_event_to_user(buffer + retval, event)) + if (evdev_event_to_user(buffer + retval, &event)) return -EFAULT; - client->tail = (client->tail + 1) & (EVDEV_BUFFER_SIZE - 1); retval += evdev_event_size(); } @@ -409,8 +570,8 @@ static int str_to_user(const char *str, unsigned int maxlen, void __user *p) return copy_to_user(p, str, len) ? -EFAULT : len; } -static long evdev_ioctl_handler(struct file *file, unsigned int cmd, - void __user *p, int compat_mode) +static long evdev_do_ioctl(struct file *file, unsigned int cmd, + void __user *p, int compat_mode) { struct evdev_client *client = file->private_data; struct evdev *evdev = client->evdev; @@ -421,215 +582,289 @@ static long evdev_ioctl_handler(struct file *file, unsigned int cmd, int i, t, u, v; int error; - if (!evdev->exist) - return -ENODEV; - switch (cmd) { - case EVIOCGVERSION: - return put_user(EV_VERSION, ip); + case EVIOCGVERSION: + return put_user(EV_VERSION, ip); - case EVIOCGID: - if (copy_to_user(p, &dev->id, sizeof(struct input_id))) - return -EFAULT; - return 0; + case EVIOCGID: + if (copy_to_user(p, &dev->id, sizeof(struct input_id))) + return -EFAULT; + return 0; - case EVIOCGREP: - if (!test_bit(EV_REP, dev->evbit)) - return -ENOSYS; - if (put_user(dev->rep[REP_DELAY], ip)) - return -EFAULT; - if (put_user(dev->rep[REP_PERIOD], ip + 1)) - return -EFAULT; - return 0; + case EVIOCGREP: + if (!test_bit(EV_REP, dev->evbit)) + return -ENOSYS; + if (put_user(dev->rep[REP_DELAY], ip)) + return -EFAULT; + if (put_user(dev->rep[REP_PERIOD], ip + 1)) + return -EFAULT; + return 0; - case EVIOCSREP: - if (!test_bit(EV_REP, dev->evbit)) - return -ENOSYS; - if (get_user(u, ip)) - return -EFAULT; - if (get_user(v, ip + 1)) - return -EFAULT; + case EVIOCSREP: + if (!test_bit(EV_REP, dev->evbit)) + return -ENOSYS; + if (get_user(u, ip)) + return -EFAULT; + if (get_user(v, ip + 1)) + return -EFAULT; - input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u); - input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v); + input_inject_event(&evdev->handle, EV_REP, REP_DELAY, u); + input_inject_event(&evdev->handle, EV_REP, REP_PERIOD, v); - return 0; + return 0; - case EVIOCGKEYCODE: - if (get_user(t, ip)) - return -EFAULT; + case EVIOCGKEYCODE: + if (get_user(t, ip)) + return -EFAULT; - error = dev->getkeycode(dev, t, &v); - if (error) - return error; + error = dev->getkeycode(dev, t, &v); + if (error) + return error; - if (put_user(v, ip + 1)) - return -EFAULT; + if (put_user(v, ip + 1)) + return -EFAULT; - return 0; + return 0; - case EVIOCSKEYCODE: - if (get_user(t, ip) || get_user(v, ip + 1)) - return -EFAULT; + case EVIOCSKEYCODE: + if (get_user(t, ip) || get_user(v, ip + 1)) + return -EFAULT; - return dev->setkeycode(dev, t, v); + return dev->setkeycode(dev, t, v); - case EVIOCSFF: - if (copy_from_user(&effect, p, sizeof(effect))) - return -EFAULT; + case EVIOCSFF: + if (copy_from_user(&effect, p, sizeof(effect))) + return -EFAULT; - error = input_ff_upload(dev, &effect, file); + error = input_ff_upload(dev, &effect, file); - if (put_user(effect.id, &(((struct ff_effect __user *)p)->id))) - return -EFAULT; + if (put_user(effect.id, &(((struct ff_effect __user *)p)->id))) + return -EFAULT; - return error; + return error; - case EVIOCRMFF: - return input_ff_erase(dev, (int)(unsigned long) p, file); + case EVIOCRMFF: + return input_ff_erase(dev, (int)(unsigned long) p, file); - case EVIOCGEFFECTS: - i = test_bit(EV_FF, dev->evbit) ? dev->ff->max_effects : 0; - if (put_user(i, ip)) - return -EFAULT; - return 0; - - case EVIOCGRAB: - if (p) { - if (evdev->grab) - return -EBUSY; - if (input_grab_device(&evdev->handle)) - return -EBUSY; - evdev->grab = client; - return 0; - } else { - if (evdev->grab != client) - return -EINVAL; - input_release_device(&evdev->handle); - evdev->grab = NULL; - return 0; - } + case EVIOCGEFFECTS: + i = test_bit(EV_FF, dev->evbit) ? + dev->ff->max_effects : 0; + if (put_user(i, ip)) + return -EFAULT; + return 0; + + case EVIOCGRAB: + if (p) + return evdev_grab(evdev, client); + else + return evdev_ungrab(evdev, client); - default: + default: - if (_IOC_TYPE(cmd) != 'E') - return -EINVAL; + if (_IOC_TYPE(cmd) != 'E') + return -EINVAL; - if (_IOC_DIR(cmd) == _IOC_READ) { + if (_IOC_DIR(cmd) == _IOC_READ) { - if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0,0))) { + if ((_IOC_NR(cmd) & ~EV_MAX) == _IOC_NR(EVIOCGBIT(0, 0))) { - unsigned long *bits; - int len; + unsigned long *bits; + int len; - switch (_IOC_NR(cmd) & EV_MAX) { - case 0: bits = dev->evbit; len = EV_MAX; break; - case EV_KEY: bits = dev->keybit; len = KEY_MAX; break; - case EV_REL: bits = dev->relbit; len = REL_MAX; break; - case EV_ABS: bits = dev->absbit; len = ABS_MAX; break; - case EV_MSC: bits = dev->mscbit; len = MSC_MAX; break; - case EV_LED: bits = dev->ledbit; len = LED_MAX; break; - case EV_SND: bits = dev->sndbit; len = SND_MAX; break; - case EV_FF: bits = dev->ffbit; len = FF_MAX; break; - case EV_SW: bits = dev->swbit; len = SW_MAX; break; - default: return -EINVAL; - } - return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode); - } + switch (_IOC_NR(cmd) & EV_MAX) { - if (_IOC_NR(cmd) == _IOC_NR(EVIOCGKEY(0))) - return bits_to_user(dev->key, KEY_MAX, _IOC_SIZE(cmd), - p, compat_mode); + case 0: bits = dev->evbit; len = EV_MAX; break; + case EV_KEY: bits = dev->keybit; len = KEY_MAX; break; + case EV_REL: bits = dev->relbit; len = REL_MAX; break; + case EV_ABS: bits = dev->absbit; len = ABS_MAX; break; + case EV_MSC: bits = dev->mscbit; len = MSC_MAX; break; + case EV_LED: bits = dev->ledbit; len = LED_MAX; break; + case EV_SND: bits = dev->sndbit; len = SND_MAX; break; + case EV_FF: bits = dev->ffbit; len = FF_MAX; break; + case EV_SW: bits = dev->swbit; len = SW_MAX; break; + default: return -EINVAL; + } + return bits_to_user(bits, len, _IOC_SIZE(cmd), p, compat_mode); + } - if (_IOC_NR(cmd) == _IOC_NR(EVIOCGLED(0))) - return bits_to_user(dev->led, LED_MAX, _IOC_SIZE(cmd), - p, compat_mode); + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGKEY(0))) + return bits_to_user(dev->key, KEY_MAX, _IOC_SIZE(cmd), + p, compat_mode); - if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSND(0))) - return bits_to_user(dev->snd, SND_MAX, _IOC_SIZE(cmd), - p, compat_mode); + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGLED(0))) + return bits_to_user(dev->led, LED_MAX, _IOC_SIZE(cmd), + p, compat_mode); - if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSW(0))) - return bits_to_user(dev->sw, SW_MAX, _IOC_SIZE(cmd), - p, compat_mode); + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSND(0))) + return bits_to_user(dev->snd, SND_MAX, _IOC_SIZE(cmd), + p, compat_mode); - if (_IOC_NR(cmd) == _IOC_NR(EVIOCGNAME(0))) - return str_to_user(dev->name, _IOC_SIZE(cmd), p); + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGSW(0))) + return bits_to_user(dev->sw, SW_MAX, _IOC_SIZE(cmd), + p, compat_mode); - if (_IOC_NR(cmd) == _IOC_NR(EVIOCGPHYS(0))) - return str_to_user(dev->phys, _IOC_SIZE(cmd), p); + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGNAME(0))) + return str_to_user(dev->name, _IOC_SIZE(cmd), p); - if (_IOC_NR(cmd) == _IOC_NR(EVIOCGUNIQ(0))) - return str_to_user(dev->uniq, _IOC_SIZE(cmd), p); + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGPHYS(0))) + return str_to_user(dev->phys, _IOC_SIZE(cmd), p); - if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) { + if (_IOC_NR(cmd) == _IOC_NR(EVIOCGUNIQ(0))) + return str_to_user(dev->uniq, _IOC_SIZE(cmd), p); - t = _IOC_NR(cmd) & ABS_MAX; + if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCGABS(0))) { - abs.value = dev->abs[t]; - abs.minimum = dev->absmin[t]; - abs.maximum = dev->absmax[t]; - abs.fuzz = dev->absfuzz[t]; - abs.flat = dev->absflat[t]; + t = _IOC_NR(cmd) & ABS_MAX; - if (copy_to_user(p, &abs, sizeof(struct input_absinfo))) - return -EFAULT; + abs.value = dev->abs[t]; + abs.minimum = dev->absmin[t]; + abs.maximum = dev->absmax[t]; + abs.fuzz = dev->absfuzz[t]; + abs.flat = dev->absflat[t]; - return 0; - } + if (copy_to_user(p, &abs, sizeof(struct input_absinfo))) + return -EFAULT; + return 0; } - if (_IOC_DIR(cmd) == _IOC_WRITE) { + } + + if (_IOC_DIR(cmd) == _IOC_WRITE) { - if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) { + if ((_IOC_NR(cmd) & ~ABS_MAX) == _IOC_NR(EVIOCSABS(0))) { - t = _IOC_NR(cmd) & ABS_MAX; + t = _IOC_NR(cmd) & ABS_MAX; - if (copy_from_user(&abs, p, sizeof(struct input_absinfo))) - return -EFAULT; + if (copy_from_user(&abs, p, + sizeof(struct input_absinfo))) + return -EFAULT; - dev->abs[t] = abs.value; - dev->absmin[t] = abs.minimum; - dev->absmax[t] = abs.maximum; - dev->absfuzz[t] = abs.fuzz; - dev->absflat[t] = abs.flat; + /* + * Take event lock to ensure that we are not + * changing device parameters in the middle + * of event. + */ + spin_lock_irq(&dev->event_lock); - return 0; - } + dev->abs[t] = abs.value; + dev->absmin[t] = abs.minimum; + dev->absmax[t] = abs.maximum; + dev->absfuzz[t] = abs.fuzz; + dev->absflat[t] = abs.flat; + + spin_unlock_irq(&dev->event_lock); + + return 0; } + } } return -EINVAL; } +static long evdev_ioctl_handler(struct file *file, unsigned int cmd, + void __user *p, int compat_mode) +{ + struct evdev_client *client = file->private_data; + struct evdev *evdev = client->evdev; + int retval; + + retval = mutex_lock_interruptible(&evdev->mutex); + if (retval) + return retval; + + if (!evdev->exist) { + retval = -ENODEV; + goto out; + } + + retval = evdev_do_ioctl(file, cmd, p, compat_mode); + + out: + mutex_unlock(&evdev->mutex); + return retval; +} + static long evdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { return evdev_ioctl_handler(file, cmd, (void __user *)arg, 0); } #ifdef CONFIG_COMPAT -static long evdev_ioctl_compat(struct file *file, unsigned int cmd, unsigned long arg) +static long evdev_ioctl_compat(struct file *file, + unsigned int cmd, unsigned long arg) { return evdev_ioctl_handler(file, cmd, compat_ptr(arg), 1); } #endif static const struct file_operations evdev_fops = { - .owner = THIS_MODULE, - .read = evdev_read, - .write = evdev_write, - .poll = evdev_poll, - .open = evdev_open, - .release = evdev_release, - .unlocked_ioctl = evdev_ioctl, + .owner = THIS_MODULE, + .read = evdev_read, + .write = evdev_write, + .poll = evdev_poll, + .open = evdev_open, + .release = evdev_release, + .unlocked_ioctl = evdev_ioctl, #ifdef CONFIG_COMPAT - .compat_ioctl = evdev_ioctl_compat, + .compat_ioctl = evdev_ioctl_compat, #endif - .fasync = evdev_fasync, - .flush = evdev_flush + .fasync = evdev_fasync, + .flush = evdev_flush }; +static int evdev_install_chrdev(struct evdev *evdev) +{ + /* + * No need to do any locking here as calls to connect and + * disconnect are serialized by the input core + */ + evdev_table[evdev->minor] = evdev; + return 0; +} + +static void evdev_remove_chrdev(struct evdev *evdev) +{ + /* + * Lock evdev table to prevent race with evdev_open() + */ + mutex_lock(&evdev_table_mutex); + evdev_table[evdev->minor] = NULL; + mutex_unlock(&evdev_table_mutex); +} + +/* + * Mark device non-existent. This disables writes, ioctls and + * prevents new users from opening the device. Already posted + * blocking reads will stay, however new ones will fail. + */ +static void evdev_mark_dead(struct evdev *evdev) +{ + mutex_lock(&evdev->mutex); + evdev->exist = 0; + mutex_unlock(&evdev->mutex); +} + +static void evdev_cleanup(struct evdev *evdev) +{ + struct input_handle *handle = &evdev->handle; + + evdev_mark_dead(evdev); + evdev_hangup(evdev); + evdev_remove_chrdev(evdev); + + /* evdev is marked dead so no one else accesses evdev->open */ + if (evdev->open) { + input_flush_device(handle, NULL); + input_close_device(handle); + } +} + +/* + * Create new evdev device. Note that input core serializes calls + * to connect and disconnect so we don't need to lock evdev_table here. + */ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { @@ -637,7 +872,10 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, int minor; int error; - for (minor = 0; minor < EVDEV_MINORS && evdev_table[minor]; minor++); + for (minor = 0; minor < EVDEV_MINORS; minor++) + if (!evdev_table[minor]) + break; + if (minor == EVDEV_MINORS) { printk(KERN_ERR "evdev: no more free evdev devices\n"); return -ENFILE; @@ -648,38 +886,44 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, return -ENOMEM; INIT_LIST_HEAD(&evdev->client_list); + spin_lock_init(&evdev->client_lock); + mutex_init(&evdev->mutex); init_waitqueue_head(&evdev->wait); + snprintf(evdev->name, sizeof(evdev->name), "event%d", minor); evdev->exist = 1; evdev->minor = minor; + evdev->handle.dev = dev; evdev->handle.name = evdev->name; evdev->handle.handler = handler; evdev->handle.private = evdev; - snprintf(evdev->name, sizeof(evdev->name), "event%d", minor); - snprintf(evdev->dev.bus_id, sizeof(evdev->dev.bus_id), - "event%d", minor); + strlcpy(evdev->dev.bus_id, evdev->name, sizeof(evdev->dev.bus_id)); + evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); evdev->dev.class = &input_class; evdev->dev.parent = &dev->dev; - evdev->dev.devt = MKDEV(INPUT_MAJOR, EVDEV_MINOR_BASE + minor); evdev->dev.release = evdev_free; device_initialize(&evdev->dev); - evdev_table[minor] = evdev; - - error = device_add(&evdev->dev); + error = input_register_handle(&evdev->handle); if (error) goto err_free_evdev; - error = input_register_handle(&evdev->handle); + error = evdev_install_chrdev(evdev); + if (error) + goto err_unregister_handle; + + error = device_add(&evdev->dev); if (error) - goto err_delete_evdev; + goto err_cleanup_evdev; return 0; - err_delete_evdev: - device_del(&evdev->dev); + err_cleanup_evdev: + evdev_cleanup(evdev); + err_unregister_handle: + input_unregister_handle(&evdev->handle); err_free_evdev: put_device(&evdev->dev); return error; @@ -688,21 +932,10 @@ static int evdev_connect(struct input_handler *handler, struct input_dev *dev, static void evdev_disconnect(struct input_handle *handle) { struct evdev *evdev = handle->private; - struct evdev_client *client; - input_unregister_handle(handle); device_del(&evdev->dev); - - evdev->exist = 0; - - if (evdev->open) { - input_flush_device(handle, NULL); - input_close_device(handle); - list_for_each_entry(client, &evdev->client_list, node) - kill_fasync(&client->fasync, SIGIO, POLL_HUP); - wake_up_interruptible(&evdev->wait); - } - + evdev_cleanup(evdev); + input_unregister_handle(handle); put_device(&evdev->dev); } @@ -714,13 +947,13 @@ static const struct input_device_id evdev_ids[] = { MODULE_DEVICE_TABLE(input, evdev_ids); static struct input_handler evdev_handler = { - .event = evdev_event, - .connect = evdev_connect, - .disconnect = evdev_disconnect, - .fops = &evdev_fops, - .minor = EVDEV_MINOR_BASE, - .name = "evdev", - .id_table = evdev_ids, + .event = evdev_event, + .connect = evdev_connect, + .disconnect = evdev_disconnect, + .fops = &evdev_fops, + .minor = EVDEV_MINOR_BASE, + .name = "evdev", + .id_table = evdev_ids, }; static int __init evdev_init(void) -- cgit v1.2.3 From 464b241575f3700e14492e34f26bcd1794280f55 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 30 Aug 2007 00:22:24 -0400 Subject: Input: mousedev - implement proper locking Signed-off-by: Dmitry Torokhov --- drivers/input/mousedev.c | 742 ++++++++++++++++++++++++++++++----------------- 1 file changed, 470 insertions(+), 272 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c index 9173916b8be..715def79390 100644 --- a/drivers/input/mousedev.c +++ b/drivers/input/mousedev.c @@ -61,9 +61,11 @@ struct mousedev { int open; int minor; char name[16]; + struct input_handle handle; wait_queue_head_t wait; struct list_head client_list; - struct input_handle handle; + spinlock_t client_lock; /* protects client_list */ + struct mutex mutex; struct device dev; struct list_head mixdev_node; @@ -113,108 +115,137 @@ static unsigned char mousedev_imex_seq[] = { 0xf3, 200, 0xf3, 200, 0xf3, 80 }; static struct input_handler mousedev_handler; static struct mousedev *mousedev_table[MOUSEDEV_MINORS]; +static DEFINE_MUTEX(mousedev_table_mutex); static struct mousedev *mousedev_mix; static LIST_HEAD(mousedev_mix_list); +static void mixdev_open_devices(void); +static void mixdev_close_devices(void); + #define fx(i) (mousedev->old_x[(mousedev->pkt_count - (i)) & 03]) #define fy(i) (mousedev->old_y[(mousedev->pkt_count - (i)) & 03]) -static void mousedev_touchpad_event(struct input_dev *dev, struct mousedev *mousedev, unsigned int code, int value) +static void mousedev_touchpad_event(struct input_dev *dev, + struct mousedev *mousedev, + unsigned int code, int value) { int size, tmp; enum { FRACTION_DENOM = 128 }; switch (code) { - case ABS_X: - fx(0) = value; - if (mousedev->touch && mousedev->pkt_count >= 2) { - size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; - if (size == 0) - size = 256 * 2; - tmp = ((value - fx(2)) * (256 * FRACTION_DENOM)) / size; - tmp += mousedev->frac_dx; - mousedev->packet.dx = tmp / FRACTION_DENOM; - mousedev->frac_dx = tmp - mousedev->packet.dx * FRACTION_DENOM; - } - break; - case ABS_Y: - fy(0) = value; - if (mousedev->touch && mousedev->pkt_count >= 2) { - /* use X size to keep the same scale */ - size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; - if (size == 0) - size = 256 * 2; - tmp = -((value - fy(2)) * (256 * FRACTION_DENOM)) / size; - tmp += mousedev->frac_dy; - mousedev->packet.dy = tmp / FRACTION_DENOM; - mousedev->frac_dy = tmp - mousedev->packet.dy * FRACTION_DENOM; - } - break; + case ABS_X: + fx(0) = value; + if (mousedev->touch && mousedev->pkt_count >= 2) { + size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; + if (size == 0) + size = 256 * 2; + tmp = ((value - fx(2)) * 256 * FRACTION_DENOM) / size; + tmp += mousedev->frac_dx; + mousedev->packet.dx = tmp / FRACTION_DENOM; + mousedev->frac_dx = + tmp - mousedev->packet.dx * FRACTION_DENOM; + } + break; + + case ABS_Y: + fy(0) = value; + if (mousedev->touch && mousedev->pkt_count >= 2) { + /* use X size to keep the same scale */ + size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; + if (size == 0) + size = 256 * 2; + tmp = -((value - fy(2)) * 256 * FRACTION_DENOM) / size; + tmp += mousedev->frac_dy; + mousedev->packet.dy = tmp / FRACTION_DENOM; + mousedev->frac_dy = tmp - + mousedev->packet.dy * FRACTION_DENOM; + } + break; } } -static void mousedev_abs_event(struct input_dev *dev, struct mousedev *mousedev, unsigned int code, int value) +static void mousedev_abs_event(struct input_dev *dev, struct mousedev *mousedev, + unsigned int code, int value) { int size; switch (code) { - case ABS_X: - size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; - if (size == 0) - size = xres ? : 1; - if (value > dev->absmax[ABS_X]) - value = dev->absmax[ABS_X]; - if (value < dev->absmin[ABS_X]) - value = dev->absmin[ABS_X]; - mousedev->packet.x = ((value - dev->absmin[ABS_X]) * xres) / size; - mousedev->packet.abs_event = 1; - break; - case ABS_Y: - size = dev->absmax[ABS_Y] - dev->absmin[ABS_Y]; - if (size == 0) - size = yres ? : 1; - if (value > dev->absmax[ABS_Y]) - value = dev->absmax[ABS_Y]; - if (value < dev->absmin[ABS_Y]) - value = dev->absmin[ABS_Y]; - mousedev->packet.y = yres - ((value - dev->absmin[ABS_Y]) * yres) / size; - mousedev->packet.abs_event = 1; - break; + case ABS_X: + size = dev->absmax[ABS_X] - dev->absmin[ABS_X]; + if (size == 0) + size = xres ? : 1; + if (value > dev->absmax[ABS_X]) + value = dev->absmax[ABS_X]; + if (value < dev->absmin[ABS_X]) + value = dev->absmin[ABS_X]; + mousedev->packet.x = + ((value - dev->absmin[ABS_X]) * xres) / size; + mousedev->packet.abs_event = 1; + break; + + case ABS_Y: + size = dev->absmax[ABS_Y] - dev->absmin[ABS_Y]; + if (size == 0) + size = yres ? : 1; + if (value > dev->absmax[ABS_Y]) + value = dev->absmax[ABS_Y]; + if (value < dev->absmin[ABS_Y]) + value = dev->absmin[ABS_Y]; + mousedev->packet.y = yres - + ((value - dev->absmin[ABS_Y]) * yres) / size; + mousedev->packet.abs_event = 1; + break; } } -static void mousedev_rel_event(struct mousedev *mousedev, unsigned int code, int value) +static void mousedev_rel_event(struct mousedev *mousedev, + unsigned int code, int value) { switch (code) { - case REL_X: mousedev->packet.dx += value; break; - case REL_Y: mousedev->packet.dy -= value; break; - case REL_WHEEL: mousedev->packet.dz -= value; break; + case REL_X: + mousedev->packet.dx += value; + break; + + case REL_Y: + mousedev->packet.dy -= value; + break; + + case REL_WHEEL: + mousedev->packet.dz -= value; + break; } } -static void mousedev_key_event(struct mousedev *mousedev, unsigned int code, int value) +static void mousedev_key_event(struct mousedev *mousedev, + unsigned int code, int value) { int index; switch (code) { - case BTN_TOUCH: - case BTN_0: - case BTN_LEFT: index = 0; break; - case BTN_STYLUS: - case BTN_1: - case BTN_RIGHT: index = 1; break; - case BTN_2: - case BTN_FORWARD: - case BTN_STYLUS2: - case BTN_MIDDLE: index = 2; break; - case BTN_3: - case BTN_BACK: - case BTN_SIDE: index = 3; break; - case BTN_4: - case BTN_EXTRA: index = 4; break; - default: return; + + case BTN_TOUCH: + case BTN_0: + case BTN_LEFT: index = 0; break; + + case BTN_STYLUS: + case BTN_1: + case BTN_RIGHT: index = 1; break; + + case BTN_2: + case BTN_FORWARD: + case BTN_STYLUS2: + case BTN_MIDDLE: index = 2; break; + + case BTN_3: + case BTN_BACK: + case BTN_SIDE: index = 3; break; + + case BTN_4: + case BTN_EXTRA: index = 4; break; + + default: return; } if (value) { @@ -226,19 +257,22 @@ static void mousedev_key_event(struct mousedev *mousedev, unsigned int code, int } } -static void mousedev_notify_readers(struct mousedev *mousedev, struct mousedev_hw_data *packet) +static void mousedev_notify_readers(struct mousedev *mousedev, + struct mousedev_hw_data *packet) { struct mousedev_client *client; struct mousedev_motion *p; - unsigned long flags; + unsigned int new_head; int wake_readers = 0; - list_for_each_entry(client, &mousedev->client_list, node) { - spin_lock_irqsave(&client->packet_lock, flags); + list_for_each_entry_rcu(client, &mousedev->client_list, node) { + + /* Just acquire the lock, interrupts already disabled */ + spin_lock(&client->packet_lock); p = &client->packets[client->head]; if (client->ready && p->buttons != mousedev->packet.buttons) { - unsigned int new_head = (client->head + 1) % PACKET_QUEUE_LEN; + new_head = (client->head + 1) % PACKET_QUEUE_LEN; if (new_head != client->tail) { p = &client->packets[client->head = new_head]; memset(p, 0, sizeof(struct mousedev_motion)); @@ -253,19 +287,22 @@ static void mousedev_notify_readers(struct mousedev *mousedev, struct mousedev_h } client->pos_x += packet->dx; - client->pos_x = client->pos_x < 0 ? 0 : (client->pos_x >= xres ? xres : client->pos_x); + client->pos_x = client->pos_x < 0 ? + 0 : (client->pos_x >= xres ? xres : client->pos_x); client->pos_y += packet->dy; - client->pos_y = client->pos_y < 0 ? 0 : (client->pos_y >= yres ? yres : client->pos_y); + client->pos_y = client->pos_y < 0 ? + 0 : (client->pos_y >= yres ? yres : client->pos_y); p->dx += packet->dx; p->dy += packet->dy; p->dz += packet->dz; p->buttons = mousedev->packet.buttons; - if (p->dx || p->dy || p->dz || p->buttons != client->last_buttons) + if (p->dx || p->dy || p->dz || + p->buttons != client->last_buttons) client->ready = 1; - spin_unlock_irqrestore(&client->packet_lock, flags); + spin_unlock(&client->packet_lock); if (client->ready) { kill_fasync(&client->fasync, SIGIO, POLL_IN); @@ -281,7 +318,8 @@ static void mousedev_touchpad_touch(struct mousedev *mousedev, int value) { if (!value) { if (mousedev->touch && - time_before(jiffies, mousedev->touch + msecs_to_jiffies(tap_time))) { + time_before(jiffies, + mousedev->touch + msecs_to_jiffies(tap_time))) { /* * Toggle left button to emulate tap. * We rely on the fact that mousedev_mix always has 0 @@ -290,7 +328,8 @@ static void mousedev_touchpad_touch(struct mousedev *mousedev, int value) set_bit(0, &mousedev->packet.buttons); set_bit(0, &mousedev_mix->packet.buttons); mousedev_notify_readers(mousedev, &mousedev_mix->packet); - mousedev_notify_readers(mousedev_mix, &mousedev_mix->packet); + mousedev_notify_readers(mousedev_mix, + &mousedev_mix->packet); clear_bit(0, &mousedev->packet.buttons); clear_bit(0, &mousedev_mix->packet.buttons); } @@ -302,54 +341,61 @@ static void mousedev_touchpad_touch(struct mousedev *mousedev, int value) mousedev->touch = jiffies; } -static void mousedev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) +static void mousedev_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) { struct mousedev *mousedev = handle->private; switch (type) { - case EV_ABS: - /* Ignore joysticks */ - if (test_bit(BTN_TRIGGER, handle->dev->keybit)) - return; - if (test_bit(BTN_TOOL_FINGER, handle->dev->keybit)) - mousedev_touchpad_event(handle->dev, mousedev, code, value); - else - mousedev_abs_event(handle->dev, mousedev, code, value); + case EV_ABS: + /* Ignore joysticks */ + if (test_bit(BTN_TRIGGER, handle->dev->keybit)) + return; - break; + if (test_bit(BTN_TOOL_FINGER, handle->dev->keybit)) + mousedev_touchpad_event(handle->dev, + mousedev, code, value); + else + mousedev_abs_event(handle->dev, mousedev, code, value); - case EV_REL: - mousedev_rel_event(mousedev, code, value); - break; + break; - case EV_KEY: - if (value != 2) { - if (code == BTN_TOUCH && test_bit(BTN_TOOL_FINGER, handle->dev->keybit)) - mousedev_touchpad_touch(mousedev, value); - else - mousedev_key_event(mousedev, code, value); - } - break; + case EV_REL: + mousedev_rel_event(mousedev, code, value); + break; - case EV_SYN: - if (code == SYN_REPORT) { - if (mousedev->touch) { - mousedev->pkt_count++; - /* Input system eats duplicate events, but we need all of them - * to do correct averaging so apply present one forward - */ - fx(0) = fx(1); - fy(0) = fy(1); - } - - mousedev_notify_readers(mousedev, &mousedev->packet); - mousedev_notify_readers(mousedev_mix, &mousedev->packet); - - mousedev->packet.dx = mousedev->packet.dy = mousedev->packet.dz = 0; - mousedev->packet.abs_event = 0; + case EV_KEY: + if (value != 2) { + if (code == BTN_TOUCH && + test_bit(BTN_TOOL_FINGER, handle->dev->keybit)) + mousedev_touchpad_touch(mousedev, value); + else + mousedev_key_event(mousedev, code, value); + } + break; + + case EV_SYN: + if (code == SYN_REPORT) { + if (mousedev->touch) { + mousedev->pkt_count++; + /* + * Input system eats duplicate events, + * but we need all of them to do correct + * averaging so apply present one forward + */ + fx(0) = fx(1); + fy(0) = fy(1); } - break; + + mousedev_notify_readers(mousedev, &mousedev->packet); + mousedev_notify_readers(mousedev_mix, &mousedev->packet); + + mousedev->packet.dx = mousedev->packet.dy = + mousedev->packet.dz = 0; + mousedev->packet.abs_event = 0; + } + break; } } @@ -367,41 +413,45 @@ static void mousedev_free(struct device *dev) { struct mousedev *mousedev = container_of(dev, struct mousedev, dev); - mousedev_table[mousedev->minor] = NULL; kfree(mousedev); } -static int mixdev_add_device(struct mousedev *mousedev) +static int mousedev_open_device(struct mousedev *mousedev) { - int error; - - if (mousedev_mix->open) { - error = input_open_device(&mousedev->handle); - if (error) - return error; + int retval; - mousedev->open++; - mousedev->mixdev_open = 1; - } + retval = mutex_lock_interruptible(&mousedev->mutex); + if (retval) + return retval; - get_device(&mousedev->dev); - list_add_tail(&mousedev->mixdev_node, &mousedev_mix_list); + if (mousedev->minor == MOUSEDEV_MIX) + mixdev_open_devices(); + else if (!mousedev->exist) + retval = -ENODEV; + else if (!mousedev->open++) + retval = input_open_device(&mousedev->handle); - return 0; + mutex_unlock(&mousedev->mutex); + return retval; } -static void mixdev_remove_device(struct mousedev *mousedev) +static void mousedev_close_device(struct mousedev *mousedev) { - if (mousedev->mixdev_open) { - mousedev->mixdev_open = 0; - if (!--mousedev->open && mousedev->exist) - input_close_device(&mousedev->handle); - } + mutex_lock(&mousedev->mutex); - list_del_init(&mousedev->mixdev_node); - put_device(&mousedev->dev); + if (mousedev->minor == MOUSEDEV_MIX) + mixdev_close_devices(); + else if (mousedev->exist && !--mousedev->open) + input_close_device(&mousedev->handle); + + mutex_unlock(&mousedev->mutex); } +/* + * Open all available devices so they can all be multiplexed in one. + * stream. Note that this function is called with mousedev_mix->mutex + * held. + */ static void mixdev_open_devices(void) { struct mousedev *mousedev; @@ -411,16 +461,19 @@ static void mixdev_open_devices(void) list_for_each_entry(mousedev, &mousedev_mix_list, mixdev_node) { if (!mousedev->mixdev_open) { - if (!mousedev->open && mousedev->exist) - if (input_open_device(&mousedev->handle)) - continue; + if (mousedev_open_device(mousedev)) + continue; - mousedev->open++; mousedev->mixdev_open = 1; } } } +/* + * Close all devices that were opened as part of multiplexed + * device. Note that this function is called with mousedev_mix->mutex + * held. + */ static void mixdev_close_devices(void) { struct mousedev *mousedev; @@ -431,33 +484,50 @@ static void mixdev_close_devices(void) list_for_each_entry(mousedev, &mousedev_mix_list, mixdev_node) { if (mousedev->mixdev_open) { mousedev->mixdev_open = 0; - if (!--mousedev->open && mousedev->exist) - input_close_device(&mousedev->handle); + mousedev_close_device(mousedev); } } } + +static void mousedev_attach_client(struct mousedev *mousedev, + struct mousedev_client *client) +{ + spin_lock(&mousedev->client_lock); + list_add_tail_rcu(&client->node, &mousedev->client_list); + spin_unlock(&mousedev->client_lock); + /* + * We don't use synchronize_rcu() here because read-side + * critical section is protected by a spinlock (dev->event_lock) + * instead of rcu_read_lock(). + */ + synchronize_sched(); +} + +static void mousedev_detach_client(struct mousedev *mousedev, + struct mousedev_client *client) +{ + spin_lock(&mousedev->client_lock); + list_del_rcu(&client->node); + spin_unlock(&mousedev->client_lock); + synchronize_sched(); +} + static int mousedev_release(struct inode *inode, struct file *file) { struct mousedev_client *client = file->private_data; struct mousedev *mousedev = client->mousedev; mousedev_fasync(-1, file, 0); - - list_del(&client->node); + mousedev_detach_client(mousedev, client); kfree(client); - if (mousedev->minor == MOUSEDEV_MIX) - mixdev_close_devices(); - else if (!--mousedev->open && mousedev->exist) - input_close_device(&mousedev->handle); - + mousedev_close_device(mousedev); put_device(&mousedev->dev); return 0; } - static int mousedev_open(struct inode *inode, struct file *file) { struct mousedev_client *client; @@ -475,12 +545,17 @@ static int mousedev_open(struct inode *inode, struct file *file) if (i >= MOUSEDEV_MINORS) return -ENODEV; + error = mutex_lock_interruptible(&mousedev_table_mutex); + if (error) + return error; mousedev = mousedev_table[i]; + if (mousedev) + get_device(&mousedev->dev); + mutex_unlock(&mousedev_table_mutex); + if (!mousedev) return -ENODEV; - get_device(&mousedev->dev); - client = kzalloc(sizeof(struct mousedev_client), GFP_KERNEL); if (!client) { error = -ENOMEM; @@ -491,21 +566,17 @@ static int mousedev_open(struct inode *inode, struct file *file) client->pos_x = xres / 2; client->pos_y = yres / 2; client->mousedev = mousedev; - list_add_tail(&client->node, &mousedev->client_list); + mousedev_attach_client(mousedev, client); - if (mousedev->minor == MOUSEDEV_MIX) - mixdev_open_devices(); - else if (!mousedev->open++ && mousedev->exist) { - error = input_open_device(&mousedev->handle); - if (error) - goto err_free_client; - } + error = mousedev_open_device(mousedev); + if (error) + goto err_free_client; file->private_data = client; return 0; err_free_client: - list_del(&client->node); + mousedev_detach_client(mousedev, client); kfree(client); err_put_mousedev: put_device(&mousedev->dev); @@ -517,41 +588,41 @@ static inline int mousedev_limit_delta(int delta, int limit) return delta > limit ? limit : (delta < -limit ? -limit : delta); } -static void mousedev_packet(struct mousedev_client *client, signed char *ps2_data) +static void mousedev_packet(struct mousedev_client *client, + signed char *ps2_data) { - struct mousedev_motion *p; - unsigned long flags; - - spin_lock_irqsave(&client->packet_lock, flags); - p = &client->packets[client->tail]; + struct mousedev_motion *p = &client->packets[client->tail]; - ps2_data[0] = 0x08 | ((p->dx < 0) << 4) | ((p->dy < 0) << 5) | (p->buttons & 0x07); + ps2_data[0] = 0x08 | + ((p->dx < 0) << 4) | ((p->dy < 0) << 5) | (p->buttons & 0x07); ps2_data[1] = mousedev_limit_delta(p->dx, 127); ps2_data[2] = mousedev_limit_delta(p->dy, 127); p->dx -= ps2_data[1]; p->dy -= ps2_data[2]; switch (client->mode) { - case MOUSEDEV_EMUL_EXPS: - ps2_data[3] = mousedev_limit_delta(p->dz, 7); - p->dz -= ps2_data[3]; - ps2_data[3] = (ps2_data[3] & 0x0f) | ((p->buttons & 0x18) << 1); - client->bufsiz = 4; - break; - - case MOUSEDEV_EMUL_IMPS: - ps2_data[0] |= ((p->buttons & 0x10) >> 3) | ((p->buttons & 0x08) >> 1); - ps2_data[3] = mousedev_limit_delta(p->dz, 127); - p->dz -= ps2_data[3]; - client->bufsiz = 4; - break; - - case MOUSEDEV_EMUL_PS2: - default: - ps2_data[0] |= ((p->buttons & 0x10) >> 3) | ((p->buttons & 0x08) >> 1); - p->dz = 0; - client->bufsiz = 3; - break; + case MOUSEDEV_EMUL_EXPS: + ps2_data[3] = mousedev_limit_delta(p->dz, 7); + p->dz -= ps2_data[3]; + ps2_data[3] = (ps2_data[3] & 0x0f) | ((p->buttons & 0x18) << 1); + client->bufsiz = 4; + break; + + case MOUSEDEV_EMUL_IMPS: + ps2_data[0] |= + ((p->buttons & 0x10) >> 3) | ((p->buttons & 0x08) >> 1); + ps2_data[3] = mousedev_limit_delta(p->dz, 127); + p->dz -= ps2_data[3]; + client->bufsiz = 4; + break; + + case MOUSEDEV_EMUL_PS2: + default: + ps2_data[0] |= + ((p->buttons & 0x10) >> 3) | ((p->buttons & 0x08) >> 1); + p->dz = 0; + client->bufsiz = 3; + break; } if (!p->dx && !p->dy && !p->dz) { @@ -561,12 +632,56 @@ static void mousedev_packet(struct mousedev_client *client, signed char *ps2_dat } else client->tail = (client->tail + 1) % PACKET_QUEUE_LEN; } - - spin_unlock_irqrestore(&client->packet_lock, flags); } +static void mousedev_generate_response(struct mousedev_client *client, + int command) +{ + client->ps2[0] = 0xfa; /* ACK */ -static ssize_t mousedev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) + switch (command) { + + case 0xeb: /* Poll */ + mousedev_packet(client, &client->ps2[1]); + client->bufsiz++; /* account for leading ACK */ + break; + + case 0xf2: /* Get ID */ + switch (client->mode) { + case MOUSEDEV_EMUL_PS2: + client->ps2[1] = 0; + break; + case MOUSEDEV_EMUL_IMPS: + client->ps2[1] = 3; + break; + case MOUSEDEV_EMUL_EXPS: + client->ps2[1] = 4; + break; + } + client->bufsiz = 2; + break; + + case 0xe9: /* Get info */ + client->ps2[1] = 0x60; client->ps2[2] = 3; client->ps2[3] = 200; + client->bufsiz = 4; + break; + + case 0xff: /* Reset */ + client->impsseq = client->imexseq = 0; + client->mode = MOUSEDEV_EMUL_PS2; + client->ps2[1] = 0xaa; client->ps2[2] = 0x00; + client->bufsiz = 3; + break; + + default: + client->bufsiz = 1; + break; + } + client->buffer = client->bufsiz; +} + +static ssize_t mousedev_write(struct file *file, const char __user *buffer, + size_t count, loff_t *ppos) { struct mousedev_client *client = file->private_data; unsigned char c; @@ -577,6 +692,8 @@ static ssize_t mousedev_write(struct file *file, const char __user *buffer, size if (get_user(c, buffer + i)) return -EFAULT; + spin_lock_irq(&client->packet_lock); + if (c == mousedev_imex_seq[client->imexseq]) { if (++client->imexseq == MOUSEDEV_SEQ_LEN) { client->imexseq = 0; @@ -593,68 +710,39 @@ static ssize_t mousedev_write(struct file *file, const char __user *buffer, size } else client->impsseq = 0; - client->ps2[0] = 0xfa; - - switch (c) { - - case 0xeb: /* Poll */ - mousedev_packet(client, &client->ps2[1]); - client->bufsiz++; /* account for leading ACK */ - break; - - case 0xf2: /* Get ID */ - switch (client->mode) { - case MOUSEDEV_EMUL_PS2: client->ps2[1] = 0; break; - case MOUSEDEV_EMUL_IMPS: client->ps2[1] = 3; break; - case MOUSEDEV_EMUL_EXPS: client->ps2[1] = 4; break; - } - client->bufsiz = 2; - break; - - case 0xe9: /* Get info */ - client->ps2[1] = 0x60; client->ps2[2] = 3; client->ps2[3] = 200; - client->bufsiz = 4; - break; - - case 0xff: /* Reset */ - client->impsseq = client->imexseq = 0; - client->mode = MOUSEDEV_EMUL_PS2; - client->ps2[1] = 0xaa; client->ps2[2] = 0x00; - client->bufsiz = 3; - break; - - default: - client->bufsiz = 1; - break; - } + mousedev_generate_response(client, c); - client->buffer = client->bufsiz; + spin_unlock_irq(&client->packet_lock); } kill_fasync(&client->fasync, SIGIO, POLL_IN); - wake_up_interruptible(&client->mousedev->wait); return count; } -static ssize_t mousedev_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos) +static ssize_t mousedev_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) { struct mousedev_client *client = file->private_data; + struct mousedev *mousedev = client->mousedev; + signed char data[sizeof(client->ps2)]; int retval = 0; - if (!client->ready && !client->buffer && (file->f_flags & O_NONBLOCK)) + if (!client->ready && !client->buffer && mousedev->exist && + (file->f_flags & O_NONBLOCK)) return -EAGAIN; - retval = wait_event_interruptible(client->mousedev->wait, - !client->mousedev->exist || client->ready || client->buffer); - + retval = wait_event_interruptible(mousedev->wait, + !mousedev->exist || client->ready || client->buffer); if (retval) return retval; - if (!client->mousedev->exist) + if (!mousedev->exist) return -ENODEV; + spin_lock_irq(&client->packet_lock); + if (!client->buffer && client->ready) { mousedev_packet(client, client->ps2); client->buffer = client->bufsiz; @@ -663,9 +751,12 @@ static ssize_t mousedev_read(struct file *file, char __user *buffer, size_t coun if (count > client->buffer) count = client->buffer; + memcpy(data, client->ps2 + client->bufsiz - client->buffer, count); client->buffer -= count; - if (copy_to_user(buffer, client->ps2 + client->bufsiz - client->buffer - count, count)) + spin_unlock_irq(&client->packet_lock); + + if (copy_to_user(buffer, data, count)) return -EFAULT; return count; @@ -692,6 +783,60 @@ static const struct file_operations mousedev_fops = { .fasync = mousedev_fasync, }; +static int mousedev_install_chrdev(struct mousedev *mousedev) +{ + mousedev_table[mousedev->minor] = mousedev; + return 0; +} + +static void mousedev_remove_chrdev(struct mousedev *mousedev) +{ + mutex_lock(&mousedev_table_mutex); + mousedev_table[mousedev->minor] = NULL; + mutex_unlock(&mousedev_table_mutex); +} + +/* + * Mark device non-existent. This disables writes, ioctls and + * prevents new users from opening the device. Already posted + * blocking reads will stay, however new ones will fail. + */ +static void mousedev_mark_dead(struct mousedev *mousedev) +{ + mutex_lock(&mousedev->mutex); + mousedev->exist = 0; + mutex_unlock(&mousedev->mutex); +} + +/* + * Wake up users waiting for IO so they can disconnect from + * dead device. + */ +static void mousedev_hangup(struct mousedev *mousedev) +{ + struct mousedev_client *client; + + spin_lock(&mousedev->client_lock); + list_for_each_entry(client, &mousedev->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + spin_unlock(&mousedev->client_lock); + + wake_up_interruptible(&mousedev->wait); +} + +static void mousedev_cleanup(struct mousedev *mousedev) +{ + struct input_handle *handle = &mousedev->handle; + + mousedev_mark_dead(mousedev); + mousedev_hangup(mousedev); + mousedev_remove_chrdev(mousedev); + + /* mousedev is marked dead so no one else accesses mousedev->open */ + if (mousedev->open) + input_close_device(handle); +} + static struct mousedev *mousedev_create(struct input_dev *dev, struct input_handler *handler, int minor) @@ -707,6 +852,10 @@ static struct mousedev *mousedev_create(struct input_dev *dev, INIT_LIST_HEAD(&mousedev->client_list); INIT_LIST_HEAD(&mousedev->mixdev_node); + spin_lock_init(&mousedev->client_lock); + mutex_init(&mousedev->mutex); + lockdep_set_subclass(&mousedev->mutex, + minor == MOUSEDEV_MIX ? MOUSEDEV_MIX : 0); init_waitqueue_head(&mousedev->wait); if (minor == MOUSEDEV_MIX) @@ -731,14 +880,27 @@ static struct mousedev *mousedev_create(struct input_dev *dev, mousedev->dev.release = mousedev_free; device_initialize(&mousedev->dev); - mousedev_table[minor] = mousedev; + if (minor != MOUSEDEV_MIX) { + error = input_register_handle(&mousedev->handle); + if (error) + goto err_free_mousedev; + } + + error = mousedev_install_chrdev(mousedev); + if (error) + goto err_unregister_handle; error = device_add(&mousedev->dev); if (error) - goto err_free_mousedev; + goto err_cleanup_mousedev; return mousedev; + err_cleanup_mousedev: + mousedev_cleanup(mousedev); + err_unregister_handle: + if (minor != MOUSEDEV_MIX) + input_unregister_handle(&mousedev->handle); err_free_mousedev: put_device(&mousedev->dev); err_out: @@ -747,29 +909,64 @@ static struct mousedev *mousedev_create(struct input_dev *dev, static void mousedev_destroy(struct mousedev *mousedev) { - struct mousedev_client *client; - device_del(&mousedev->dev); - mousedev->exist = 0; + mousedev_cleanup(mousedev); + if (mousedev->minor != MOUSEDEV_MIX) + input_unregister_handle(&mousedev->handle); + put_device(&mousedev->dev); +} - if (mousedev->open) { - input_close_device(&mousedev->handle); - list_for_each_entry(client, &mousedev->client_list, node) - kill_fasync(&client->fasync, SIGIO, POLL_HUP); - wake_up_interruptible(&mousedev->wait); +static int mixdev_add_device(struct mousedev *mousedev) +{ + int retval; + + retval = mutex_lock_interruptible(&mousedev_mix->mutex); + if (retval) + return retval; + + if (mousedev_mix->open) { + retval = mousedev_open_device(mousedev); + if (retval) + goto out; + + mousedev->mixdev_open = 1; + } + + get_device(&mousedev->dev); + list_add_tail(&mousedev->mixdev_node, &mousedev_mix_list); + + out: + mutex_unlock(&mousedev_mix->mutex); + return retval; +} + +static void mixdev_remove_device(struct mousedev *mousedev) +{ + mutex_lock(&mousedev_mix->mutex); + + if (mousedev->mixdev_open) { + mousedev->mixdev_open = 0; + mousedev_close_device(mousedev); } + list_del_init(&mousedev->mixdev_node); + mutex_unlock(&mousedev_mix->mutex); + put_device(&mousedev->dev); } -static int mousedev_connect(struct input_handler *handler, struct input_dev *dev, +static int mousedev_connect(struct input_handler *handler, + struct input_dev *dev, const struct input_device_id *id) { struct mousedev *mousedev; int minor; int error; - for (minor = 0; minor < MOUSEDEV_MINORS && mousedev_table[minor]; minor++); + for (minor = 0; minor < MOUSEDEV_MINORS; minor++) + if (!mousedev_table[minor]) + break; + if (minor == MOUSEDEV_MINORS) { printk(KERN_ERR "mousedev: no more free mousedev devices\n"); return -ENFILE; @@ -779,21 +976,13 @@ static int mousedev_connect(struct input_handler *handler, struct input_dev *dev if (IS_ERR(mousedev)) return PTR_ERR(mousedev); - error = input_register_handle(&mousedev->handle); - if (error) - goto err_delete_mousedev; - error = mixdev_add_device(mousedev); - if (error) - goto err_unregister_handle; + if (error) { + mousedev_destroy(mousedev); + return error; + } return 0; - - err_unregister_handle: - input_unregister_handle(&mousedev->handle); - err_delete_mousedev: - device_unregister(&mousedev->dev); - return error; } static void mousedev_disconnect(struct input_handle *handle) @@ -801,33 +990,42 @@ static void mousedev_disconnect(struct input_handle *handle) struct mousedev *mousedev = handle->private; mixdev_remove_device(mousedev); - input_unregister_handle(handle); mousedev_destroy(mousedev); } static const struct input_device_id mousedev_ids[] = { { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_RELBIT, + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_RELBIT, .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, .keybit = { [LONG(BTN_LEFT)] = BIT(BTN_LEFT) }, .relbit = { BIT(REL_X) | BIT(REL_Y) }, - }, /* A mouse like device, at least one button, two relative axes */ + }, /* A mouse like device, at least one button, + two relative axes */ { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_RELBIT, + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_RELBIT, .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, .relbit = { BIT(REL_WHEEL) }, }, /* A separate scrollwheel */ { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, .evbit = { BIT(EV_KEY) | BIT(EV_ABS) }, .keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) }, .absbit = { BIT(ABS_X) | BIT(ABS_Y) }, - }, /* A tablet like device, at least touch detection, two absolute axes */ + }, /* A tablet like device, at least touch detection, + two absolute axes */ { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, .evbit = { BIT(EV_KEY) | BIT(EV_ABS) }, .keybit = { [LONG(BTN_TOOL_FINGER)] = BIT(BTN_TOOL_FINGER) }, - .absbit = { BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE) | BIT(ABS_TOOL_WIDTH) }, + .absbit = { BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE) | + BIT(ABS_TOOL_WIDTH) }, }, /* A touchpad */ { }, /* Terminating entry */ -- cgit v1.2.3 From b126207ccdfe492fbc339c18d4898b1b5353fc6b Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 30 Aug 2007 00:22:32 -0400 Subject: Input: joydev - implement proper locking Signed-off-by: Dmitry Torokhov --- drivers/input/joydev.c | 743 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 492 insertions(+), 251 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/joydev.c b/drivers/input/joydev.c index a9a0180bfd4..a4272d63c4d 100644 --- a/drivers/input/joydev.c +++ b/drivers/input/joydev.c @@ -43,6 +43,8 @@ struct joydev { struct input_handle handle; wait_queue_head_t wait; struct list_head client_list; + spinlock_t client_lock; /* protects client_list */ + struct mutex mutex; struct device dev; struct js_corr corr[ABS_MAX + 1]; @@ -61,31 +63,61 @@ struct joydev_client { int head; int tail; int startup; + spinlock_t buffer_lock; /* protects access to buffer, head and tail */ struct fasync_struct *fasync; struct joydev *joydev; struct list_head node; }; static struct joydev *joydev_table[JOYDEV_MINORS]; +static DEFINE_MUTEX(joydev_table_mutex); static int joydev_correct(int value, struct js_corr *corr) { switch (corr->type) { - case JS_CORR_NONE: - break; - case JS_CORR_BROKEN: - value = value > corr->coef[0] ? (value < corr->coef[1] ? 0 : - ((corr->coef[3] * (value - corr->coef[1])) >> 14)) : - ((corr->coef[2] * (value - corr->coef[0])) >> 14); - break; - default: - return 0; + + case JS_CORR_NONE: + break; + + case JS_CORR_BROKEN: + value = value > corr->coef[0] ? (value < corr->coef[1] ? 0 : + ((corr->coef[3] * (value - corr->coef[1])) >> 14)) : + ((corr->coef[2] * (value - corr->coef[0])) >> 14); + break; + + default: + return 0; } return value < -32767 ? -32767 : (value > 32767 ? 32767 : value); } -static void joydev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) +static void joydev_pass_event(struct joydev_client *client, + struct js_event *event) +{ + struct joydev *joydev = client->joydev; + + /* + * IRQs already disabled, just acquire the lock + */ + spin_lock(&client->buffer_lock); + + client->buffer[client->head] = *event; + + if (client->startup == joydev->nabs + joydev->nkey) { + client->head++; + client->head &= JOYDEV_BUFFER_SIZE - 1; + if (client->tail == client->head) + client->startup = 0; + } + + spin_unlock(&client->buffer_lock); + + kill_fasync(&client->fasync, SIGIO, POLL_IN); +} + +static void joydev_event(struct input_handle *handle, + unsigned int type, unsigned int code, int value) { struct joydev *joydev = handle->private; struct joydev_client *client; @@ -93,39 +125,32 @@ static void joydev_event(struct input_handle *handle, unsigned int type, unsigne switch (type) { - case EV_KEY: - if (code < BTN_MISC || value == 2) - return; - event.type = JS_EVENT_BUTTON; - event.number = joydev->keymap[code - BTN_MISC]; - event.value = value; - break; - - case EV_ABS: - event.type = JS_EVENT_AXIS; - event.number = joydev->absmap[code]; - event.value = joydev_correct(value, joydev->corr + event.number); - if (event.value == joydev->abs[event.number]) - return; - joydev->abs[event.number] = event.value; - break; + case EV_KEY: + if (code < BTN_MISC || value == 2) + return; + event.type = JS_EVENT_BUTTON; + event.number = joydev->keymap[code - BTN_MISC]; + event.value = value; + break; - default: + case EV_ABS: + event.type = JS_EVENT_AXIS; + event.number = joydev->absmap[code]; + event.value = joydev_correct(value, + &joydev->corr[event.number]); + if (event.value == joydev->abs[event.number]) return; + joydev->abs[event.number] = event.value; + break; + + default: + return; } event.time = jiffies_to_msecs(jiffies); - list_for_each_entry(client, &joydev->client_list, node) { - - memcpy(client->buffer + client->head, &event, sizeof(struct js_event)); - - if (client->startup == joydev->nabs + joydev->nkey) - if (client->tail == (client->head = (client->head + 1) & (JOYDEV_BUFFER_SIZE - 1))) - client->startup = 0; - - kill_fasync(&client->fasync, SIGIO, POLL_IN); - } + list_for_each_entry_rcu(client, &joydev->client_list, node) + joydev_pass_event(client, &event); wake_up_interruptible(&joydev->wait); } @@ -144,23 +169,85 @@ static void joydev_free(struct device *dev) { struct joydev *joydev = container_of(dev, struct joydev, dev); - joydev_table[joydev->minor] = NULL; kfree(joydev); } +static void joydev_attach_client(struct joydev *joydev, + struct joydev_client *client) +{ + spin_lock(&joydev->client_lock); + list_add_tail_rcu(&client->node, &joydev->client_list); + spin_unlock(&joydev->client_lock); + /* + * We don't use synchronize_rcu() here because read-side + * critical section is protected by a spinlock (dev->event_lock) + * instead of rcu_read_lock(). + */ + synchronize_sched(); +} + +static void joydev_detach_client(struct joydev *joydev, + struct joydev_client *client) +{ + spin_lock(&joydev->client_lock); + list_del_rcu(&client->node); + spin_unlock(&joydev->client_lock); + synchronize_sched(); +} + +static int joydev_open_device(struct joydev *joydev) +{ + int retval; + + retval = mutex_lock_interruptible(&joydev->mutex); + if (retval) + return retval; + + if (!joydev->exist) + retval = -ENODEV; + else if (!joydev->open++) + retval = input_open_device(&joydev->handle); + + mutex_unlock(&joydev->mutex); + return retval; +} + +static void joydev_close_device(struct joydev *joydev) +{ + mutex_lock(&joydev->mutex); + + if (joydev->exist && !--joydev->open) + input_close_device(&joydev->handle); + + mutex_unlock(&joydev->mutex); +} + +/* + * Wake up users waiting for IO so they can disconnect from + * dead device. + */ +static void joydev_hangup(struct joydev *joydev) +{ + struct joydev_client *client; + + spin_lock(&joydev->client_lock); + list_for_each_entry(client, &joydev->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + spin_unlock(&joydev->client_lock); + + wake_up_interruptible(&joydev->wait); +} + static int joydev_release(struct inode *inode, struct file *file) { struct joydev_client *client = file->private_data; struct joydev *joydev = client->joydev; joydev_fasync(-1, file, 0); - - list_del(&client->node); + joydev_detach_client(joydev, client); kfree(client); - if (!--joydev->open && joydev->exist) - input_close_device(&joydev->handle); - + joydev_close_device(joydev); put_device(&joydev->dev); return 0; @@ -176,11 +263,16 @@ static int joydev_open(struct inode *inode, struct file *file) if (i >= JOYDEV_MINORS) return -ENODEV; + error = mutex_lock_interruptible(&joydev_table_mutex); + if (error) + return error; joydev = joydev_table[i]; - if (!joydev || !joydev->exist) - return -ENODEV; + if (joydev) + get_device(&joydev->dev); + mutex_unlock(&joydev_table_mutex); - get_device(&joydev->dev); + if (!joydev) + return -ENODEV; client = kzalloc(sizeof(struct joydev_client), GFP_KERNEL); if (!client) { @@ -188,37 +280,129 @@ static int joydev_open(struct inode *inode, struct file *file) goto err_put_joydev; } + spin_lock_init(&client->buffer_lock); client->joydev = joydev; - list_add_tail(&client->node, &joydev->client_list); + joydev_attach_client(joydev, client); - if (!joydev->open++ && joydev->exist) { - error = input_open_device(&joydev->handle); - if (error) - goto err_free_client; - } + error = joydev_open_device(joydev); + if (error) + goto err_free_client; file->private_data = client; return 0; err_free_client: - list_del(&client->node); + joydev_detach_client(joydev, client); kfree(client); err_put_joydev: put_device(&joydev->dev); return error; } -static ssize_t joydev_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +static int joydev_generate_startup_event(struct joydev_client *client, + struct input_dev *input, + struct js_event *event) { - return -EINVAL; + struct joydev *joydev = client->joydev; + int have_event; + + spin_lock_irq(&client->buffer_lock); + + have_event = client->startup < joydev->nabs + joydev->nkey; + + if (have_event) { + + event->time = jiffies_to_msecs(jiffies); + if (client->startup < joydev->nkey) { + event->type = JS_EVENT_BUTTON | JS_EVENT_INIT; + event->number = client->startup; + event->value = !!test_bit(joydev->keypam[event->number], + input->key); + } else { + event->type = JS_EVENT_AXIS | JS_EVENT_INIT; + event->number = client->startup - joydev->nkey; + event->value = joydev->abs[event->number]; + } + client->startup++; + } + + spin_unlock_irq(&client->buffer_lock); + + return have_event; +} + +static int joydev_fetch_next_event(struct joydev_client *client, + struct js_event *event) +{ + int have_event; + + spin_lock_irq(&client->buffer_lock); + + have_event = client->head != client->tail; + if (have_event) { + *event = client->buffer[client->tail++]; + client->tail &= JOYDEV_BUFFER_SIZE - 1; + } + + spin_unlock_irq(&client->buffer_lock); + + return have_event; +} + +/* + * Old joystick interface + */ +static ssize_t joydev_0x_read(struct joydev_client *client, + struct input_dev *input, + char __user *buf) +{ + struct joydev *joydev = client->joydev; + struct JS_DATA_TYPE data; + int i; + + spin_lock_irq(&input->event_lock); + + /* + * Get device state + */ + for (data.buttons = i = 0; i < 32 && i < joydev->nkey; i++) + data.buttons |= + test_bit(joydev->keypam[i], input->key) ? (1 << i) : 0; + data.x = (joydev->abs[0] / 256 + 128) >> joydev->glue.JS_CORR.x; + data.y = (joydev->abs[1] / 256 + 128) >> joydev->glue.JS_CORR.y; + + /* + * Reset reader's event queue + */ + spin_lock(&client->buffer_lock); + client->startup = 0; + client->tail = client->head; + spin_unlock(&client->buffer_lock); + + spin_unlock_irq(&input->event_lock); + + if (copy_to_user(buf, &data, sizeof(struct JS_DATA_TYPE))) + return -EFAULT; + + return sizeof(struct JS_DATA_TYPE); } -static ssize_t joydev_read(struct file *file, char __user *buf, size_t count, loff_t *ppos) +static inline int joydev_data_pending(struct joydev_client *client) +{ + struct joydev *joydev = client->joydev; + + return client->startup < joydev->nabs + joydev->nkey || + client->head != client->tail; +} + +static ssize_t joydev_read(struct file *file, char __user *buf, + size_t count, loff_t *ppos) { struct joydev_client *client = file->private_data; struct joydev *joydev = client->joydev; struct input_dev *input = joydev->handle.dev; - int retval = 0; + struct js_event event; + int retval; if (!joydev->exist) return -ENODEV; @@ -226,68 +410,35 @@ static ssize_t joydev_read(struct file *file, char __user *buf, size_t count, lo if (count < sizeof(struct js_event)) return -EINVAL; - if (count == sizeof(struct JS_DATA_TYPE)) { - - struct JS_DATA_TYPE data; - int i; + if (count == sizeof(struct JS_DATA_TYPE)) + return joydev_0x_read(client, input, buf); - for (data.buttons = i = 0; i < 32 && i < joydev->nkey; i++) - data.buttons |= test_bit(joydev->keypam[i], input->key) ? (1 << i) : 0; - data.x = (joydev->abs[0] / 256 + 128) >> joydev->glue.JS_CORR.x; - data.y = (joydev->abs[1] / 256 + 128) >> joydev->glue.JS_CORR.y; - - if (copy_to_user(buf, &data, sizeof(struct JS_DATA_TYPE))) - return -EFAULT; - - client->startup = 0; - client->tail = client->head; - - return sizeof(struct JS_DATA_TYPE); - } - - if (client->startup == joydev->nabs + joydev->nkey && - client->head == client->tail && (file->f_flags & O_NONBLOCK)) + if (!joydev_data_pending(client) && (file->f_flags & O_NONBLOCK)) return -EAGAIN; retval = wait_event_interruptible(joydev->wait, - !joydev->exist || - client->startup < joydev->nabs + joydev->nkey || - client->head != client->tail); + !joydev->exist || joydev_data_pending(client)); if (retval) return retval; if (!joydev->exist) return -ENODEV; - while (client->startup < joydev->nabs + joydev->nkey && retval + sizeof(struct js_event) <= count) { - - struct js_event event; - - event.time = jiffies_to_msecs(jiffies); - - if (client->startup < joydev->nkey) { - event.type = JS_EVENT_BUTTON | JS_EVENT_INIT; - event.number = client->startup; - event.value = !!test_bit(joydev->keypam[event.number], input->key); - } else { - event.type = JS_EVENT_AXIS | JS_EVENT_INIT; - event.number = client->startup - joydev->nkey; - event.value = joydev->abs[event.number]; - } + while (retval + sizeof(struct js_event) <= count && + joydev_generate_startup_event(client, input, &event)) { if (copy_to_user(buf + retval, &event, sizeof(struct js_event))) return -EFAULT; - client->startup++; retval += sizeof(struct js_event); } - while (client->head != client->tail && retval + sizeof(struct js_event) <= count) { + while (retval + sizeof(struct js_event) <= count && + joydev_fetch_next_event(client, &event)) { - if (copy_to_user(buf + retval, client->buffer + client->tail, sizeof(struct js_event))) + if (copy_to_user(buf + retval, &event, sizeof(struct js_event))) return -EFAULT; - client->tail = (client->tail + 1) & (JOYDEV_BUFFER_SIZE - 1); retval += sizeof(struct js_event); } @@ -301,126 +452,144 @@ static unsigned int joydev_poll(struct file *file, poll_table *wait) struct joydev *joydev = client->joydev; poll_wait(file, &joydev->wait, wait); - return ((client->head != client->tail || client->startup < joydev->nabs + joydev->nkey) ? - (POLLIN | POLLRDNORM) : 0) | (joydev->exist ? 0 : (POLLHUP | POLLERR)); + return (joydev_data_pending(client) ? (POLLIN | POLLRDNORM) : 0) | + (joydev->exist ? 0 : (POLLHUP | POLLERR)); } -static int joydev_ioctl_common(struct joydev *joydev, unsigned int cmd, void __user *argp) +static int joydev_ioctl_common(struct joydev *joydev, + unsigned int cmd, void __user *argp) { struct input_dev *dev = joydev->handle.dev; int i, j; switch (cmd) { - case JS_SET_CAL: - return copy_from_user(&joydev->glue.JS_CORR, argp, + case JS_SET_CAL: + return copy_from_user(&joydev->glue.JS_CORR, argp, sizeof(joydev->glue.JS_CORR)) ? -EFAULT : 0; - case JS_GET_CAL: - return copy_to_user(argp, &joydev->glue.JS_CORR, + case JS_GET_CAL: + return copy_to_user(argp, &joydev->glue.JS_CORR, sizeof(joydev->glue.JS_CORR)) ? -EFAULT : 0; - case JS_SET_TIMEOUT: - return get_user(joydev->glue.JS_TIMEOUT, (s32 __user *) argp); + case JS_SET_TIMEOUT: + return get_user(joydev->glue.JS_TIMEOUT, (s32 __user *) argp); - case JS_GET_TIMEOUT: - return put_user(joydev->glue.JS_TIMEOUT, (s32 __user *) argp); + case JS_GET_TIMEOUT: + return put_user(joydev->glue.JS_TIMEOUT, (s32 __user *) argp); - case JSIOCGVERSION: - return put_user(JS_VERSION, (__u32 __user *) argp); + case JSIOCGVERSION: + return put_user(JS_VERSION, (__u32 __user *) argp); - case JSIOCGAXES: - return put_user(joydev->nabs, (__u8 __user *) argp); + case JSIOCGAXES: + return put_user(joydev->nabs, (__u8 __user *) argp); - case JSIOCGBUTTONS: - return put_user(joydev->nkey, (__u8 __user *) argp); + case JSIOCGBUTTONS: + return put_user(joydev->nkey, (__u8 __user *) argp); - case JSIOCSCORR: - if (copy_from_user(joydev->corr, argp, - sizeof(joydev->corr[0]) * joydev->nabs)) - return -EFAULT; - for (i = 0; i < joydev->nabs; i++) { - j = joydev->abspam[i]; - joydev->abs[i] = joydev_correct(dev->abs[j], joydev->corr + i); - } - return 0; + case JSIOCSCORR: + if (copy_from_user(joydev->corr, argp, + sizeof(joydev->corr[0]) * joydev->nabs)) + return -EFAULT; - case JSIOCGCORR: - return copy_to_user(argp, joydev->corr, - sizeof(joydev->corr[0]) * joydev->nabs) ? -EFAULT : 0; + for (i = 0; i < joydev->nabs; i++) { + j = joydev->abspam[i]; + joydev->abs[i] = joydev_correct(dev->abs[j], + &joydev->corr[i]); + } + return 0; - case JSIOCSAXMAP: - if (copy_from_user(joydev->abspam, argp, sizeof(__u8) * (ABS_MAX + 1))) - return -EFAULT; - for (i = 0; i < joydev->nabs; i++) { - if (joydev->abspam[i] > ABS_MAX) - return -EINVAL; - joydev->absmap[joydev->abspam[i]] = i; - } - return 0; - - case JSIOCGAXMAP: - return copy_to_user(argp, joydev->abspam, - sizeof(__u8) * (ABS_MAX + 1)) ? -EFAULT : 0; - - case JSIOCSBTNMAP: - if (copy_from_user(joydev->keypam, argp, sizeof(__u16) * (KEY_MAX - BTN_MISC + 1))) + case JSIOCGCORR: + return copy_to_user(argp, joydev->corr, + sizeof(joydev->corr[0]) * joydev->nabs) ? -EFAULT : 0; + + case JSIOCSAXMAP: + if (copy_from_user(joydev->abspam, argp, + sizeof(__u8) * (ABS_MAX + 1))) + return -EFAULT; + + for (i = 0; i < joydev->nabs; i++) { + if (joydev->abspam[i] > ABS_MAX) + return -EINVAL; + joydev->absmap[joydev->abspam[i]] = i; + } + return 0; + + case JSIOCGAXMAP: + return copy_to_user(argp, joydev->abspam, + sizeof(__u8) * (ABS_MAX + 1)) ? -EFAULT : 0; + + case JSIOCSBTNMAP: + if (copy_from_user(joydev->keypam, argp, + sizeof(__u16) * (KEY_MAX - BTN_MISC + 1))) + return -EFAULT; + + for (i = 0; i < joydev->nkey; i++) { + if (joydev->keypam[i] > KEY_MAX || + joydev->keypam[i] < BTN_MISC) + return -EINVAL; + joydev->keymap[joydev->keypam[i] - BTN_MISC] = i; + } + + return 0; + + case JSIOCGBTNMAP: + return copy_to_user(argp, joydev->keypam, + sizeof(__u16) * (KEY_MAX - BTN_MISC + 1)) ? -EFAULT : 0; + + default: + if ((cmd & ~IOCSIZE_MASK) == JSIOCGNAME(0)) { + int len; + if (!dev->name) + return 0; + len = strlen(dev->name) + 1; + if (len > _IOC_SIZE(cmd)) + len = _IOC_SIZE(cmd); + if (copy_to_user(argp, dev->name, len)) return -EFAULT; - for (i = 0; i < joydev->nkey; i++) { - if (joydev->keypam[i] > KEY_MAX || joydev->keypam[i] < BTN_MISC) - return -EINVAL; - joydev->keymap[joydev->keypam[i] - BTN_MISC] = i; - } - return 0; - - case JSIOCGBTNMAP: - return copy_to_user(argp, joydev->keypam, - sizeof(__u16) * (KEY_MAX - BTN_MISC + 1)) ? -EFAULT : 0; - - default: - if ((cmd & ~(_IOC_SIZEMASK << _IOC_SIZESHIFT)) == JSIOCGNAME(0)) { - int len; - if (!dev->name) - return 0; - len = strlen(dev->name) + 1; - if (len > _IOC_SIZE(cmd)) - len = _IOC_SIZE(cmd); - if (copy_to_user(argp, dev->name, len)) - return -EFAULT; - return len; - } + return len; + } } return -EINVAL; } #ifdef CONFIG_COMPAT -static long joydev_compat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +static long joydev_compat_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { struct joydev_client *client = file->private_data; struct joydev *joydev = client->joydev; void __user *argp = (void __user *)arg; s32 tmp32; struct JS_DATA_SAVE_TYPE_32 ds32; - int err; + int retval; - if (!joydev->exist) - return -ENODEV; + retval = mutex_lock_interruptible(&joydev->mutex); + if (retval) + return retval; + + if (!joydev->exist) { + retval = -ENODEV; + goto out; + } + + switch (cmd) { - switch(cmd) { case JS_SET_TIMELIMIT: - err = get_user(tmp32, (s32 __user *) arg); - if (err == 0) + retval = get_user(tmp32, (s32 __user *) arg); + if (retval == 0) joydev->glue.JS_TIMELIMIT = tmp32; break; + case JS_GET_TIMELIMIT: tmp32 = joydev->glue.JS_TIMELIMIT; - err = put_user(tmp32, (s32 __user *) arg); + retval = put_user(tmp32, (s32 __user *) arg); break; case JS_SET_ALL: - err = copy_from_user(&ds32, argp, - sizeof(ds32)) ? -EFAULT : 0; - if (err == 0) { + retval = copy_from_user(&ds32, argp, + sizeof(ds32)) ? -EFAULT : 0; + if (retval == 0) { joydev->glue.JS_TIMEOUT = ds32.JS_TIMEOUT; joydev->glue.BUSY = ds32.BUSY; joydev->glue.JS_EXPIRETIME = ds32.JS_EXPIRETIME; @@ -438,55 +607,119 @@ static long joydev_compat_ioctl(struct file *file, unsigned int cmd, unsigned lo ds32.JS_SAVE = joydev->glue.JS_SAVE; ds32.JS_CORR = joydev->glue.JS_CORR; - err = copy_to_user(argp, &ds32, sizeof(ds32)) ? -EFAULT : 0; + retval = copy_to_user(argp, &ds32, sizeof(ds32)) ? -EFAULT : 0; break; default: - err = joydev_ioctl_common(joydev, cmd, argp); + retval = joydev_ioctl_common(joydev, cmd, argp); + break; } - return err; + + out: + mutex_unlock(&joydev->mutex); + return retval; } #endif /* CONFIG_COMPAT */ -static int joydev_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg) +static long joydev_ioctl(struct file *file, + unsigned int cmd, unsigned long arg) { struct joydev_client *client = file->private_data; struct joydev *joydev = client->joydev; void __user *argp = (void __user *)arg; + int retval; - if (!joydev->exist) - return -ENODEV; + retval = mutex_lock_interruptible(&joydev->mutex); + if (retval) + return retval; - switch(cmd) { - case JS_SET_TIMELIMIT: - return get_user(joydev->glue.JS_TIMELIMIT, (long __user *) arg); - case JS_GET_TIMELIMIT: - return put_user(joydev->glue.JS_TIMELIMIT, (long __user *) arg); - case JS_SET_ALL: - return copy_from_user(&joydev->glue, argp, - sizeof(joydev->glue)) ? -EFAULT : 0; - case JS_GET_ALL: - return copy_to_user(argp, &joydev->glue, - sizeof(joydev->glue)) ? -EFAULT : 0; - default: - return joydev_ioctl_common(joydev, cmd, argp); + if (!joydev->exist) { + retval = -ENODEV; + goto out; } + + switch (cmd) { + + case JS_SET_TIMELIMIT: + retval = get_user(joydev->glue.JS_TIMELIMIT, + (long __user *) arg); + break; + + case JS_GET_TIMELIMIT: + retval = put_user(joydev->glue.JS_TIMELIMIT, + (long __user *) arg); + break; + + case JS_SET_ALL: + retval = copy_from_user(&joydev->glue, argp, + sizeof(joydev->glue)) ? -EFAULT: 0; + break; + + case JS_GET_ALL: + retval = copy_to_user(argp, &joydev->glue, + sizeof(joydev->glue)) ? -EFAULT : 0; + break; + + default: + retval = joydev_ioctl_common(joydev, cmd, argp); + break; + } + out: + mutex_unlock(&joydev->mutex); + return retval; } static const struct file_operations joydev_fops = { - .owner = THIS_MODULE, - .read = joydev_read, - .write = joydev_write, - .poll = joydev_poll, - .open = joydev_open, - .release = joydev_release, - .ioctl = joydev_ioctl, + .owner = THIS_MODULE, + .read = joydev_read, + .poll = joydev_poll, + .open = joydev_open, + .release = joydev_release, + .unlocked_ioctl = joydev_ioctl, #ifdef CONFIG_COMPAT - .compat_ioctl = joydev_compat_ioctl, + .compat_ioctl = joydev_compat_ioctl, #endif - .fasync = joydev_fasync, + .fasync = joydev_fasync, }; +static int joydev_install_chrdev(struct joydev *joydev) +{ + joydev_table[joydev->minor] = joydev; + return 0; +} + +static void joydev_remove_chrdev(struct joydev *joydev) +{ + mutex_lock(&joydev_table_mutex); + joydev_table[joydev->minor] = NULL; + mutex_unlock(&joydev_table_mutex); +} + +/* + * Mark device non-existant. This disables writes, ioctls and + * prevents new users from opening the device. Already posted + * blocking reads will stay, however new ones will fail. + */ +static void joydev_mark_dead(struct joydev *joydev) +{ + mutex_lock(&joydev->mutex); + joydev->exist = 0; + mutex_unlock(&joydev->mutex); +} + +static void joydev_cleanup(struct joydev *joydev) +{ + struct input_handle *handle = &joydev->handle; + + joydev_mark_dead(joydev); + joydev_hangup(joydev); + joydev_remove_chrdev(joydev); + + /* joydev is marked dead so noone else accesses joydev->open */ + if (joydev->open) + input_close_device(handle); +} + static int joydev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { @@ -494,7 +727,10 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev, int i, j, t, minor; int error; - for (minor = 0; minor < JOYDEV_MINORS && joydev_table[minor]; minor++); + for (minor = 0; minor < JOYDEV_MINORS; minor++) + if (!joydev_table[minor]) + break; + if (minor == JOYDEV_MINORS) { printk(KERN_ERR "joydev: no more free joydev devices\n"); return -ENFILE; @@ -505,15 +741,19 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev, return -ENOMEM; INIT_LIST_HEAD(&joydev->client_list); + spin_lock_init(&joydev->client_lock); + mutex_init(&joydev->mutex); init_waitqueue_head(&joydev->wait); + snprintf(joydev->name, sizeof(joydev->name), "js%d", minor); + joydev->exist = 1; joydev->minor = minor; + joydev->exist = 1; joydev->handle.dev = dev; joydev->handle.name = joydev->name; joydev->handle.handler = handler; joydev->handle.private = joydev; - snprintf(joydev->name, sizeof(joydev->name), "js%d", minor); for (i = 0; i < ABS_MAX + 1; i++) if (test_bit(i, dev->absbit)) { @@ -545,67 +785,65 @@ static int joydev_connect(struct input_handler *handler, struct input_dev *dev, } joydev->corr[i].type = JS_CORR_BROKEN; joydev->corr[i].prec = dev->absfuzz[j]; - joydev->corr[i].coef[0] = (dev->absmax[j] + dev->absmin[j]) / 2 - dev->absflat[j]; - joydev->corr[i].coef[1] = (dev->absmax[j] + dev->absmin[j]) / 2 + dev->absflat[j]; - if (!(t = ((dev->absmax[j] - dev->absmin[j]) / 2 - 2 * dev->absflat[j]))) - continue; - joydev->corr[i].coef[2] = (1 << 29) / t; - joydev->corr[i].coef[3] = (1 << 29) / t; - - joydev->abs[i] = joydev_correct(dev->abs[j], joydev->corr + i); + joydev->corr[i].coef[0] = + (dev->absmax[j] + dev->absmin[j]) / 2 - dev->absflat[j]; + joydev->corr[i].coef[1] = + (dev->absmax[j] + dev->absmin[j]) / 2 + dev->absflat[j]; + + t = (dev->absmax[j] - dev->absmin[j]) / 2 - 2 * dev->absflat[j]; + if (t) { + joydev->corr[i].coef[2] = (1 << 29) / t; + joydev->corr[i].coef[3] = (1 << 29) / t; + + joydev->abs[i] = joydev_correct(dev->abs[j], + joydev->corr + i); + } } - snprintf(joydev->dev.bus_id, sizeof(joydev->dev.bus_id), - "js%d", minor); + strlcpy(joydev->dev.bus_id, joydev->name, sizeof(joydev->dev.bus_id)); + joydev->dev.devt = MKDEV(INPUT_MAJOR, JOYDEV_MINOR_BASE + minor); joydev->dev.class = &input_class; joydev->dev.parent = &dev->dev; - joydev->dev.devt = MKDEV(INPUT_MAJOR, JOYDEV_MINOR_BASE + minor); joydev->dev.release = joydev_free; device_initialize(&joydev->dev); - joydev_table[minor] = joydev; - - error = device_add(&joydev->dev); + error = input_register_handle(&joydev->handle); if (error) goto err_free_joydev; - error = input_register_handle(&joydev->handle); + error = joydev_install_chrdev(joydev); if (error) - goto err_delete_joydev; + goto err_unregister_handle; + + error = device_add(&joydev->dev); + if (error) + goto err_cleanup_joydev; return 0; - err_delete_joydev: - device_del(&joydev->dev); + err_cleanup_joydev: + joydev_cleanup(joydev); + err_unregister_handle: + input_unregister_handle(&joydev->handle); err_free_joydev: put_device(&joydev->dev); return error; } - static void joydev_disconnect(struct input_handle *handle) { struct joydev *joydev = handle->private; - struct joydev_client *client; - input_unregister_handle(handle); device_del(&joydev->dev); - - joydev->exist = 0; - - if (joydev->open) { - input_close_device(handle); - list_for_each_entry(client, &joydev->client_list, node) - kill_fasync(&client->fasync, SIGIO, POLL_HUP); - wake_up_interruptible(&joydev->wait); - } - + joydev_cleanup(joydev); + input_unregister_handle(handle); put_device(&joydev->dev); } static const struct input_device_id joydev_blacklist[] = { { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT, + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_KEYBIT, .evbit = { BIT(EV_KEY) }, .keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) }, }, /* Avoid itouchpads, touchscreens and tablets */ @@ -614,17 +852,20 @@ static const struct input_device_id joydev_blacklist[] = { static const struct input_device_id joydev_ids[] = { { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, .evbit = { BIT(EV_ABS) }, .absbit = { BIT(ABS_X) }, }, { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, .evbit = { BIT(EV_ABS) }, .absbit = { BIT(ABS_WHEEL) }, }, { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, + .flags = INPUT_DEVICE_ID_MATCH_EVBIT | + INPUT_DEVICE_ID_MATCH_ABSBIT, .evbit = { BIT(EV_ABS) }, .absbit = { BIT(ABS_THROTTLE) }, }, @@ -634,14 +875,14 @@ static const struct input_device_id joydev_ids[] = { MODULE_DEVICE_TABLE(input, joydev_ids); static struct input_handler joydev_handler = { - .event = joydev_event, - .connect = joydev_connect, - .disconnect = joydev_disconnect, - .fops = &joydev_fops, - .minor = JOYDEV_MINOR_BASE, - .name = "joydev", - .id_table = joydev_ids, - .blacklist = joydev_blacklist, + .event = joydev_event, + .connect = joydev_connect, + .disconnect = joydev_disconnect, + .fops = &joydev_fops, + .minor = JOYDEV_MINOR_BASE, + .name = "joydev", + .id_table = joydev_ids, + .blacklist = joydev_blacklist, }; static int __init joydev_init(void) -- cgit v1.2.3 From b9d2d110b10f7b4788d0fdd328cf57e34b767817 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 30 Aug 2007 00:22:39 -0400 Subject: Input: tsdev - implement proper locking Signed-off-by: Dmitry Torokhov --- drivers/input/tsdev.c | 388 +++++++++++++++++++++++++++++++++++--------------- 1 file changed, 276 insertions(+), 112 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/tsdev.c b/drivers/input/tsdev.c index d2f882e98e5..c189f1dd569 100644 --- a/drivers/input/tsdev.c +++ b/drivers/input/tsdev.c @@ -112,6 +112,8 @@ struct tsdev { struct input_handle handle; wait_queue_head_t wait; struct list_head client_list; + spinlock_t client_lock; /* protects client_list */ + struct mutex mutex; struct device dev; int x, y, pressure; @@ -122,8 +124,9 @@ struct tsdev_client { struct fasync_struct *fasync; struct list_head node; struct tsdev *tsdev; + struct ts_event buffer[TSDEV_BUFFER_SIZE]; int head, tail; - struct ts_event event[TSDEV_BUFFER_SIZE]; + spinlock_t buffer_lock; /* protects access to buffer, head and tail */ int raw; }; @@ -137,6 +140,7 @@ struct tsdev_client { #define TS_SET_CAL _IOW(IOC_H3600_TS_MAGIC, 11, struct ts_calibration) static struct tsdev *tsdev_table[TSDEV_MINORS/2]; +static DEFINE_MUTEX(tsdev_table_mutex); static int tsdev_fasync(int fd, struct file *file, int on) { @@ -144,9 +148,91 @@ static int tsdev_fasync(int fd, struct file *file, int on) int retval; retval = fasync_helper(fd, file, on, &client->fasync); + return retval < 0 ? retval : 0; } +static void tsdev_free(struct device *dev) +{ + struct tsdev *tsdev = container_of(dev, struct tsdev, dev); + + kfree(tsdev); +} + +static void tsdev_attach_client(struct tsdev *tsdev, struct tsdev_client *client) +{ + spin_lock(&tsdev->client_lock); + list_add_tail_rcu(&client->node, &tsdev->client_list); + spin_unlock(&tsdev->client_lock); + synchronize_sched(); +} + +static void tsdev_detach_client(struct tsdev *tsdev, struct tsdev_client *client) +{ + spin_lock(&tsdev->client_lock); + list_del_rcu(&client->node); + spin_unlock(&tsdev->client_lock); + synchronize_sched(); +} + +static int tsdev_open_device(struct tsdev *tsdev) +{ + int retval; + + retval = mutex_lock_interruptible(&tsdev->mutex); + if (retval) + return retval; + + if (!tsdev->exist) + retval = -ENODEV; + else if (!tsdev->open++) + retval = input_open_device(&tsdev->handle); + + mutex_unlock(&tsdev->mutex); + return retval; +} + +static void tsdev_close_device(struct tsdev *tsdev) +{ + mutex_lock(&tsdev->mutex); + + if (tsdev->exist && !--tsdev->open) + input_close_device(&tsdev->handle); + + mutex_unlock(&tsdev->mutex); +} + +/* + * Wake up users waiting for IO so they can disconnect from + * dead device. + */ +static void tsdev_hangup(struct tsdev *tsdev) +{ + struct tsdev_client *client; + + spin_lock(&tsdev->client_lock); + list_for_each_entry(client, &tsdev->client_list, node) + kill_fasync(&client->fasync, SIGIO, POLL_HUP); + spin_unlock(&tsdev->client_lock); + + wake_up_interruptible(&tsdev->wait); +} + +static int tsdev_release(struct inode *inode, struct file *file) +{ + struct tsdev_client *client = file->private_data; + struct tsdev *tsdev = client->tsdev; + + tsdev_fasync(-1, file, 0); + tsdev_detach_client(tsdev, client); + kfree(client); + + tsdev_close_device(tsdev); + put_device(&tsdev->dev); + + return 0; +} + static int tsdev_open(struct inode *inode, struct file *file) { int i = iminor(inode) - TSDEV_MINOR_BASE; @@ -161,11 +247,16 @@ static int tsdev_open(struct inode *inode, struct file *file) if (i >= TSDEV_MINORS) return -ENODEV; + error = mutex_lock_interruptible(&tsdev_table_mutex); + if (error) + return error; tsdev = tsdev_table[i & TSDEV_MINOR_MASK]; - if (!tsdev || !tsdev->exist) - return -ENODEV; + if (tsdev) + get_device(&tsdev->dev); + mutex_unlock(&tsdev_table_mutex); - get_device(&tsdev->dev); + if (!tsdev) + return -ENODEV; client = kzalloc(sizeof(struct tsdev_client), GFP_KERNEL); if (!client) { @@ -173,51 +264,42 @@ static int tsdev_open(struct inode *inode, struct file *file) goto err_put_tsdev; } + spin_lock_init(&client->buffer_lock); client->tsdev = tsdev; - client->raw = (i >= TSDEV_MINORS / 2) ? 1 : 0; - list_add_tail(&client->node, &tsdev->client_list); + client->raw = i >= TSDEV_MINORS / 2; + tsdev_attach_client(tsdev, client); - if (!tsdev->open++ && tsdev->exist) { - error = input_open_device(&tsdev->handle); - if (error) - goto err_free_client; - } + error = tsdev_open_device(tsdev); + if (error) + goto err_free_client; file->private_data = client; return 0; err_free_client: - list_del(&client->node); + tsdev_detach_client(tsdev, client); kfree(client); err_put_tsdev: put_device(&tsdev->dev); return error; } -static void tsdev_free(struct device *dev) -{ - struct tsdev *tsdev = container_of(dev, struct tsdev, dev); - - tsdev_table[tsdev->minor] = NULL; - kfree(tsdev); -} - -static int tsdev_release(struct inode *inode, struct file *file) +static int tsdev_fetch_next_event(struct tsdev_client *client, + struct ts_event *event) { - struct tsdev_client *client = file->private_data; - struct tsdev *tsdev = client->tsdev; + int have_event; - tsdev_fasync(-1, file, 0); - - list_del(&client->node); - kfree(client); + spin_lock_irq(&client->buffer_lock); - if (!--tsdev->open && tsdev->exist) - input_close_device(&tsdev->handle); + have_event = client->head != client->tail; + if (have_event) { + *event = client->buffer[client->tail++]; + client->tail &= TSDEV_BUFFER_SIZE - 1; + } - put_device(&tsdev->dev); + spin_unlock_irq(&client->buffer_lock); - return 0; + return have_event; } static ssize_t tsdev_read(struct file *file, char __user *buffer, size_t count, @@ -225,9 +307,11 @@ static ssize_t tsdev_read(struct file *file, char __user *buffer, size_t count, { struct tsdev_client *client = file->private_data; struct tsdev *tsdev = client->tsdev; - int retval = 0; + struct ts_event event; + int retval; - if (client->head == client->tail && tsdev->exist && (file->f_flags & O_NONBLOCK)) + if (client->head == client->tail && tsdev->exist && + (file->f_flags & O_NONBLOCK)) return -EAGAIN; retval = wait_event_interruptible(tsdev->wait, @@ -238,13 +322,14 @@ static ssize_t tsdev_read(struct file *file, char __user *buffer, size_t count, if (!tsdev->exist) return -ENODEV; - while (client->head != client->tail && - retval + sizeof (struct ts_event) <= count) { - if (copy_to_user (buffer + retval, client->event + client->tail, - sizeof (struct ts_event))) + while (retval + sizeof(struct ts_event) <= count && + tsdev_fetch_next_event(client, &event)) { + + if (copy_to_user(buffer + retval, &event, + sizeof(struct ts_event))) return -EFAULT; - client->tail = (client->tail + 1) & (TSDEV_BUFFER_SIZE - 1); - retval += sizeof (struct ts_event); + + retval += sizeof(struct ts_event); } return retval; @@ -261,14 +346,23 @@ static unsigned int tsdev_poll(struct file *file, poll_table *wait) (tsdev->exist ? 0 : (POLLHUP | POLLERR)); } -static int tsdev_ioctl(struct inode *inode, struct file *file, - unsigned int cmd, unsigned long arg) +static long tsdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct tsdev_client *client = file->private_data; struct tsdev *tsdev = client->tsdev; int retval = 0; + retval = mutex_lock_interruptible(&tsdev->mutex); + if (retval) + return retval; + + if (!tsdev->exist) { + retval = -ENODEV; + goto out; + } + switch (cmd) { + case TS_GET_CAL: if (copy_to_user((void __user *)arg, &tsdev->cal, sizeof (struct ts_calibration))) @@ -277,7 +371,7 @@ static int tsdev_ioctl(struct inode *inode, struct file *file, case TS_SET_CAL: if (copy_from_user(&tsdev->cal, (void __user *)arg, - sizeof (struct ts_calibration))) + sizeof(struct ts_calibration))) retval = -EFAULT; break; @@ -286,29 +380,79 @@ static int tsdev_ioctl(struct inode *inode, struct file *file, break; } + out: + mutex_unlock(&tsdev->mutex); return retval; } static const struct file_operations tsdev_fops = { - .owner = THIS_MODULE, - .open = tsdev_open, - .release = tsdev_release, - .read = tsdev_read, - .poll = tsdev_poll, - .fasync = tsdev_fasync, - .ioctl = tsdev_ioctl, + .owner = THIS_MODULE, + .open = tsdev_open, + .release = tsdev_release, + .read = tsdev_read, + .poll = tsdev_poll, + .fasync = tsdev_fasync, + .unlocked_ioctl = tsdev_ioctl, }; +static void tsdev_pass_event(struct tsdev *tsdev, struct tsdev_client *client, + int x, int y, int pressure, int millisecs) +{ + struct ts_event *event; + int tmp; + + /* Interrupts are already disabled, just acquire the lock */ + spin_lock(&client->buffer_lock); + + event = &client->buffer[client->head++]; + client->head &= TSDEV_BUFFER_SIZE - 1; + + /* Calibration */ + if (!client->raw) { + x = ((x * tsdev->cal.xscale) >> 8) + tsdev->cal.xtrans; + y = ((y * tsdev->cal.yscale) >> 8) + tsdev->cal.ytrans; + if (tsdev->cal.xyswap) { + tmp = x; x = y; y = tmp; + } + } + + event->millisecs = millisecs; + event->x = x; + event->y = y; + event->pressure = pressure; + + spin_unlock(&client->buffer_lock); + + kill_fasync(&client->fasync, SIGIO, POLL_IN); +} + +static void tsdev_distribute_event(struct tsdev *tsdev) +{ + struct tsdev_client *client; + struct timeval time; + int millisecs; + + do_gettimeofday(&time); + millisecs = time.tv_usec / 1000; + + list_for_each_entry_rcu(client, &tsdev->client_list, node) + tsdev_pass_event(tsdev, client, + tsdev->x, tsdev->y, + tsdev->pressure, millisecs); +} + static void tsdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) { struct tsdev *tsdev = handle->private; - struct tsdev_client *client; - struct timeval time; + struct input_dev *dev = handle->dev; + int wake_up_readers = 0; switch (type) { + case EV_ABS: switch (code) { + case ABS_X: tsdev->x = value; break; @@ -318,9 +462,9 @@ static void tsdev_event(struct input_handle *handle, unsigned int type, break; case ABS_PRESSURE: - if (value > handle->dev->absmax[ABS_PRESSURE]) - value = handle->dev->absmax[ABS_PRESSURE]; - value -= handle->dev->absmin[ABS_PRESSURE]; + if (value > dev->absmax[ABS_PRESSURE]) + value = dev->absmax[ABS_PRESSURE]; + value -= dev->absmin[ABS_PRESSURE]; if (value < 0) value = 0; tsdev->pressure = value; @@ -330,6 +474,7 @@ static void tsdev_event(struct input_handle *handle, unsigned int type, case EV_REL: switch (code) { + case REL_X: tsdev->x += value; if (tsdev->x < 0) @@ -351,6 +496,7 @@ static void tsdev_event(struct input_handle *handle, unsigned int type, case EV_KEY: if (code == BTN_TOUCH || code == BTN_MOUSE) { switch (value) { + case 0: tsdev->pressure = 0; break; @@ -362,49 +508,71 @@ static void tsdev_event(struct input_handle *handle, unsigned int type, } } break; + + case EV_SYN: + if (code == SYN_REPORT) { + tsdev_distribute_event(tsdev); + wake_up_readers = 1; + } + break; } - if (type != EV_SYN || code != SYN_REPORT) - return; + if (wake_up_readers) + wake_up_interruptible(&tsdev->wait); +} + +static int tsdev_install_chrdev(struct tsdev *tsdev) +{ + tsdev_table[tsdev->minor] = tsdev; + return 0; +} - list_for_each_entry(client, &tsdev->client_list, node) { - int x, y, tmp; +static void tsdev_remove_chrdev(struct tsdev *tsdev) +{ + mutex_lock(&tsdev_table_mutex); + tsdev_table[tsdev->minor] = NULL; + mutex_unlock(&tsdev_table_mutex); +} - do_gettimeofday(&time); - client->event[client->head].millisecs = time.tv_usec / 1000; - client->event[client->head].pressure = tsdev->pressure; +/* + * Mark device non-existant. This disables writes, ioctls and + * prevents new users from opening the device. Already posted + * blocking reads will stay, however new ones will fail. + */ +static void tsdev_mark_dead(struct tsdev *tsdev) +{ + mutex_lock(&tsdev->mutex); + tsdev->exist = 0; + mutex_unlock(&tsdev->mutex); +} - x = tsdev->x; - y = tsdev->y; +static void tsdev_cleanup(struct tsdev *tsdev) +{ + struct input_handle *handle = &tsdev->handle; - /* Calibration */ - if (!client->raw) { - x = ((x * tsdev->cal.xscale) >> 8) + tsdev->cal.xtrans; - y = ((y * tsdev->cal.yscale) >> 8) + tsdev->cal.ytrans; - if (tsdev->cal.xyswap) { - tmp = x; x = y; y = tmp; - } - } + tsdev_mark_dead(tsdev); + tsdev_hangup(tsdev); + tsdev_remove_chrdev(tsdev); - client->event[client->head].x = x; - client->event[client->head].y = y; - client->head = (client->head + 1) & (TSDEV_BUFFER_SIZE - 1); - kill_fasync(&client->fasync, SIGIO, POLL_IN); - } - wake_up_interruptible(&tsdev->wait); + /* tsdev is marked dead so noone else accesses tsdev->open */ + if (tsdev->open) + input_close_device(handle); } static int tsdev_connect(struct input_handler *handler, struct input_dev *dev, const struct input_device_id *id) { struct tsdev *tsdev; - int minor, delta; + int delta; + int minor; int error; - for (minor = 0; minor < TSDEV_MINORS / 2 && tsdev_table[minor]; minor++); - if (minor >= TSDEV_MINORS / 2) { - printk(KERN_ERR - "tsdev: You have way too many touchscreens\n"); + for (minor = 0; minor < TSDEV_MINORS / 2; minor++) + if (!tsdev_table[minor]) + break; + + if (minor == TSDEV_MINORS) { + printk(KERN_ERR "tsdev: no more free tsdev devices\n"); return -ENFILE; } @@ -413,15 +581,18 @@ static int tsdev_connect(struct input_handler *handler, struct input_dev *dev, return -ENOMEM; INIT_LIST_HEAD(&tsdev->client_list); + spin_lock_init(&tsdev->client_lock); + mutex_init(&tsdev->mutex); init_waitqueue_head(&tsdev->wait); + snprintf(tsdev->name, sizeof(tsdev->name), "ts%d", minor); tsdev->exist = 1; tsdev->minor = minor; + tsdev->handle.dev = dev; tsdev->handle.name = tsdev->name; tsdev->handle.handler = handler; tsdev->handle.private = tsdev; - snprintf(tsdev->name, sizeof(tsdev->name), "ts%d", minor); /* Precompute the rough calibration matrix */ delta = dev->absmax [ABS_X] - dev->absmin [ABS_X] + 1; @@ -436,28 +607,31 @@ static int tsdev_connect(struct input_handler *handler, struct input_dev *dev, tsdev->cal.yscale = (yres << 8) / delta; tsdev->cal.ytrans = - ((dev->absmin [ABS_Y] * tsdev->cal.yscale) >> 8); - snprintf(tsdev->dev.bus_id, sizeof(tsdev->dev.bus_id), - "ts%d", minor); + strlcpy(tsdev->dev.bus_id, tsdev->name, sizeof(tsdev->dev.bus_id)); + tsdev->dev.devt = MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor); tsdev->dev.class = &input_class; tsdev->dev.parent = &dev->dev; - tsdev->dev.devt = MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor); tsdev->dev.release = tsdev_free; device_initialize(&tsdev->dev); - tsdev_table[minor] = tsdev; - - error = device_add(&tsdev->dev); + error = input_register_handle(&tsdev->handle); if (error) goto err_free_tsdev; - error = input_register_handle(&tsdev->handle); + error = tsdev_install_chrdev(tsdev); if (error) - goto err_delete_tsdev; + goto err_unregister_handle; + + error = device_add(&tsdev->dev); + if (error) + goto err_cleanup_tsdev; return 0; - err_delete_tsdev: - device_del(&tsdev->dev); + err_cleanup_tsdev: + tsdev_cleanup(tsdev); + err_unregister_handle: + input_unregister_handle(&tsdev->handle); err_free_tsdev: put_device(&tsdev->dev); return error; @@ -466,20 +640,10 @@ static int tsdev_connect(struct input_handler *handler, struct input_dev *dev, static void tsdev_disconnect(struct input_handle *handle) { struct tsdev *tsdev = handle->private; - struct tsdev_client *client; - input_unregister_handle(handle); device_del(&tsdev->dev); - - tsdev->exist = 0; - - if (tsdev->open) { - input_close_device(handle); - list_for_each_entry(client, &tsdev->client_list, node) - kill_fasync(&client->fasync, SIGIO, POLL_HUP); - wake_up_interruptible(&tsdev->wait); - } - + tsdev_cleanup(tsdev); + input_unregister_handle(handle); put_device(&tsdev->dev); } @@ -510,13 +674,13 @@ static const struct input_device_id tsdev_ids[] = { MODULE_DEVICE_TABLE(input, tsdev_ids); static struct input_handler tsdev_handler = { - .event = tsdev_event, - .connect = tsdev_connect, - .disconnect = tsdev_disconnect, - .fops = &tsdev_fops, - .minor = TSDEV_MINOR_BASE, - .name = "tsdev", - .id_table = tsdev_ids, + .event = tsdev_event, + .connect = tsdev_connect, + .disconnect = tsdev_disconnect, + .fops = &tsdev_fops, + .minor = TSDEV_MINOR_BASE, + .name = "tsdev", + .id_table = tsdev_ids, }; static int __init tsdev_init(void) -- cgit v1.2.3 From 554fc1935365ddba0936dfb6dc4088ba4ef23a4f Mon Sep 17 00:00:00 2001 From: Alon Ziv Date: Thu, 30 Aug 2007 00:22:48 -0400 Subject: Input: psmouse - reset harder during probe Some rodents appear to be extra-finicky, and require both PSMOUSE_RESET_DIS and PSMOUSE_RESET_BAT before they are unconfused enough to be probed. Signed-off-by: Alon Ziv Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/psmouse-base.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/mouse/psmouse-base.c b/drivers/input/mouse/psmouse-base.c index b9f0fb2530e..07352575653 100644 --- a/drivers/input/mouse/psmouse-base.c +++ b/drivers/input/mouse/psmouse-base.c @@ -648,9 +648,10 @@ static int psmouse_extensions(struct psmouse *psmouse, /* * Reset to defaults in case the device got confused by extended - * protocol probes. Note that we do full reset becuase some mice - * put themselves to sleep when see PSMOUSE_RESET_DIS. + * protocol probes. Note that we follow up with full reset because + * some mice put themselves to sleep when they see PSMOUSE_RESET_DIS. */ + ps2_command(&psmouse->ps2dev, NULL, PSMOUSE_CMD_RESET_DIS); psmouse_reset(psmouse); if (max_proto >= PSMOUSE_IMEX && im_explorer_detect(psmouse, set_properties) == 0) -- cgit v1.2.3 From a14a84014167c970886b44503f0736b015f4375e Mon Sep 17 00:00:00 2001 From: Ondrej Zary Date: Tue, 4 Sep 2007 23:45:01 -0400 Subject: Input: usbtouchscreen - add support for IdealTEK URTC1000 This patch adds support for IdealTEK URTC1000 touchscreen controllers. Documentation can be downloaded at: http://projects.tbmn.org/cgi-bin/trac.cgi/wiki/urtc-1000 Signed-off-by: Ondrej Zary Signed-off-by: Daniel Ritz Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/Kconfig | 6 +++ drivers/input/touchscreen/usbtouchscreen.c | 59 +++++++++++++++++++++++++++++- 2 files changed, 64 insertions(+), 1 deletion(-) (limited to 'drivers/input') diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index f929fcdbae2..f787ee69dc5 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -191,6 +191,7 @@ config TOUCHSCREEN_USB_COMPOSITE - Gunze AHL61 - DMC TSC-10/25 - IRTOUCHSYSTEMS/UNITOP + - IdealTEK URTC1000 Have a look at for a usage description and the required user-space stuff. @@ -238,4 +239,9 @@ config TOUCHSCREEN_USB_IRTOUCH bool "IRTOUCHSYSTEMS/UNITOP device support" if EMBEDDED depends on TOUCHSCREEN_USB_COMPOSITE +config TOUCHSCREEN_USB_IDEALTEK + default y + bool "IdealTEK URTC1000 device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + endif diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c index b407028ffc5..796b837deee 100644 --- a/drivers/input/touchscreen/usbtouchscreen.c +++ b/drivers/input/touchscreen/usbtouchscreen.c @@ -10,6 +10,7 @@ * - Gunze AHL61 * - DMC TSC-10/25 * - IRTOUCHSYSTEMS/UNITOP + * - IdealTEK URTC1000 * * Copyright (C) 2004-2006 by Daniel Ritz * Copyright (C) by Todd E. Johnson (mtouchusb.c) @@ -92,7 +93,7 @@ struct usbtouch_usb { }; -#if defined(CONFIG_TOUCHSCREEN_USB_EGALAX) || defined(CONFIG_TOUCHSCREEN_USB_ETURBO) +#if defined(CONFIG_TOUCHSCREEN_USB_EGALAX) || defined(CONFIG_TOUCHSCREEN_USB_ETURBO) || defined(CONFIG_TOUCHSCREEN_USB_IDEALTEK) #define MULTI_PACKET #endif @@ -112,6 +113,7 @@ enum { DEVTYPE_GUNZE, DEVTYPE_DMC_TSC10, DEVTYPE_IRTOUCH, + DEVTYPE_IDEALTEK, }; static struct usb_device_id usbtouch_devices[] = { @@ -157,6 +159,10 @@ static struct usb_device_id usbtouch_devices[] = { {USB_DEVICE(0x6615, 0x0001), .driver_info = DEVTYPE_IRTOUCH}, #endif +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK + {USB_DEVICE(0x1391, 0x1000), .driver_info = DEVTYPE_IDEALTEK}, +#endif + {} }; @@ -437,6 +443,43 @@ static int irtouch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) #endif +/***************************************************************************** + * IdealTEK URTC1000 Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK +static int idealtek_get_pkt_len(unsigned char *buf, int len) +{ + if (buf[0] & 0x80) + return 5; + if (buf[0] == 0x01) + return len; + return 0; +} + +static int idealtek_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + switch (pkt[0] & 0x98) { + case 0x88: + /* touch data in IdealTEK mode */ + dev->x = (pkt[1] << 5) | (pkt[2] >> 2); + dev->y = (pkt[3] << 5) | (pkt[4] >> 2); + dev->touch = (pkt[0] & 0x40) ? 1 : 0; + return 1; + + case 0x98: + /* touch data in MT emulation mode */ + dev->x = (pkt[2] << 5) | (pkt[1] >> 2); + dev->y = (pkt[4] << 5) | (pkt[3] >> 2); + dev->touch = (pkt[0] & 0x40) ? 1 : 0; + return 1; + + default: + return 0; + } +} +#endif + + /***************************************************************************** * the different device descriptors */ @@ -537,6 +580,20 @@ static struct usbtouch_device_info usbtouch_dev_info[] = { .read_data = irtouch_read_data, }, #endif + +#ifdef CONFIG_TOUCHSCREEN_USB_IDEALTEK + [DEVTYPE_IDEALTEK] = { + .min_xc = 0x0, + .max_xc = 0x0fff, + .min_yc = 0x0, + .max_yc = 0x0fff, + .rept_size = 8, + .flags = USBTOUCH_FLG_BUFFER, + .process_pkt = usbtouch_process_multi, + .get_pkt_len = idealtek_get_pkt_len, + .read_data = idealtek_read_data, + }, +#endif }; -- cgit v1.2.3 From dac4ae0daa1be36ab015973ed9e9dc04a2684395 Mon Sep 17 00:00:00 2001 From: William Pettersson Date: Wed, 5 Sep 2007 00:18:44 -0400 Subject: Input: ALPS - add support for model found in Dell Vostro 1400 Signed-off-by: William Pettersson Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/alps.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/input') diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index 2c5f11a4f6b..a810ff8d2c0 100644 --- a/drivers/input/mouse/alps.c +++ b/drivers/input/mouse/alps.c @@ -53,6 +53,7 @@ static const struct alps_model_info alps_model_data[] = { { { 0x20, 0x02, 0x0e }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* XXX */ { { 0x22, 0x02, 0x0a }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, { { 0x22, 0x02, 0x14 }, 0xff, 0xff, ALPS_PASS | ALPS_DUALPOINT }, /* Dell Latitude D600 */ + { { 0x73, 0x02, 0x50 }, 0xcf, 0xff, ALPS_FW_BK_1 } /* Dell Vostro 1400 */ }; /* -- cgit v1.2.3 From f76f672e093b1dc3abff3a21ae6b939b08d192e7 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 5 Sep 2007 00:22:17 -0400 Subject: Input: lifebook - add signature of Panasonic CF-72 Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/lifebook.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'drivers/input') diff --git a/drivers/input/mouse/lifebook.c b/drivers/input/mouse/lifebook.c index 1740cadd959..b5ee34615e0 100644 --- a/drivers/input/mouse/lifebook.c +++ b/drivers/input/mouse/lifebook.c @@ -96,6 +96,14 @@ static struct dmi_system_id lifebook_dmi_table[] = { }, .callback = lifebook_set_6byte_proto, }, + { + .ident = "CF-72", + .matches = { + DMI_MATCH(DMI_PRODUCT_NAME, "CF-72"), + }, + .callback = lifebook_set_serio_phys, + .driver_data = "isa0060/serio3", + }, { .ident = "Lifebook B142", .matches = { -- cgit v1.2.3 From 2a8281d72da5dd8da025e6822dadd23a35383895 Mon Sep 17 00:00:00 2001 From: Daniel Ritz Date: Mon, 10 Sep 2007 01:31:40 -0400 Subject: Input: usbtouchscreen - support DMC devices with empty EEPROM A reply of 0x0600 means all OK, 0x1501 means OK, but EEPROM empty. The behavior with an empty EEPROM is the same as without one at all so do not fail loading the driver. Signed-off-by: Daniel Ritz Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/usbtouchscreen.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'drivers/input') diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c index 796b837deee..13fbda0895b 100644 --- a/drivers/input/touchscreen/usbtouchscreen.c +++ b/drivers/input/touchscreen/usbtouchscreen.c @@ -402,7 +402,8 @@ static int dmc_tsc10_init(struct usbtouch_usb *usbtouch) TSC10_RATE_150, 0, buf, 2, USB_CTRL_SET_TIMEOUT); if (ret < 0) return ret; - if (buf[0] != 0x06 || buf[1] != 0x00) + if ((buf[0] != 0x06 || buf[1] != 0x00) && + (buf[0] != 0x15 || buf[1] != 0x01)) return -ENODEV; /* start sending data */ -- cgit v1.2.3 From 006df3024431a50262d4a2898d25924f84fb697a Mon Sep 17 00:00:00 2001 From: Anti Sullin Date: Wed, 26 Sep 2007 00:01:03 -0400 Subject: Input: gpio_keys - verify that supplied GPIO numbers are valid As David Brownell pointed out, gpio_keys driver does not check return code of gpio_to_irq(). This patch adds the gpio_to_irq return code check to gpio_keys and moves the IRQ edge type setting to request_irq flags to avoid changing the irq type before we have confirmed we can use it. Signed-off-by: Anti Sullin Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/gpio_keys.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index 739212252b0..b3069bc00f0 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -78,12 +78,24 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) int irq = gpio_to_irq(button->gpio); unsigned int type = button->type ?: EV_KEY; - set_irq_type(irq, IRQ_TYPE_EDGE_BOTH); - error = request_irq(irq, gpio_keys_isr, IRQF_SAMPLE_RANDOM, - button->desc ? button->desc : "gpio_keys", - pdev); + if (irq < 0) { + error = irq; + printk(KERN_ERR + "gpio-keys: " + "Unable to get irq number for GPIO %d," + "error %d\n", + button->gpio, error); + goto fail; + } + + error = request_irq(irq, gpio_keys_isr, + IRQF_SAMPLE_RANDOM | IRQF_TRIGGER_RISING | + IRQF_TRIGGER_FALLING, + button->desc ? button->desc : "gpio_keys", + pdev); if (error) { - printk(KERN_ERR "gpio-keys: unable to claim irq %d; error %d\n", + printk(KERN_ERR + "gpio-keys: Unable to claim irq %d; error %d\n", irq, error); goto fail; } @@ -93,16 +105,19 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) error = input_register_device(input); if (error) { - printk(KERN_ERR "Unable to register gpio-keys input device\n"); + printk(KERN_ERR + "gpio-keys: Unable to register input device, " + "error: %d\n", error); goto fail; } return 0; fail: - for (i = i - 1; i >= 0; i--) + while (--i >= 0) free_irq(gpio_to_irq(pdata->buttons[i].gpio), pdev); + platform_set_drvdata(pdev, NULL); input_free_device(input); return error; -- cgit v1.2.3 From e15b02138b89d7bc053817e6f7601e92e29d371c Mon Sep 17 00:00:00 2001 From: Anti Sullin Date: Wed, 26 Sep 2007 00:01:17 -0400 Subject: Input: gpio-keys - add suspend/resume support This patch adds suspend/resume support and enables wakeup from gpio_keys buttons. Signed-off-by: Anti Sullin Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/gpio_keys.c | 52 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) (limited to 'drivers/input') diff --git a/drivers/input/keyboard/gpio_keys.c b/drivers/input/keyboard/gpio_keys.c index b3069bc00f0..3d6820b4465 100644 --- a/drivers/input/keyboard/gpio_keys.c +++ b/drivers/input/keyboard/gpio_keys.c @@ -55,6 +55,7 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; struct input_dev *input; int i, error; + int wakeup = 0; input = input_allocate_device(); if (!input) @@ -100,6 +101,9 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) goto fail; } + if (button->wakeup) + wakeup = 1; + input_set_capability(input, type, button->code); } @@ -111,6 +115,8 @@ static int __devinit gpio_keys_probe(struct platform_device *pdev) goto fail; } + device_init_wakeup(&pdev->dev, wakeup); + return 0; fail: @@ -129,6 +135,8 @@ static int __devexit gpio_keys_remove(struct platform_device *pdev) struct input_dev *input = platform_get_drvdata(pdev); int i; + device_init_wakeup(&pdev->dev, 0); + for (i = 0; i < pdata->nbuttons; i++) { int irq = gpio_to_irq(pdata->buttons[i].gpio); free_irq(irq, pdev); @@ -139,9 +147,53 @@ static int __devexit gpio_keys_remove(struct platform_device *pdev) return 0; } + +#ifdef CONFIG_PM +static int gpio_keys_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + int i; + + if (device_may_wakeup(&pdev->dev)) { + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_keys_button *button = &pdata->buttons[i]; + if (button->wakeup) { + int irq = gpio_to_irq(button->gpio); + enable_irq_wake(irq); + } + } + } + + return 0; +} + +static int gpio_keys_resume(struct platform_device *pdev) +{ + struct gpio_keys_platform_data *pdata = pdev->dev.platform_data; + int i; + + if (device_may_wakeup(&pdev->dev)) { + for (i = 0; i < pdata->nbuttons; i++) { + struct gpio_keys_button *button = &pdata->buttons[i]; + if (button->wakeup) { + int irq = gpio_to_irq(button->gpio); + disable_irq_wake(irq); + } + } + } + + return 0; +} +#else +#define gpio_keys_suspend NULL +#define gpio_keys_resume NULL +#endif + struct platform_driver gpio_keys_device_driver = { .probe = gpio_keys_probe, .remove = __devexit_p(gpio_keys_remove), + .suspend = gpio_keys_suspend, + .resume = gpio_keys_resume, .driver = { .name = "gpio-keys", } -- cgit v1.2.3 From 14134b31ae646220a5a451e38aae1dd023d457cb Mon Sep 17 00:00:00 2001 From: Stephen Hemminger Date: Wed, 26 Sep 2007 00:01:25 -0400 Subject: Input: polled device power saving For slow running polling, it saves power to align wakeups on tick boundary. Signed-off-by: Stephen Hemminger Signed-off-by: Dmitry Torokhov --- drivers/input/input-polldev.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/input-polldev.c b/drivers/input/input-polldev.c index b773d4c756a..92b359894e8 100644 --- a/drivers/input/input-polldev.c +++ b/drivers/input/input-polldev.c @@ -70,6 +70,7 @@ static int input_open_polled_device(struct input_dev *input) { struct input_polled_dev *dev = input->private; int error; + unsigned long ticks; error = input_polldev_start_workqueue(); if (error) @@ -78,8 +79,10 @@ static int input_open_polled_device(struct input_dev *input) if (dev->flush) dev->flush(dev); - queue_delayed_work(polldev_wq, &dev->work, - msecs_to_jiffies(dev->poll_interval)); + ticks = msecs_to_jiffies(dev->poll_interval); + if (ticks >= HZ) + ticks = round_jiffies(ticks); + queue_delayed_work(polldev_wq, &dev->work, ticks); return 0; } -- cgit v1.2.3 From 2f9d2827afab6a6ad82b657b1d7fe00cbffa65f8 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Wed, 26 Sep 2007 00:02:06 -0400 Subject: Input: xpad - fix dependancy on LEDS class The driver can not be built-in when LEDS class is a module. Signed-off-by: Dmitry Torokhov --- drivers/input/joystick/Kconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/input') diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig index e2abe18e575..7c662ee594a 100644 --- a/drivers/input/joystick/Kconfig +++ b/drivers/input/joystick/Kconfig @@ -277,7 +277,7 @@ config JOYSTICK_XPAD_FF config JOYSTICK_XPAD_LEDS bool "LED Support for Xbox360 controller 'BigX' LED" - depends on LEDS_CLASS && JOYSTICK_XPAD + depends on JOYSTICK_XPAD && (LEDS_CLASS=y || LEDS_CLASS=JOYSTICK_XPAD) ---help--- This option enables support for the LED which surrounds the Big X on XBox 360 controller. -- cgit v1.2.3 From 1788180eec4a9444249b0cbe788b1fac6592976c Mon Sep 17 00:00:00 2001 From: Rene Herman Date: Wed, 26 Sep 2007 00:02:19 -0400 Subject: Input: ucb1400_ts - use schedule_timeout_uninterruptible Given that the code is not checking for signals it should use uninterruptible sleep. Signed-off-by: Rene Herman Acked-by: Nicolas Pitre Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/ucb1400_ts.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/touchscreen/ucb1400_ts.c b/drivers/input/touchscreen/ucb1400_ts.c index 36f94401915..86aed64ec0f 100644 --- a/drivers/input/touchscreen/ucb1400_ts.c +++ b/drivers/input/touchscreen/ucb1400_ts.c @@ -130,8 +130,7 @@ static unsigned int ucb1400_adc_read(struct ucb1400 *ucb, u16 adc_channel) if (val & UCB_ADC_DAT_VALID) break; /* yield to other processes */ - set_current_state(TASK_INTERRUPTIBLE); - schedule_timeout(1); + schedule_timeout_uninterruptible(1); } return UCB_ADC_DAT_VALUE(val); -- cgit v1.2.3 From 2aa2cb9e7a20910d890f874e16a43a81b725ec54 Mon Sep 17 00:00:00 2001 From: Kristoffer Ericson Date: Wed, 26 Sep 2007 00:02:35 -0400 Subject: Input: add support for HP Jornada onboard keyboard (HP6XX) Signed-off-by: Kristoffer Ericson Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/Kconfig | 11 ++ drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/jornada680_kbd.c | 277 ++++++++++++++++++++++++++++++++ 3 files changed, 289 insertions(+) create mode 100644 drivers/input/keyboard/jornada680_kbd.c (limited to 'drivers/input') diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index c97d5eb0075..1881900f24c 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -208,6 +208,17 @@ config KEYBOARD_HIL This driver implements support for HIL-keyboards attached to your machine, so normally you should say Y here. +config KEYBOARD_HP6XX + tristate "HP Jornada 6XX Keyboard support" + depends on SH_HP6XX + select INPUT_POLLDEV + help + This adds support for the onboard keyboard found on + HP Jornada 620/660/680/690. + + To compile this driver as a module, choose M here: the + module will be called jornada680_kbd. + config KEYBOARD_OMAP tristate "TI OMAP keypad support" depends on (ARCH_OMAP1 || ARCH_OMAP2) diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 28d211b87b1..7ecd2b05a28 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -21,4 +21,5 @@ obj-$(CONFIG_KEYBOARD_OMAP) += omap-keypad.o obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keyboard.o obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o +obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o diff --git a/drivers/input/keyboard/jornada680_kbd.c b/drivers/input/keyboard/jornada680_kbd.c new file mode 100644 index 00000000000..bec1cf48372 --- /dev/null +++ b/drivers/input/keyboard/jornada680_kbd.c @@ -0,0 +1,277 @@ +/* + * drivers/input/keyboard/jornada680_kbd.c + * + * HP Jornada 620/660/680/690 scan keyboard platform driver + * Copyright (C) 2007 Kristoffer Ericson + * + * Based on hp680_keyb.c + * Copyright (C) 2006 Paul Mundt + * Copyright (C) 2005 Andriy Skulysh + * Split from drivers/input/keyboard/hp600_keyb.c + * Copyright (C) 2000 Yaegashi Takeshi (hp6xx kbd scan routine and translation table) + * Copyright (C) 2000 Niibe Yutaka (HP620 Keyb translation table) + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define PCCR 0xa4000104 +#define PDCR 0xa4000106 +#define PECR 0xa4000108 +#define PFCR 0xa400010a +#define PCDR 0xa4000124 +#define PDDR 0xa4000126 +#define PEDR 0xa4000128 +#define PFDR 0xa400012a +#define PGDR 0xa400012c +#define PHDR 0xa400012e +#define PJDR 0xa4000130 +#define PKDR 0xa4000132 +#define PLDR 0xa4000134 + +static const unsigned short jornada_scancodes[] = { +/* PTD1 */ KEY_CAPSLOCK, KEY_MACRO, KEY_LEFTCTRL, 0, KEY_ESC, 0, 0, 0, /* 1 -> 8 */ + KEY_F1, KEY_F2, KEY_F3, KEY_F8, KEY_F7, KEY_F2, KEY_F4, KEY_F5, /* 9 -> 16 */ +/* PTD5 */ KEY_SLASH, KEY_APOSTROPHE, KEY_ENTER, 0, KEY_Z, 0, 0, 0, /* 17 -> 24 */ + KEY_X, KEY_C, KEY_V, KEY_DOT, KEY_COMMA, KEY_M, KEY_B, KEY_N, /* 25 -> 32 */ +/* PTD7 */ KEY_KP2, KEY_KP6, 0, 0, 0, 0, 0, 0, /* 33 -> 40 */ + 0, 0, 0, KEY_KP4, 0, 0, KEY_LEFTALT, KEY_HANJA, /* 41 -> 48 */ +/* PTE0 */ 0, 0, 0, 0, KEY_FINANCE, 0, 0, 0, /* 49 -> 56 */ + KEY_LEFTCTRL, 0, KEY_SPACE, KEY_KPDOT, KEY_VOLUMEUP, 249, 0, 0, /* 57 -> 64 */ +/* PTE1 */ KEY_SEMICOLON, KEY_RIGHTBRACE, KEY_BACKSLASH, 0, KEY_A, 0, 0, 0,/* 65 -> 72 */ + KEY_S, KEY_D, KEY_F, KEY_L, KEY_K, KEY_J, KEY_G, KEY_H, /* 73 -> 80 */ +/* PTE3 */ KEY_KP8, KEY_LEFTMETA, KEY_RIGHTSHIFT, 0, KEY_TAB, 0, 0,0, /* 81 -> 88 */ + 0, KEY_LEFTSHIFT, 0, 0, 0, 0, 0, 0, /* 89 -> 96 */ +/* PTE6 */ KEY_P, KEY_LEFTBRACE, KEY_BACKSPACE, 0, KEY_Q, 0, 0, 0, /* 97 -> 104 */ + KEY_W, KEY_E, KEY_R, KEY_O, KEY_I, KEY_U, KEY_T, KEY_R, /* 105 -> 112 */ +/* PTE7 */ KEY_0, KEY_MINUS, KEY_EQUAL, 0, KEY_1, 0, 0, 0, /* 113 -> 120 */ + KEY_2, KEY_3, KEY_4, KEY_9, KEY_8, KEY_7, KEY_5, KEY_6, /* 121 -> 128 */ +/* **** */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0 +}; + +#define JORNADA_SCAN_SIZE 18 + +struct jornadakbd { + struct input_polled_dev *poll_dev; + unsigned short keymap[ARRAY_SIZE(jornada_scancodes)]; + unsigned char length; + unsigned char old_scan[JORNADA_SCAN_SIZE]; + unsigned char new_scan[JORNADA_SCAN_SIZE]; +}; + +static void jornada_parse_kbd(struct jornadakbd *jornadakbd) +{ + struct input_dev *input_dev = jornadakbd->poll_dev->input; + unsigned short *keymap = jornadakbd->keymap; + unsigned int sync_me = 0; + unsigned int i, j; + + for (i = 0; i < JORNADA_SCAN_SIZE; i++) { + unsigned char new = jornadakbd->new_scan[i]; + unsigned char old = jornadakbd->old_scan[i]; + unsigned int xor = new ^ old; + + if (xor == 0) + continue; + + for (j = 0; j < 8; j++) { + unsigned int bit = 1 << j; + if (xor & bit) { + unsigned int scancode = (i << 3) + j; + input_event(input_dev, + EV_MSC, MSC_SCAN, scancode); + input_report_key(input_dev, + keymap[scancode], + !(new & bit)); + sync_me = 1; + } + } + } + + if (sync_me) + input_sync(input_dev); +} + +static void jornada_scan_keyb(unsigned char *s) +{ + int i; + unsigned short ec_static, dc_static; /* = UINT16_t */ + unsigned char matrix_switch[] = { + 0xfd, 0xff, /* PTD1 PD(1) */ + 0xdf, 0xff, /* PTD5 PD(5) */ + 0x7f, 0xff, /* PTD7 PD(7) */ + 0xff, 0xfe, /* PTE0 PE(0) */ + 0xff, 0xfd, /* PTE1 PE(1) */ + 0xff, 0xf7, /* PTE3 PE(3) */ + 0xff, 0xbf, /* PTE6 PE(6) */ + 0xff, 0x7f, /* PTE7 PE(7) */ + }, *t = matrix_switch; + /* PD(x) : + 1. 0xcc0c & (1~(1 << (2*(x)+1))))) + 2. (0xf0cf & 0xfffff) */ + /* PE(x) : + 1. 0xcc0c & 0xffff + 2. 0xf0cf & (1~(1 << (2*(x)+1))))) */ + unsigned short matrix_PDE[] = { + 0xcc04, 0xf0cf, /* PD(1) */ + 0xc40c, 0xf0cf, /* PD(5) */ + 0x4c0c, 0xf0cf, /* PD(7) */ + 0xcc0c, 0xf0cd, /* PE(0) */ + 0xcc0c, 0xf0c7, /* PE(1) */ + 0xcc0c, 0xf04f, /* PE(3) */ + 0xcc0c, 0xd0cf, /* PE(6) */ + 0xcc0c, 0x70cf, /* PE(7) */ + }, *y = matrix_PDE; + + /* Save these control reg bits */ + dc_static = (ctrl_inw(PDCR) & (~0xcc0c)); + ec_static = (ctrl_inw(PECR) & (~0xf0cf)); + + for (i = 0; i < 8; i++) { + /* disable output for all but the one we want to scan */ + ctrl_outw((dc_static | *y++), PDCR); + ctrl_outw((ec_static | *y++), PECR); + udelay(5); + + /* Get scanline row */ + ctrl_outb(*t++, PDDR); + ctrl_outb(*t++, PEDR); + udelay(50); + + /* Read data */ + *s++ = ctrl_inb(PCDR); + *s++ = ctrl_inb(PFDR); + } + /* Scan no lines */ + ctrl_outb(0xff, PDDR); + ctrl_outb(0xff, PEDR); + + /* Enable all scanlines */ + ctrl_outw((dc_static | (0x5555 & 0xcc0c)),PDCR); + ctrl_outw((ec_static | (0x5555 & 0xf0cf)),PECR); + + /* Ignore extra keys and events */ + *s++ = ctrl_inb(PGDR); + *s++ = ctrl_inb(PHDR); +} + +static void jornadakbd680_poll(struct input_polled_dev *dev) +{ + struct jornadakbd *jornadakbd = dev->private; + + jornada_scan_keyb(jornadakbd->new_scan); + jornada_parse_kbd(jornadakbd); + memcpy(jornadakbd->old_scan, jornadakbd->new_scan, JORNADA_SCAN_SIZE); +} + +static int __devinit jornada680kbd_probe(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd; + struct input_polled_dev *poll_dev; + struct input_dev *input_dev; + int i, error; + + jornadakbd = kzalloc(sizeof(struct jornadakbd), GFP_KERNEL); + if (!jornadakbd) + return -ENOMEM; + + poll_dev = input_allocate_polled_device(); + if (!poll_dev) { + error = -ENOMEM; + goto failed; + } + + platform_set_drvdata(pdev, jornadakbd); + + jornadakbd->poll_dev = poll_dev; + + memcpy(jornadakbd->keymap, jornada_scancodes, + sizeof(jornadakbd->keymap)); + + poll_dev->private = jornadakbd; + poll_dev->poll = jornadakbd680_poll; + poll_dev->poll_interval = 50; /* msec */ + + input_dev = poll_dev->input; + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->name = "HP Jornada 680 keyboard"; + input_dev->phys = "jornadakbd/input0"; + input_dev->keycode = jornadakbd->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(jornada_scancodes); + input_dev->dev.parent = &pdev->dev; + input_dev->id.bustype = BUS_HOST; + + for (i = 0; i < 128; i++) + if (jornadakbd->keymap[i]) + __set_bit(jornadakbd->keymap[i], input_dev->keybit); + __clear_bit(KEY_RESERVED, input_dev->keybit); + + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + + error = input_register_polled_device(jornadakbd->poll_dev); + if (error) + goto failed; + + return 0; + + failed: + printk(KERN_ERR "Jornadakbd: failed to register driver, error: %d\n", + error); + platform_set_drvdata(pdev, NULL); + input_free_polled_device(poll_dev); + kfree(jornadakbd); + return error; + +} + +static int __devexit jornada680kbd_remove(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + + platform_set_drvdata(pdev, NULL); + input_unregister_polled_device(jornadakbd->poll_dev); + input_free_polled_device(jornadakbd->poll_dev); + kfree(jornadakbd); + + return 0; +} + +static struct platform_driver jornada680kbd_driver = { + .driver = { + .name = "jornada680_kbd", + }, + .probe = jornada680kbd_probe, + .remove = __devexit_p(jornada680kbd_remove), +}; + +static int __init jornada680kbd_init(void) +{ + return platform_driver_register(&jornada680kbd_driver); +} + +static void __exit jornada680kbd_exit(void) +{ + platform_driver_unregister(&jornada680kbd_driver); +} + +module_init(jornada680kbd_init); +module_exit(jornada680kbd_exit); + +MODULE_AUTHOR("Kristoffer Ericson "); +MODULE_DESCRIPTION("HP Jornada 620/660/680/690 Keyboard Driver"); +MODULE_LICENSE("GPLv2"); -- cgit v1.2.3 From b0a4e1aa4063550faf7bc1c33b37c587796e931f Mon Sep 17 00:00:00 2001 From: Kristoffer Ericson Date: Wed, 26 Sep 2007 00:02:49 -0400 Subject: Input: add support for HP Jornada 7xx onboard keyboard The driver supports onboard keyboards of HP Jornada 710/720/728 Signed-off-by: Kristoffer Ericson Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/Kconfig | 10 ++ drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/jornada720_kbd.c | 181 ++++++++++++++++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 drivers/input/keyboard/jornada720_kbd.c (limited to 'drivers/input') diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 1881900f24c..18303f2bbaf 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -219,6 +219,16 @@ config KEYBOARD_HP6XX To compile this driver as a module, choose M here: the module will be called jornada680_kbd. +config KEYBOARD_HP7XX + tristate "HP Jornada 7XX Keyboard Driver" + depends on SA1100_JORNADA720_SSP && SA1100_SSP + help + Say Y here to add support for the HP Jornada 7xx (710/720/728) + onboard keyboard. + + To compile this driver as a module, choose M here: the + module will be called jornada720_kbd. + config KEYBOARD_OMAP tristate "TI OMAP keypad support" depends on (ARCH_OMAP1 || ARCH_OMAP2) diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 7ecd2b05a28..4cc7dc947db 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -22,4 +22,5 @@ obj-$(CONFIG_KEYBOARD_PXA27x) += pxa27x_keyboard.o obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o +obj-$(CONFIG_KEYBOARD_HP7XX) += jornada720_kbd.o diff --git a/drivers/input/keyboard/jornada720_kbd.c b/drivers/input/keyboard/jornada720_kbd.c new file mode 100644 index 00000000000..dd8a037f728 --- /dev/null +++ b/drivers/input/keyboard/jornada720_kbd.c @@ -0,0 +1,181 @@ +/* + * drivers/input/keyboard/jornada720_kbd.c + * + * HP Jornada 720 keyboard platform driver + * + * Copyright (C) 2006/2007 Kristoffer Ericson + * + * Copyright (C) 2006 jornada 720 kbd driver by + Filip Zyzniewsk + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +MODULE_AUTHOR("Kristoffer Ericson "); +MODULE_DESCRIPTION("HP Jornada 710/720/728 keyboard driver"); +MODULE_LICENSE("GPLv2"); + +static unsigned short jornada_std_keymap[128] = { /* ROW */ + 0, KEY_ESC, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, KEY_F7, /* #1 */ + KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_MUTE, /* -> */ + 0, KEY_1, KEY_2, KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, /* #2 */ + KEY_0, KEY_MINUS, KEY_EQUAL,0, 0, 0, /* -> */ + 0, KEY_Q, KEY_W, KEY_E, KEY_R, KEY_T, KEY_Y, KEY_U, KEY_I, KEY_O, /* #3 */ + KEY_P, KEY_BACKSLASH, KEY_BACKSPACE, 0, 0, 0, /* -> */ + 0, KEY_A, KEY_S, KEY_D, KEY_F, KEY_G, KEY_H, KEY_J, KEY_K, KEY_L, /* #4 */ + KEY_SEMICOLON, KEY_LEFTBRACE, KEY_RIGHTBRACE, 0, 0, 0, /* -> */ + 0, KEY_Z, KEY_X, KEY_C, KEY_V, KEY_B, KEY_N, KEY_M, KEY_COMMA, /* #5 */ + KEY_DOT, KEY_KPMINUS, KEY_APOSTROPHE, KEY_ENTER, 0, 0,0, /* -> */ + 0, KEY_TAB, 0, KEY_LEFTSHIFT, 0, KEY_APOSTROPHE, 0, 0, 0, 0, /* #6 */ + KEY_UP, 0, KEY_RIGHTSHIFT, 0, 0, 0,0, 0, 0, 0, 0, KEY_LEFTALT, KEY_GRAVE, /* -> */ + 0, 0, KEY_LEFT, KEY_DOWN, KEY_RIGHT, 0, 0, 0, 0,0, KEY_KPASTERISK, /* -> */ + KEY_LEFTCTRL, 0, KEY_SPACE, 0, 0, 0, KEY_SLASH, KEY_DELETE, 0, 0, /* -> */ + 0, 0, 0, KEY_POWER, /* -> */ +}; + +struct jornadakbd { + unsigned short keymap[ARRAY_SIZE(jornada_std_keymap)]; + struct input_dev *input; +}; + +static irqreturn_t jornada720_kbd_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + u8 count, kbd_data; + + /* startup ssp with spinlock */ + jornada_ssp_start(); + + if (jornada_ssp_inout(GETSCANKEYCODE) != TXDUMMY) { + printk(KERN_DEBUG + "jornada720_kbd: " + "GetKeycode command failed with ETIMEDOUT, " + "flushed bus\n"); + } else { + /* How many keycodes are waiting for us? */ + count = jornada_ssp_byte(TXDUMMY); + + /* Lets drag them out one at a time */ + while (count--) { + /* Exchange TxDummy for location (keymap[kbddata]) */ + kbd_data = jornada_ssp_byte(TXDUMMY); + + input_report_key(jornadakbd->input, + jornadakbd->keymap[kbd_data & 0x7f], + !(kbd_data & 0x80)); + input_sync(jornadakbd->input); + } + } + + /* release spinlock and turn off ssp */ + jornada_ssp_end(); + + return IRQ_HANDLED; +}; + +static int __devinit jornada720_kbd_probe(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd; + struct input_dev *input_dev; + int i, err; + + jornadakbd = kzalloc(sizeof(struct jornadakbd), GFP_KERNEL); + input_dev = input_allocate_device(); + if (!jornadakbd || !input_dev) { + err = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(pdev, jornadakbd); + + memcpy(jornadakbd->keymap, jornada_std_keymap, + sizeof(jornada_std_keymap)); + jornadakbd->input = input_dev; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + input_dev->name = "HP Jornada 720 keyboard"; + input_dev->phys = "jornadakbd/input0"; + input_dev->keycode = jornadakbd->keymap; + input_dev->keycodesize = sizeof(unsigned short); + input_dev->keycodemax = ARRAY_SIZE(jornada_std_keymap); + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + for (i = 0; i < ARRAY_SIZE(jornadakbd->keymap); i++) + __set_bit(jornadakbd->keymap[i], input_dev->keybit); + __clear_bit(KEY_RESERVED, input_dev->keybit); + + err = request_irq(IRQ_GPIO0, + jornada720_kbd_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_FALLING, + "jornadakbd", pdev); + if (err) { + printk(KERN_INFO "jornadakbd720_kbd: Unable to grab IRQ\n"); + goto fail1; + } + + err = input_register_device(jornadakbd->input); + if (err) + goto fail2; + + return 0; + + fail2: /* IRQ, DEVICE, MEMORY */ + free_irq(IRQ_GPIO0, pdev); + fail1: /* DEVICE, MEMORY */ + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); + kfree(jornadakbd); + return err; +}; + +static int __devexit jornada720_kbd_remove(struct platform_device *pdev) +{ + struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); + + free_irq(IRQ_GPIO0, pdev); + platform_set_drvdata(pdev, NULL); + input_unregister_device(jornadakbd->input); + kfree(jornadakbd); + + return 0; +} + +static struct platform_driver jornada720_kbd_driver = { + .driver = { + .name = "jornada720_kbd", + }, + .probe = jornada720_kbd_probe, + .remove = __devexit_p(jornada720_kbd_remove), +}; + +static int __init jornada720_kbd_init(void) +{ + return platform_driver_register(&jornada720_kbd_driver); +} + +static void __exit jornada720_kbd_exit(void) +{ + platform_driver_unregister(&jornada720_kbd_driver); +} + +module_init(jornada720_kbd_init); +module_exit(jornada720_kbd_exit); -- cgit v1.2.3 From 5637f02a9cf84f2c00fd8a0f6561c375bb19103b Mon Sep 17 00:00:00 2001 From: Kristoffer Ericson Date: Wed, 26 Sep 2007 00:02:56 -0400 Subject: Input: add support for the HP Jornada 7xx (710/720/728) touchscreen Signed-off-by: Kristoffer Ericson Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/Kconfig | 10 ++ drivers/input/touchscreen/Makefile | 1 + drivers/input/touchscreen/jornada720_ts.c | 182 ++++++++++++++++++++++++++++++ 3 files changed, 193 insertions(+) create mode 100644 drivers/input/touchscreen/jornada720_ts.c (limited to 'drivers/input') diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index f787ee69dc5..e73ce6ba3dc 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -126,6 +126,16 @@ config TOUCHSCREEN_HP600 To compile this driver as a module, choose M here: the module will be called hp680_ts_input. +config TOUCHSCREEN_HP7XX + tristate "HP Jornada 710/720/728 touchscreen" + depends on SA1100_JORNADA720_SSP + help + Say Y here if you have a HP Jornada 710/720/728 and want + to support the built-in touchscreen. + + To compile this driver as a module, choose M here: the + module will be called jornada720_ts. + config TOUCHSCREEN_PENMOUNT tristate "Penmount serial touchscreen" select SERIO diff --git a/drivers/input/touchscreen/Makefile b/drivers/input/touchscreen/Makefile index 5de8933c499..35d4097df35 100644 --- a/drivers/input/touchscreen/Makefile +++ b/drivers/input/touchscreen/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_TOUCHSCREEN_FUJITSU) += fujitsu_ts.o obj-$(CONFIG_TOUCHSCREEN_MTOUCH) += mtouch.o obj-$(CONFIG_TOUCHSCREEN_MK712) += mk712.o obj-$(CONFIG_TOUCHSCREEN_HP600) += hp680_ts_input.o +obj-$(CONFIG_TOUCHSCREEN_HP7XX) += jornada720_ts.o obj-$(CONFIG_TOUCHSCREEN_USB_COMPOSITE) += usbtouchscreen.o obj-$(CONFIG_TOUCHSCREEN_PENMOUNT) += penmount.o obj-$(CONFIG_TOUCHSCREEN_TOUCHRIGHT) += touchright.o diff --git a/drivers/input/touchscreen/jornada720_ts.c b/drivers/input/touchscreen/jornada720_ts.c new file mode 100644 index 00000000000..42a1c9a1940 --- /dev/null +++ b/drivers/input/touchscreen/jornada720_ts.c @@ -0,0 +1,182 @@ +/* + * drivers/input/touchscreen/jornada720_ts.c + * + * Copyright (C) 2007 Kristoffer Ericson + * + * Copyright (C) 2006 Filip Zyzniewski + * based on HP Jornada 56x touchscreen driver by Alex Lange + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * HP Jornada 710/720/729 Touchscreen Driver + */ + +#include +#include +#include +#include +#include + +#include +#include + +MODULE_AUTHOR("Kristoffer Ericson "); +MODULE_DESCRIPTION("HP Jornada 710/720/728 touchscreen driver"); +MODULE_LICENSE("GPLv2"); + +struct jornada_ts { + struct input_dev *dev; + int x_data[4]; /* X sample values */ + int y_data[4]; /* Y sample values */ +}; + +static void jornada720_ts_collect_data(struct jornada_ts *jornada_ts) +{ + + /* 3 low word X samples */ + jornada_ts->x_data[0] = jornada_ssp_byte(TXDUMMY); + jornada_ts->x_data[1] = jornada_ssp_byte(TXDUMMY); + jornada_ts->x_data[2] = jornada_ssp_byte(TXDUMMY); + + /* 3 low word Y samples */ + jornada_ts->y_data[0] = jornada_ssp_byte(TXDUMMY); + jornada_ts->y_data[1] = jornada_ssp_byte(TXDUMMY); + jornada_ts->y_data[2] = jornada_ssp_byte(TXDUMMY); + + /* combined x samples bits */ + jornada_ts->x_data[3] = jornada_ssp_byte(TXDUMMY); + + /* combined y samples bits */ + jornada_ts->y_data[3] = jornada_ssp_byte(TXDUMMY); +} + +static int jornada720_ts_average(int coords[4]) +{ + int coord, high_bits = coords[3]; + + coord = coords[0] | ((high_bits & 0x03) << 8); + coord += coords[1] | ((high_bits & 0x0c) << 6); + coord += coords[2] | ((high_bits & 0x30) << 4); + + return coord / 3; +} + +static irqreturn_t jornada720_ts_interrupt(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct jornada_ts *jornada_ts = platform_get_drvdata(pdev); + struct input_dev *input = jornada_ts->dev; + int x, y; + + /* If GPIO_GPIO9 is set to high then report pen up */ + if (GPLR & GPIO_GPIO(9)) { + input_report_key(input, BTN_TOUCH, 0); + input_sync(input); + } else { + jornada_ssp_start(); + + /* proper reply to request is always TXDUMMY */ + if (jornada_ssp_inout(GETTOUCHSAMPLES) == TXDUMMY) { + jornada720_ts_collect_data(jornada_ts); + + x = jornada720_ts_average(jornada_ts->x_data); + y = jornada720_ts_average(jornada_ts->y_data); + + input_report_key(input, BTN_TOUCH, 1); + input_report_abs(input, ABS_X, x); + input_report_abs(input, ABS_Y, y); + input_sync(input); + } + + jornada_ssp_end(); + } + + return IRQ_HANDLED; +} + +static int __devinit jornada720_ts_probe(struct platform_device *pdev) +{ + struct jornada_ts *jornada_ts; + struct input_dev *input_dev; + int error; + + jornada_ts = kzalloc(sizeof(struct jornada_ts), GFP_KERNEL); + input_dev = input_allocate_device(); + + if (!jornada_ts || !input_dev) { + error = -ENOMEM; + goto fail1; + } + + platform_set_drvdata(pdev, jornada_ts); + + jornada_ts->dev = input_dev; + + input_dev->name = "HP Jornada 7xx Touchscreen"; + input_dev->phys = "jornadats/input0"; + input_dev->id.bustype = BUS_HOST; + input_dev->dev.parent = &pdev->dev; + + input_dev->evbit[0] = BIT(EV_KEY) | BIT(EV_ABS); + input_dev->keybit[LONG(BTN_TOUCH)] = BIT(BTN_TOUCH); + input_set_abs_params(input_dev, ABS_X, 270, 3900, 0, 0); + input_set_abs_params(input_dev, ABS_Y, 180, 3700, 0, 0); + + error = request_irq(IRQ_GPIO9, + jornada720_ts_interrupt, + IRQF_DISABLED | IRQF_TRIGGER_RISING, + "HP7XX Touchscreen driver", pdev); + if (error) { + printk(KERN_INFO "HP7XX TS : Unable to acquire irq!\n"); + goto fail1; + } + + error = input_register_device(jornada_ts->dev); + if (error) + goto fail2; + + return 0; + + fail2: + free_irq(IRQ_GPIO9, pdev); + fail1: + platform_set_drvdata(pdev, NULL); + input_free_device(input_dev); + kfree(jornada_ts); + return error; +} + +static int __devexit jornada720_ts_remove(struct platform_device *pdev) +{ + struct jornada_ts *jornada_ts = platform_get_drvdata(pdev); + + free_irq(IRQ_GPIO9, pdev); + platform_set_drvdata(pdev, NULL); + input_unregister_device(jornada_ts->dev); + kfree(jornada_ts); + + return 0; +} + +static struct platform_driver jornada720_ts_driver = { + .probe = jornada720_ts_probe, + .remove = __devexit_p(jornada720_ts_remove), + .driver = { + .name = "jornada_ts", + }, +}; + +static int __init jornada720_ts_init(void) +{ + return platform_driver_register(&jornada720_ts_driver); +} + +static void __exit jornada720_ts_exit(void) +{ + platform_driver_unregister(&jornada720_ts_driver); +} + +module_init(jornada720_ts_init); +module_exit(jornada720_ts_exit); -- cgit v1.2.3 From 72f9df4023f9e44b29a48a5ea4954d89b18363fe Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 11 Oct 2007 00:48:48 -0400 Subject: Input: jornada720_kbd - send MSC_SCAN events Send MSC_SCAN events to userspace to ease task of adjusting keymap. Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/jornada720_kbd.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/keyboard/jornada720_kbd.c b/drivers/input/keyboard/jornada720_kbd.c index dd8a037f728..e6696b3c941 100644 --- a/drivers/input/keyboard/jornada720_kbd.c +++ b/drivers/input/keyboard/jornada720_kbd.c @@ -59,7 +59,8 @@ static irqreturn_t jornada720_kbd_interrupt(int irq, void *dev_id) { struct platform_device *pdev = dev_id; struct jornadakbd *jornadakbd = platform_get_drvdata(pdev); - u8 count, kbd_data; + struct input_dev *input = jornadakbd->input; + u8 count, kbd_data, scan_code; /* startup ssp with spinlock */ jornada_ssp_start(); @@ -77,11 +78,12 @@ static irqreturn_t jornada720_kbd_interrupt(int irq, void *dev_id) while (count--) { /* Exchange TxDummy for location (keymap[kbddata]) */ kbd_data = jornada_ssp_byte(TXDUMMY); + scan_code = kbd_data & 0x7f; - input_report_key(jornadakbd->input, - jornadakbd->keymap[kbd_data & 0x7f], + input_event(input, EV_MSC, MSC_SCAN, scan_code); + input_report_key(input, jornadakbd->keymap[scan_code], !(kbd_data & 0x80)); - input_sync(jornadakbd->input); + input_sync(input); } } @@ -123,6 +125,8 @@ static int __devinit jornada720_kbd_probe(struct platform_device *pdev) __set_bit(jornadakbd->keymap[i], input_dev->keybit); __clear_bit(KEY_RESERVED, input_dev->keybit); + input_set_capability(input_dev, EV_MSC, MSC_SCAN); + err = request_irq(IRQ_GPIO0, jornada720_kbd_interrupt, IRQF_DISABLED | IRQF_TRIGGER_FALLING, -- cgit v1.2.3 From f493018ebc3f94d64e12bc848db0906700bf73a2 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Thu, 11 Oct 2007 00:49:19 -0400 Subject: Input: ALPS - add signature for ThinkPad R61 Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/alps.c | 1 + 1 file changed, 1 insertion(+) (limited to 'drivers/input') diff --git a/drivers/input/mouse/alps.c b/drivers/input/mouse/alps.c index a810ff8d2c0..64d70a9b714 100644 --- a/drivers/input/mouse/alps.c +++ b/drivers/input/mouse/alps.c @@ -48,6 +48,7 @@ static const struct alps_model_info alps_model_data[] = { { { 0x63, 0x02, 0x50 }, 0xef, 0xef, ALPS_FW_BK_1 }, /* NEC Versa L320 */ { { 0x63, 0x02, 0x64 }, 0xf8, 0xf8, 0 }, { { 0x63, 0x03, 0xc8 }, 0xf8, 0xf8, ALPS_PASS }, /* Dell Latitude D800 */ + { { 0x73, 0x00, 0x0a }, 0xf8, 0xf8, ALPS_DUALPOINT }, /* ThinkPad R61 8918-5QG */ { { 0x73, 0x02, 0x0a }, 0xf8, 0xf8, 0 }, { { 0x73, 0x02, 0x14 }, 0xf8, 0xf8, ALPS_FW_BK_2 }, /* Ahtec Laptop */ { { 0x20, 0x02, 0x0e }, 0xf8, 0xf8, ALPS_PASS | ALPS_DUALPOINT }, /* XXX */ -- cgit v1.2.3 From 064450140f1eab959bd0eca0245f449993216074 Mon Sep 17 00:00:00 2001 From: Oliver Neukum Date: Fri, 12 Oct 2007 14:18:40 -0400 Subject: Input: fix open count handling in input interfaces If input_open_device() fails we should not leave interfaces marked as opened. Signed-off-by: Oliver Neukum Signed-off-by: Dmitry Torokhov --- drivers/input/evdev.c | 5 ++++- drivers/input/joydev.c | 5 ++++- drivers/input/mousedev.c | 5 ++++- drivers/input/tsdev.c | 5 ++++- 4 files changed, 16 insertions(+), 4 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index bfee4f9a771..d404aa8680e 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -192,8 +192,11 @@ static int evdev_open_device(struct evdev *evdev) if (!evdev->exist) retval = -ENODEV; - else if (!evdev->open++) + else if (!evdev->open++) { retval = input_open_device(&evdev->handle); + if (retval) + evdev->open--; + } mutex_unlock(&evdev->mutex); return retval; diff --git a/drivers/input/joydev.c b/drivers/input/joydev.c index a4272d63c4d..f306c97f556 100644 --- a/drivers/input/joydev.c +++ b/drivers/input/joydev.c @@ -205,8 +205,11 @@ static int joydev_open_device(struct joydev *joydev) if (!joydev->exist) retval = -ENODEV; - else if (!joydev->open++) + else if (!joydev->open++) { retval = input_open_device(&joydev->handle); + if (retval) + joydev->open--; + } mutex_unlock(&joydev->mutex); return retval; diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c index 715def79390..cc36edbb912 100644 --- a/drivers/input/mousedev.c +++ b/drivers/input/mousedev.c @@ -428,8 +428,11 @@ static int mousedev_open_device(struct mousedev *mousedev) mixdev_open_devices(); else if (!mousedev->exist) retval = -ENODEV; - else if (!mousedev->open++) + else if (!mousedev->open++) { retval = input_open_device(&mousedev->handle); + if (retval) + mousedev->open--; + } mutex_unlock(&mousedev->mutex); return retval; diff --git a/drivers/input/tsdev.c b/drivers/input/tsdev.c index c189f1dd569..12023349375 100644 --- a/drivers/input/tsdev.c +++ b/drivers/input/tsdev.c @@ -185,8 +185,11 @@ static int tsdev_open_device(struct tsdev *tsdev) if (!tsdev->exist) retval = -ENODEV; - else if (!tsdev->open++) + else if (!tsdev->open++) { retval = input_open_device(&tsdev->handle); + if (retval) + tsdev->open--; + } mutex_unlock(&tsdev->mutex); return retval; -- cgit v1.2.3 From 9d5657db8c4a485b56b5c9174b52bab39b2fd16e Mon Sep 17 00:00:00 2001 From: Ilya Frolov Date: Fri, 12 Oct 2007 14:19:40 -0400 Subject: Input: usbtouchscreen - add support for GeneralTouch devices Signed-off-by: Ilya Frolov Signed-off-by: Dmitry Torokhov --- drivers/input/touchscreen/Kconfig | 5 +++++ drivers/input/touchscreen/usbtouchscreen.c | 31 ++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) (limited to 'drivers/input') diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index e73ce6ba3dc..e3e0baa1a15 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -254,4 +254,9 @@ config TOUCHSCREEN_USB_IDEALTEK bool "IdealTEK URTC1000 device support" if EMBEDDED depends on TOUCHSCREEN_USB_COMPOSITE +config TOUCHSCREEN_USB_GENERAL_TOUCH + default y + bool "GeneralTouch Touchscreen device support" if EMBEDDED + depends on TOUCHSCREEN_USB_COMPOSITE + endif diff --git a/drivers/input/touchscreen/usbtouchscreen.c b/drivers/input/touchscreen/usbtouchscreen.c index 13fbda0895b..9b3a26cd1a8 100644 --- a/drivers/input/touchscreen/usbtouchscreen.c +++ b/drivers/input/touchscreen/usbtouchscreen.c @@ -114,6 +114,7 @@ enum { DEVTYPE_DMC_TSC10, DEVTYPE_IRTOUCH, DEVTYPE_IDEALTEK, + DEVTYPE_GENERAL_TOUCH, }; static struct usb_device_id usbtouch_devices[] = { @@ -163,6 +164,10 @@ static struct usb_device_id usbtouch_devices[] = { {USB_DEVICE(0x1391, 0x1000), .driver_info = DEVTYPE_IDEALTEK}, #endif +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH + {USB_DEVICE(0x0dfc, 0x0001), .driver_info = DEVTYPE_GENERAL_TOUCH}, +#endif + {} }; @@ -480,6 +485,20 @@ static int idealtek_read_data(struct usbtouch_usb *dev, unsigned char *pkt) } #endif +/***************************************************************************** + * General Touch Part + */ +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH +static int general_touch_read_data(struct usbtouch_usb *dev, unsigned char *pkt) +{ + dev->x = ((pkt[2] & 0x0F) << 8) | pkt[1] ; + dev->y = ((pkt[4] & 0x0F) << 8) | pkt[3] ; + dev->press = pkt[5] & 0xff; + dev->touch = pkt[0] & 0x01; + + return 1; +} +#endif /***************************************************************************** * the different device descriptors @@ -595,6 +614,18 @@ static struct usbtouch_device_info usbtouch_dev_info[] = { .read_data = idealtek_read_data, }, #endif + +#ifdef CONFIG_TOUCHSCREEN_USB_GENERAL_TOUCH + [DEVTYPE_GENERAL_TOUCH] = { + .min_xc = 0x0, + .max_xc = 0x0500, + .min_yc = 0x0, + .max_yc = 0x0500, + .rept_size = 7, + .read_data = general_touch_read_data, + } +#endif + }; -- cgit v1.2.3 From 62e729b648c980dfdbfdf8d677cde0d78cd4a51d Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 12 Oct 2007 14:19:50 -0400 Subject: Input: lifebook - fix X and Y axis range Possible range when using 6-byte protocol is 4096 and 1024 for 3-byte protocol. We had it reversed. Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/lifebook.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'drivers/input') diff --git a/drivers/input/mouse/lifebook.c b/drivers/input/mouse/lifebook.c index b5ee34615e0..9561dee120c 100644 --- a/drivers/input/mouse/lifebook.c +++ b/drivers/input/mouse/lifebook.c @@ -290,7 +290,7 @@ static int lifebook_create_relative_device(struct psmouse *psmouse) int lifebook_init(struct psmouse *psmouse) { struct input_dev *dev1 = psmouse->dev; - int max_coord = lifebook_use_6byte_proto ? 1024 : 4096; + int max_coord = lifebook_use_6byte_proto ? 4096 : 1024; if (lifebook_absolute_mode(psmouse)) return -1; -- cgit v1.2.3 From 9360353f4aa40688eef4c71e4688411490727e40 Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Fri, 12 Oct 2007 14:20:00 -0400 Subject: Input: omap-keyboard - don't pretend we support changing keymap The driver's keymap is a mix of hardware codes and keycodes and so may not be used with default implementations of getkeycode() and setkeycode(). Also some whitespace cleanup. Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/omap-keypad.c | 22 +++++++++------------- 1 file changed, 9 insertions(+), 13 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/keyboard/omap-keypad.c b/drivers/input/keyboard/omap-keypad.c index 3a228634f10..76f1969552c 100644 --- a/drivers/input/keyboard/omap-keypad.c +++ b/drivers/input/keyboard/omap-keypad.c @@ -233,7 +233,7 @@ static void omap_kp_tasklet(unsigned long data) omap_writew(0, OMAP_MPUIO_BASE + OMAP_MPUIO_KBD_MASKIT); kp_cur_group = -1; } - } + } } static ssize_t omap_kp_enable_show(struct device *dev, @@ -318,7 +318,7 @@ static int __init omap_kp_probe(struct platform_device *pdev) keymap = pdata->keymap; if (pdata->rep) - set_bit(EV_REP, input_dev->evbit); + __set_bit(EV_REP, input_dev->evbit); if (pdata->delay) omap_kp->delay = pdata->delay; @@ -365,9 +365,9 @@ static int __init omap_kp_probe(struct platform_device *pdev) goto err2; /* setup input device */ - set_bit(EV_KEY, input_dev->evbit); + __set_bit(EV_KEY, input_dev->evbit); for (i = 0; keymap[i] != 0; i++) - set_bit(keymap[i] & KEY_MAX, input_dev->keybit); + __set_bit(keymap[i] & KEY_MAX, input_dev->keybit); input_dev->name = "omap-keypad"; input_dev->phys = "omap-keypad/input0"; input_dev->dev.parent = &pdev->dev; @@ -377,10 +377,6 @@ static int __init omap_kp_probe(struct platform_device *pdev) input_dev->id.product = 0x0001; input_dev->id.version = 0x0100; - input_dev->keycode = keymap; - input_dev->keycodesize = sizeof(unsigned int); - input_dev->keycodemax = pdata->keymapsize; - ret = input_register_device(omap_kp->input); if (ret < 0) { printk(KERN_ERR "Unable to register omap-keypad input device\n"); @@ -403,15 +399,15 @@ static int __init omap_kp_probe(struct platform_device *pdev) } else { for (irq_idx = 0; irq_idx < omap_kp->rows; irq_idx++) { if (request_irq(OMAP_GPIO_IRQ(row_gpios[irq_idx]), - omap_kp_interrupt, + omap_kp_interrupt, IRQF_TRIGGER_FALLING, - "omap-keypad", omap_kp) < 0) + "omap-keypad", omap_kp) < 0) goto err5; } } return 0; err5: - for (i = irq_idx-1; i >=0; i--) + for (i = irq_idx - 1; i >=0; i--) free_irq(row_gpios[i], 0); err4: input_unregister_device(omap_kp->input); @@ -440,9 +436,9 @@ static int omap_kp_remove(struct platform_device *pdev) if (cpu_is_omap24xx()) { int i; for (i = 0; i < omap_kp->cols; i++) - omap_free_gpio(col_gpios[i]); + omap_free_gpio(col_gpios[i]); for (i = 0; i < omap_kp->rows; i++) { - omap_free_gpio(row_gpios[i]); + omap_free_gpio(row_gpios[i]); free_irq(OMAP_GPIO_IRQ(row_gpios[i]), 0); } } else { -- cgit v1.2.3 From b11d2127c4893a7315d1e16273bc8560049fa3ca Mon Sep 17 00:00:00 2001 From: Adrian McMenamin Date: Fri, 12 Oct 2007 20:36:13 -0400 Subject: Input: add support for SEGA Dreamcast keyboard Signed-off by: Adrian McMenamin Acked-by: Arjan van de Ven Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/Kconfig | 10 ++ drivers/input/keyboard/Makefile | 1 + drivers/input/keyboard/maple_keyb.c | 252 ++++++++++++++++++++++++++++++++++++ 3 files changed, 263 insertions(+) create mode 100644 drivers/input/keyboard/maple_keyb.c (limited to 'drivers/input') diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 18303f2bbaf..f6f79e880b2 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -274,4 +274,14 @@ config KEYBOARD_GPIO To compile this driver as a module, choose M here: the module will be called gpio-keys. +config KEYBOARD_MAPLE + tristate "Maple bus keyboard" + depends on SH_DREAMCAST && MAPLE + help + Say Y here if you have a Dreamcast console running Linux and have + a keyboard attached to its Maple bus. + + To compile this driver as a module, choose M here: the + module will be called maple_keyb. + endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 4cc7dc947db..2776aa4a3a9 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -23,4 +23,5 @@ obj-$(CONFIG_KEYBOARD_AAED2000) += aaed2000_kbd.o obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o obj-$(CONFIG_KEYBOARD_HP7XX) += jornada720_kbd.o +obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o diff --git a/drivers/input/keyboard/maple_keyb.c b/drivers/input/keyboard/maple_keyb.c new file mode 100644 index 00000000000..2b404284c28 --- /dev/null +++ b/drivers/input/keyboard/maple_keyb.c @@ -0,0 +1,252 @@ +/* + * SEGA Dreamcast keyboard driver + * Based on drivers/usb/usbkbd.c + * Copyright YAEGASHI Takeshi, 2001 + * Porting to 2.6 Copyright Adrian McMenamin, 2007 + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +/* Very simple mutex to ensure proper cleanup */ +static DEFINE_MUTEX(maple_keyb_mutex); + +#define NR_SCANCODES 256 + +MODULE_AUTHOR("YAEGASHI Takeshi, Adrian McMenamin"); +MODULE_DESCRIPTION("SEGA Dreamcast keyboard driver"); +MODULE_LICENSE("GPL"); + +struct dc_kbd { + struct input_dev *dev; + unsigned short keycode[NR_SCANCODES]; + unsigned char new[8]; + unsigned char old[8]; +}; + +static const unsigned short dc_kbd_keycode[NR_SCANCODES] = { + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_A, KEY_B, KEY_C, KEY_D, + KEY_E, KEY_F, KEY_G, KEY_H, KEY_I, KEY_J, KEY_K, KEY_L, + KEY_M, KEY_N, KEY_O, KEY_P, KEY_Q, KEY_R, KEY_S, KEY_T, + KEY_U, KEY_V, KEY_W, KEY_X, KEY_Y, KEY_Z, KEY_1, KEY_2, + KEY_3, KEY_4, KEY_5, KEY_6, KEY_7, KEY_8, KEY_9, KEY_0, + KEY_ENTER, KEY_ESC, KEY_BACKSPACE, KEY_TAB, KEY_SPACE, KEY_MINUS, KEY_EQUAL, KEY_LEFTBRACE, + KEY_RIGHTBRACE, KEY_BACKSLASH, KEY_BACKSLASH, KEY_SEMICOLON, KEY_APOSTROPHE, KEY_GRAVE, KEY_COMMA, + KEY_DOT, KEY_SLASH, KEY_CAPSLOCK, KEY_F1, KEY_F2, KEY_F3, KEY_F4, KEY_F5, KEY_F6, + KEY_F7, KEY_F8, KEY_F9, KEY_F10, KEY_F11, KEY_F12, KEY_SYSRQ, + KEY_SCROLLLOCK, KEY_PAUSE, KEY_INSERT, KEY_HOME, KEY_PAGEUP, KEY_DELETE, + KEY_END, KEY_PAGEDOWN, KEY_RIGHT, KEY_LEFT, KEY_DOWN, KEY_UP, + KEY_NUMLOCK, KEY_KPSLASH, KEY_KPASTERISK, KEY_KPMINUS, KEY_KPPLUS, KEY_KPENTER, KEY_KP1, KEY_KP2, + KEY_KP3, KEY_KP4, KEY_KP5, KEY_KP6, KEY_KP7, KEY_KP8, KEY_KP9, KEY_KP0, KEY_KPDOT, + KEY_102ND, KEY_COMPOSE, KEY_POWER, KEY_KPEQUAL, KEY_F13, KEY_F14, KEY_F15, + KEY_F16, KEY_F17, KEY_F18, KEY_F19, KEY_F20, + KEY_F21, KEY_F22, KEY_F23, KEY_F24, KEY_OPEN, KEY_HELP, KEY_PROPS, KEY_FRONT, + KEY_STOP, KEY_AGAIN, KEY_UNDO, KEY_CUT, KEY_COPY, KEY_PASTE, KEY_FIND, KEY_MUTE, + KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_KPCOMMA, KEY_RESERVED, KEY_RO, KEY_KATAKANAHIRAGANA , KEY_YEN, + KEY_HENKAN, KEY_MUHENKAN, KEY_KPJPCOMMA, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_HANGEUL, KEY_HANJA, KEY_KATAKANA, KEY_HIRAGANA, KEY_ZENKAKUHANKAKU, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, + KEY_LEFTCTRL, KEY_LEFTSHIFT, KEY_LEFTALT, KEY_LEFTMETA, KEY_RIGHTCTRL, KEY_RIGHTSHIFT, KEY_RIGHTALT, KEY_RIGHTMETA, + KEY_PLAYPAUSE, KEY_STOPCD, KEY_PREVIOUSSONG, KEY_NEXTSONG, KEY_EJECTCD, KEY_VOLUMEUP, KEY_VOLUMEDOWN, KEY_MUTE, + KEY_WWW, KEY_BACK, KEY_FORWARD, KEY_STOP, KEY_FIND, KEY_SCROLLUP, KEY_SCROLLDOWN, KEY_EDIT, KEY_SLEEP, + KEY_SCREENLOCK, KEY_REFRESH, KEY_CALC, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED, KEY_RESERVED +}; + +static void dc_scan_kbd(struct dc_kbd *kbd) +{ + struct input_dev *dev = kbd->dev; + void *ptr; + int code, keycode; + int i; + + for (i = 0; i < 8; i++) { + code = i + 224; + keycode = kbd->keycode[code]; + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, (kbd->new[0] >> i) & 1); + } + + for (i = 2; i < 8; i++) { + ptr = memchr(kbd->new + 2, kbd->old[i], 6); + code = kbd->old[i]; + if (code > 3 && ptr == NULL) { + keycode = kbd->keycode[code]; + if (keycode) { + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, 0); + } else + printk(KERN_DEBUG "maple_keyb: " + "Unknown key (scancode %#x) released.", + code); + } + ptr = memchr(kbd->old + 2, kbd->new[i], 6); + code = kbd->new[i]; + if (code > 3 && ptr) { + keycode = kbd->keycode[code]; + if (keycode) { + input_event(dev, EV_MSC, MSC_SCAN, code); + input_report_key(dev, keycode, 1); + } else + printk(KERN_DEBUG "maple_keyb: " + "Unknown key (scancode %#x) pressed.", + code); + } + } + input_sync(dev); + memcpy(kbd->old, kbd->new, 8); +} + +static void dc_kbd_callback(struct mapleq *mq) +{ + struct maple_device *mapledev = mq->dev; + struct dc_kbd *kbd = mapledev->private_data; + unsigned long *buf = mq->recvbuf; + + /* + * We should always be getting the lock because the only + * time it may be locked if driver is in cleanup phase. + */ + if (likely(mutex_trylock(&maple_keyb_mutex))) { + + if (buf[1] == mapledev->function) { + memcpy(kbd->new, buf + 2, 8); + dc_scan_kbd(kbd); + } + + mutex_unlock(&maple_keyb_mutex); + } +} + +static int dc_kbd_connect(struct maple_device *mdev) +{ + int i, error; + struct dc_kbd *kbd; + struct input_dev *dev; + + if (!(mdev->function & MAPLE_FUNC_KEYBOARD)) + return -EINVAL; + + kbd = kzalloc(sizeof(struct dc_kbd), GFP_KERNEL); + dev = input_allocate_device(); + if (!kbd || !dev) { + error = -ENOMEM; + goto fail; + } + + mdev->private_data = kbd; + + kbd->dev = dev; + memcpy(kbd->keycode, dc_kbd_keycode, sizeof(kbd->keycode)); + + dev->name = mdev->product_name; + dev->evbit[0] = BIT(EV_KEY) | BIT(EV_REP); + dev->keycode = kbd->keycode; + dev->keycodesize = sizeof (unsigned short); + dev->keycodemax = ARRAY_SIZE(kbd->keycode); + dev->id.bustype = BUS_HOST; + dev->dev.parent = &mdev->dev; + + for (i = 0; i < NR_SCANCODES; i++) + __set_bit(dc_kbd_keycode[i], dev->keybit); + __clear_bit(KEY_RESERVED, dev->keybit); + + input_set_capability(dev, EV_MSC, MSC_SCAN); + input_set_drvdata(dev, kbd); + + error = input_register_device(dev); + if (error) + goto fail; + + /* Maple polling is locked to VBLANK - which may be just 50/s */ + maple_getcond_callback(mdev, dc_kbd_callback, HZ/50, MAPLE_FUNC_KEYBOARD); + return 0; + + fail: + input_free_device(dev); + kfree(kbd); + mdev->private_data = NULL; + return error; +} + +static void dc_kbd_disconnect(struct maple_device *mdev) +{ + struct dc_kbd *kbd; + + mutex_lock(&maple_keyb_mutex); + + kbd = mdev->private_data; + mdev->private_data = NULL; + input_unregister_device(kbd->dev); + kfree(kbd); + + mutex_unlock(&maple_keyb_mutex); +} + +/* allow the keyboard to be used */ +static int probe_maple_kbd(struct device *dev) +{ + struct maple_device *mdev = to_maple_dev(dev); + struct maple_driver *mdrv = to_maple_driver(dev->driver); + int error; + + error = dc_kbd_connect(mdev); + if (error) + return error; + + mdev->driver = mdrv; + mdev->registered = 1; + + return 0; +} + +static struct maple_driver dc_kbd_driver = { + .function = MAPLE_FUNC_KEYBOARD, + .connect = dc_kbd_connect, + .disconnect = dc_kbd_disconnect, + .drv = { + .name = "Dreamcast_keyboard", + .probe = probe_maple_kbd, + }, +}; + +static int __init dc_kbd_init(void) +{ + return maple_driver_register(&dc_kbd_driver.drv); +} + +static void __exit dc_kbd_exit(void) +{ + driver_unregister(&dc_kbd_driver.drv); +} + +module_init(dc_kbd_init); +module_exit(dc_kbd_exit); -- cgit v1.2.3 From 937ad5c1e35191d29d305280525394fe87f4ac4f Mon Sep 17 00:00:00 2001 From: Soeren Sonnenburg Date: Sat, 13 Oct 2007 00:31:15 -0400 Subject: Input: appletouch - another fix for idle reset logic Make sure we reset idlecount when we get a good (non-empty) packet. Signed-off-by: Soeren Sonnenburg Signed-off-by: Dmitry Torokhov --- drivers/input/mouse/appletouch.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/mouse/appletouch.c b/drivers/input/mouse/appletouch.c index a1804bfdbb8..0117817bf53 100644 --- a/drivers/input/mouse/appletouch.c +++ b/drivers/input/mouse/appletouch.c @@ -502,18 +502,23 @@ static void atp_complete(struct urb* urb) /* reset the accumulator on release */ memset(dev->xy_acc, 0, sizeof(dev->xy_acc)); + } + + /* Geyser 3 will continue to send packets continually after + the first touch unless reinitialised. Do so if it's been + idle for a while in order to avoid waking the kernel up + several hundred times a second */ - /* Geyser 3 will continue to send packets continually after - the first touch unless reinitialised. Do so if it's been - idle for a while in order to avoid waking the kernel up - several hundred times a second */ - if (!key && atp_is_geyser_3(dev)) { + if (atp_is_geyser_3(dev)) { + if (!x && !y && !key) { dev->idlecount++; if (dev->idlecount == 10) { dev->valid = 0; schedule_work(&dev->work); } } + else + dev->idlecount = 0; } input_report_key(dev->input, BTN_LEFT, key); -- cgit v1.2.3 From 8f740ef391fc81cb887fa08d213cf67b843cb3b7 Mon Sep 17 00:00:00 2001 From: Michael Hennerich Date: Sat, 13 Oct 2007 00:36:46 -0400 Subject: Input: add support for Blackfin BF54x Keypad controller Signed-off-by: Michael Hennerich Signed-off-by: Bryan Wu Signed-off-by: Dmitry Torokhov --- drivers/input/keyboard/Kconfig | 9 + drivers/input/keyboard/Makefile | 2 +- drivers/input/keyboard/bf54x-keys.c | 382 ++++++++++++++++++++++++++++++++++++ 3 files changed, 392 insertions(+), 1 deletion(-) create mode 100644 drivers/input/keyboard/bf54x-keys.c (limited to 'drivers/input') diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index f6f79e880b2..2316a018fae 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -284,4 +284,13 @@ config KEYBOARD_MAPLE To compile this driver as a module, choose M here: the module will be called maple_keyb. +config KEYBOARD_BFIN + tristate "Blackfin BF54x keypad support" + depends on BF54x + help + Say Y here if you want to use the BF54x keypad. + + To compile this driver as a module, choose M here: the + module will be called bf54x-keys. + endif diff --git a/drivers/input/keyboard/Makefile b/drivers/input/keyboard/Makefile index 2776aa4a3a9..e97455fdcc8 100644 --- a/drivers/input/keyboard/Makefile +++ b/drivers/input/keyboard/Makefile @@ -24,4 +24,4 @@ obj-$(CONFIG_KEYBOARD_GPIO) += gpio_keys.o obj-$(CONFIG_KEYBOARD_HP6XX) += jornada680_kbd.o obj-$(CONFIG_KEYBOARD_HP7XX) += jornada720_kbd.o obj-$(CONFIG_KEYBOARD_MAPLE) += maple_keyb.o - +obj-$(CONFIG_KEYBOARD_BFIN) += bf54x-keys.o diff --git a/drivers/input/keyboard/bf54x-keys.c b/drivers/input/keyboard/bf54x-keys.c new file mode 100644 index 00000000000..a67b29b089e --- /dev/null +++ b/drivers/input/keyboard/bf54x-keys.c @@ -0,0 +1,382 @@ +/* + * File: drivers/input/keyboard/bf54x-keys.c + * Based on: + * Author: Michael Hennerich + * + * Created: + * Description: keypad driver for Analog Devices Blackfin BF54x Processors + * + * + * Modified: + * Copyright 2007 Analog Devices Inc. + * + * Bugs: Enter bugs at http://blackfin.uclinux.org/ + * + * 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, see the file COPYING, or write + * to the Free Software Foundation, Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#define DRV_NAME "bf54x-keys" +#define TIME_SCALE 100 /* 100 ns */ +#define MAX_MULT (0xFF * TIME_SCALE) +#define MAX_RC 8 /* Max Row/Col */ + +static const u16 per_rows[] = { + P_KEY_ROW7, + P_KEY_ROW6, + P_KEY_ROW5, + P_KEY_ROW4, + P_KEY_ROW3, + P_KEY_ROW2, + P_KEY_ROW1, + P_KEY_ROW0, + 0 +}; + +static const u16 per_cols[] = { + P_KEY_COL7, + P_KEY_COL6, + P_KEY_COL5, + P_KEY_COL4, + P_KEY_COL3, + P_KEY_COL2, + P_KEY_COL1, + P_KEY_COL0, + 0 +}; + +struct bf54x_kpad { + struct input_dev *input; + int irq; + unsigned short lastkey; + unsigned short *keycode; + struct timer_list timer; + unsigned int keyup_test_jiffies; +}; + +static inline int bfin_kpad_find_key(struct bf54x_kpad *bf54x_kpad, + struct input_dev *input, u16 keyident) +{ + u16 i; + + for (i = 0; i < input->keycodemax; i++) + if (bf54x_kpad->keycode[i + input->keycodemax] == keyident) + return bf54x_kpad->keycode[i]; + return -1; +} + +static inline void bfin_keycodecpy(unsigned short *keycode, + const unsigned int *pdata_kc, + unsigned short keymapsize) +{ + unsigned int i; + + for (i = 0; i < keymapsize; i++) { + keycode[i] = pdata_kc[i] & 0xffff; + keycode[i + keymapsize] = pdata_kc[i] >> 16; + } +} + +static inline u16 bfin_kpad_get_prescale(u32 timescale) +{ + u32 sclk = get_sclk(); + + return ((((sclk / 1000) * timescale) / 1024) - 1); +} + +static inline u16 bfin_kpad_get_keypressed(struct bf54x_kpad *bf54x_kpad) +{ + return (bfin_read_KPAD_STAT() & KPAD_PRESSED); +} + +static inline void bfin_kpad_clear_irq(void) +{ + bfin_write_KPAD_STAT(0xFFFF); + bfin_write_KPAD_ROWCOL(0xFFFF); +} + +static void bfin_kpad_timer(unsigned long data) +{ + struct platform_device *pdev = (struct platform_device *) data; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + if (bfin_kpad_get_keypressed(bf54x_kpad)) { + /* Try again later */ + mod_timer(&bf54x_kpad->timer, + jiffies + bf54x_kpad->keyup_test_jiffies); + return; + } + + input_report_key(bf54x_kpad->input, bf54x_kpad->lastkey, 0); + input_sync(bf54x_kpad->input); + + /* Clear IRQ Status */ + + bfin_kpad_clear_irq(); + enable_irq(bf54x_kpad->irq); +} + +static irqreturn_t bfin_kpad_isr(int irq, void *dev_id) +{ + struct platform_device *pdev = dev_id; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + struct input_dev *input = bf54x_kpad->input; + int key; + u16 rowcol = bfin_read_KPAD_ROWCOL(); + + key = bfin_kpad_find_key(bf54x_kpad, input, rowcol); + + input_report_key(input, key, 1); + input_sync(input); + + if (bfin_kpad_get_keypressed(bf54x_kpad)) { + disable_irq(bf54x_kpad->irq); + bf54x_kpad->lastkey = key; + mod_timer(&bf54x_kpad->timer, + jiffies + bf54x_kpad->keyup_test_jiffies); + } else { + input_report_key(input, key, 0); + input_sync(input); + + bfin_kpad_clear_irq(); + } + + return IRQ_HANDLED; +} + +static int __devinit bfin_kpad_probe(struct platform_device *pdev) +{ + struct bf54x_kpad *bf54x_kpad; + struct bfin_kpad_platform_data *pdata = pdev->dev.platform_data; + struct input_dev *input; + int i, error; + + if (!pdata->rows || !pdata->cols || !pdata->keymap) { + printk(KERN_ERR DRV_NAME + ": No rows, cols or keymap from pdata\n"); + return -EINVAL; + } + + if (!pdata->keymapsize || + pdata->keymapsize > (pdata->rows * pdata->cols)) { + printk(KERN_ERR DRV_NAME ": Invalid keymapsize\n"); + return -EINVAL; + } + + bf54x_kpad = kzalloc(sizeof(struct bf54x_kpad), GFP_KERNEL); + if (!bf54x_kpad) + return -ENOMEM; + + platform_set_drvdata(pdev, bf54x_kpad); + + /* Allocate memory for keymap followed by private LUT */ + bf54x_kpad->keycode = kmalloc(pdata->keymapsize * + sizeof(unsigned short) * 2, GFP_KERNEL); + if (!bf54x_kpad->keycode) { + error = -ENOMEM; + goto out; + } + + if (!pdata->debounce_time || !pdata->debounce_time > MAX_MULT || + !pdata->coldrive_time || !pdata->coldrive_time > MAX_MULT) { + printk(KERN_ERR DRV_NAME + ": Invalid Debounce/Columdrive Time from pdata\n"); + bfin_write_KPAD_MSEL(0xFF0); /* Default MSEL */ + } else { + bfin_write_KPAD_MSEL( + ((pdata->debounce_time / TIME_SCALE) + & DBON_SCALE) | + (((pdata->coldrive_time / TIME_SCALE) << 8) + & COLDRV_SCALE)); + + } + + if (!pdata->keyup_test_interval) + bf54x_kpad->keyup_test_jiffies = msecs_to_jiffies(50); + else + bf54x_kpad->keyup_test_jiffies = + msecs_to_jiffies(pdata->keyup_test_interval); + + if (peripheral_request_list((u16 *)&per_rows[MAX_RC - pdata->rows], + DRV_NAME)) { + printk(KERN_ERR DRV_NAME + ": Requesting Peripherals failed\n"); + error = -EFAULT; + goto out0; + } + + if (peripheral_request_list((u16 *)&per_cols[MAX_RC - pdata->cols], + DRV_NAME)) { + printk(KERN_ERR DRV_NAME + ": Requesting Peripherals failed\n"); + error = -EFAULT; + goto out1; + } + + bf54x_kpad->irq = platform_get_irq(pdev, 0); + if (bf54x_kpad->irq < 0) { + error = -ENODEV; + goto out2; + } + + error = request_irq(bf54x_kpad->irq, bfin_kpad_isr, + IRQF_SAMPLE_RANDOM, DRV_NAME, pdev); + if (error) { + printk(KERN_ERR DRV_NAME + ": unable to claim irq %d; error %d\n", + bf54x_kpad->irq, error); + error = -EBUSY; + goto out2; + } + + input = input_allocate_device(); + if (!input) { + error = -ENOMEM; + goto out3; + } + + bf54x_kpad->input = input; + + input->name = pdev->name; + input->phys = "bf54x-keys/input0"; + input->dev.parent = &pdev->dev; + + input_set_drvdata(input, bf54x_kpad); + + input->id.bustype = BUS_HOST; + input->id.vendor = 0x0001; + input->id.product = 0x0001; + input->id.version = 0x0100; + + input->keycodesize = sizeof(unsigned short); + input->keycodemax = pdata->keymapsize; + input->keycode = bf54x_kpad->keycode; + + bfin_keycodecpy(bf54x_kpad->keycode, pdata->keymap, pdata->keymapsize); + + /* setup input device */ + __set_bit(EV_KEY, input->evbit); + + if (pdata->repeat) + __set_bit(EV_REP, input->evbit); + + for (i = 0; i < input->keycodemax; i++) + __set_bit(bf54x_kpad->keycode[i] & KEY_MAX, input->keybit); + __clear_bit(KEY_RESERVED, input->keybit); + + error = input_register_device(input); + if (error) { + printk(KERN_ERR DRV_NAME + ": Unable to register input device (%d)\n", error); + goto out4; + } + + /* Init Keypad Key Up/Release test timer */ + + setup_timer(&bf54x_kpad->timer, bfin_kpad_timer, (unsigned long) pdev); + + bfin_write_KPAD_PRESCALE(bfin_kpad_get_prescale(TIME_SCALE)); + + bfin_write_KPAD_CTL((((pdata->cols - 1) << 13) & KPAD_COLEN) | + (((pdata->rows - 1) << 10) & KPAD_ROWEN) | + (2 & KPAD_IRQMODE)); + + bfin_write_KPAD_CTL(bfin_read_KPAD_CTL() | KPAD_EN); + + printk(KERN_ERR DRV_NAME + ": Blackfin BF54x Keypad registered IRQ %d\n", bf54x_kpad->irq); + + return 0; + +out4: + input_free_device(input); +out3: + free_irq(bf54x_kpad->irq, pdev); +out2: + peripheral_free_list((u16 *)&per_cols[MAX_RC - pdata->cols]); +out1: + peripheral_free_list((u16 *)&per_rows[MAX_RC - pdata->rows]); +out0: + kfree(bf54x_kpad->keycode); +out: + kfree(bf54x_kpad); + platform_set_drvdata(pdev, NULL); + + return error; +} + +static int __devexit bfin_kpad_remove(struct platform_device *pdev) +{ + struct bfin_kpad_platform_data *pdata = pdev->dev.platform_data; + struct bf54x_kpad *bf54x_kpad = platform_get_drvdata(pdev); + + del_timer_sync(&bf54x_kpad->timer); + free_irq(bf54x_kpad->irq, pdev); + + input_unregister_device(bf54x_kpad->input); + + peripheral_free_list((u16 *)&per_rows[MAX_RC - pdata->rows]); + peripheral_free_list((u16 *)&per_cols[MAX_RC - pdata->cols]); + + kfree(bf54x_kpad->keycode); + kfree(bf54x_kpad); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +struct platform_driver bfin_kpad_device_driver = { + .probe = bfin_kpad_probe, + .remove = __devexit_p(bfin_kpad_remove), + .driver = { + .name = DRV_NAME, + } +}; + +static int __init bfin_kpad_init(void) +{ + return platform_driver_register(&bfin_kpad_device_driver); +} + +static void __exit bfin_kpad_exit(void) +{ + platform_driver_unregister(&bfin_kpad_device_driver); +} + +module_init(bfin_kpad_init); +module_exit(bfin_kpad_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Michael Hennerich "); +MODULE_DESCRIPTION("Keypad driver for BF54x Processors"); -- cgit v1.2.3 From 70093178b6eda34e4a4fb18cc4a48a9eacc01d98 Mon Sep 17 00:00:00 2001 From: Richard Purdie Date: Sat, 13 Oct 2007 00:38:52 -0400 Subject: Input: remove tsdev interface Remove the obsolete tsdev.c driver as scheduled. Signed-off-by: Richard Purdie Signed-off-by: Dmitry Torokhov --- drivers/input/Kconfig | 22 -- drivers/input/Makefile | 1 - drivers/input/tsdev.c | 700 ------------------------------------------------- 3 files changed, 723 deletions(-) delete mode 100644 drivers/input/tsdev.c (limited to 'drivers/input') diff --git a/drivers/input/Kconfig b/drivers/input/Kconfig index 2d87357e2b2..63512d906f0 100644 --- a/drivers/input/Kconfig +++ b/drivers/input/Kconfig @@ -114,28 +114,6 @@ config INPUT_JOYDEV To compile this driver as a module, choose M here: the module will be called joydev. -config INPUT_TSDEV - tristate "Touchscreen interface" - ---help--- - Say Y here if you have an application that only can understand the - Compaq touchscreen protocol for absolute pointer data. This is - useful namely for embedded configurations. - - If unsure, say N. - - To compile this driver as a module, choose M here: the - module will be called tsdev. - -config INPUT_TSDEV_SCREEN_X - int "Horizontal screen resolution" - depends on INPUT_TSDEV - default "240" - -config INPUT_TSDEV_SCREEN_Y - int "Vertical screen resolution" - depends on INPUT_TSDEV - default "320" - config INPUT_EVDEV tristate "Event interface" help diff --git a/drivers/input/Makefile b/drivers/input/Makefile index 15eb752697b..99af903bd3c 100644 --- a/drivers/input/Makefile +++ b/drivers/input/Makefile @@ -13,7 +13,6 @@ obj-$(CONFIG_INPUT_POLLDEV) += input-polldev.o obj-$(CONFIG_INPUT_MOUSEDEV) += mousedev.o obj-$(CONFIG_INPUT_JOYDEV) += joydev.o obj-$(CONFIG_INPUT_EVDEV) += evdev.o -obj-$(CONFIG_INPUT_TSDEV) += tsdev.o obj-$(CONFIG_INPUT_EVBUG) += evbug.o obj-$(CONFIG_INPUT_KEYBOARD) += keyboard/ diff --git a/drivers/input/tsdev.c b/drivers/input/tsdev.c deleted file mode 100644 index 12023349375..00000000000 --- a/drivers/input/tsdev.c +++ /dev/null @@ -1,700 +0,0 @@ -/* - * $Id: tsdev.c,v 1.15 2002/04/10 16:50:19 jsimmons Exp $ - * - * Copyright (c) 2001 "Crazy" james Simmons - * - * Compaq touchscreen protocol driver. The protocol emulated by this driver - * is obsolete; for new programs use the tslib library which can read directly - * from evdev and perform dejittering, variance filtering and calibration - - * all in user space, not at kernel level. The meaning of this driver is - * to allow usage of newer input drivers with old applications that use the - * old /dev/h3600_ts and /dev/h3600_tsraw devices. - * - * 09-Apr-2004: Andrew Zabolotny - * Fixed to actually work, not just output random numbers. - * Added support for both h3600_ts and h3600_tsraw protocol - * emulation. - */ - -/* - * 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 - * - * Should you need to contact me, the author, you can do so either by - * e-mail - mail your message to . - */ - -#define TSDEV_MINOR_BASE 128 -#define TSDEV_MINORS 32 -/* First 16 devices are h3600_ts compatible; second 16 are h3600_tsraw */ -#define TSDEV_MINOR_MASK 15 -#define TSDEV_BUFFER_SIZE 64 - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifndef CONFIG_INPUT_TSDEV_SCREEN_X -#define CONFIG_INPUT_TSDEV_SCREEN_X 240 -#endif -#ifndef CONFIG_INPUT_TSDEV_SCREEN_Y -#define CONFIG_INPUT_TSDEV_SCREEN_Y 320 -#endif - -/* This driver emulates both protocols of the old h3600_ts and h3600_tsraw - * devices. The first one must output X/Y data in 'cooked' format, e.g. - * filtered, dejittered and calibrated. Second device just outputs raw - * data received from the hardware. - * - * This driver doesn't support filtering and dejittering; it supports only - * calibration. Filtering and dejittering must be done in the low-level - * driver, if needed, because it may gain additional benefits from knowing - * the low-level details, the nature of noise and so on. - * - * The driver precomputes a calibration matrix given the initial xres and - * yres values (quite innacurate for most touchscreens) that will result - * in a more or less expected range of output values. The driver supports - * the TS_SET_CAL ioctl, which will replace the calibration matrix with a - * new one, supposedly generated from the values taken from the raw device. - */ - -MODULE_AUTHOR("James Simmons "); -MODULE_DESCRIPTION("Input driver to touchscreen converter"); -MODULE_LICENSE("GPL"); - -static int xres = CONFIG_INPUT_TSDEV_SCREEN_X; -module_param(xres, uint, 0); -MODULE_PARM_DESC(xres, "Horizontal screen resolution (can be negative for X-mirror)"); - -static int yres = CONFIG_INPUT_TSDEV_SCREEN_Y; -module_param(yres, uint, 0); -MODULE_PARM_DESC(yres, "Vertical screen resolution (can be negative for Y-mirror)"); - -/* From Compaq's Touch Screen Specification version 0.2 (draft) */ -struct ts_event { - short pressure; - short x; - short y; - short millisecs; -}; - -struct ts_calibration { - int xscale; - int xtrans; - int yscale; - int ytrans; - int xyswap; -}; - -struct tsdev { - int exist; - int open; - int minor; - char name[8]; - struct input_handle handle; - wait_queue_head_t wait; - struct list_head client_list; - spinlock_t client_lock; /* protects client_list */ - struct mutex mutex; - struct device dev; - - int x, y, pressure; - struct ts_calibration cal; -}; - -struct tsdev_client { - struct fasync_struct *fasync; - struct list_head node; - struct tsdev *tsdev; - struct ts_event buffer[TSDEV_BUFFER_SIZE]; - int head, tail; - spinlock_t buffer_lock; /* protects access to buffer, head and tail */ - int raw; -}; - -/* The following ioctl codes are defined ONLY for backward compatibility. - * Don't use tsdev for new developement; use the tslib library instead. - * Touchscreen calibration is a fully userspace task. - */ -/* Use 'f' as magic number */ -#define IOC_H3600_TS_MAGIC 'f' -#define TS_GET_CAL _IOR(IOC_H3600_TS_MAGIC, 10, struct ts_calibration) -#define TS_SET_CAL _IOW(IOC_H3600_TS_MAGIC, 11, struct ts_calibration) - -static struct tsdev *tsdev_table[TSDEV_MINORS/2]; -static DEFINE_MUTEX(tsdev_table_mutex); - -static int tsdev_fasync(int fd, struct file *file, int on) -{ - struct tsdev_client *client = file->private_data; - int retval; - - retval = fasync_helper(fd, file, on, &client->fasync); - - return retval < 0 ? retval : 0; -} - -static void tsdev_free(struct device *dev) -{ - struct tsdev *tsdev = container_of(dev, struct tsdev, dev); - - kfree(tsdev); -} - -static void tsdev_attach_client(struct tsdev *tsdev, struct tsdev_client *client) -{ - spin_lock(&tsdev->client_lock); - list_add_tail_rcu(&client->node, &tsdev->client_list); - spin_unlock(&tsdev->client_lock); - synchronize_sched(); -} - -static void tsdev_detach_client(struct tsdev *tsdev, struct tsdev_client *client) -{ - spin_lock(&tsdev->client_lock); - list_del_rcu(&client->node); - spin_unlock(&tsdev->client_lock); - synchronize_sched(); -} - -static int tsdev_open_device(struct tsdev *tsdev) -{ - int retval; - - retval = mutex_lock_interruptible(&tsdev->mutex); - if (retval) - return retval; - - if (!tsdev->exist) - retval = -ENODEV; - else if (!tsdev->open++) { - retval = input_open_device(&tsdev->handle); - if (retval) - tsdev->open--; - } - - mutex_unlock(&tsdev->mutex); - return retval; -} - -static void tsdev_close_device(struct tsdev *tsdev) -{ - mutex_lock(&tsdev->mutex); - - if (tsdev->exist && !--tsdev->open) - input_close_device(&tsdev->handle); - - mutex_unlock(&tsdev->mutex); -} - -/* - * Wake up users waiting for IO so they can disconnect from - * dead device. - */ -static void tsdev_hangup(struct tsdev *tsdev) -{ - struct tsdev_client *client; - - spin_lock(&tsdev->client_lock); - list_for_each_entry(client, &tsdev->client_list, node) - kill_fasync(&client->fasync, SIGIO, POLL_HUP); - spin_unlock(&tsdev->client_lock); - - wake_up_interruptible(&tsdev->wait); -} - -static int tsdev_release(struct inode *inode, struct file *file) -{ - struct tsdev_client *client = file->private_data; - struct tsdev *tsdev = client->tsdev; - - tsdev_fasync(-1, file, 0); - tsdev_detach_client(tsdev, client); - kfree(client); - - tsdev_close_device(tsdev); - put_device(&tsdev->dev); - - return 0; -} - -static int tsdev_open(struct inode *inode, struct file *file) -{ - int i = iminor(inode) - TSDEV_MINOR_BASE; - struct tsdev_client *client; - struct tsdev *tsdev; - int error; - - printk(KERN_WARNING "tsdev (compaq touchscreen emulation) is scheduled " - "for removal.\nSee Documentation/feature-removal-schedule.txt " - "for details.\n"); - - if (i >= TSDEV_MINORS) - return -ENODEV; - - error = mutex_lock_interruptible(&tsdev_table_mutex); - if (error) - return error; - tsdev = tsdev_table[i & TSDEV_MINOR_MASK]; - if (tsdev) - get_device(&tsdev->dev); - mutex_unlock(&tsdev_table_mutex); - - if (!tsdev) - return -ENODEV; - - client = kzalloc(sizeof(struct tsdev_client), GFP_KERNEL); - if (!client) { - error = -ENOMEM; - goto err_put_tsdev; - } - - spin_lock_init(&client->buffer_lock); - client->tsdev = tsdev; - client->raw = i >= TSDEV_MINORS / 2; - tsdev_attach_client(tsdev, client); - - error = tsdev_open_device(tsdev); - if (error) - goto err_free_client; - - file->private_data = client; - return 0; - - err_free_client: - tsdev_detach_client(tsdev, client); - kfree(client); - err_put_tsdev: - put_device(&tsdev->dev); - return error; -} - -static int tsdev_fetch_next_event(struct tsdev_client *client, - struct ts_event *event) -{ - int have_event; - - spin_lock_irq(&client->buffer_lock); - - have_event = client->head != client->tail; - if (have_event) { - *event = client->buffer[client->tail++]; - client->tail &= TSDEV_BUFFER_SIZE - 1; - } - - spin_unlock_irq(&client->buffer_lock); - - return have_event; -} - -static ssize_t tsdev_read(struct file *file, char __user *buffer, size_t count, - loff_t *ppos) -{ - struct tsdev_client *client = file->private_data; - struct tsdev *tsdev = client->tsdev; - struct ts_event event; - int retval; - - if (client->head == client->tail && tsdev->exist && - (file->f_flags & O_NONBLOCK)) - return -EAGAIN; - - retval = wait_event_interruptible(tsdev->wait, - client->head != client->tail || !tsdev->exist); - if (retval) - return retval; - - if (!tsdev->exist) - return -ENODEV; - - while (retval + sizeof(struct ts_event) <= count && - tsdev_fetch_next_event(client, &event)) { - - if (copy_to_user(buffer + retval, &event, - sizeof(struct ts_event))) - return -EFAULT; - - retval += sizeof(struct ts_event); - } - - return retval; -} - -/* No kernel lock - fine */ -static unsigned int tsdev_poll(struct file *file, poll_table *wait) -{ - struct tsdev_client *client = file->private_data; - struct tsdev *tsdev = client->tsdev; - - poll_wait(file, &tsdev->wait, wait); - return ((client->head == client->tail) ? 0 : (POLLIN | POLLRDNORM)) | - (tsdev->exist ? 0 : (POLLHUP | POLLERR)); -} - -static long tsdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) -{ - struct tsdev_client *client = file->private_data; - struct tsdev *tsdev = client->tsdev; - int retval = 0; - - retval = mutex_lock_interruptible(&tsdev->mutex); - if (retval) - return retval; - - if (!tsdev->exist) { - retval = -ENODEV; - goto out; - } - - switch (cmd) { - - case TS_GET_CAL: - if (copy_to_user((void __user *)arg, &tsdev->cal, - sizeof (struct ts_calibration))) - retval = -EFAULT; - break; - - case TS_SET_CAL: - if (copy_from_user(&tsdev->cal, (void __user *)arg, - sizeof(struct ts_calibration))) - retval = -EFAULT; - break; - - default: - retval = -EINVAL; - break; - } - - out: - mutex_unlock(&tsdev->mutex); - return retval; -} - -static const struct file_operations tsdev_fops = { - .owner = THIS_MODULE, - .open = tsdev_open, - .release = tsdev_release, - .read = tsdev_read, - .poll = tsdev_poll, - .fasync = tsdev_fasync, - .unlocked_ioctl = tsdev_ioctl, -}; - -static void tsdev_pass_event(struct tsdev *tsdev, struct tsdev_client *client, - int x, int y, int pressure, int millisecs) -{ - struct ts_event *event; - int tmp; - - /* Interrupts are already disabled, just acquire the lock */ - spin_lock(&client->buffer_lock); - - event = &client->buffer[client->head++]; - client->head &= TSDEV_BUFFER_SIZE - 1; - - /* Calibration */ - if (!client->raw) { - x = ((x * tsdev->cal.xscale) >> 8) + tsdev->cal.xtrans; - y = ((y * tsdev->cal.yscale) >> 8) + tsdev->cal.ytrans; - if (tsdev->cal.xyswap) { - tmp = x; x = y; y = tmp; - } - } - - event->millisecs = millisecs; - event->x = x; - event->y = y; - event->pressure = pressure; - - spin_unlock(&client->buffer_lock); - - kill_fasync(&client->fasync, SIGIO, POLL_IN); -} - -static void tsdev_distribute_event(struct tsdev *tsdev) -{ - struct tsdev_client *client; - struct timeval time; - int millisecs; - - do_gettimeofday(&time); - millisecs = time.tv_usec / 1000; - - list_for_each_entry_rcu(client, &tsdev->client_list, node) - tsdev_pass_event(tsdev, client, - tsdev->x, tsdev->y, - tsdev->pressure, millisecs); -} - -static void tsdev_event(struct input_handle *handle, unsigned int type, - unsigned int code, int value) -{ - struct tsdev *tsdev = handle->private; - struct input_dev *dev = handle->dev; - int wake_up_readers = 0; - - switch (type) { - - case EV_ABS: - switch (code) { - - case ABS_X: - tsdev->x = value; - break; - - case ABS_Y: - tsdev->y = value; - break; - - case ABS_PRESSURE: - if (value > dev->absmax[ABS_PRESSURE]) - value = dev->absmax[ABS_PRESSURE]; - value -= dev->absmin[ABS_PRESSURE]; - if (value < 0) - value = 0; - tsdev->pressure = value; - break; - } - break; - - case EV_REL: - switch (code) { - - case REL_X: - tsdev->x += value; - if (tsdev->x < 0) - tsdev->x = 0; - else if (tsdev->x > xres) - tsdev->x = xres; - break; - - case REL_Y: - tsdev->y += value; - if (tsdev->y < 0) - tsdev->y = 0; - else if (tsdev->y > yres) - tsdev->y = yres; - break; - } - break; - - case EV_KEY: - if (code == BTN_TOUCH || code == BTN_MOUSE) { - switch (value) { - - case 0: - tsdev->pressure = 0; - break; - - case 1: - if (!tsdev->pressure) - tsdev->pressure = 1; - break; - } - } - break; - - case EV_SYN: - if (code == SYN_REPORT) { - tsdev_distribute_event(tsdev); - wake_up_readers = 1; - } - break; - } - - if (wake_up_readers) - wake_up_interruptible(&tsdev->wait); -} - -static int tsdev_install_chrdev(struct tsdev *tsdev) -{ - tsdev_table[tsdev->minor] = tsdev; - return 0; -} - -static void tsdev_remove_chrdev(struct tsdev *tsdev) -{ - mutex_lock(&tsdev_table_mutex); - tsdev_table[tsdev->minor] = NULL; - mutex_unlock(&tsdev_table_mutex); -} - -/* - * Mark device non-existant. This disables writes, ioctls and - * prevents new users from opening the device. Already posted - * blocking reads will stay, however new ones will fail. - */ -static void tsdev_mark_dead(struct tsdev *tsdev) -{ - mutex_lock(&tsdev->mutex); - tsdev->exist = 0; - mutex_unlock(&tsdev->mutex); -} - -static void tsdev_cleanup(struct tsdev *tsdev) -{ - struct input_handle *handle = &tsdev->handle; - - tsdev_mark_dead(tsdev); - tsdev_hangup(tsdev); - tsdev_remove_chrdev(tsdev); - - /* tsdev is marked dead so noone else accesses tsdev->open */ - if (tsdev->open) - input_close_device(handle); -} - -static int tsdev_connect(struct input_handler *handler, struct input_dev *dev, - const struct input_device_id *id) -{ - struct tsdev *tsdev; - int delta; - int minor; - int error; - - for (minor = 0; minor < TSDEV_MINORS / 2; minor++) - if (!tsdev_table[minor]) - break; - - if (minor == TSDEV_MINORS) { - printk(KERN_ERR "tsdev: no more free tsdev devices\n"); - return -ENFILE; - } - - tsdev = kzalloc(sizeof(struct tsdev), GFP_KERNEL); - if (!tsdev) - return -ENOMEM; - - INIT_LIST_HEAD(&tsdev->client_list); - spin_lock_init(&tsdev->client_lock); - mutex_init(&tsdev->mutex); - init_waitqueue_head(&tsdev->wait); - - snprintf(tsdev->name, sizeof(tsdev->name), "ts%d", minor); - tsdev->exist = 1; - tsdev->minor = minor; - - tsdev->handle.dev = dev; - tsdev->handle.name = tsdev->name; - tsdev->handle.handler = handler; - tsdev->handle.private = tsdev; - - /* Precompute the rough calibration matrix */ - delta = dev->absmax [ABS_X] - dev->absmin [ABS_X] + 1; - if (delta == 0) - delta = 1; - tsdev->cal.xscale = (xres << 8) / delta; - tsdev->cal.xtrans = - ((dev->absmin [ABS_X] * tsdev->cal.xscale) >> 8); - - delta = dev->absmax [ABS_Y] - dev->absmin [ABS_Y] + 1; - if (delta == 0) - delta = 1; - tsdev->cal.yscale = (yres << 8) / delta; - tsdev->cal.ytrans = - ((dev->absmin [ABS_Y] * tsdev->cal.yscale) >> 8); - - strlcpy(tsdev->dev.bus_id, tsdev->name, sizeof(tsdev->dev.bus_id)); - tsdev->dev.devt = MKDEV(INPUT_MAJOR, TSDEV_MINOR_BASE + minor); - tsdev->dev.class = &input_class; - tsdev->dev.parent = &dev->dev; - tsdev->dev.release = tsdev_free; - device_initialize(&tsdev->dev); - - error = input_register_handle(&tsdev->handle); - if (error) - goto err_free_tsdev; - - error = tsdev_install_chrdev(tsdev); - if (error) - goto err_unregister_handle; - - error = device_add(&tsdev->dev); - if (error) - goto err_cleanup_tsdev; - - return 0; - - err_cleanup_tsdev: - tsdev_cleanup(tsdev); - err_unregister_handle: - input_unregister_handle(&tsdev->handle); - err_free_tsdev: - put_device(&tsdev->dev); - return error; -} - -static void tsdev_disconnect(struct input_handle *handle) -{ - struct tsdev *tsdev = handle->private; - - device_del(&tsdev->dev); - tsdev_cleanup(tsdev); - input_unregister_handle(handle); - put_device(&tsdev->dev); -} - -static const struct input_device_id tsdev_ids[] = { - { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_RELBIT, - .evbit = { BIT(EV_KEY) | BIT(EV_REL) }, - .keybit = { [LONG(BTN_LEFT)] = BIT(BTN_LEFT) }, - .relbit = { BIT(REL_X) | BIT(REL_Y) }, - }, /* A mouse like device, at least one button, two relative axes */ - - { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, - .evbit = { BIT(EV_KEY) | BIT(EV_ABS) }, - .keybit = { [LONG(BTN_TOUCH)] = BIT(BTN_TOUCH) }, - .absbit = { BIT(ABS_X) | BIT(ABS_Y) }, - }, /* A tablet like device, at least touch detection, two absolute axes */ - - { - .flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_ABSBIT, - .evbit = { BIT(EV_ABS) }, - .absbit = { BIT(ABS_X) | BIT(ABS_Y) | BIT(ABS_PRESSURE) }, - }, /* A tablet like device with several gradations of pressure */ - - {} /* Terminating entry */ -}; - -MODULE_DEVICE_TABLE(input, tsdev_ids); - -static struct input_handler tsdev_handler = { - .event = tsdev_event, - .connect = tsdev_connect, - .disconnect = tsdev_disconnect, - .fops = &tsdev_fops, - .minor = TSDEV_MINOR_BASE, - .name = "tsdev", - .id_table = tsdev_ids, -}; - -static int __init tsdev_init(void) -{ - return input_register_handler(&tsdev_handler); -} - -static void __exit tsdev_exit(void) -{ - input_unregister_handler(&tsdev_handler); -} - -module_init(tsdev_init); -module_exit(tsdev_exit); -- cgit v1.2.3 From 82ba56c273911f7eda79849cfa0fc2d2e5a3b75b Mon Sep 17 00:00:00 2001 From: Dmitry Torokhov Date: Sat, 13 Oct 2007 15:46:55 -0400 Subject: Input: use full RCU API RT guys alerted me to the fact that in their tree spinlocks are preemptible and it is better to use full RCU API (rcu_read_lock()/rcu_read_unlock()) to be safe. Signed-off-by: Dmitry Torokhov --- drivers/input/evdev.c | 22 +++++++++------------- drivers/input/input.c | 36 +++++++++++++++--------------------- drivers/input/joydev.c | 11 ++++------- drivers/input/mousedev.c | 11 ++++------- 4 files changed, 32 insertions(+), 48 deletions(-) (limited to 'drivers/input') diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index 27026f7d3c0..1d62c8b88e1 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -63,10 +63,7 @@ static void evdev_pass_event(struct evdev_client *client, } /* - * Pass incoming event to all connected clients. Note that we are - * caleld under a spinlock with interrupts off so we don't need - * to use rcu_read_lock() here. Writers will be using syncronize_sched() - * instead of synchrnoize_rcu(). + * Pass incoming event to all connected clients. */ static void evdev_event(struct input_handle *handle, unsigned int type, unsigned int code, int value) @@ -80,6 +77,8 @@ static void evdev_event(struct input_handle *handle, event.code = code; event.value = value; + rcu_read_lock(); + client = rcu_dereference(evdev->grab); if (client) evdev_pass_event(client, &event); @@ -87,6 +86,8 @@ static void evdev_event(struct input_handle *handle, list_for_each_entry_rcu(client, &evdev->client_list, node) evdev_pass_event(client, &event); + rcu_read_unlock(); + wake_up_interruptible(&evdev->wait); } @@ -142,12 +143,7 @@ static int evdev_grab(struct evdev *evdev, struct evdev_client *client) return error; rcu_assign_pointer(evdev->grab, client); - /* - * We don't use synchronize_rcu() here because read-side - * critical section is protected by a spinlock instead - * of rcu_read_lock(). - */ - synchronize_sched(); + synchronize_rcu(); return 0; } @@ -158,7 +154,7 @@ static int evdev_ungrab(struct evdev *evdev, struct evdev_client *client) return -EINVAL; rcu_assign_pointer(evdev->grab, NULL); - synchronize_sched(); + synchronize_rcu(); input_release_device(&evdev->handle); return 0; @@ -170,7 +166,7 @@ static void evdev_attach_client(struct evdev *evdev, spin_lock(&evdev->client_lock); list_add_tail_rcu(&client->node, &evdev->client_list); spin_unlock(&evdev->client_lock); - synchronize_sched(); + synchronize_rcu(); } static void evdev_detach_client(struct evdev *evdev, @@ -179,7 +175,7 @@ static void evdev_detach_client(struct evdev *evdev, spin_lock(&evdev->client_lock); list_del_rcu(&client->node); spin_unlock(&evdev->client_lock); - synchronize_sched(); + synchronize_rcu(); } static int evdev_open_device(struct evdev *evdev) diff --git a/drivers/input/input.c b/drivers/input/input.c index 3070c7aa123..2f2b020cd62 100644 --- a/drivers/input/input.c +++ b/drivers/input/input.c @@ -65,16 +65,16 @@ static int input_defuzz_abs_event(int value, int old_val, int fuzz) /* * Pass event through all open handles. This function is called with - * dev->event_lock held and interrupts disabled. Because of that we - * do not need to use rcu_read_lock() here although we are using RCU - * to access handle list. Note that because of that write-side uses - * synchronize_sched() instead of synchronize_ru(). + * dev->event_lock held and interrupts disabled. */ static void input_pass_event(struct input_dev *dev, unsigned int type, unsigned int code, int value) { - struct input_handle *handle = rcu_dereference(dev->grab); + struct input_handle *handle; + + rcu_read_lock(); + handle = rcu_dereference(dev->grab); if (handle) handle->handler->event(handle, type, code, value); else @@ -82,6 +82,7 @@ static void input_pass_event(struct input_dev *dev, if (handle->open) handle->handler->event(handle, type, code, value); + rcu_read_unlock(); } /* @@ -293,9 +294,11 @@ void input_inject_event(struct input_handle *handle, if (is_event_supported(type, dev->evbit, EV_MAX)) { spin_lock_irqsave(&dev->event_lock, flags); + rcu_read_lock(); grab = rcu_dereference(dev->grab); if (!grab || grab == handle) input_handle_event(dev, type, code, value); + rcu_read_unlock(); spin_unlock_irqrestore(&dev->event_lock, flags); } @@ -325,11 +328,7 @@ int input_grab_device(struct input_handle *handle) } rcu_assign_pointer(dev->grab, handle); - /* - * Not using synchronize_rcu() because read-side is protected - * by a spinlock with interrupts off instead of rcu_read_lock(). - */ - synchronize_sched(); + synchronize_rcu(); out: mutex_unlock(&dev->mutex); @@ -344,7 +343,7 @@ static void __input_release_device(struct input_handle *handle) if (dev->grab == handle) { rcu_assign_pointer(dev->grab, NULL); /* Make sure input_pass_event() notices that grab is gone */ - synchronize_sched(); + synchronize_rcu(); list_for_each_entry(handle, &dev->h_list, d_node) if (handle->open && handle->handler->start) @@ -404,7 +403,7 @@ int input_open_device(struct input_handle *handle) * Make sure we are not delivering any more events * through this handle */ - synchronize_sched(); + synchronize_rcu(); } } @@ -451,11 +450,11 @@ void input_close_device(struct input_handle *handle) if (!--handle->open) { /* - * synchronize_sched() makes sure that input_pass_event() + * synchronize_rcu() makes sure that input_pass_event() * completed and that no more input events are delivered * through this handle */ - synchronize_sched(); + synchronize_rcu(); } mutex_unlock(&dev->mutex); @@ -1477,12 +1476,7 @@ int input_register_handle(struct input_handle *handle) return error; list_add_tail_rcu(&handle->d_node, &dev->h_list); mutex_unlock(&dev->mutex); - /* - * We don't use synchronize_rcu() here because we rely - * on dev->event_lock to protect read-side critical - * section in input_pass_event(). - */ - synchronize_sched(); + synchronize_rcu(); /* * Since we are supposed to be called from ->connect() @@ -1521,7 +1515,7 @@ void input_unregister_handle(struct input_handle *handle) mutex_lock(&dev->mutex); list_del_rcu(&handle->d_node); mutex_unlock(&dev->mutex); - synchronize_sched(); + synchronize_rcu(); } EXPORT_SYMBOL(input_unregister_handle); diff --git a/drivers/input/joydev.c b/drivers/input/joydev.c index f306c97f556..2b201f9aa02 100644 --- a/drivers/input/joydev.c +++ b/drivers/input/joydev.c @@ -149,8 +149,10 @@ static void joydev_event(struct input_handle *handle, event.time = jiffies_to_msecs(jiffies); + rcu_read_lock(); list_for_each_entry_rcu(client, &joydev->client_list, node) joydev_pass_event(client, &event); + rcu_read_unlock(); wake_up_interruptible(&joydev->wait); } @@ -178,12 +180,7 @@ static void joydev_attach_client(struct joydev *joydev, spin_lock(&joydev->client_lock); list_add_tail_rcu(&client->node, &joydev->client_list); spin_unlock(&joydev->client_lock); - /* - * We don't use synchronize_rcu() here because read-side - * critical section is protected by a spinlock (dev->event_lock) - * instead of rcu_read_lock(). - */ - synchronize_sched(); + synchronize_rcu(); } static void joydev_detach_client(struct joydev *joydev, @@ -192,7 +189,7 @@ static void joydev_detach_client(struct joydev *joydev, spin_lock(&joydev->client_lock); list_del_rcu(&client->node); spin_unlock(&joydev->client_lock); - synchronize_sched(); + synchronize_rcu(); } static int joydev_open_device(struct joydev *joydev) diff --git a/drivers/input/mousedev.c b/drivers/input/mousedev.c index cc36edbb912..79146d6ed2a 100644 --- a/drivers/input/mousedev.c +++ b/drivers/input/mousedev.c @@ -265,6 +265,7 @@ static void mousedev_notify_readers(struct mousedev *mousedev, unsigned int new_head; int wake_readers = 0; + rcu_read_lock(); list_for_each_entry_rcu(client, &mousedev->client_list, node) { /* Just acquire the lock, interrupts already disabled */ @@ -309,6 +310,7 @@ static void mousedev_notify_readers(struct mousedev *mousedev, wake_readers = 1; } } + rcu_read_unlock(); if (wake_readers) wake_up_interruptible(&mousedev->wait); @@ -499,12 +501,7 @@ static void mousedev_attach_client(struct mousedev *mousedev, spin_lock(&mousedev->client_lock); list_add_tail_rcu(&client->node, &mousedev->client_list); spin_unlock(&mousedev->client_lock); - /* - * We don't use synchronize_rcu() here because read-side - * critical section is protected by a spinlock (dev->event_lock) - * instead of rcu_read_lock(). - */ - synchronize_sched(); + synchronize_rcu(); } static void mousedev_detach_client(struct mousedev *mousedev, @@ -513,7 +510,7 @@ static void mousedev_detach_client(struct mousedev *mousedev, spin_lock(&mousedev->client_lock); list_del_rcu(&client->node); spin_unlock(&mousedev->client_lock); - synchronize_sched(); + synchronize_rcu(); } static int mousedev_release(struct inode *inode, struct file *file) -- cgit v1.2.3