Commit 09c8b00a authored by Pierre-Loup A. Griffais's avatar Pierre-Loup A. Griffais Committed by Dmitry Torokhov

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

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>
parent 93a017aa
......@@ -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>
......@@ -319,10 +321,12 @@ MODULE_DEVICE_TABLE(usb, xpad_table);
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 */
......@@ -343,8 +347,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
*
......@@ -424,11 +432,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) */
......@@ -495,7 +501,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
......@@ -513,24 +542,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();
}
/*
......@@ -659,7 +692,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);
......@@ -1001,14 +1034,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;
......@@ -1097,8 +1123,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)
......@@ -1181,6 +1210,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:
......@@ -1241,6 +1271,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) {
......@@ -1277,10 +1308,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
......@@ -1292,7 +1319,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.
......@@ -1304,13 +1331,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:
......@@ -1327,17 +1356,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);
......
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