| From 0f42c57afcb17eb28d45ee6d3eb91e4407716020 Mon Sep 17 00:00:00 2001 |
| From: "Pierre-Loup A. Griffais" <pgriffais@valvesoftware.com> |
| Date: Wed, 9 Dec 2015 11:46:25 -0800 |
| Subject: Input: xpad - handle "present" and "gone" correctly |
| |
| [ Upstream commit 09c8b00ae3e16c8d0fd4beb2ca064502a76c0f17 ] |
| |
| Handle the "a new device is present" message properly by dynamically |
| creating the input device at this point in time. This means we now do not |
| "preallocate" all 4 devices when a single wireless base station is seen. |
| This requires a workqueue as we are in interrupt context when we learn |
| about this. |
| |
| Also properly disconnect any devices that we are told are removed. |
| |
| Signed-off-by: "Pierre-Loup A. Griffais" <pgriffais@valvesoftware.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| Signed-off-by: Pavel Rojtberg <rojtberg@gmail.com> |
| Signed-off-by: Dmitry Torokhov <dmitry.torokhov@gmail.com> |
| Signed-off-by: Sasha Levin <sashal@kernel.org> |
| --- |
| drivers/input/joystick/xpad.c | 107 ++++++++++++++++++++++------------ |
| 1 file changed, 69 insertions(+), 38 deletions(-) |
| |
| diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c |
| index 2f16d07db8ef..88de7b17bf7d 100644 |
| --- a/drivers/input/joystick/xpad.c |
| +++ b/drivers/input/joystick/xpad.c |
| @@ -76,6 +76,8 @@ |
| */ |
| |
| #include <linux/kernel.h> |
| +#include <linux/input.h> |
| +#include <linux/rcupdate.h> |
| #include <linux/slab.h> |
| #include <linux/stat.h> |
| #include <linux/module.h> |
| @@ -334,10 +336,12 @@ struct xpad_output_packet { |
| |
| struct usb_xpad { |
| struct input_dev *dev; /* input device interface */ |
| + struct input_dev __rcu *x360w_dev; |
| struct usb_device *udev; /* usb device */ |
| struct usb_interface *intf; /* usb interface */ |
| |
| - int pad_present; |
| + bool pad_present; |
| + bool input_created; |
| |
| struct urb *irq_in; /* urb for interrupt in report */ |
| unsigned char *idata; /* input data */ |
| @@ -362,8 +366,12 @@ struct usb_xpad { |
| int xtype; /* type of xbox device */ |
| int pad_nr; /* the order x360 pads were attached */ |
| const char *name; /* name of the device */ |
| + struct work_struct work; /* init/remove device from callback */ |
| }; |
| |
| +static int xpad_init_input(struct usb_xpad *xpad); |
| +static void xpad_deinit_input(struct usb_xpad *xpad); |
| + |
| /* |
| * xpad_process_packet |
| * |
| @@ -443,11 +451,9 @@ 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, |
| +static void xpad360_process_packet(struct usb_xpad *xpad, struct input_dev *dev, |
| u16 cmd, unsigned char *data) |
| { |
| - struct input_dev *dev = xpad->dev; |
| - |
| /* digital pad */ |
| if (xpad->mapping & MAP_DPAD_TO_BUTTONS) { |
| /* dpad as buttons (left, right, up, down) */ |
| @@ -514,7 +520,30 @@ static void xpad360_process_packet(struct usb_xpad *xpad, |
| input_sync(dev); |
| } |
| |
| -static void xpad_identify_controller(struct usb_xpad *xpad); |
| +static void xpad_presence_work(struct work_struct *work) |
| +{ |
| + struct usb_xpad *xpad = container_of(work, struct usb_xpad, work); |
| + int error; |
| + |
| + if (xpad->pad_present) { |
| + error = xpad_init_input(xpad); |
| + if (error) { |
| + /* complain only, not much else we can do here */ |
| + dev_err(&xpad->dev->dev, |
| + "unable to init device: %d\n", error); |
| + } else { |
| + rcu_assign_pointer(xpad->x360w_dev, xpad->dev); |
| + } |
| + } else { |
| + RCU_INIT_POINTER(xpad->x360w_dev, NULL); |
| + synchronize_rcu(); |
| + /* |
| + * Now that we are sure xpad360w_process_packet is not |
| + * using input device we can get rid of it. |
| + */ |
| + xpad_deinit_input(xpad); |
| + } |
| +} |
| |
| /* |
| * xpad360w_process_packet |
| @@ -532,24 +561,28 @@ static void xpad_identify_controller(struct usb_xpad *xpad); |
| */ |
| static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data) |
| { |
| + struct input_dev *dev; |
| + bool present; |
| + |
| /* Presence change */ |
| if (data[0] & 0x08) { |
| - if (data[1] & 0x80) { |
| - xpad->pad_present = 1; |
| - /* |
| - * Light up the segment corresponding to |
| - * controller number. |
| - */ |
| - xpad_identify_controller(xpad); |
| - } else |
| - xpad->pad_present = 0; |
| + present = (data[1] & 0x80) != 0; |
| + |
| + if (xpad->pad_present != present) { |
| + xpad->pad_present = present; |
| + schedule_work(&xpad->work); |
| + } |
| } |
| |
| /* Valid pad data */ |
| if (data[1] != 0x1) |
| return; |
| |
| - xpad360_process_packet(xpad, cmd, &data[4]); |
| + rcu_read_lock(); |
| + dev = rcu_dereference(xpad->x360w_dev); |
| + if (dev) |
| + xpad360_process_packet(xpad, dev, cmd, &data[4]); |
| + rcu_read_unlock(); |
| } |
| |
| /* |
| @@ -678,7 +711,7 @@ static void xpad_irq_in(struct urb *urb) |
| |
| switch (xpad->xtype) { |
| case XTYPE_XBOX360: |
| - xpad360_process_packet(xpad, 0, xpad->idata); |
| + xpad360_process_packet(xpad, xpad->dev, 0, xpad->idata); |
| break; |
| case XTYPE_XBOX360W: |
| xpad360w_process_packet(xpad, 0, xpad->idata); |
| @@ -1132,14 +1165,7 @@ static int xpad_led_probe(struct usb_xpad *xpad) |
| if (error) |
| goto err_free_id; |
| |
| - if (xpad->xtype == XTYPE_XBOX360) { |
| - /* |
| - * Light up the segment corresponding to controller |
| - * number on wired devices. On wireless we'll do that |
| - * when they respond to "presence" packet. |
| - */ |
| - xpad_identify_controller(xpad); |
| - } |
| + xpad_identify_controller(xpad); |
| |
| return 0; |
| |
| @@ -1223,8 +1249,11 @@ static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs) |
| |
| static void xpad_deinit_input(struct usb_xpad *xpad) |
| { |
| - xpad_led_disconnect(xpad); |
| - input_unregister_device(xpad->dev); |
| + if (xpad->input_created) { |
| + xpad->input_created = false; |
| + xpad_led_disconnect(xpad); |
| + input_unregister_device(xpad->dev); |
| + } |
| } |
| |
| static int xpad_init_input(struct usb_xpad *xpad) |
| @@ -1313,6 +1342,7 @@ static int xpad_init_input(struct usb_xpad *xpad) |
| if (error) |
| goto err_disconnect_led; |
| |
| + xpad->input_created = true; |
| return 0; |
| |
| err_disconnect_led: |
| @@ -1366,6 +1396,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id |
| xpad->mapping = xpad_device[i].mapping; |
| xpad->xtype = xpad_device[i].xtype; |
| xpad->name = xpad_device[i].name; |
| + INIT_WORK(&xpad->work, xpad_presence_work); |
| |
| if (xpad->xtype == XTYPE_UNKNOWN) { |
| if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) { |
| @@ -1415,10 +1446,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id |
| |
| usb_set_intfdata(intf, xpad); |
| |
| - error = xpad_init_input(xpad); |
| - if (error) |
| - goto err_deinit_output; |
| - |
| if (xpad->xtype == XTYPE_XBOX360W) { |
| /* |
| * Submit the int URB immediately rather than waiting for open |
| @@ -1430,7 +1457,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id |
| xpad->irq_in->dev = xpad->udev; |
| error = usb_submit_urb(xpad->irq_in, GFP_KERNEL); |
| if (error) |
| - goto err_deinit_input; |
| + goto err_deinit_output; |
| |
| /* |
| * Send presence packet. |
| @@ -1442,13 +1469,15 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id |
| error = xpad_inquiry_pad_presence(xpad); |
| if (error) |
| goto err_kill_in_urb; |
| + } else { |
| + error = xpad_init_input(xpad); |
| + if (error) |
| + goto err_deinit_output; |
| } |
| return 0; |
| |
| err_kill_in_urb: |
| usb_kill_urb(xpad->irq_in); |
| -err_deinit_input: |
| - xpad_deinit_input(xpad); |
| err_deinit_output: |
| xpad_deinit_output(xpad); |
| err_free_in_urb: |
| @@ -1465,17 +1494,19 @@ static void xpad_disconnect(struct usb_interface *intf) |
| { |
| struct usb_xpad *xpad = usb_get_intfdata (intf); |
| |
| - xpad_deinit_input(xpad); |
| - xpad_deinit_output(xpad); |
| - |
| - if (xpad->xtype == XTYPE_XBOX360W) { |
| + if (xpad->xtype == XTYPE_XBOX360W) |
| usb_kill_urb(xpad->irq_in); |
| - } |
| + |
| + cancel_work_sync(&xpad->work); |
| + |
| + xpad_deinit_input(xpad); |
| |
| usb_free_urb(xpad->irq_in); |
| usb_free_coherent(xpad->udev, XPAD_PKT_LEN, |
| xpad->idata, xpad->idata_dma); |
| |
| + xpad_deinit_output(xpad); |
| + |
| kfree(xpad); |
| |
| usb_set_intfdata(intf, NULL); |
| -- |
| 2.17.1 |
| |