public inbox for linux-input@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] xpad: Overhaul device data for wireless devices
@ 2026-03-14  7:50 Sanjay Govind
  2026-04-04  5:33 ` Dmitry Torokhov
  0 siblings, 1 reply; 4+ messages in thread
From: Sanjay Govind @ 2026-03-14  7:50 UTC (permalink / raw)
  To: Dmitry Torokhov, Vicki Pfau, Nilton Perim Neto, Mario Limonciello,
	Sanjay Govind, Pierre-Loup A. Griffais
  Cc: Antheas Kapenekakis, linux-input, linux-kernel

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.

Signed-off-by: Sanjay Govind <sanjay.govind9@gmail.com>
---
 drivers/input/joystick/xpad.c | 138 +++++++++++++++++++++++++++++++++-
 1 file changed, 135 insertions(+), 3 deletions(-)

diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index bf4accf3f581..2490eb21a534 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -94,6 +94,22 @@
 #define XTYPE_XBOXONE     3
 #define XTYPE_UNKNOWN     4
 
+#define FLAG_FORCE_FEEDBACK	0x01
+
+#define SUBTYPE_GAMEPAD			 0x01
+#define SUBTYPE_WHEEL			 0x02
+#define SUBTYPE_ARCADE_STICK	 0x03
+#define SUBTYPE_FLIGHT_SICK		 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
  */
@@ -795,6 +811,9 @@ 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;
 	const char *name;		/* name of the device */
 	struct work_struct work;	/* init/remove device from callback */
 	time64_t mode_btn_down_ts;
@@ -807,6 +826,8 @@ 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
@@ -1026,12 +1047,46 @@ static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned cha
 	if (data[0] & 0x08) {
 		present = (data[1] & 0x80) != 0;
 
-		if (xpad->pad_present != present) {
+		/* delay prescence until after we get the link report */
+		if (!present && xpad->pad_present) {
 			xpad->pad_present = present;
 			schedule_work(&xpad->work);
 		}
 	}
 
+	/* Link report */
+	if (data[0] == 0x00 && data[1] == 0x0F) {
+		xpad->sub_type = data[25] & 0x7f;
+
+		/* Decode vendor id from link report */
+		xpad->wireless_vid = ((data[0x16] & 0xf) | data[0x18] << 4) << 8 | data[0x17];
+
+		if ((data[25] & 0x80) != 0)
+			xpad->flags |= FLAG_FORCE_FEEDBACK;
+
+		if (!xpad->pad_present) {
+			xpad->pad_present = true;
+			schedule_work(&xpad->work);
+		}
+		xpad_inquiry_pad_capabilities(xpad);
+	}
+
+	/* Capabilities report */
+	if (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 easliy.
+		 * Not all of them do though, so check the vids match
+		 * before extracting that info.
+		 */
+		if (((data[11] << 8) | data[10]) == xpad->wireless_vid) {
+			xpad->dev->id.product = (data[13] << 8) | data[12];
+			xpad->dev->id.version = (data[15] << 8) | data[14];
+		}
+	}
+
 	/* Valid pad data */
 	if (data[1] != 0x1)
 		return;
@@ -1495,6 +1550,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_CMD_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;
@@ -1965,8 +2045,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;
+		/*
+		 * x360w controllers on windows put the subtype into the product
+		 * for wheels and gamepads, but it makes sense to do it for all
+		 * subtypes
+		 */
+		input_dev->id.product = 0x02a0 + xpad->sub_type;
+		/* If the Link report has provided a vid, it won't be set to 1 */
+		if (xpad->wireless_vid != 1)
+			input_dev->id.vendor = xpad->wireless_vid;
+		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_SICK:
+			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;
-- 
2.53.0


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

* Re: [PATCH] xpad: Overhaul device data for wireless devices
  2026-03-14  7:50 [PATCH] xpad: Overhaul device data for wireless devices Sanjay Govind
@ 2026-04-04  5:33 ` Dmitry Torokhov
  2026-04-04  6:35   ` Sanjay Govind
  0 siblings, 1 reply; 4+ messages in thread
From: Dmitry Torokhov @ 2026-04-04  5:33 UTC (permalink / raw)
  To: Sanjay Govind
  Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello,
	Pierre-Loup A. Griffais, Antheas Kapenekakis, linux-input,
	linux-kernel

Hi Sanjay,

On Sat, Mar 14, 2026 at 08:50:32PM +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.
> 
> Signed-off-by: Sanjay Govind <sanjay.govind9@gmail.com>
> ---
>  drivers/input/joystick/xpad.c | 138 +++++++++++++++++++++++++++++++++-
>  1 file changed, 135 insertions(+), 3 deletions(-)
> 
> diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
> index bf4accf3f581..2490eb21a534 100644
> --- a/drivers/input/joystick/xpad.c
> +++ b/drivers/input/joystick/xpad.c
> @@ -94,6 +94,22 @@
>  #define XTYPE_XBOXONE     3
>  #define XTYPE_UNKNOWN     4
>  
> +#define FLAG_FORCE_FEEDBACK	0x01
> +
> +#define SUBTYPE_GAMEPAD			 0x01
> +#define SUBTYPE_WHEEL			 0x02
> +#define SUBTYPE_ARCADE_STICK	 0x03
> +#define SUBTYPE_FLIGHT_SICK		 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
>   */
> @@ -795,6 +811,9 @@ 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;
>  	const char *name;		/* name of the device */
>  	struct work_struct work;	/* init/remove device from callback */
>  	time64_t mode_btn_down_ts;
> @@ -807,6 +826,8 @@ 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
> @@ -1026,12 +1047,46 @@ static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned cha
>  	if (data[0] & 0x08) {
>  		present = (data[1] & 0x80) != 0;
>  
> -		if (xpad->pad_present != present) {
> +		/* delay prescence until after we get the link report */

s/prescence/presence

But I would say "Only handle no longer being present here. When
a controller appears mark it as present only after receiving link
report".


> +		if (!present && xpad->pad_present) {
>  			xpad->pad_present = present;
>  			schedule_work(&xpad->work);
>  		}
>  	}
>  
> +	/* Link report */
> +	if (data[0] == 0x00 && data[1] == 0x0F) {
> +		xpad->sub_type = data[25] & 0x7f;
> +
> +		/* Decode vendor id from link report */
> +		xpad->wireless_vid = ((data[0x16] & 0xf) | data[0x18] << 4) << 8 | data[0x17];
> +
> +		if ((data[25] & 0x80) != 0)
> +			xpad->flags |= FLAG_FORCE_FEEDBACK;
> +
> +		if (!xpad->pad_present) {
> +			xpad->pad_present = true;
> +			schedule_work(&xpad->work);
> +		}
> +		xpad_inquiry_pad_capabilities(xpad);
> +	}
> +
> +	/* Capabilities report */
> +	if (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 easliy.

easily.

> +		 * Not all of them do though, so check the vids match
> +		 * before extracting that info.
> +		 */
> +		if (((data[11] << 8) | data[10]) == xpad->wireless_vid) {

get_unaaligned_le16()

> +			xpad->dev->id.product = (data[13] << 8) | data[12];

get_unaligned_le16()

> +			xpad->dev->id.version = (data[15] << 8) | data[14];

get_unaligned_le16()

However you should not change product and version once input device is
registered. So you should delay not until after link packet is received,
but until after capabilities packet.

You might need an enum instead of bool for controller state.

> +		}
> +	}
> +
>  	/* Valid pad data */
>  	if (data[1] != 0x1)
>  		return;
> @@ -1495,6 +1550,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_CMD_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;
> @@ -1965,8 +2045,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;
> +		/*
> +		 * x360w controllers on windows put the subtype into the product
> +		 * for wheels and gamepads, but it makes sense to do it for all
> +		 * subtypes
> +		 */
> +		input_dev->id.product = 0x02a0 + xpad->sub_type;

Another option is to use version field...

> +		/* If the Link report has provided a vid, it won't be set to 1 */
> +		if (xpad->wireless_vid != 1)
> +			input_dev->id.vendor = xpad->wireless_vid;
> +		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_SICK:
> +			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;
> +		}

Maybe have an array of

	[type] = "Name"

instead of this switch?

Thanks.

-- 
Dmitry

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

* Re: [PATCH] xpad: Overhaul device data for wireless devices
  2026-04-04  5:33 ` Dmitry Torokhov
@ 2026-04-04  6:35   ` Sanjay Govind
  2026-04-04  6:39     ` Dmitry Torokhov
  0 siblings, 1 reply; 4+ messages in thread
From: Sanjay Govind @ 2026-04-04  6:35 UTC (permalink / raw)
  To: Dmitry Torokhov
  Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello,
	Pierre-Loup A. Griffais, Antheas Kapenekakis, linux-input,
	linux-kernel

> However you should not change product and version once input device is
> registered. So you should delay not until after link packet is received,
> but until after the capabilities packet.
Yeah i wanted to do that originally, but the main issue with that is
some gamepads don't actually send the capabilities data at all. An
Easy solution I could think of is to just start a timer and continue
the init if it expires and we haven't gotten the capabilities data.

> Another option is to use version field...
Yeah that is reasonable, I had just gone with putting it in the
product id like that as it made things easier for SDL, and microsoft
had done that for the wheel subtype already, so some apps already have
the 0x02a2 pid.
Happy to change that though if you'd prefer i use the version field.

> Maybe have an array of
>
>   [type] = "Name"
>
> instead of this switch?
The main problem is there's a lot of gaps in the list of subtypes, and
this also isn't an exhaustive list, there are a few other subtypes out
there that were used for some bespoke controllers, and so using switch
at least means they can keep a generic name if they are used.

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

* Re: [PATCH] xpad: Overhaul device data for wireless devices
  2026-04-04  6:35   ` Sanjay Govind
@ 2026-04-04  6:39     ` Dmitry Torokhov
  0 siblings, 0 replies; 4+ messages in thread
From: Dmitry Torokhov @ 2026-04-04  6:39 UTC (permalink / raw)
  To: Sanjay Govind
  Cc: Vicki Pfau, Nilton Perim Neto, Mario Limonciello,
	Pierre-Loup A. Griffais, Antheas Kapenekakis, linux-input,
	linux-kernel

On April 3, 2026 11:35:35 PM PDT, Sanjay Govind <sanjay.govind9@gmail.com> wrote:
>> However you should not change product and version once input device is
>> registered. So you should delay not until after link packet is received,
>> but until after the capabilities packet.
>Yeah i wanted to do that originally, but the main issue with that is
>some gamepads don't actually send the capabilities data at all. An
>Easy solution I could think of is to just start a timer and continue
>the init if it expires and we haven't gotten the capabilities data.

That sounds sensible. 

>
>> Another option is to use version field...
>Yeah that is reasonable, I had just gone with putting it in the
>product id like that as it made things easier for SDL, and microsoft
>had done that for the wheel subtype already, so some apps already have
>the 0x02a2 pid.
>Happy to change that though if you'd prefer i use the version field.

No, this is fine too.

>
>> Maybe have an array of
>>
>>   [type] = "Name"
>>
>> instead of this switch?
>The main problem is there's a lot of gaps in the list of subtypes, and
>this also isn't an exhaustive list, there are a few other subtypes out
>there that were used for some bespoke controllers, and so using switch
>at least means they can keep a generic name if they are used.

OK, then let's keep it as switch. 

Thanks. 

-- 
Dmitry

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

end of thread, other threads:[~2026-04-04  6:39 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-14  7:50 [PATCH] xpad: Overhaul device data for wireless devices Sanjay Govind
2026-04-04  5:33 ` Dmitry Torokhov
2026-04-04  6:35   ` Sanjay Govind
2026-04-04  6:39     ` Dmitry Torokhov

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