mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v9] xpad: Overhaul device data for wireless devices
@ 2026-10-15 22:01 Sanjay Govind
  2026-10-21 21:07 ` Dmitry Torokhov
  2026-10-21 22:10 ` [PATCH v10] Input: xpad - overhaul " Sanjay Govind
  0 siblings, 2 replies; 3+ messages in thread
From: Sanjay Govind @ 2026-10-15 22:01 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Antheas Kapenekakis, Qbeliw Tanaka, Kees Cook, Shengyu Qu,
	Kyohei Kadota, Elliot Tester, Dmitriy Zharov, Zoltan Illes,
	linux-input, linux-kernel, Sanjay Govind

Xbox 360 wireless controllers expose information in the link and
capabilities reports.

Extract and use the vendor id for wireless controllers, and use
the subtype to build a nicer device name and product id.

Some xbox 360 controllers put a vid and pid into the stick capability
data, so check if this was done, and pull the vid, pid and revision from
there.

To make sure this information is available when the input device is
created, delay creating it until 500ms after the pad is reported as
present. Some devices never send a capabilities report, so the
timeout ensures they are still initialized.

Note that this changes the ids reported to userspace: wireless pads
were previously all reported as 045e:02a1. They now report the pad's
own vendor id, and a product id of 0x02a0 + subtype, unless the pad
provides its own product id in its capabilities data. This matches
Windows for gamepads (0x02a1) and wheels (0x02a2). Windows reports
all other subtypes as 0x02a9, but as Linux has no other way to expose
the xinput subtype to userspace, the subtype is kept in the product
id for those too. SDL has already been updated to handle this:
https://github.com/libsdl-org/SDL/commit/fdc5efe1cbbcbffda016fbab9d5d9083ba040470

Signed-off-by: Sanjay Govind <sanjay.govind9@gmail.com>
---
v2: Delay marking device as present until after capabilities or timeout
v3: Fix issues when receiving incorrect or missing link and capabilities reports
v4: Clear wireless state when processing device prescence change
v5: Fix typo, fix some potential race conditions with work scheduling
v6: Fix typo, address potential time-of-check time-of-use issues with presence work
v7: Explicitly check if the input device has been initialized already when processing presence changes
v8: Clear presence state when suspending, always clear x360w flags when calling xpad_deinit_input
    https://lore.kernel.org/linux-input/20260818073159.909188-4-sanjay.govind9@gmail.com/
v9: Track a pad generation so a pad that is replaced before the presence
    work runs gets a fresh input device instead of keeping the old one.
    Clear x360w state on the disconnect event rather than in
    xpad_deinit_input, so it can't wipe a new pad's link report.
    Reschedule presence work whenever the input device doesn't match the
    presence state, replacing the v8 suspend change.
    Only take pid/version from the capabilities report when the link
    report provided a vid. Send the capabilities request from its own
    output packet slot. Mask the high vid nibble.
---
 drivers/input/joystick/xpad.c | 241 ++++++++++++++++++++++++++++++----
 1 file changed, 219 insertions(+), 22 deletions(-)

diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index feb8f368f834..bcad597b29b4 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -34,6 +34,7 @@
 #include <linux/slab.h>
 #include <linux/stat.h>
 #include <linux/module.h>
+#include <linux/unaligned.h>
 #include <linux/usb/input.h>
 #include <linux/usb/quirks.h>
 
@@ -60,6 +61,26 @@
 #define XTYPE_XBOXONE     3
 #define XTYPE_UNKNOWN     4
 
+/*
+ * Capability flags reported by x360w pads. Bit 0 matches
+ * XINPUT_CAPS_FFB_SUPPORTED, which is also set from the link report.
+ */
+#define FLAG_FORCE_FEEDBACK	0x01
+
+#define SUBTYPE_GAMEPAD			0x01
+#define SUBTYPE_WHEEL			0x02
+#define SUBTYPE_ARCADE_STICK		0x03
+#define SUBTYPE_FLIGHT_STICK		0x04
+#define SUBTYPE_DANCE_PAD		0x05
+#define SUBTYPE_GUITAR			0x06
+#define SUBTYPE_GUITAR_ALTERNATE	0x07
+#define SUBTYPE_DRUM_KIT		0x08
+#define SUBTYPE_GUITAR_BASS		0x0B
+#define SUBTYPE_RB_KEYBOARD		0x0F
+#define SUBTYPE_ARCADE_PAD		0x13
+#define SUBTYPE_TURNTABLE		0x17
+#define SUBTYPE_PRO_GUITAR		0x19
+
 /* Send power-off packet to xpad360w after holding the mode button for this many
  * seconds
  */
@@ -734,9 +755,10 @@ struct xpad_output_packet {
 #define XPAD_OUT_CMD_IDX	0
 #define XPAD_OUT_FF_IDX		1
 #define XPAD_OUT_LED_IDX	(1 + IS_ENABLED(CONFIG_JOYSTICK_XPAD_FF))
-#define XPAD_NUM_OUT_PACKETS	(1 + \
+#define XPAD_OUT_CAPS_IDX	(1 + \
 				 IS_ENABLED(CONFIG_JOYSTICK_XPAD_FF) + \
 				 IS_ENABLED(CONFIG_JOYSTICK_XPAD_LEDS))
+#define XPAD_NUM_OUT_PACKETS	(XPAD_OUT_CAPS_IDX + 1)
 
 struct usb_xpad {
 	struct input_dev *dev;		/* input device interface */
@@ -746,6 +768,8 @@ struct usb_xpad {
 
 	bool pad_present;
 	bool input_created;
+	unsigned int pad_gen;		/* bumped every time an x360w pad goes away */
+	unsigned int input_gen;		/* pad_gen the input device was created for */
 
 	struct urb *irq_in;		/* urb for interrupt in report */
 	unsigned char *idata;		/* input data */
@@ -773,8 +797,13 @@ struct usb_xpad {
 	int xtype;			/* type of xbox device */
 	int packet_type;		/* type of the extended packet */
 	int pad_nr;			/* the order x360 pads were attached */
+	u8 sub_type;
+	u16 flags;
+	u16 wireless_vid;
+	u16 wireless_pid;
+	u16 wireless_version;
 	const char *name;		/* name of the device */
-	struct work_struct work;	/* init/remove device from callback */
+	struct delayed_work work;	/* init/remove device from callback */
 	time64_t mode_btn_down_ts;
 	bool delay_init;		/* init packets should be delayed */
 	bool delayed_init_done;
@@ -785,6 +814,7 @@ static void xpad_deinit_input(struct usb_xpad *xpad);
 static int xpad_start_input(struct usb_xpad *xpad);
 static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num);
 static void xpad360w_poweroff_controller(struct usb_xpad *xpad);
+static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad);
 
 /*
  *	xpad_process_packet
@@ -958,19 +988,16 @@ static void xpad360_process_packet(struct usb_xpad *xpad, struct input_dev *dev,
 
 static void xpad_presence_work(struct work_struct *work)
 {
-	struct usb_xpad *xpad = container_of(work, struct usb_xpad, work);
+	struct usb_xpad *xpad = container_of(work, struct usb_xpad, work.work);
+	unsigned int gen;
 	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 {
+	/*
+	 * Tear down the input device if the pad went away, or if it was
+	 * replaced by a (possibly different) pad since the device was created.
+	 */
+	if (xpad->input_created &&
+	    (!xpad->pad_present || xpad->input_gen != READ_ONCE(xpad->pad_gen))) {
 		RCU_INIT_POINTER(xpad->x360w_dev, NULL);
 		synchronize_rcu();
 		/*
@@ -979,6 +1006,19 @@ static void xpad_presence_work(struct work_struct *work)
 		 */
 		xpad_deinit_input(xpad);
 	}
+
+	if (xpad->pad_present && !xpad->input_created) {
+		gen = READ_ONCE(xpad->pad_gen);
+		error = xpad_init_input(xpad);
+		if (error) {
+			/* complain only, not much else we can do here */
+			dev_err(&xpad->intf->dev,
+				"unable to init device: %d\n", error);
+		} else {
+			xpad->input_gen = gen;
+			rcu_assign_pointer(xpad->x360w_dev, xpad->dev);
+		}
+	}
 }
 
 /*
@@ -995,18 +1035,98 @@ static void xpad_presence_work(struct work_struct *work)
  * 01.1 - Pad state (Bytes 4+) valid
  *
  */
-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, u32 len)
 {
 	struct input_dev *dev;
 	bool present;
+	u16 parsed_vid;
 
 	/* Presence change */
 	if (data[0] & 0x08) {
 		present = (data[1] & 0x80) != 0;
 
-		if (xpad->pad_present != present) {
+		if (xpad->pad_present && !present) {
+			/* Forget everything we learned about the old pad */
+			WRITE_ONCE(xpad->pad_gen, xpad->pad_gen + 1);
+			xpad->sub_type = 0;
+			xpad->flags = 0;
+			xpad->wireless_vid = 0;
+			xpad->wireless_pid = 0;
+			xpad->wireless_version = 0;
+		}
+
+		/*
+		 * Also reschedule if the input device does not match the
+		 * presence state, e.g. because pending work was cancelled
+		 * on suspend.
+		 */
+		if (xpad->pad_present != present ||
+		    xpad->input_created != present) {
 			xpad->pad_present = present;
-			schedule_work(&xpad->work);
+			if (present) {
+				/*
+				 * Delay marking device as present, so we can make sure
+				 * we have received all the information from the capabilities
+				 * report. Some devices don't send one, so the delay
+				 * guarantees that these devices are still initialized.
+				 */
+				mod_delayed_work(system_percpu_wq,
+						 &xpad->work, msecs_to_jiffies(500));
+			} else {
+				mod_delayed_work(system_percpu_wq, &xpad->work, 0);
+			}
+		}
+	}
+
+	/* Link report */
+	if (len >= 26 && data[0] == 0x00 && data[1] == 0x0F) {
+		xpad->sub_type = data[25] & 0x7f;
+
+		/* Decode vendor id from link report */
+		parsed_vid = ((data[0x16] & 0xf) | (data[0x18] & 0xf) << 4) << 8 |
+			     data[0x17];
+
+		/*
+		 * If the link report doesn't provide a proper vid, it sets the vid to 1.
+		 * In that case we zero out wireless_vid, so that we fall back to the vid
+		 * from the receiver instead.
+		 */
+		if (parsed_vid == 1)
+			parsed_vid = 0;
+
+		/*
+		 * x360w controllers on windows put the subtype into the product
+		 * for wheels and gamepads, but it makes sense to do it for all
+		 * subtypes. This will be used if the capabilities report
+		 * doesn't provide us with a product id later.
+		 */
+		xpad->wireless_vid = parsed_vid;
+		xpad->wireless_pid = 0x02a0 + xpad->sub_type;
+		xpad->wireless_version = 0;
+		xpad->flags = 0;
+
+		if ((data[25] & 0x80) != 0)
+			xpad->flags |= FLAG_FORCE_FEEDBACK;
+
+		xpad_inquiry_pad_capabilities(xpad);
+	}
+
+	/* Capabilities report */
+	if (len >= 21 && data[0] == 0x00 && data[1] == 0x05 && data[5] == 0x12) {
+		xpad->flags |= data[20];
+		/*
+		 * A bunch of vendors started putting vids and pids
+		 * into capabilities data because they can't be
+		 * retrieved by xinput easily.
+		 * Not all of them do though, so check the vids match
+		 * before extracting that info. A vid of 0 means the link
+		 * report did not provide one, so there is nothing to match.
+		 */
+		if (xpad->wireless_vid &&
+		    get_unaligned_le16(data + 10) == xpad->wireless_vid &&
+		    get_unaligned_le16(data + 12)) {
+			xpad->wireless_pid = get_unaligned_le16(data + 12);
+			xpad->wireless_version = get_unaligned_le16(data + 14);
 		}
 	}
 
@@ -1232,7 +1352,7 @@ static void xpad_irq_in(struct urb *urb)
 		xpad360_process_packet(xpad, xpad->dev, 0, xpad->idata);
 		break;
 	case XTYPE_XBOX360W:
-		xpad360w_process_packet(xpad, 0, xpad->idata);
+		xpad360w_process_packet(xpad, 0, xpad->idata, urb->actual_length);
 		break;
 	case XTYPE_XBOXONE:
 		xpadone_process_packet(xpad, 0, xpad->idata, urb->actual_length);
@@ -1473,6 +1593,31 @@ static int xpad_inquiry_pad_presence(struct usb_xpad *xpad)
 	return xpad_try_sending_next_out_packet(xpad);
 }
 
+static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad)
+{
+	struct xpad_output_packet *packet =
+			&xpad->out_packets[XPAD_OUT_CAPS_IDX];
+
+	guard(spinlock_irqsave)(&xpad->odata_lock);
+
+	packet->data[0] = 0x00;
+	packet->data[1] = 0x00;
+	packet->data[2] = 0x02;
+	packet->data[3] = 0x80;
+	packet->data[4] = 0x00;
+	packet->data[5] = 0x00;
+	packet->data[6] = 0x00;
+	packet->data[7] = 0x00;
+	packet->data[8] = 0x00;
+	packet->data[9] = 0x00;
+	packet->data[10] = 0x00;
+	packet->data[11] = 0x00;
+	packet->len = 12;
+	packet->pending = true;
+
+	return xpad_try_sending_next_out_packet(xpad);
+}
+
 static int xpad_start_xbox_one(struct usb_xpad *xpad)
 {
 	int error;
@@ -1870,8 +2015,8 @@ 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);
+	/* Cancel any pending presence work */
+	cancel_delayed_work_sync(&xpad->work);
 }
 
 static int xpad_open(struct input_dev *dev)
@@ -1943,8 +2088,60 @@ static int xpad_init_input(struct usb_xpad *xpad)
 	usb_to_input_id(xpad->udev, &input_dev->id);
 
 	if (xpad->xtype == XTYPE_XBOX360W) {
-		/* x360w controllers and the receiver have different ids */
-		input_dev->id.product = 0x02a1;
+		if (xpad->wireless_vid)
+			input_dev->id.vendor = xpad->wireless_vid;
+		if (xpad->wireless_pid)
+			input_dev->id.product = xpad->wireless_pid;
+		else
+			/* Default product id for x360w controllers */
+			input_dev->id.product = 0x02a1;
+		if (xpad->wireless_version)
+			input_dev->id.version = xpad->wireless_version;
+		switch (xpad->sub_type) {
+		case SUBTYPE_GAMEPAD:
+			input_dev->name = "Xbox 360 Wireless Controller";
+			break;
+		case SUBTYPE_WHEEL:
+			input_dev->name = "Xbox 360 Wireless Wheel";
+			break;
+		case SUBTYPE_ARCADE_STICK:
+			input_dev->name = "Xbox 360 Wireless Arcade Stick";
+			break;
+		case SUBTYPE_FLIGHT_STICK:
+			input_dev->name = "Xbox 360 Wireless Flight Stick";
+			break;
+		case SUBTYPE_DANCE_PAD:
+			input_dev->name = "Xbox 360 Wireless Dance Pad";
+			break;
+		case SUBTYPE_GUITAR:
+			input_dev->name = "Xbox 360 Wireless Guitar";
+			break;
+		case SUBTYPE_GUITAR_ALTERNATE:
+			input_dev->name = "Xbox 360 Wireless Alternate Guitar";
+			break;
+		case SUBTYPE_GUITAR_BASS:
+			input_dev->name = "Xbox 360 Wireless Bass Guitar";
+			break;
+		case SUBTYPE_DRUM_KIT:
+			/* Vendors used force feedback flag to differentiate these */
+			if (xpad->flags & FLAG_FORCE_FEEDBACK)
+				input_dev->name = "Xbox 360 Wireless Guitar Hero Drum Kit";
+			else
+				input_dev->name = "Xbox 360 Wireless Rock Band Drum Kit";
+			break;
+		case SUBTYPE_RB_KEYBOARD:
+			input_dev->name = "Xbox 360 Wireless Rock Band Keyboard";
+			break;
+		case SUBTYPE_ARCADE_PAD:
+			input_dev->name = "Xbox 360 Wireless Arcade Pad";
+			break;
+		case SUBTYPE_TURNTABLE:
+			input_dev->name = "Xbox 360 Wireless DJ Hero Turntable";
+			break;
+		case SUBTYPE_PRO_GUITAR:
+			input_dev->name = "Xbox 360 Wireless Rock Band Pro Guitar";
+			break;
+		}
 	}
 
 	input_dev->dev.parent = &xpad->intf->dev;
@@ -2084,7 +2281,7 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
 		xpad->delay_init = true;
 
 	xpad->packet_type = PKT_XB;
-	INIT_WORK(&xpad->work, xpad_presence_work);
+	INIT_DELAYED_WORK(&xpad->work, xpad_presence_work);
 
 	if (xpad->xtype == XTYPE_UNKNOWN) {
 		if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {

base-commit: 29ee387509a184c3bfff9e26831aacc7b2edc02a
-- 
2.55.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH v9] xpad: Overhaul device data for wireless devices
  2026-10-15 22:01 [PATCH v9] xpad: Overhaul device data for wireless devices Sanjay Govind
@ 2026-10-21 21:07 ` Dmitry Torokhov
  2026-10-21 22:10 ` [PATCH v10] Input: xpad - overhaul " Sanjay Govind
  1 sibling, 0 replies; 3+ messages in thread
From: Dmitry Torokhov @ 2026-10-21 21:07 UTC (permalink / raw)
  To: Sanjay Govind
  Cc: Antheas Kapenekakis, Qbeliw Tanaka, Kees Cook, Shengyu Qu,
	Kyohei Kadota, Elliot Tester, Dmitriy Zharov, Zoltan Illes,
	linux-input, linux-kernel

Hi Sanjay,

On Fri, Oct 16, 2026 at 11:01:28AM +1300, Sanjay Govind wrote:
> Xbox 360 wireless controllers expose information in the link and
> capabilities reports.
>
> Extract and use the vendor id for wireless controllers, and use
> the subtype to build a nicer device name and product id.
>
> Some xbox 360 controllers put a vid and pid into the stick capability
> data, so check if this was done, and pull the vid, pid and revision from
> there.
>
> To make sure this information is available when the input device is
> created, delay creating it until 500ms after the pad is reported as
> present. Some devices never send a capabilities report, so the
> timeout ensures they are still initialized.
...
> @@ -746,6 +768,8 @@ struct usb_xpad {
>  
>  	bool pad_present;
>  	bool input_created;
> +	unsigned int pad_gen;		/* bumped every time an x360w pad goes away */
> +	unsigned int input_gen;		/* pad_gen the input device was created for */
...
> +	u8 sub_type;
> +	u16 flags;
> +	u16 wireless_vid;
> +	u16 wireless_pid;
> +	u16 wireless_version;

I saw your reply to Sashiko about adding a spinlock and snapshotting
the wireless state for v10. Stepping back and looking at the driver as
a whole, grouping the wire-level discovery state into a dedicated
struct protected by a spinlock will also help clean up a few other
races and edge cases in the current state machine:

	struct xpad360w_pad_info {
		bool present;
		unsigned int gen;
		u8 sub_type;
		u8 flags;
		u16 vid;
		u16 pid;
		u16 version;
	};

> @@ -958,19 +988,16 @@ static void xpad360_process_packet(struct usb_xpad *xpad, struct input_dev *dev,
>  
>  static void xpad_presence_work(struct work_struct *work)
>  {
> -	struct usb_xpad *xpad = container_of(work, struct usb_xpad, work);
> +	struct usb_xpad *xpad = container_of(work, struct usb_xpad, work.work);
> +	unsigned int gen;
>  	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 {
> +	/*
> +	 * Tear down the input device if the pad went away, or if it was
> +	 * replaced by a (possibly different) pad since the device was created.
> +	 */
> +	if (xpad->input_created &&
> +	    (!xpad->pad_present || xpad->input_gen != READ_ONCE(xpad->pad_gen))) {
>  		RCU_INIT_POINTER(xpad->x360w_dev, NULL);
>  		synchronize_rcu();
>  		/*
> @@ -979,6 +1006,19 @@ static void xpad_presence_work(struct work_struct *work)
>  		 */
>  		xpad_deinit_input(xpad);
>  	}
> +
> +	if (xpad->pad_present && !xpad->input_created) {
> +		gen = READ_ONCE(xpad->pad_gen);
> +		error = xpad_init_input(xpad);
> +		if (error) {
> +			/* complain only, not much else we can do here */
> +			dev_err(&xpad->intf->dev,
> +				"unable to init device: %d\n", error);
> +		} else {
> +			xpad->input_gen = gen;
> +			rcu_assign_pointer(xpad->x360w_dev, xpad->dev);
> +		}
> +	}
>  }

There are two issues here with how pad_gen and xpad_init_input()
interact:

1. In xpad360w_process_packet(), pad_gen is incremented on disconnect
before pad_present is set to false, whereas xpad_presence_work() checks
xpad->pad_present before reading pad_gen. If xpad_presence_work() races
with a disconnect packet, it can see pad_present == true, read the newly
incremented pad_gen (along with the zeroed metadata), and set
input_gen = pad_gen. When a new controller connects later, pad_gen is
not incremented on connect, so input_gen == pad_gen and the new
controller will never replace the bogus input device.

Bumping gen when a pad connects (or on both connect and disconnect) and
snapshotting the entire struct xpad360w_pad_info atomically under the
spinlock at the start of xpad_presence_work() avoids this completely.

2. xpad_init_input() sleeps while registering the input and LED devices.
If the pad disconnects (or is replaced by another pad, which pushes the
delayed work timer out by 500ms) while xpad_init_input() is executing,
xpad_presence_work() will still publish xpad->x360w_dev upon return.
After xpad_init_input() succeeds, please re-check under the spinlock
that pad_info.present is still true and pad_info.gen == info.gen before
setting xpad->input_gen and assigning xpad->x360w_dev; otherwise tear
the newly created device down via xpad_deinit_input(xpad).

> @@ -995,18 +1035,98 @@ static void xpad_presence_work(struct work_struct *work)
>  static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data, u32 len)
>  {
>  	struct input_dev *dev;
>  	bool present;
> +	u16 parsed_vid;
>  
>  	/* Presence change */
>  	if (data[0] & 0x08) {
>  		present = (data[1] & 0x80) != 0;
>  
> -		if (xpad->pad_present != present) {
> +		if (xpad->pad_present && !present) {
> +			/* Forget everything we learned about the old pad */
> +			WRITE_ONCE(xpad->pad_gen, xpad->pad_gen + 1);
> +			xpad->sub_type = 0;
> +			xpad->flags = 0;
> +			xpad->wireless_vid = 0;
> +			xpad->wireless_pid = 0;
> +			xpad->wireless_version = 0;
> +		}
> +
> +		/*
> +		 * Also reschedule if the input device does not match the
> +		 * presence state, e.g. because pending work was cancelled
> +		 * on suspend.
> +		 */
> +		if (xpad->pad_present != present ||
> +		    xpad->input_created != present) {
>  			xpad->pad_present = present;
> -			schedule_work(&xpad->work);
> +			if (present) {
> +				/*
> +				 * Delay marking device as present, so we can make sure
> +				 * we have received all the information from the capabilities
> +				 * report. Some devices don't send one, so the delay
> +				 * guarantees that these devices are still initialized.
> +				 */
> +				mod_delayed_work(system_percpu_wq,
> +						 &xpad->work, msecs_to_jiffies(500));
> +			} else {
> +				mod_delayed_work(system_percpu_wq, &xpad->work, 0);
> +			}
> +		}
>  	}

Checking xpad->input_created != present here causes a problem during the
500ms discovery window: while waiting for the timer to expire (or while
xpad_init_input() is running), pad_present is true and input_created is
still false. Any status packet (data[0] & 0x08) arriving during that
window with present == true (for example, headset status bit 01.6
changing) will see input_created != present and push the 500ms timer
back again.

The only reason pad_present and input_created can get out of sync across
suspend is that xpad_suspend() calls xpad360w_stop_input(), which cancels
any pending delayed work via cancel_delayed_work_sync(&xpad->work).
Instead of checking input_created in the IRQ handler, you can simply
reconcile the state in xpad_suspend() after stopping input and sending
the optional poweroff command:

	if (xpad->xtype == XTYPE_XBOX360W) {
		xpad360w_stop_input(xpad);

		if (auto_poweroff && xpad->pad_info.present)
			xpad360w_poweroff_controller(xpad);

		xpad->pad_info.present = xpad->input_created;
	}

Also, when a pad disconnects (xpad->pad_info.present && !present), it
would be good to do RCU_INIT_POINTER(xpad->x360w_dev, NULL) immediately
in xpad360w_process_packet() so no further input reports are routed to
the old input_dev while teardown work is pending (especially if another
pad connects right away and reschedules the delayed work for +500ms).

> +	/* Capabilities report */
> +	if (len >= 21 && data[0] == 0x00 && data[1] == 0x05 && data[5] == 0x12) {
> +		xpad->flags |= data[20];
> +		/*
> +		 * A bunch of vendors started putting vids and pids
> +		 * into capabilities data because they can't be
> +		 * retrieved by xinput easily.
> +		 * Not all of them do though, so check the vids match
> +		 * before extracting that info. A vid of 0 means the link
> +		 * report did not provide one, so there is nothing to match.
> +		 */
> +		if (xpad->wireless_vid &&
> +		    get_unaligned_le16(data + 10) == xpad->wireless_vid &&
> +		    get_unaligned_le16(data + 12)) {
> +			xpad->wireless_pid = get_unaligned_le16(data + 12);
> +			xpad->wireless_version = get_unaligned_le16(data + 14);
>  		}
>  	}

Once the capabilities report arrives, we have all the identification
data the controller will ever send. If the presence work is currently
waiting for the 500ms fallback timeout, we can expedite it:

	if (delayed_work_pending(&xpad->work))
		mod_delayed_work(system_percpu_wq, &xpad->work, 0);

That way controllers that respond to the capabilities inquiry are
registered (and have their LEDs initialized) immediately without waiting
the full 500ms, while controllers that do not respond still fall back to
the 500ms timeout.

> +static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad)
> +{
...
> +	return xpad_try_sending_next_out_packet(xpad);
> +}

Since the caller ignores the return value, please make
xpad_inquiry_pad_capabilities() return void (matching
xpadone_ack_mode_report() and xpad360w_poweroff_controller()).

> @@ -1943,8 +2088,60 @@ static int xpad_init_input(struct usb_xpad *xpad)
>  	usb_to_input_id(xpad->udev, &input_dev->id);
>  
>  	if (xpad->xtype == XTYPE_XBOX360W) {
> -		/* x360w controllers and the receiver have different ids */
> -		input_dev->id.product = 0x02a1;
> +		if (xpad->wireless_vid)
> +			input_dev->id.vendor = xpad->wireless_vid;
...
> +		case SUBTYPE_PRO_GUITAR:
> +			input_dev->name = "Xbox 360 Wireless Rock Band Pro Guitar";
> +			break;
> +		}
>  	}

Please factor this Xbox 360 wireless ID/name setup out into a small
helper function (taking input_dev and the snapshotted
const struct xpad360w_pad_info *info) so xpad_init_input() stays
concise.

Thanks.

-- 
Dmitry

^ permalink raw reply	[flat|nested] 3+ messages in thread

* [PATCH v10] Input: xpad - overhaul device data for wireless devices
  2026-10-15 22:01 [PATCH v9] xpad: Overhaul device data for wireless devices Sanjay Govind
  2026-10-21 21:07 ` Dmitry Torokhov
@ 2026-10-21 22:10 ` Sanjay Govind
  1 sibling, 0 replies; 3+ messages in thread
From: Sanjay Govind @ 2026-10-21 22:10 UTC (permalink / raw)
  To: Dmitry Torokhov, Sanjay Govind, Zoltan Illes, Dmitriy Zharov,
	Qbeliw Tanaka, Elliot Tester, Shengyu Qu
  Cc: Antheas Kapenekakis, Kees Cook, Kyohei Kadota, linux-input, linux-kernel

Xbox 360 wireless controllers expose identification information in their
link and capabilities reports. Use the vendor ID and subtype from the
link report to give each controller a more descriptive name and product
ID. Some controllers also encode their vendor ID, product ID and revision
in the stick capability data; use those IDs when the vendor matches the
link report.

Defer input device registration until the capabilities report arrives,
with a 500ms fallback for controllers that do not respond. If discovery
does not provide identification data, retain the receiver-derived
identity and the existing default product ID of 0x02a1.

Protect the wireless discovery state with a spinlock and initialize the
input device from a consistent snapshot. Track controller generations
and revalidate presence and generation after registration, so a
disconnect or replacement during registration cannot publish a stale
input device. Stop routing input immediately on disconnect and reconcile
cancelled discovery or teardown work across suspend.

This changes the IDs reported to userspace when discovery succeeds.
Wireless controllers previously used the receiver's vendor ID and a
product ID of 0x02a1. They now use the controller's vendor ID and a product
ID of 0x02a0 + subtype, unless the capabilities report supplies a product
ID. This matches Windows for gamepads (0x02a1) and wheels (0x02a2).
Windows reports all other subtypes as 0x02a9, but Linux has no other way
to expose the XInput subtype to userspace, so retain the subtype in the
product ID for those devices as well. SDL already handles these IDs:
https://github.com/libsdl-org/SDL/commit/fdc5efe1cbbcbffda016fbab9d5d9083ba040470

Assisted-by: LLM
Signed-off-by: Sanjay Govind <sanjay.govind9@gmail.com>
---
Tool assistance:
Copilot SDK in VS Code assisted with implementing review feedback, code
review, testing preparation, and drafting the commit message. Hardware
testing was performed by the submitter.

Changes in v10:
- Address Sashiko's metadata race report by snapshotting the wireless
  discovery state under a spinlock.
- Following Dmitry's review, group presence, generation and identification
  fields in struct xpad360w_pad_info and increment the generation on connect.
- Revalidate presence and generation under the lock before publishing a
  newly registered input device; tear it down if the controller changed.
- Clear the RCU input pointer immediately when a disconnect is reported.
- Schedule discovery only on presence transitions, so repeated status
  reports do not extend the 500ms timeout.
- Reconcile presence with input device state on suspend, including
  completing teardown if work for an old controller was cancelled.
- Expedite pending registration when a capabilities report arrives.
- Make the capabilities inquiry helper return void and extract wireless
  ID/name setup into a separate helper.

Testing:
- Built with W=1, with force feedback and LED code enabled and disabled.
- Loaded an external module built against the running 7.2.3-arch1-3
  kernel, using an unofficial Xbox 360 wireless receiver, two wireless
  gamepads and a wireless guitar.
- SDL2 enumeration showed both gamepads as 045e:02a1 and the guitar as
  1430:02a7, named "Xbox 360 Wireless Alternate Guitar". Inputs worked.
- Repeated battery-removal/reconnect testing produced fresh input devices.
  One registration used the receiver-name fallback; this did not recur
  in subsequent attempts.
- Powered off multiple controllers and powered them back on together;
  reconnection worked.
- Suspend/resume testing succeeded: both gamepads and the guitar
  registered with the expected names, and inputs worked afterward.
- In a separate test-only build, discarding wireless link reports forced
  all three devices to register with the receiver-name fallback. Inputs
  still worked. This fallback identity matches the pre-patch behaviour.
  This verifies the missing-identification fallback, not
  the packet ordering responsible for the earlier transient event.
  The fault-injection changes are not included in this patch.

Previous revisions:
v9: Track controller generations, clear discovery state on disconnect,
    use a separate capabilities output slot, and validate vendor IDs.
    https://lore.kernel.org/linux-input/20261015220126.102766-3-sanjay.govind9@gmail.com/
v8: Clear presence state on suspend and wireless flags on input teardown.
    https://lore.kernel.org/linux-input/20260818073159.909188-4-sanjay.govind9@gmail.com/
v7: Check whether the input device has already been initialized.
v6: Address presence-work time-of-check/time-of-use issues.
v5: Fix work scheduling races and a typo.
v4: Clear wireless state on presence changes.
v3: Handle incorrect or missing link and capabilities reports.
v2: Defer registration until capabilities discovery or timeout.
---
 drivers/input/joystick/xpad.c | 282 ++++++++++++++++++++++++++++++----
 1 file changed, 251 insertions(+), 31 deletions(-)

diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index feb8f368f834..41348e62fe19 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -34,6 +34,7 @@
 #include <linux/slab.h>
 #include <linux/stat.h>
 #include <linux/module.h>
+#include <linux/unaligned.h>
 #include <linux/usb/input.h>
 #include <linux/usb/quirks.h>
 
@@ -60,6 +61,26 @@
 #define XTYPE_XBOXONE     3
 #define XTYPE_UNKNOWN     4
 
+/*
+ * Capability flags reported by x360w pads. Bit 0 matches
+ * XINPUT_CAPS_FFB_SUPPORTED, which is also set from the link report.
+ */
+#define FLAG_FORCE_FEEDBACK	0x01
+
+#define SUBTYPE_GAMEPAD			0x01
+#define SUBTYPE_WHEEL			0x02
+#define SUBTYPE_ARCADE_STICK		0x03
+#define SUBTYPE_FLIGHT_STICK		0x04
+#define SUBTYPE_DANCE_PAD		0x05
+#define SUBTYPE_GUITAR			0x06
+#define SUBTYPE_GUITAR_ALTERNATE	0x07
+#define SUBTYPE_DRUM_KIT		0x08
+#define SUBTYPE_GUITAR_BASS		0x0B
+#define SUBTYPE_RB_KEYBOARD		0x0F
+#define SUBTYPE_ARCADE_PAD		0x13
+#define SUBTYPE_TURNTABLE		0x17
+#define SUBTYPE_PRO_GUITAR		0x19
+
 /* Send power-off packet to xpad360w after holding the mode button for this many
  * seconds
  */
@@ -734,9 +755,21 @@ struct xpad_output_packet {
 #define XPAD_OUT_CMD_IDX	0
 #define XPAD_OUT_FF_IDX		1
 #define XPAD_OUT_LED_IDX	(1 + IS_ENABLED(CONFIG_JOYSTICK_XPAD_FF))
-#define XPAD_NUM_OUT_PACKETS	(1 + \
+#define XPAD_OUT_CAPS_IDX	(1 + \
 				 IS_ENABLED(CONFIG_JOYSTICK_XPAD_FF) + \
 				 IS_ENABLED(CONFIG_JOYSTICK_XPAD_LEDS))
+#define XPAD_NUM_OUT_PACKETS	(XPAD_OUT_CAPS_IDX + 1)
+
+/* Discovery state learned from x360w presence, link and capabilities reports */
+struct xpad360w_pad_info {
+	bool present;
+	unsigned int gen;
+	u8 sub_type;
+	u8 flags;
+	u16 vid;
+	u16 pid;
+	u16 version;
+};
 
 struct usb_xpad {
 	struct input_dev *dev;		/* input device interface */
@@ -744,8 +777,10 @@ struct usb_xpad {
 	struct usb_device *udev;	/* usb device */
 	struct usb_interface *intf;	/* usb interface */
 
-	bool pad_present;
 	bool input_created;
+	unsigned int input_gen;		/* generation the input device was created for */
+	struct xpad360w_pad_info pad_info;
+	spinlock_t x360w_lock;		/* protects pad_info and x360w_dev publication */
 
 	struct urb *irq_in;		/* urb for interrupt in report */
 	unsigned char *idata;		/* input data */
@@ -774,17 +809,19 @@ struct usb_xpad {
 	int packet_type;		/* type of the extended packet */
 	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 */
+	struct delayed_work work;	/* init/remove device from callback */
 	time64_t mode_btn_down_ts;
 	bool delay_init;		/* init packets should be delayed */
 	bool delayed_init_done;
 };
 
-static int xpad_init_input(struct usb_xpad *xpad);
+static int xpad_init_input(struct usb_xpad *xpad,
+			   const struct xpad360w_pad_info *info);
 static void xpad_deinit_input(struct usb_xpad *xpad);
 static int xpad_start_input(struct usb_xpad *xpad);
 static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num);
 static void xpad360w_poweroff_controller(struct usb_xpad *xpad);
+static void xpad_inquiry_pad_capabilities(struct usb_xpad *xpad);
 
 /*
  *	xpad_process_packet
@@ -940,7 +977,7 @@ static void xpad360_process_packet(struct usb_xpad *xpad, struct input_dev *dev,
 
 	/* XBOX360W controllers can't be turned off without driver assistance */
 	if (xpad->xtype == XTYPE_XBOX360W) {
-		if (xpad->mode_btn_down_ts > 0 && xpad->pad_present &&
+		if (xpad->mode_btn_down_ts > 0 && xpad->pad_info.present &&
 		    ((ktime_get_seconds() - xpad->mode_btn_down_ts) >=
 		     XPAD360W_POWEROFF_TIMEOUT)) {
 			xpad360w_poweroff_controller(xpad);
@@ -958,19 +995,20 @@ static void xpad360_process_packet(struct usb_xpad *xpad, struct input_dev *dev,
 
 static void xpad_presence_work(struct work_struct *work)
 {
-	struct usb_xpad *xpad = container_of(work, struct usb_xpad, work);
+	struct usb_xpad *xpad = container_of(work, struct usb_xpad, work.work);
+	struct xpad360w_pad_info info;
 	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 {
+	/*
+	 * Take a consistent snapshot, as the reports are processed
+	 * concurrently from the irq handler.
+	 */
+	scoped_guard(spinlock_irqsave, &xpad->x360w_lock) {
+		info = xpad->pad_info;
+	}
+
+	/* Remove the input device if its controller disconnected or changed. */
+	if (xpad->input_created && (!info.present || xpad->input_gen != info.gen)) {
 		RCU_INIT_POINTER(xpad->x360w_dev, NULL);
 		synchronize_rcu();
 		/*
@@ -979,6 +1017,26 @@ static void xpad_presence_work(struct work_struct *work)
 		 */
 		xpad_deinit_input(xpad);
 	}
+
+	if (info.present && !xpad->input_created) {
+		error = xpad_init_input(xpad, &info);
+		if (error) {
+			/* complain only, not much else we can do here */
+			dev_err(&xpad->intf->dev,
+				"unable to init device: %d\n", error);
+		} else {
+			scoped_guard(spinlock_irqsave, &xpad->x360w_lock) {
+				if (xpad->pad_info.present &&
+				    xpad->pad_info.gen == info.gen) {
+					xpad->input_gen = info.gen;
+					rcu_assign_pointer(xpad->x360w_dev, xpad->dev);
+					return;
+				}
+			}
+
+			xpad_deinit_input(xpad);
+		}
+	}
 }
 
 /*
@@ -995,19 +1053,83 @@ static void xpad_presence_work(struct work_struct *work)
  * 01.1 - Pad state (Bytes 4+) valid
  *
  */
-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, u32 len)
 {
+	struct xpad360w_pad_info *info = &xpad->pad_info;
 	struct input_dev *dev;
 	bool present;
+	u16 parsed_vid;
 
 	/* Presence change */
 	if (data[0] & 0x08) {
 		present = (data[1] & 0x80) != 0;
 
-		if (xpad->pad_present != present) {
-			xpad->pad_present = present;
-			schedule_work(&xpad->work);
+		guard(spinlock_irqsave)(&xpad->x360w_lock);
+
+		if (info->present != present) {
+			if (!present) {
+				RCU_INIT_POINTER(xpad->x360w_dev, NULL);
+				/* Forget everything we learned about the old pad */
+				*info = (struct xpad360w_pad_info) { .gen = info->gen };
+			}
+			info->present = present;
+			if (present) {
+				info->gen++;
+				/*
+				 * Allow discovery to complete; some pads never
+				 * send capabilities.
+				 */
+				mod_delayed_work(system_percpu_wq,
+						 &xpad->work, msecs_to_jiffies(500));
+			} else {
+				mod_delayed_work(system_percpu_wq, &xpad->work, 0);
+			}
+		}
+	}
+
+	/* Link report */
+	if (len >= 26 && data[0] == 0x00 && data[1] == 0x0F) {
+		/* Decode vendor id from link report */
+		parsed_vid = ((data[0x16] & 0xf) | (data[0x18] & 0xf) << 4) << 8 |
+			     data[0x17];
+
+		/* VID 1 means unavailable; fall back to the receiver's VID. */
+		if (parsed_vid == 1)
+			parsed_vid = 0;
+
+		/* Use a subtype-derived PID unless capabilities supply one. */
+		scoped_guard(spinlock_irqsave, &xpad->x360w_lock) {
+			info->sub_type = data[25] & 0x7f;
+			info->vid = parsed_vid;
+			info->pid = 0x02a0 + info->sub_type;
+			info->version = 0;
+			info->flags = 0;
+
+			if ((data[25] & 0x80) != 0)
+				info->flags |= FLAG_FORCE_FEEDBACK;
 		}
+
+		xpad_inquiry_pad_capabilities(xpad);
+	}
+
+	/* Capabilities report */
+	if (len >= 21 && data[0] == 0x00 && data[1] == 0x05 && data[5] == 0x12) {
+		guard(spinlock_irqsave)(&xpad->x360w_lock);
+
+		info->flags |= data[20];
+		/*
+		 * Some vendors encode identification in the stick capabilities.
+		 * Only trust it when the VID matches the link report.
+		 */
+		if (info->vid &&
+		    get_unaligned_le16(data + 10) == info->vid &&
+		    get_unaligned_le16(data + 12)) {
+			info->pid = get_unaligned_le16(data + 12);
+			info->version = get_unaligned_le16(data + 14);
+		}
+
+		if (info->present && delayed_work_pending(&xpad->work))
+			mod_delayed_work(system_percpu_wq, &xpad->work, 0);
 	}
 
 	/* Valid pad data */
@@ -1232,7 +1354,7 @@ static void xpad_irq_in(struct urb *urb)
 		xpad360_process_packet(xpad, xpad->dev, 0, xpad->idata);
 		break;
 	case XTYPE_XBOX360W:
-		xpad360w_process_packet(xpad, 0, xpad->idata);
+		xpad360w_process_packet(xpad, 0, xpad->idata, urb->actual_length);
 		break;
 	case XTYPE_XBOXONE:
 		xpadone_process_packet(xpad, 0, xpad->idata, urb->actual_length);
@@ -1473,6 +1595,31 @@ static int xpad_inquiry_pad_presence(struct usb_xpad *xpad)
 	return xpad_try_sending_next_out_packet(xpad);
 }
 
+static void xpad_inquiry_pad_capabilities(struct usb_xpad *xpad)
+{
+	struct xpad_output_packet *packet =
+			&xpad->out_packets[XPAD_OUT_CAPS_IDX];
+
+	guard(spinlock_irqsave)(&xpad->odata_lock);
+
+	packet->data[0] = 0x00;
+	packet->data[1] = 0x00;
+	packet->data[2] = 0x02;
+	packet->data[3] = 0x80;
+	packet->data[4] = 0x00;
+	packet->data[5] = 0x00;
+	packet->data[6] = 0x00;
+	packet->data[7] = 0x00;
+	packet->data[8] = 0x00;
+	packet->data[9] = 0x00;
+	packet->data[10] = 0x00;
+	packet->data[11] = 0x00;
+	packet->len = 12;
+	packet->pending = true;
+
+	xpad_try_sending_next_out_packet(xpad);
+}
+
 static int xpad_start_xbox_one(struct usb_xpad *xpad)
 {
 	int error;
@@ -1870,8 +2017,8 @@ 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);
+	/* Cancel any pending presence work */
+	cancel_delayed_work_sync(&xpad->work);
 }
 
 static int xpad_open(struct input_dev *dev)
@@ -1928,7 +2075,68 @@ static void xpad_deinit_input(struct usb_xpad *xpad)
 	}
 }
 
-static int xpad_init_input(struct usb_xpad *xpad)
+static void xpad360w_set_input_info(struct input_dev *input_dev,
+				   const struct xpad360w_pad_info *info)
+{
+	if (info->vid)
+		input_dev->id.vendor = info->vid;
+	if (info->pid)
+		input_dev->id.product = info->pid;
+	else
+		/* Default product id for x360w controllers */
+		input_dev->id.product = 0x02a1;
+	if (info->version)
+		input_dev->id.version = info->version;
+
+	switch (info->sub_type) {
+	case SUBTYPE_GAMEPAD:
+		input_dev->name = "Xbox 360 Wireless Controller";
+		break;
+	case SUBTYPE_WHEEL:
+		input_dev->name = "Xbox 360 Wireless Wheel";
+		break;
+	case SUBTYPE_ARCADE_STICK:
+		input_dev->name = "Xbox 360 Wireless Arcade Stick";
+		break;
+	case SUBTYPE_FLIGHT_STICK:
+		input_dev->name = "Xbox 360 Wireless Flight Stick";
+		break;
+	case SUBTYPE_DANCE_PAD:
+		input_dev->name = "Xbox 360 Wireless Dance Pad";
+		break;
+	case SUBTYPE_GUITAR:
+		input_dev->name = "Xbox 360 Wireless Guitar";
+		break;
+	case SUBTYPE_GUITAR_ALTERNATE:
+		input_dev->name = "Xbox 360 Wireless Alternate Guitar";
+		break;
+	case SUBTYPE_GUITAR_BASS:
+		input_dev->name = "Xbox 360 Wireless Bass Guitar";
+		break;
+	case SUBTYPE_DRUM_KIT:
+		/* Vendors used force feedback flag to differentiate these */
+		if (info->flags & FLAG_FORCE_FEEDBACK)
+			input_dev->name = "Xbox 360 Wireless Guitar Hero Drum Kit";
+		else
+			input_dev->name = "Xbox 360 Wireless Rock Band Drum Kit";
+		break;
+	case SUBTYPE_RB_KEYBOARD:
+		input_dev->name = "Xbox 360 Wireless Rock Band Keyboard";
+		break;
+	case SUBTYPE_ARCADE_PAD:
+		input_dev->name = "Xbox 360 Wireless Arcade Pad";
+		break;
+	case SUBTYPE_TURNTABLE:
+		input_dev->name = "Xbox 360 Wireless DJ Hero Turntable";
+		break;
+	case SUBTYPE_PRO_GUITAR:
+		input_dev->name = "Xbox 360 Wireless Rock Band Pro Guitar";
+		break;
+	}
+}
+
+static int xpad_init_input(struct usb_xpad *xpad,
+			   const struct xpad360w_pad_info *info)
 {
 	struct input_dev *input_dev;
 	int i, error;
@@ -1942,10 +2150,8 @@ static int xpad_init_input(struct usb_xpad *xpad)
 	input_dev->phys = xpad->phys;
 	usb_to_input_id(xpad->udev, &input_dev->id);
 
-	if (xpad->xtype == XTYPE_XBOX360W) {
-		/* x360w controllers and the receiver have different ids */
-		input_dev->id.product = 0x02a1;
-	}
+	if (xpad->xtype == XTYPE_XBOX360W)
+		xpad360w_set_input_info(input_dev, info);
 
 	input_dev->dev.parent = &xpad->intf->dev;
 
@@ -2084,7 +2290,8 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
 		xpad->delay_init = true;
 
 	xpad->packet_type = PKT_XB;
-	INIT_WORK(&xpad->work, xpad_presence_work);
+	INIT_DELAYED_WORK(&xpad->work, xpad_presence_work);
+	spin_lock_init(&xpad->x360w_lock);
 
 	if (xpad->xtype == XTYPE_UNKNOWN) {
 		if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {
@@ -2201,7 +2408,8 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
 		 */
 		udev->quirks |= USB_QUIRK_RESET_RESUME;
 	} else {
-		error = xpad_init_input(xpad);
+		/* Only x360w devices use the device info */
+		error = xpad_init_input(xpad, NULL);
 		if (error)
 			goto err_deinit_output;
 	}
@@ -2263,8 +2471,20 @@ static int xpad_suspend(struct usb_interface *intf, pm_message_t message)
 		 * Unless explicitly disabled, power them down
 		 * so they don't just sit there flashing.
 		 */
-		if (auto_poweroff && xpad->pad_present)
+		if (auto_poweroff && xpad->pad_info.present)
 			xpad360w_poweroff_controller(xpad);
+
+		/* Finish teardown if suspend cancelled work for an old pad. */
+		if (xpad->input_created &&
+		    (!xpad->pad_info.present ||
+		     xpad->input_gen != xpad->pad_info.gen)) {
+			RCU_INIT_POINTER(xpad->x360w_dev, NULL);
+			synchronize_rcu();
+			xpad_deinit_input(xpad);
+		}
+
+		/* A cancelled discovery must be restarted by the resume inquiry. */
+		xpad->pad_info.present = xpad->input_created;
 	} else {
 		guard(mutex)(&input->mutex);
 

base-commit: 29ee387509a184c3bfff9e26831aacc7b2edc02a
-- 
2.55.0


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-10-21 22:11 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-15 22:01 [PATCH v9] xpad: Overhaul device data for wireless devices Sanjay Govind
2026-10-21 21:07 ` Dmitry Torokhov
2026-10-21 22:10 ` [PATCH v10] Input: xpad - overhaul " Sanjay Govind

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®