From nobody Mon Sep 28 20:05:10 2026 Received: from mail-pf1-f176.google.com (mail-pf1-f176.google.com [209.85.210.176]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 6A15139DBE5 for ; Tue, 18 Aug 2026 07:35:00 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.210.176 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787038507; cv=none; b=VoTzpoXDmVpgsRLS2j7pfQn7lQTV4vALMkJVgCOHWivkis8im68RJKyPYQ47QFLUB9v+cZFaAhCMJZ7wE44eRx/CJmEQLt4fXczYgnopIU7bSRKyeszoYlD/dlvPX/nnrKLRKOEGdSUu4//WarQrZdrzX1Y/FV5ri7VHvkm8gXw= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1787038507; c=relaxed/simple; bh=0z0gTdzoPcPrh3eZRI1686pp6b8s3zg8HJAqtnjRmsE=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=IEmgtVqvPoPChZw7+c/IvoqHZvjI163Tr52n5e2uxxxLDeQMv9aX+sA997O3odjwNqO8wO5ISZ89+7gfWUjgvmYQ0IIbhfmf4eYiQj3whVRcIVHrHTYLp/rUF8tYgZEXeaE4oIo1dZAAhqnf3Zh+5s/bo3raWVSPMwbN02LvDhE= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=C7TQCeaw; arc=none smtp.client-ip=209.85.210.176 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="C7TQCeaw" Received: by mail-pf1-f176.google.com with SMTP id d2e1a72fcca58-848743155bcso1377603b3a.0 for ; Tue, 18 Aug 2026 00:34:59 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1787038494; x=1787643294; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to:content-type; bh=9T7KEDvoqKG+U4UzPWx4jpA0TMt+PJFCC2frFJ9DG0A=; b=C7TQCeawOXP3mqLuoT4sK39f1hPwQjKiBB/zTSPhRmN+nlR5vJGMWSKN8aBMAxQ950 bXkf1LlzhuR2dqtHjAReCyBzbsAWgbJnUzdx2rGJUbaYuj4v+noH3ploFRT8ShEgh36x Ep0kc6tj4XoYO/QD2+6jBt3uWV4PMeWjw9szTsv7KdD/PZW64y37WNXohkcIvRU+V6Za sQYjtQN2FeVrvPca9wdUioGmi7zLKVg5vMxDYfpOtUwh/PhkYsWbnTYIaSpKusdWkUoR 42FiMDAKkXNQrgX5j+M3EJ0Tg0XBGWWwgG73NfL48VLH8lG2KmP83Q/X4CXEw/SVZmHK WPjw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1787038494; x=1787643294; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to:content-type; bh=9T7KEDvoqKG+U4UzPWx4jpA0TMt+PJFCC2frFJ9DG0A=; b=MOx7vD6Nuqo8tz7T28sPGyYPw4iO7PJ+j2CoKnUmCiI71IaCj/R7JqjF7xF2nW17sg wFfCLj5ozGc0H/LOj0+1X9bZt/7mRwTsLSSHYIcc/rDMZeGQHQqe9BI+Oqghc6mZN2yL y6ch1ne/s71c59k1LGY0VMZFOlpFqG3tY+p75NOQUX/YG0HvOKxYkQ2eoHGAxLJXg771 wtkDTPIyscsO7mISltaXcjMAkfDH82QhWM0y0O1/zYgeTYw+BRUilbCfttmLdZSoostx T/7TvVLqs80XCcXvlZM8CxaYBbWzGL3Kv+UiX+qtUmKd3eB22K1FjqtPw53KlQXikLZH d5gA== X-Forwarded-Encrypted: i=1; AHgh+RptlIXEdBrtzseH4WwyxgypYlIF/vbEzu0OpgvXiuWPnKvzI7gZZbtlcHHn11jbZ2fURB43Dm3uNUkxXuM=@vger.kernel.org X-Gm-Message-State: AOJu0YxTkowinrqoDIpF6vjj27jJJyBgXuhEf7OMTBx3lvhlMeM1coR6 mOszdPxwFKmj/I57vK/yO2aLauVmmTkmYTpGksvT8g+QOQ6IIPMmxJsD X-Gm-Gg: AR+sD10bZWvyNfQm+dlc3GPGUHgrXXIN6Oc3iQPer/Cc/nvPUjhoOFhZm9kSdr3UqIU EY9S/SX4TcJmnXKoylH42A8kunHy2aLeykHyV2OELxX+u5Kz7VR6zkz3q0ElPvV4XIDD6tXtxFT +6hmYR14oN7NRWg4TMEBCXqvtLIF2aFZeUqmQjBJF2PqSY9gDxhh6Vt71P2zziuqjBfttU1QMRI gLLbCvMxtAzbA3fXLr/SqwS+I1VGMU30DNF7N/CkalE3mzUFPYp/eJGesjYHalPxglSmB9BB1cL /wI+aMIN4QxP1By1uaq9G+qPYwsZI/gXZPtVEYcq+n0Og4kcyWVpaV2y8NMggH78IGzp2dZssHx /ZZsgPaNrQE530sqYEhqnX6hPyCCdztVq3wrb7FcFFtYPmZLPf01p6zZwdgNDExrBheObinzeNN hgJFwDLypbMyBP9pY2ZGqxAzuacCJ1PRbbrKzJuYcdQH8tHFyOpI+mlTgtJW1+I3EEHIt29Ftcs sWy5UcrOqqv3lLtMtgTc3lW92BKwR1bQsmtiWc5TMfUMClEVck4qi1nmtXd9QoC9prf/LM1m0YZ HeFaCGRgGC6MgIexFBWi5pI= X-Received: by 2002:a05:6a20:1609:b0:3c1:657:4c95 with SMTP id adf61e73a8af0-3cc71ae4f6amr34464161637.15.1787038493557; Tue, 18 Aug 2026 00:34:53 -0700 (PDT) Received: from sanjays-pc.govzhome.govindz.co.nz ([14.137.23.5]) by smtp.gmail.com with ESMTPSA id 5a478bee46e88-3267c3876c9sm14307366eec.24.2026.08.18.00.34.48 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 18 Aug 2026 00:34:52 -0700 (PDT) From: Sanjay Govind To: Dmitry Torokhov , Sanjay Govind , Antheas Kapenekakis , Qbeliw Tanaka , Kees Cook , Shengyu Qu , Kyohei Kadota , Elliot Tester Cc: Dmitriy Zharov , Zoltan Illes , linux-input@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH V8] xpad: Overhaul device data for wireless devices Date: Tue, 18 Aug 2026 19:32:00 +1200 Message-ID: <20260818073159.909188-4-sanjay.govind9@gmail.com> X-Mailer: git-send-email 2.55.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" 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 --- v2: Delay marking device as present until after capabilities or timeout v3: Fix issues when receiving incorrect or missing link and capabilities re= ports 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 prese= nce 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 cal= ling 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 #include #include +#include #include #include =20 @@ -60,6 +61,22 @@ #define XTYPE_XBOXONE 3 #define XTYPE_UNKNOWN 4 =20 +#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 thi= s 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); + =20 /* * xpad_process_packet @@ -960,19 +984,12 @@ static void xpad360_process_packet(struct usb_xpad *x= pad, struct input_dev *dev, =20 static void xpad_presence_work(struct work_struct *work) { - struct usb_xpad *xpad =3D container_of(work, struct usb_xpad, work); + struct usb_xpad *xpad =3D container_of(work, struct usb_xpad, work.work); int error; - - if (xpad->pad_present) { - error =3D 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 =3D=3D 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 *wor= k) * using input device we can get rid of it. */ xpad_deinit_input(xpad); + } else { + error =3D 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); + } } } =20 @@ -997,10 +1023,11 @@ static void xpad_presence_work(struct work_struct *w= ork) * 01.1 - Pad state (Bytes 4+) valid * */ -static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsign= ed char *data) +static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsign= ed char *data, u32 len) { struct input_dev *dev; bool present; + u16 parsed_vid; =20 /* Presence change */ if (data[0] & 0x08) { @@ -1008,7 +1035,65 @@ static void xpad360w_process_packet(struct usb_xpad = *xpad, u16 cmd, unsigned cha =20 if (xpad->pad_present !=3D present) { xpad->pad_present =3D 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 >=3D 26 && data[0] =3D=3D 0x00 && data[1] =3D=3D 0x0F) { + xpad->sub_type =3D data[25] & 0x7f; + + /* Decode vendor id from link report */ + parsed_vid =3D ((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 =3D=3D 1) + parsed_vid =3D 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 =3D parsed_vid; + xpad->wireless_pid =3D 0x02a0 + xpad->sub_type; + xpad->wireless_version =3D 0; + + if ((data[25] & 0x80) !=3D 0) + xpad->flags |=3D FLAG_FORCE_FEEDBACK; + + xpad_inquiry_pad_capabilities(xpad); + } + + /* Capabilities report */ + if (len >=3D 21 && data[0] =3D=3D 0x00 && data[1] =3D=3D 0x05 && data[5] = =3D=3D 0x12) { + xpad->flags |=3D 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) =3D=3D xpad->wireless_vid) { + xpad->wireless_pid =3D get_unaligned_le16(data + 12); + xpad->wireless_version =3D get_unaligned_le16(data + 14); } } =20 @@ -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); } =20 +static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad) +{ + struct xpad_output_packet *packet =3D + &xpad->out_packets[XPAD_OUT_CMD_IDX]; + + guard(spinlock_irqsave)(&xpad->odata_lock); + + packet->data[0] =3D 0x00; + packet->data[1] =3D 0x00; + packet->data[2] =3D 0x02; + packet->data[3] =3D 0x80; + packet->data[4] =3D 0x00; + packet->data[5] =3D 0x00; + packet->data[6] =3D 0x00; + packet->data[7] =3D 0x00; + packet->data[8] =3D 0x00; + packet->data[9] =3D 0x00; + packet->data[10] =3D 0x00; + packet->data[11] =3D 0x00; + packet->len =3D 12; + packet->pending =3D 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); =20 - /* 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); } =20 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) =20 static void xpad_deinit_input(struct usb_xpad *xpad) { + xpad->wireless_vid =3D 0; + xpad->wireless_pid =3D 0; + xpad->wireless_version =3D 0; + xpad->flags =3D 0; + xpad->sub_type =3D 0; if (xpad->input_created) { xpad->input_created =3D 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); =20 if (xpad->xtype =3D=3D XTYPE_XBOX360W) { - /* x360w controllers and the receiver have different ids */ - input_dev->id.product =3D 0x02a1; + if (xpad->wireless_vid) + input_dev->id.vendor =3D xpad->wireless_vid; + if (xpad->wireless_pid) + input_dev->id.product =3D xpad->wireless_pid; + else + /* Default product id for x360w controllers */ + input_dev->id.product =3D 0x02a1; + if (xpad->wireless_version) + input_dev->id.version =3D xpad->wireless_version; + switch (xpad->sub_type) { + case SUBTYPE_GAMEPAD: + input_dev->name =3D "Xbox 360 Wireless Controller"; + break; + case SUBTYPE_WHEEL: + input_dev->name =3D "Xbox 360 Wireless Wheel"; + break; + case SUBTYPE_ARCADE_STICK: + input_dev->name =3D "Xbox 360 Wireless Arcade Stick"; + break; + case SUBTYPE_FLIGHT_STICK: + input_dev->name =3D "Xbox 360 Wireless Flight Stick"; + break; + case SUBTYPE_DANCE_PAD: + input_dev->name =3D "Xbox 360 Wireless Dance Pad"; + break; + case SUBTYPE_GUITAR: + input_dev->name =3D "Xbox 360 Wireless Guitar"; + break; + case SUBTYPE_GUITAR_ALTERNATE: + input_dev->name =3D "Xbox 360 Wireless Alternate Guitar"; + break; + case SUBTYPE_GUITAR_BASS: + input_dev->name =3D "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 =3D "Xbox 360 Wireless Guitar Hero Drum Kit"; + else + input_dev->name =3D "Xbox 360 Wireless Rock Band Drum Kit"; + break; + case SUBTYPE_RB_KEYBOARD: + input_dev->name =3D "Xbox 360 Wireless Rock Band Keyboard"; + break; + case SUBTYPE_ARCADE_PAD: + input_dev->name =3D "Xbox 360 Wireless Arcade Pad"; + break; + case SUBTYPE_TURNTABLE: + input_dev->name =3D "Xbox 360 Wireless DJ Hero Turntable"; + break; + case SUBTYPE_PRO_GUITAR: + input_dev->name =3D "Xbox 360 Wireless Rock Band Pro Guitar"; + break; + } } =20 input_dev->dev.parent =3D &xpad->intf->dev; @@ -2086,7 +2253,7 @@ static int xpad_probe(struct usb_interface *intf, con= st struct usb_device_id *id xpad->delay_init =3D true; =20 xpad->packet_type =3D PKT_XB; - INIT_WORK(&xpad->work, xpad_presence_work); + INIT_DELAYED_WORK(&xpad->work, xpad_presence_work); =20 if (xpad->xtype =3D=3D XTYPE_UNKNOWN) { if (intf->cur_altsetting->desc.bInterfaceClass =3D=3D USB_CLASS_VENDOR_S= PEC) { @@ -2267,6 +2434,7 @@ static int xpad_suspend(struct usb_interface *intf, p= m_message_t message) */ if (auto_poweroff && xpad->pad_present) xpad360w_poweroff_controller(xpad); + xpad->pad_present =3D false; } else { guard(mutex)(&input->mutex); =20 --=20 2.55.0