Commit 40af28e5 authored by Pavel Rojtberg's avatar Pavel Rojtberg Committed by Greg Kroah-Hartman

Input: xpad - workaround dead irq_out after suspend/ resume

[ Upstream commit 4220f7db ]

The irq_out urb is dead after suspend/ resume on my x360 wr pad. (also
reproduced by Zachary Lund [0]) Work around this by implementing
suspend, resume, and reset_resume callbacks and properly shutting down
URBs on suspend and restarting them on resume.

[0]: https://github.com/paroj/xpad/issues/6Signed-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>
parent 0d93b73d
...@@ -82,6 +82,7 @@ ...@@ -82,6 +82,7 @@
#include <linux/stat.h> #include <linux/stat.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/usb/input.h> #include <linux/usb/input.h>
#include <linux/usb/quirks.h>
#define DRIVER_AUTHOR "Marko Friedemann <mfr@bmx-chemnitz.de>" #define DRIVER_AUTHOR "Marko Friedemann <mfr@bmx-chemnitz.de>"
#define DRIVER_DESC "X-Box pad driver" #define DRIVER_DESC "X-Box pad driver"
...@@ -348,9 +349,10 @@ struct usb_xpad { ...@@ -348,9 +349,10 @@ struct usb_xpad {
dma_addr_t idata_dma; dma_addr_t idata_dma;
struct urb *irq_out; /* urb for interrupt out report */ struct urb *irq_out; /* urb for interrupt out report */
struct usb_anchor irq_out_anchor;
bool irq_out_active; /* we must not use an active URB */ bool irq_out_active; /* we must not use an active URB */
unsigned char *odata; /* output data */
u8 odata_serial; /* serial number for xbox one protocol */ u8 odata_serial; /* serial number for xbox one protocol */
unsigned char *odata; /* output data */
dma_addr_t odata_dma; dma_addr_t odata_dma;
spinlock_t odata_lock; spinlock_t odata_lock;
...@@ -767,11 +769,13 @@ static int xpad_try_sending_next_out_packet(struct usb_xpad *xpad) ...@@ -767,11 +769,13 @@ static int xpad_try_sending_next_out_packet(struct usb_xpad *xpad)
int error; int error;
if (!xpad->irq_out_active && xpad_prepare_next_out_packet(xpad)) { if (!xpad->irq_out_active && xpad_prepare_next_out_packet(xpad)) {
usb_anchor_urb(xpad->irq_out, &xpad->irq_out_anchor);
error = usb_submit_urb(xpad->irq_out, GFP_ATOMIC); error = usb_submit_urb(xpad->irq_out, GFP_ATOMIC);
if (error) { if (error) {
dev_err(&xpad->intf->dev, dev_err(&xpad->intf->dev,
"%s - usb_submit_urb failed with result %d\n", "%s - usb_submit_urb failed with result %d\n",
__func__, error); __func__, error);
usb_unanchor_urb(xpad->irq_out);
return -EIO; return -EIO;
} }
...@@ -813,11 +817,13 @@ static void xpad_irq_out(struct urb *urb) ...@@ -813,11 +817,13 @@ static void xpad_irq_out(struct urb *urb)
} }
if (xpad->irq_out_active) { if (xpad->irq_out_active) {
usb_anchor_urb(urb, &xpad->irq_out_anchor);
error = usb_submit_urb(urb, GFP_ATOMIC); error = usb_submit_urb(urb, GFP_ATOMIC);
if (error) { if (error) {
dev_err(dev, dev_err(dev,
"%s - usb_submit_urb failed with result %d\n", "%s - usb_submit_urb failed with result %d\n",
__func__, error); __func__, error);
usb_unanchor_urb(urb);
xpad->irq_out_active = false; xpad->irq_out_active = false;
} }
} }
...@@ -834,6 +840,8 @@ static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad) ...@@ -834,6 +840,8 @@ static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad)
if (xpad->xtype == XTYPE_UNKNOWN) if (xpad->xtype == XTYPE_UNKNOWN)
return 0; return 0;
init_usb_anchor(&xpad->irq_out_anchor);
xpad->odata = usb_alloc_coherent(xpad->udev, XPAD_PKT_LEN, xpad->odata = usb_alloc_coherent(xpad->udev, XPAD_PKT_LEN,
GFP_KERNEL, &xpad->odata_dma); GFP_KERNEL, &xpad->odata_dma);
if (!xpad->odata) { if (!xpad->odata) {
...@@ -868,8 +876,14 @@ static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad) ...@@ -868,8 +876,14 @@ static int xpad_init_output(struct usb_interface *intf, struct usb_xpad *xpad)
static void xpad_stop_output(struct usb_xpad *xpad) static void xpad_stop_output(struct usb_xpad *xpad)
{ {
if (xpad->xtype != XTYPE_UNKNOWN) if (xpad->xtype != XTYPE_UNKNOWN) {
usb_kill_urb(xpad->irq_out); if (!usb_wait_anchor_empty_timeout(&xpad->irq_out_anchor,
5000)) {
dev_warn(&xpad->intf->dev,
"timed out waiting for output URB to complete, killing\n");
usb_kill_anchored_urbs(&xpad->irq_out_anchor);
}
}
} }
static void xpad_deinit_output(struct usb_xpad *xpad) static void xpad_deinit_output(struct usb_xpad *xpad)
...@@ -1198,32 +1212,73 @@ static void xpad_led_disconnect(struct usb_xpad *xpad) { } ...@@ -1198,32 +1212,73 @@ static void xpad_led_disconnect(struct usb_xpad *xpad) { }
static void xpad_identify_controller(struct usb_xpad *xpad) { } static void xpad_identify_controller(struct usb_xpad *xpad) { }
#endif #endif
static int xpad_open(struct input_dev *dev) static int xpad_start_input(struct usb_xpad *xpad)
{ {
struct usb_xpad *xpad = input_get_drvdata(dev); int error;
/* URB was submitted in probe */
if (xpad->xtype == XTYPE_XBOX360W)
return 0;
xpad->irq_in->dev = xpad->udev;
if (usb_submit_urb(xpad->irq_in, GFP_KERNEL)) if (usb_submit_urb(xpad->irq_in, GFP_KERNEL))
return -EIO; return -EIO;
if (xpad->xtype == XTYPE_XBOXONE) if (xpad->xtype == XTYPE_XBOXONE) {
return xpad_start_xbox_one(xpad); error = xpad_start_xbox_one(xpad);
if (error) {
usb_kill_urb(xpad->irq_in);
return error;
}
}
return 0; return 0;
} }
static void xpad_close(struct input_dev *dev) static void xpad_stop_input(struct usb_xpad *xpad)
{ {
struct usb_xpad *xpad = input_get_drvdata(dev); usb_kill_urb(xpad->irq_in);
}
static int xpad360w_start_input(struct usb_xpad *xpad)
{
int error;
error = usb_submit_urb(xpad->irq_in, GFP_KERNEL);
if (error)
return -EIO;
if (xpad->xtype != XTYPE_XBOX360W) /*
* Send presence packet.
* This will force the controller to resend connection packets.
* This is useful in the case we activate the module after the
* adapter has been plugged in, as it won't automatically
* send us info about the controllers.
*/
error = xpad_inquiry_pad_presence(xpad);
if (error) {
usb_kill_urb(xpad->irq_in); usb_kill_urb(xpad->irq_in);
return error;
}
xpad_stop_output(xpad); return 0;
}
static void xpad360w_stop_input(struct usb_xpad *xpad)
{
usb_kill_urb(xpad->irq_in);
/* Make sure we are done with presence work if it was scheduled */
flush_work(&xpad->work);
}
static int xpad_open(struct input_dev *dev)
{
struct usb_xpad *xpad = input_get_drvdata(dev);
return xpad_start_input(xpad);
}
static void xpad_close(struct input_dev *dev)
{
struct usb_xpad *xpad = input_get_drvdata(dev);
xpad_stop_input(xpad);
} }
static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs) static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs)
...@@ -1284,8 +1339,10 @@ static int xpad_init_input(struct usb_xpad *xpad) ...@@ -1284,8 +1339,10 @@ static int xpad_init_input(struct usb_xpad *xpad)
input_set_drvdata(input_dev, xpad); input_set_drvdata(input_dev, xpad);
input_dev->open = xpad_open; if (xpad->xtype != XTYPE_XBOX360W) {
input_dev->close = xpad_close; input_dev->open = xpad_open;
input_dev->close = xpad_close;
}
__set_bit(EV_KEY, input_dev->evbit); __set_bit(EV_KEY, input_dev->evbit);
...@@ -1459,21 +1516,17 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id ...@@ -1459,21 +1516,17 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
* exactly the message that a controller has arrived that * exactly the message that a controller has arrived that
* we're waiting for. * we're waiting for.
*/ */
xpad->irq_in->dev = xpad->udev; error = xpad360w_start_input(xpad);
error = usb_submit_urb(xpad->irq_in, GFP_KERNEL);
if (error) if (error)
goto err_deinit_output; goto err_deinit_output;
/* /*
* Send presence packet. * Wireless controllers require RESET_RESUME to work properly
* This will force the controller to resend connection packets. * after suspend. Ideally this quirk should be in usb core
* This is useful in the case we activate the module after the * quirk list, but we have too many vendors producing these
* adapter has been plugged in, as it won't automatically * controllers and we'd need to maintain 2 identical lists
* send us info about the controllers. * here in this driver and in usb core.
*/ */
error = xpad_inquiry_pad_presence(xpad); udev->quirks |= USB_QUIRK_RESET_RESUME;
if (error)
goto err_kill_in_urb;
} else { } else {
error = xpad_init_input(xpad); error = xpad_init_input(xpad);
if (error) if (error)
...@@ -1481,8 +1534,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id ...@@ -1481,8 +1534,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
} }
return 0; return 0;
err_kill_in_urb:
usb_kill_urb(xpad->irq_in);
err_deinit_output: err_deinit_output:
xpad_deinit_output(xpad); xpad_deinit_output(xpad);
err_free_in_urb: err_free_in_urb:
...@@ -1492,35 +1543,83 @@ err_free_idata: ...@@ -1492,35 +1543,83 @@ err_free_idata:
err_free_mem: err_free_mem:
kfree(xpad); kfree(xpad);
return error; return error;
} }
static void xpad_disconnect(struct usb_interface *intf) static void xpad_disconnect(struct usb_interface *intf)
{ {
struct usb_xpad *xpad = usb_get_intfdata (intf); struct usb_xpad *xpad = usb_get_intfdata(intf);
if (xpad->xtype == XTYPE_XBOX360W) if (xpad->xtype == XTYPE_XBOX360W)
usb_kill_urb(xpad->irq_in); xpad360w_stop_input(xpad);
cancel_work_sync(&xpad->work);
xpad_deinit_input(xpad); xpad_deinit_input(xpad);
/*
* Now that both input device and LED device are gone we can
* stop output URB.
*/
xpad_stop_output(xpad);
xpad_deinit_output(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);
} }
static int xpad_suspend(struct usb_interface *intf, pm_message_t message)
{
struct usb_xpad *xpad = usb_get_intfdata(intf);
struct input_dev *input = xpad->dev;
if (xpad->xtype == XTYPE_XBOX360W) {
/*
* Wireless controllers always listen to input so
* they are notified when controller shows up
* or goes away.
*/
xpad360w_stop_input(xpad);
} else {
mutex_lock(&input->mutex);
if (input->users)
xpad_stop_input(xpad);
mutex_unlock(&input->mutex);
}
xpad_stop_output(xpad);
return 0;
}
static int xpad_resume(struct usb_interface *intf)
{
struct usb_xpad *xpad = usb_get_intfdata(intf);
struct input_dev *input = xpad->dev;
int retval = 0;
if (xpad->xtype == XTYPE_XBOX360W) {
retval = xpad360w_start_input(xpad);
} else {
mutex_lock(&input->mutex);
if (input->users)
retval = xpad_start_input(xpad);
mutex_unlock(&input->mutex);
}
return retval;
}
static struct usb_driver xpad_driver = { static struct usb_driver xpad_driver = {
.name = "xpad", .name = "xpad",
.probe = xpad_probe, .probe = xpad_probe,
.disconnect = xpad_disconnect, .disconnect = xpad_disconnect,
.suspend = xpad_suspend,
.resume = xpad_resume,
.reset_resume = xpad_resume,
.id_table = xpad_table, .id_table = xpad_table,
}; };
......
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