* [PATCH V8] xpad: Overhaul device data for wireless devices
@ 2026-08-18 7:32 Sanjay Govind
2026-08-18 7:46 ` sashiko-bot
0 siblings, 1 reply; 2+ messages in thread
From: Sanjay Govind @ 2026-08-18 7:32 UTC (permalink / raw)
To: Dmitry Torokhov, Sanjay Govind, Antheas Kapenekakis,
Qbeliw Tanaka, Kees Cook, Shengyu Qu, Kyohei Kadota,
Elliot Tester
Cc: Dmitriy Zharov, Zoltan Illes, 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>
---
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
drivers/input/joystick/xpad.c | 210 ++++++++++++++++++++++++++++++----
1 file changed, 189 insertions(+), 21 deletions(-)
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index 2da0b7f1722a..4abc57a72524 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,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_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
*/
@@ -775,8 +792,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;
@@ -787,6 +809,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
@@ -960,19 +984,12 @@ 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);
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 {
+ /* Check if the pad presence has changed */
+ if (xpad->pad_present == xpad->input_created)
+ return;
+ if (xpad->input_created) {
RCU_INIT_POINTER(xpad->x360w_dev, NULL);
synchronize_rcu();
/*
@@ -980,6 +997,15 @@ static void xpad_presence_work(struct work_struct *work)
* using input device we can get rid of it.
*/
xpad_deinit_input(xpad);
+ } else {
+ 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 {
+ rcu_assign_pointer(xpad->x360w_dev, xpad->dev);
+ }
}
}
@@ -997,10 +1023,11 @@ 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) {
@@ -1008,7 +1035,65 @@ static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned cha
if (xpad->pad_present != 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] << 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;
+
+ 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 easliy.
+ * Not all of them do though, so check the vids match
+ * before extracting that info.
+ */
+ if (get_unaligned_le16(data + 10) == xpad->wireless_vid) {
+ xpad->wireless_pid = get_unaligned_le16(data + 12);
+ xpad->wireless_version = get_unaligned_le16(data + 14);
}
}
@@ -1234,7 +1319,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);
@@ -1475,6 +1560,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;
@@ -1872,8 +1982,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)
@@ -1923,6 +2033,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->wireless_vid = 0;
+ xpad->wireless_pid = 0;
+ xpad->wireless_version = 0;
+ xpad->flags = 0;
+ xpad->sub_type = 0;
if (xpad->input_created) {
xpad->input_created = false;
xpad_led_disconnect(xpad);
@@ -1945,8 +2060,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;
@@ -2086,7 +2253,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) {
@@ -2267,6 +2434,7 @@ static int xpad_suspend(struct usb_interface *intf, pm_message_t message)
*/
if (auto_poweroff && xpad->pad_present)
xpad360w_poweroff_controller(xpad);
+ xpad->pad_present = false;
} else {
guard(mutex)(&input->mutex);
--
2.55.0
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [PATCH V8] xpad: Overhaul device data for wireless devices
2026-08-18 7:32 [PATCH V8] xpad: Overhaul device data for wireless devices Sanjay Govind
@ 2026-08-18 7:46 ` sashiko-bot
0 siblings, 0 replies; 2+ messages in thread
From: sashiko-bot @ 2026-08-18 7:46 UTC (permalink / raw)
To: Sanjay Govind; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [High] Forcing `xpad->pad_present = false` during suspend creates a permanent state mismatch, leading to an input device leak on disconnect and reusing wrong device properties on reconnect.
- [Medium] Stale wireless properties (`wireless_vid`, `pid`, etc.) leak to subsequent controllers if a controller disconnects before the 500ms initialization delay completes.
- [Medium] Wireless properties are written in interrupt context and read in process context without synchronization, causing a data race.
Pre-existing issues:
- [High] Missing `cancel_delayed_work_sync` in the `xpad_probe` error path causes a Use-After-Free if initialization fails.
--
--- Patch [1]: [PATCH V8] xpad: Overhaul device data for wireless devices ---
commit 3361aff664031bdf598a6c0d59485911918ea3b4
Author: Sanjay Govind <sanjay.govind9@gmail.com>
xpad: Overhaul device data for wireless devices
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.
> diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
> index feb8f368f834e..ad24e3dd15fbb 100644
> --- a/drivers/input/joystick/xpad.c
> +++ b/drivers/input/joystick/xpad.c
[ ... ]
> @@ -958,19 +982,12 @@ 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);
> 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 {
> + /* Check if the pad presence has changed */
> + if (xpad->pad_present == xpad->input_created)
> + return;
[Severity: Medium]
If a controller connects and schedules xpad->work with a 500ms delay, and
then disconnects before the timer expires, xpad->pad_present becomes false.
When xpad_presence_work() runs, it will see pad_present (false) ==
input_created (false) and return early.
Does this early return prevent xpad_deinit_input() from clearing the stale
wireless properties, allowing a subsequent controller to erroneously inherit
them if it connects without a Link report?
[ ... ]
> @@ -995,10 +1021,11 @@ 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;
[ ... ]
> + /* 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] << 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;
> +
> + if ((data[25] & 0x80) != 0)
> + xpad->flags |= FLAG_FORCE_FEEDBACK;
[Severity: Medium]
This function executes in interrupt context, modifying properties like
wireless_vid, sub_type, and flags.
Concurrently, xpad_init_input() runs in process context (workqueue) after
the 500ms delay and reads these exact fields. Since the irq_in URB remains
active, a late Link or Capabilities report can arrive while the workqueue
executes.
Could this data race lead to torn reads or an inconsistent property state
being applied to the device?
[ ... ]
> @@ -2084,7 +2251,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);
[Severity: High]
This is a pre-existing issue, but by changing this to a delayed work with a
500ms window, it becomes much easier to trigger.
If a connection packet arrives immediately after the irq_in URB is submitted,
the interrupt handler will schedule xpad->work with a 500ms delay.
If a subsequent initialization step in xpad_probe() fails, the error path
frees the xpad struct:
err_free_idata:
usb_free_coherent(udev, XPAD_PKT_LEN, xpad->idata, xpad->idata_dma);
err_free_mem:
kfree(xpad);
return error;
Since the error path does not call cancel_delayed_work_sync(), will the
pending work execute 500ms later and trigger a use-after-free on the freed
xpad struct?
[ ... ]
> @@ -2265,6 +2432,7 @@ static int xpad_suspend(struct usb_interface *intf, pm_message_t message)
> */
> if (auto_poweroff && xpad->pad_present)
> xpad360w_poweroff_controller(xpad);
> + xpad->pad_present = false;
> } else {
> guard(mutex)(&input->mutex);
[Severity: High]
If the system suspends, xpad->pad_present is forced to false here, but
xpad->input_created remains true.
If the controller disconnects during suspend, the resume sequence receives a
status packet with present = false. Then xpad360w_process_packet() sees
xpad->pad_present != present (false != false) and drops the event.
Will this prevent xpad_presence_work() from ever being scheduled to call
xpad_deinit_input(), leaking the input device?
Furthermore, if a new controller connects, pad_present becomes true. Would
xpad_presence_work() see pad_present (true) == input_created (true) and
return early, failing to apply the new controller's properties?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260818073159.909188-4-sanjay.govind9@gmail.com?part=1
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2026-08-18 7:46 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-18 7:32 [PATCH V8] xpad: Overhaul device data for wireless devices Sanjay Govind
2026-08-18 7:46 ` sashiko-bot
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.