Commit 11de2c5c authored by Pierre-Loup A. Griffais's avatar Pierre-Loup A. Griffais Committed by Kleber Sacilotto de Souza

Input: xpad - handle "present" and "gone" correctly

BugLink: https://bugs.launchpad.net/bugs/1810967

[ Upstream commit 09c8b00a ]

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: default avatar"Pierre-Loup A. Griffais" <pgriffais@valvesoftware.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Signed-off-by: default avatarPavel Rojtberg <rojtberg@gmail.com>
Signed-off-by: default avatarDmitry Torokhov <dmitry.torokhov@gmail.com>
Signed-off-by: default avatarSasha Levin <sashal@kernel.org>
Signed-off-by: default avatarJuerg Haefliger <juergh@canonical.com>
Signed-off-by: default avatarKleber Sacilotto de Souza <kleber.souza@canonical.com>
parent b529ae62
...@@ -76,6 +76,8 @@ ...@@ -76,6 +76,8 @@
*/ */
#include <linux/kernel.h> #include <linux/kernel.h>
#include <linux/input.h>
#include <linux/rcupdate.h>
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/stat.h> #include <linux/stat.h>
#include <linux/module.h> #include <linux/module.h>
...@@ -334,10 +336,12 @@ struct xpad_output_packet { ...@@ -334,10 +336,12 @@ struct xpad_output_packet {
struct usb_xpad { struct usb_xpad {
struct input_dev *dev; /* input device interface */ struct input_dev *dev; /* input device interface */
struct input_dev __rcu *x360w_dev;
struct usb_device *udev; /* usb device */ struct usb_device *udev; /* usb device */
struct usb_interface *intf; /* usb interface */ struct usb_interface *intf; /* usb interface */
int pad_present; bool pad_present;
bool input_created;
struct urb *irq_in; /* urb for interrupt in report */ struct urb *irq_in; /* urb for interrupt in report */
unsigned char *idata; /* input data */ unsigned char *idata; /* input data */
...@@ -362,8 +366,12 @@ struct usb_xpad { ...@@ -362,8 +366,12 @@ struct usb_xpad {
int xtype; /* type of xbox device */ int xtype; /* type of xbox device */
int pad_nr; /* the order x360 pads were attached */ int pad_nr; /* the order x360 pads were attached */
const char *name; /* name of the device */ 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 * xpad_process_packet
* *
...@@ -443,11 +451,9 @@ static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *d ...@@ -443,11 +451,9 @@ static void xpad_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *d
* http://www.free60.org/wiki/Gamepad * 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) u16 cmd, unsigned char *data)
{ {
struct input_dev *dev = xpad->dev;
/* digital pad */ /* digital pad */
if (xpad->mapping & MAP_DPAD_TO_BUTTONS) { if (xpad->mapping & MAP_DPAD_TO_BUTTONS) {
/* dpad as buttons (left, right, up, down) */ /* dpad as buttons (left, right, up, down) */
...@@ -514,7 +520,30 @@ static void xpad360_process_packet(struct usb_xpad *xpad, ...@@ -514,7 +520,30 @@ static void xpad360_process_packet(struct usb_xpad *xpad,
input_sync(dev); 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 * xpad360w_process_packet
...@@ -532,24 +561,28 @@ static void xpad_identify_controller(struct usb_xpad *xpad); ...@@ -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) static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data)
{ {
struct input_dev *dev;
bool present;
/* Presence change */ /* Presence change */
if (data[0] & 0x08) { if (data[0] & 0x08) {
if (data[1] & 0x80) { present = (data[1] & 0x80) != 0;
xpad->pad_present = 1;
/* if (xpad->pad_present != present) {
* Light up the segment corresponding to xpad->pad_present = present;
* controller number. schedule_work(&xpad->work);
*/ }
xpad_identify_controller(xpad);
} else
xpad->pad_present = 0;
} }
/* Valid pad data */ /* Valid pad data */
if (data[1] != 0x1) if (data[1] != 0x1)
return; 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) ...@@ -678,7 +711,7 @@ static void xpad_irq_in(struct urb *urb)
switch (xpad->xtype) { switch (xpad->xtype) {
case XTYPE_XBOX360: case XTYPE_XBOX360:
xpad360_process_packet(xpad, 0, xpad->idata); xpad360_process_packet(xpad, xpad->dev, 0, xpad->idata);
break; break;
case XTYPE_XBOX360W: case XTYPE_XBOX360W:
xpad360w_process_packet(xpad, 0, xpad->idata); xpad360w_process_packet(xpad, 0, xpad->idata);
...@@ -1132,14 +1165,7 @@ static int xpad_led_probe(struct usb_xpad *xpad) ...@@ -1132,14 +1165,7 @@ static int xpad_led_probe(struct usb_xpad *xpad)
if (error) if (error)
goto err_free_id; goto err_free_id;
if (xpad->xtype == XTYPE_XBOX360) { xpad_identify_controller(xpad);
/*
* 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);
}
return 0; return 0;
...@@ -1223,8 +1249,11 @@ static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs) ...@@ -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) static void xpad_deinit_input(struct usb_xpad *xpad)
{ {
xpad_led_disconnect(xpad); if (xpad->input_created) {
input_unregister_device(xpad->dev); xpad->input_created = false;
xpad_led_disconnect(xpad);
input_unregister_device(xpad->dev);
}
} }
static int xpad_init_input(struct usb_xpad *xpad) static int xpad_init_input(struct usb_xpad *xpad)
...@@ -1313,6 +1342,7 @@ static int xpad_init_input(struct usb_xpad *xpad) ...@@ -1313,6 +1342,7 @@ static int xpad_init_input(struct usb_xpad *xpad)
if (error) if (error)
goto err_disconnect_led; goto err_disconnect_led;
xpad->input_created = true;
return 0; return 0;
err_disconnect_led: err_disconnect_led:
...@@ -1366,6 +1396,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id ...@@ -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->mapping = xpad_device[i].mapping;
xpad->xtype = xpad_device[i].xtype; xpad->xtype = xpad_device[i].xtype;
xpad->name = xpad_device[i].name; xpad->name = xpad_device[i].name;
INIT_WORK(&xpad->work, xpad_presence_work);
if (xpad->xtype == XTYPE_UNKNOWN) { if (xpad->xtype == XTYPE_UNKNOWN) {
if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) { 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 ...@@ -1415,10 +1446,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
usb_set_intfdata(intf, xpad); usb_set_intfdata(intf, xpad);
error = xpad_init_input(xpad);
if (error)
goto err_deinit_output;
if (xpad->xtype == XTYPE_XBOX360W) { if (xpad->xtype == XTYPE_XBOX360W) {
/* /*
* Submit the int URB immediately rather than waiting for open * 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 ...@@ -1430,7 +1457,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
xpad->irq_in->dev = xpad->udev; xpad->irq_in->dev = xpad->udev;
error = usb_submit_urb(xpad->irq_in, GFP_KERNEL); error = usb_submit_urb(xpad->irq_in, GFP_KERNEL);
if (error) if (error)
goto err_deinit_input; goto err_deinit_output;
/* /*
* Send presence packet. * Send presence packet.
...@@ -1442,13 +1469,15 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id ...@@ -1442,13 +1469,15 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
error = xpad_inquiry_pad_presence(xpad); error = xpad_inquiry_pad_presence(xpad);
if (error) if (error)
goto err_kill_in_urb; goto err_kill_in_urb;
} else {
error = xpad_init_input(xpad);
if (error)
goto err_deinit_output;
} }
return 0; return 0;
err_kill_in_urb: err_kill_in_urb:
usb_kill_urb(xpad->irq_in); usb_kill_urb(xpad->irq_in);
err_deinit_input:
xpad_deinit_input(xpad);
err_deinit_output: err_deinit_output:
xpad_deinit_output(xpad); xpad_deinit_output(xpad);
err_free_in_urb: err_free_in_urb:
...@@ -1465,17 +1494,19 @@ static void xpad_disconnect(struct usb_interface *intf) ...@@ -1465,17 +1494,19 @@ static void xpad_disconnect(struct usb_interface *intf)
{ {
struct usb_xpad *xpad = usb_get_intfdata (intf); struct usb_xpad *xpad = usb_get_intfdata (intf);
xpad_deinit_input(xpad); if (xpad->xtype == XTYPE_XBOX360W)
xpad_deinit_output(xpad);
if (xpad->xtype == XTYPE_XBOX360W) {
usb_kill_urb(xpad->irq_in); usb_kill_urb(xpad->irq_in);
}
cancel_work_sync(&xpad->work);
xpad_deinit_input(xpad);
usb_free_urb(xpad->irq_in); usb_free_urb(xpad->irq_in);
usb_free_coherent(xpad->udev, XPAD_PKT_LEN, usb_free_coherent(xpad->udev, XPAD_PKT_LEN,
xpad->idata, xpad->idata_dma); xpad->idata, xpad->idata_dma);
xpad_deinit_output(xpad);
kfree(xpad); kfree(xpad);
usb_set_intfdata(intf, NULL); usb_set_intfdata(intf, NULL);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment