* [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP
@ 2026-08-22 2:11 Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 01/12] " Vicki Pfau
` (11 more replies)
0 siblings, 12 replies; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
This is a new version of the previously submitted xbox-gip series.
This introduces a new driver for the Xbox One/Series controller protocol,
officially known as the Gaming Input Protocol, or GIP for short.
Microsoft released documentation on (some of) GIP in late 2024, upon which
this driver is based. Though the documentation was incomplete, it still
provided enough information to warrant a clean start over the previous,
incomplete implementation.
This driver is already at feature parity with the GIP support in xpad,
along with several more enhancements:
- Proper support for parsing message length and fragmented messages
- Metadata parsing, allowing for auto-detection on various parameters,
including the presence and location in the message of the share button,
as well as detection of specific device types
- Controllable LED support
- HID passthrough for the Chatpad
- Battery information support
- Arcade stick support
- Racing wheel support
- Support for the security flow, allowing use with wireless controllers
that include their own dongles. (This does not include support for the
first-party dongle yet.)
The framework set out in this driver also allows future expansion for
specialized device types and additional features more cleanly than xpad.
Future plans include:
- Adding support for more device types, such as flight sticks.
- Exposing a raw character device to enable sending vendor-specific
commands from userspace.
- Event logging to either sysfs or dmesg.
- Support for the headphone jack.
Since the last version this has included several minor bugfixes as adding
support for sending ACME messages and porting the security flow from xone
to allow for use with wireless controllers with their own dongles. It also
adds support for PDP guitar controllers like the RiffMaster.
Vicki Pfau (12):
Input: xbox_gip - Add new driver for Xbox GIP
Input: xpad - Remove Xbox One support
Input: xbox_gip - Add controllable LED support
Input: xbox_gip - Add HID relaying
Input: xbox_gip - Add battery support
Input: xbox_gip - Allow sending fragmented and ACME messages
Input: xbox_gip - Add security implementation from xone
Input: xbox_gip - Add arcade stick support
Input: xbox_gip - Add support for PDP guitar controllers
Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER
HID: Map more automobile simulation inputs
Input: xbox_gip - Add wheel support
Documentation/input/devices/xpad.rst | 17 +-
MAINTAINERS | 6 +
drivers/hid/hid-debug.c | 16 +-
drivers/hid/hid-input.c | 2 +
drivers/input/joystick/Kconfig | 2 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/gip/Kconfig | 21 +
drivers/input/joystick/gip/Makefile | 8 +
drivers/input/joystick/gip/gip-arcade-stick.c | 173 +
drivers/input/joystick/gip/gip-core.c | 3173 +++++++++++++++++
drivers/input/joystick/gip/gip-drivers.c | 222 ++
drivers/input/joystick/gip/gip-pdp-jaguar.c | 129 +
drivers/input/joystick/gip/gip-security.c | 867 +++++
drivers/input/joystick/gip/gip-wheel.c | 349 ++
drivers/input/joystick/gip/gip.h | 423 +++
drivers/input/joystick/xpad.c | 636 +---
include/uapi/linux/input-event-codes.h | 3 +
17 files changed, 5399 insertions(+), 649 deletions(-)
create mode 100644 drivers/input/joystick/gip/Kconfig
create mode 100644 drivers/input/joystick/gip/Makefile
create mode 100644 drivers/input/joystick/gip/gip-arcade-stick.c
create mode 100644 drivers/input/joystick/gip/gip-core.c
create mode 100644 drivers/input/joystick/gip/gip-drivers.c
create mode 100644 drivers/input/joystick/gip/gip-pdp-jaguar.c
create mode 100644 drivers/input/joystick/gip/gip-security.c
create mode 100644 drivers/input/joystick/gip/gip-wheel.c
create mode 100644 drivers/input/joystick/gip/gip.h
--
2.54.0
^ permalink raw reply [flat|nested] 28+ messages in thread
* [PATCH v4 01/12] Input: xbox_gip - Add new driver for Xbox GIP
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 02/12] Input: xpad - Remove Xbox One support Vicki Pfau
` (10 subsequent siblings)
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
This introduces a new driver for the Xbox One/Series controller protocol,
officially known as the Gaming Input Protocol, or GIP for short.
Microsoft released documentation on (some of) GIP in late 2024, upon which
this driver is based. Though the documentation was incomplete, it still
provided enough information to warrant a clean start over the previous,
incomplete implementation.
This driver is already at feature parity with the GIP support in xpad,
along with several more enhancements:
- Proper support for parsing message length and fragmented messages
- Metadata parsing, allowing for auto-detection on various parameters,
including the presence and location in the message of the share button,
as well as detection of specific device types
The framework set out in this driver also allows future expansion for
specialized device types and additional features more cleanly than xpad.
Future plans include:
- Adding support for more device types, such as arcade sticks, racing
wheels and flight sticks.
- Support for the security handshake, which is required for devices that
use wireless dongles.
- Exposing a raw character device to enable sending vendor-specific
commands from userspace.
- Event logging to either sysfs or dmesg.
- Support for the headphone jack.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
MAINTAINERS | 6 +
drivers/input/joystick/Kconfig | 2 +
drivers/input/joystick/Makefile | 1 +
drivers/input/joystick/gip/Kconfig | 21 +
drivers/input/joystick/gip/Makefile | 4 +
drivers/input/joystick/gip/gip-core.c | 2569 ++++++++++++++++++++++
drivers/input/joystick/gip/gip-drivers.c | 204 ++
drivers/input/joystick/gip/gip.h | 344 +++
8 files changed, 3151 insertions(+)
create mode 100644 drivers/input/joystick/gip/Kconfig
create mode 100644 drivers/input/joystick/gip/Makefile
create mode 100644 drivers/input/joystick/gip/gip-core.c
create mode 100644 drivers/input/joystick/gip/gip-drivers.c
create mode 100644 drivers/input/joystick/gip/gip.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 806bd2d80d15..7fdf1b989667 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -29347,6 +29347,12 @@ S: Maintained
F: drivers/media/rc/keymaps/rc-xbox-dvd.c
F: drivers/media/rc/xbox_remote.c
+XBOX GIP
+M: Vicki Pfau <vi@endrift.com>
+L: linux-input@vger.kernel.org
+S: Maintained
+F: drivers/input/joystick/gip/
+
XC2028/3028 TUNER DRIVER
M: Mauro Carvalho Chehab <mchehab@kernel.org>
L: linux-media@vger.kernel.org
diff --git a/drivers/input/joystick/Kconfig b/drivers/input/joystick/Kconfig
index 7755e5b454d2..d4665c80a371 100644
--- a/drivers/input/joystick/Kconfig
+++ b/drivers/input/joystick/Kconfig
@@ -291,6 +291,8 @@ config JOYSTICK_JOYDUMP
To compile this driver as a module, choose M here: the
module will be called joydump.
+source "drivers/input/joystick/gip/Kconfig"
+
config JOYSTICK_XPAD
tristate "Xbox gamepad support"
depends on USB_ARCH_HAS_HCD
diff --git a/drivers/input/joystick/Makefile b/drivers/input/joystick/Makefile
index 9976f596a920..323392921b7d 100644
--- a/drivers/input/joystick/Makefile
+++ b/drivers/input/joystick/Makefile
@@ -39,5 +39,6 @@ obj-$(CONFIG_JOYSTICK_TURBOGRAFX) += turbografx.o
obj-$(CONFIG_JOYSTICK_TWIDJOY) += twidjoy.o
obj-$(CONFIG_JOYSTICK_WARRIOR) += warrior.o
obj-$(CONFIG_JOYSTICK_WALKERA0701) += walkera0701.o
+obj-$(CONFIG_JOYSTICK_XBOX_GIP) += gip/
obj-$(CONFIG_JOYSTICK_XPAD) += xpad.o
obj-$(CONFIG_JOYSTICK_ZHENHUA) += zhenhua.o
diff --git a/drivers/input/joystick/gip/Kconfig b/drivers/input/joystick/gip/Kconfig
new file mode 100644
index 000000000000..2ce789d23178
--- /dev/null
+++ b/drivers/input/joystick/gip/Kconfig
@@ -0,0 +1,21 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+#
+# Gaming Input Protocol driver configuration
+#
+config JOYSTICK_XBOX_GIP
+ tristate "Xbox One/Series controller support"
+ depends on USB && USB_ARCH_HAS_HCD
+ help
+ Say Y here if you want to use Xbox One and Series controllers with your
+ computer. Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
+ and/or "Event interface support" (CONFIG_INPUT_EVDEV) as well.
+
+ To compile this driver as a module, choose M here: the
+ module will be called xbox_gip.
+
+config JOYSTICK_XBOX_GIP_FF
+ bool "Xbox One/Series controller rumble support"
+ depends on JOYSTICK_XBOX_GIP && INPUT
+ select INPUT_FF_MEMLESS
+ help
+ Say Y here if you want to take advantage of Xbox One/Series rumble.
diff --git a/drivers/input/joystick/gip/Makefile b/drivers/input/joystick/gip/Makefile
new file mode 100644
index 000000000000..d39f946db1ae
--- /dev/null
+++ b/drivers/input/joystick/gip/Makefile
@@ -0,0 +1,4 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+obj-$(CONFIG_JOYSTICK_XBOX_GIP) += xbox-gip.o
+# Base driver
+xbox-gip-y := gip-core.o gip-drivers.o
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
new file mode 100644
index 000000000000..12a1fb285bfc
--- /dev/null
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -0,0 +1,2569 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gaming Input Protocol driver for Xbox One/Series controllers
+ *
+ * Copyright (c) 2025 Valve Software
+ *
+ * TODO:
+ * - Audio device support
+ * - Security packet handshake
+ * - Event logging
+ * - Sending fragmented messages
+ * - Raw character device
+ * - Wheel support
+ * - Flight stick support
+ * - Arcade stick support
+ * - Split into driver-per-attachment GIP-as-a-bus approach drivers
+ *
+ * This driver is based on the Microsoft GIP spec at:
+ * https://aka.ms/gipdocs
+ * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc
+ */
+
+#include <linux/module.h>
+#include <linux/unaligned.h>
+#include <linux/uuid.h>
+#include "gip.h"
+
+#define GIP_WIRED_INTF_DATA 0
+#define GIP_WIRED_INTF_AUDIO 1
+
+#define MAX_MESSAGE_LENGTH 0x4000
+
+#define MAX_AUDIO_MESSAGES 9
+
+#define GIP_DATA_CLASS_COMMAND (0u << 5)
+#define GIP_DATA_CLASS_LOW_LATENCY (1u << 5)
+#define GIP_DATA_CLASS_STANDARD_LATENCY (2u << 5)
+#define GIP_DATA_CLASS_AUDIO (3u << 5)
+
+#define GIP_DATA_CLASS_SHIFT 5
+#define GIP_DATA_CLASS_MASK (7u << 5)
+
+/* Undocumented Elite 2 vendor messages */
+#define GIP_CMD_RAW_REPORT 0x0c
+#define GIP_CMD_GUIDE_COLOR 0x0e
+#define GIP_SL_ELITE_CONFIG 0x4d
+
+#define GIP_BTN_OFFSET_XBE1 28
+#define GIP_BTN_OFFSET_XBE2 14
+
+#define GIP_AUDIO_FORMAT_NULL 0
+#define GIP_AUDIO_FORMAT_8000HZ_1CH 1
+#define GIP_AUDIO_FORMAT_8000HZ_2CH 2
+#define GIP_AUDIO_FORMAT_12000HZ_1CH 3
+#define GIP_AUDIO_FORMAT_12000HZ_2CH 4
+#define GIP_AUDIO_FORMAT_16000HZ_1CH 5
+#define GIP_AUDIO_FORMAT_16000HZ_2CH 6
+#define GIP_AUDIO_FORMAT_20000HZ_1CH 7
+#define GIP_AUDIO_FORMAT_20000HZ_2CH 8
+#define GIP_AUDIO_FORMAT_24000HZ_1CH 9
+#define GIP_AUDIO_FORMAT_24000HZ_2CH 10
+#define GIP_AUDIO_FORMAT_32000HZ_1CH 11
+#define GIP_AUDIO_FORMAT_32000HZ_2CH 12
+#define GIP_AUDIO_FORMAT_40000HZ_1CH 13
+#define GIP_AUDIO_FORMAT_40000HZ_2CH 14
+#define GIP_AUDIO_FORMAT_48000HZ_1CH 15
+#define GIP_AUDIO_FORMAT_48000HZ_2CH 16
+#define GIP_AUDIO_FORMAT_48000HZ_6CH 32
+#define GIP_AUDIO_FORMAT_48000HZ_8CH 33
+#define MAX_GIP_AUDIO_FORMAT GIP_AUDIO_FORMAT_48000HZ_8CH
+
+/* Protocol Control constants */
+#define GIP_CONTROL_CODE_ACK 0
+#define GIP_CONTROL_CODE_NACK 1 /* obsolete */
+#define GIP_CONTROL_CODE_UNK 2 /* obsolete */
+#define GIP_CONTROL_CODE_AB 3 /* obsolete */
+#define GIP_CONTROL_CODE_MPER 4 /* obsolete */
+#define GIP_CONTROL_CODE_STOP 5 /* obsolete */
+#define GIP_CONTROL_CODE_START 6 /* obsolete */
+#define GIP_CONTROL_CODE_ERR 7 /* obsolete */
+
+/* Status Device constants */
+#define GIP_POWER_LEVEL_OFF 0
+#define GIP_POWER_LEVEL_STANDBY 1 /* obsolete */
+#define GIP_POWER_LEVEL_FULL 2
+
+#define GIP_NOT_CHARGING 0
+#define GIP_CHARGING 1
+#define GIP_CHARGE_ERROR 2
+
+#define GIP_BATTERY_ABSENT 0
+#define GIP_BATTERY_STANDARD 1
+#define GIP_BATTERY_RECHARGEABLE 2
+
+#define GIP_BATTERY_CRITICAL 0
+#define GIP_BATTERY_LOW 1
+#define GIP_BATTERY_MEDIUM 2
+#define GIP_BATTERY_FULL 3
+
+#define GIP_EVENT_FAULT 0x0002
+
+#define GIP_FAULT_UNKNOWN 0
+#define GIP_FAULT_HARD 1
+#define GIP_FAULT_NMI 2
+#define GIP_FAULT_SVC 3
+#define GIP_FAULT_PEND_SV 4
+#define GIP_FAULT_SMART_PTR 5
+#define GIP_FAULT_MCU 6
+#define GIP_FAULT_BUS 7
+#define GIP_FAULT_USAGE 8
+#define GIP_FAULT_RADIO_HANG 9
+#define GIP_FAULT_WATCHDOG 10
+#define GIP_FAULT_LINK_STALL 11
+#define GIP_FAULT_ASSERTION 12
+
+/* Metadata constants */
+#define GIP_MESSAGE_FLAG_BIG_ENDIAN BIT(0)
+#define GIP_MESSAGE_FLAG_RELIABLE BIT(1)
+#define GIP_MESSAGE_FLAG_SEQUENCED BIT(2)
+#define GIP_MESSAGE_FLAG_DOWNSTREAM BIT(3)
+#define GIP_MESSAGE_FLAG_UPSTREAM BIT(4)
+#define GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE BIT(5)
+
+#define GIP_DATA_TYPE_CUSTOM 1
+#define GIP_DATA_TYPE_AUDIO 2
+#define GIP_DATA_TYPE_SECURITY 3
+#define GIP_DATA_TYPE_GIP 4
+
+/* Set Device State constants */
+#define GIP_STATE_START 0
+#define GIP_STATE_STOP 1
+#define GIP_STATE_STANDBY 2 /* obsolete */
+#define GIP_STATE_FULL_POWER 3
+#define GIP_STATE_OFF 4
+#define GIP_STATE_QUIESCE 5
+#define GIP_STATE_UNK6 6
+#define GIP_STATE_RESET 7
+
+/* Guide Button Status constants */
+#define GIP_LED_GUIDE 0
+#define GIP_LED_IR 1 /* deprecated, for Kinect */
+
+#define GIP_LED_GUIDE_OFF 0
+#define GIP_LED_GUIDE_ON 1
+#define GIP_LED_GUIDE_FAST_BLINK 2
+#define GIP_LED_GUIDE_SLOW_BLINK 3
+#define GIP_LED_GUIDE_CHARGING_BLINK 4
+#define GIP_LED_GUIDE_RAMP_TO_LEVEL 0xd
+
+#define GIP_LED_IR_OFF 0
+#define GIP_LED_IR_ON_100MS 1
+#define GIP_LED_IR_PATTERN 4
+
+/* Direct Motor Command constants */
+#define GIP_MOTOR_RIGHT_VIBRATION BIT(0)
+#define GIP_MOTOR_LEFT_VIBRATION BIT(1)
+#define GIP_MOTOR_RIGHT_IMPULSE BIT(2)
+#define GIP_MOTOR_LEFT_IMPULSE BIT(3)
+#define GIP_MOTOR_ALL 0xf
+
+/* Extended Command constants */
+#define GIP_EXTCMD_GET_CAPABILITIES 0x00
+#define GIP_EXTCMD_GET_TELEMETRY_DATA 0x01
+#define GIP_EXTCMD_GET_SERIAL_NUMBER 0x04
+
+#define GIP_EXTENDED_STATUS_OK 0
+#define GIP_EXTENDED_STATUS_NOT_SUPPORTED 1
+#define GIP_EXTENDED_STATUS_NOT_READY 2
+#define GIP_EXTENDED_STATUS_ACCESS_DENIED 3
+#define GIP_EXTENDED_STATUS_FAILED 4
+
+/* Internal constants, not part of protocol */
+#define GIP_DEFAULT_IN_SYSTEM_MESSAGES 0x5e
+#define GIP_DEFAULT_OUT_SYSTEM_MESSAGES 0x472
+
+#define GIP_LED_GUIDE_MAX_BRIGHTNESS 100 /* Spec says 47, but larger values work */
+#define GIP_LED_GUIDE_INIT_BRIGHTNESS 20
+
+#ifndef VK_LWIN
+#define VK_LWIN 0x5b
+#endif
+
+static const guid_t guid_console_function_map =
+ GUID_INIT(0xecddd2fe, 0xd387, 0x4294, 0xbd, 0x96, 0x1a, 0x71, 0x2e, 0x3d, 0xc7, 0x7d);
+static const guid_t guid_console_function_map_overflow =
+ GUID_INIT(0x137d4bd0, 0x9347, 0x4472, 0xaa, 0x26, 0x8c, 0x34, 0xa0, 0x8f, 0xf9, 0xbd);
+static const guid_t guid_controller =
+ GUID_INIT(0x9776ff56, 0x9bfd, 0x4581, 0xad, 0x45, 0xb6, 0x45, 0xbb, 0xa5, 0x26, 0xd6);
+static const guid_t guid_dev_auth_pc_opt_out =
+ GUID_INIT(0x7a34ce77, 0x7de2, 0x45c6, 0x8c, 0xa4, 0x00, 0x42, 0xc0, 0x8b, 0xd9, 0x4a);
+static const guid_t guid_dynamic_latency_input =
+ GUID_INIT(0x87f2e56b, 0xc3bb, 0x49b1, 0x82, 0x65, 0xff, 0xff, 0xf3, 0x77, 0x99, 0xee);
+static const guid_t guid_elite_buttons =
+ GUID_INIT(0x37d19ff7, 0xb5c6, 0x49d1, 0xa7, 0x5e, 0x03, 0xb2, 0x4b, 0xef, 0x8c, 0x89);
+static const guid_t guid_headset =
+ GUID_INIT(0xbc25d1a3, 0xc24e, 0x4992, 0x9d, 0xda, 0xef, 0x4f, 0x12, 0x3e, 0xf5, 0xdc);
+
+/*
+ * The following GUIDs are observed, but the exact meanings aren't known, so
+ * for now we document them but don't use them anywhere.
+ *
+ * GamepadEmu:
+ * GUID_INIT(0xe2e5f1bc, 0xa6e6, 0x41a2, 0x8f, 0x43, 0x33, 0xcf, 0xa2, 0x51, 0x09, 0x81)
+ * IAudioOnly:
+ * GUID_INIT(0x92844cd1, 0xf7c8, 0x49ef, 0x97, 0x77, 0x46, 0x7d, 0xa7, 0x08, 0xad, 0x10)
+ * IControllerProfileModeState:
+ * GUID_INIT(0xf758dc66, 0x022c, 0x48b8, 0xa4, 0xf6, 0x45, 0x7b, 0xa8, 0x0e, 0x2a, 0x5b)
+ * ICustomAudio:
+ * GUID_INIT(0x63fd9cc9, 0x94ee, 0x4b5d, 0x9c, 0x4d, 0x8b, 0x86, 0x4c, 0x14, 0x9c, 0xac)
+ * IExtendedDeviceFlags:
+ * GUID_INIT(0x34ad9b1e, 0x36ad, 0x4fb5, 0x8a, 0xc7, 0x17, 0x23, 0x4c, 0x9f, 0x54, 0x6f)
+ * IProgrammableGamepad:
+ * GUID_INIT(0x31c1034d, 0xb5b7, 0x4551, 0x98, 0x13, 0x87, 0x69, 0xd4, 0xa0, 0xe4, 0xf9)
+ * IVirtualDevice:
+ * GUID_INIT(0xdfd26825, 0x110a, 0x4e94, 0xb9, 0x37, 0xb2, 0x7c, 0xe4, 0x7b, 0x25, 0x40)
+ * OnlineDevAuth:
+ * GUID_INIT(0x632b1fd1, 0xa3e9, 0x44f9, 0x84, 0x20, 0x5c, 0xe3, 0x44, 0xa0, 0x64, 0x04)
+ *
+ * Seen on Elite Controller, Adaptive Controller: 9ebd00a3-b5e6-4c08-a33b-673126459ec4
+ * Seen on Adaptive Controller: ce1e58c5-221c-4bdb-9c24-bf3941601320
+ * Seen on Adaptive Joystick: db02f681-5038-4219-8668-c3459c5c3293
+ * Seen on Elite 2 Controller: f758dc66-022c-48b8-a4f6-457ba80e2a5b (IControllerProfileModeState)
+ * Seen on Elite 2 Controller: 31c1034d-b5b7-4551-9813-8769d4a0e4f9 (IProgrammableGamepad)
+ * Seen on Elite 2 Controller: 34ad9b1e-36ad-4fb5-8ac7-17234c9f546f (IExtendedDeviceFlags)
+ * Seen on Elite 2 Controller: 88e0b694-6bd9-4416-a560-e7fafdfa528f
+ * Seen on Elite 2 Controller: ea96c8c0-b216-448b-be80-7e5deb0698e2
+ */
+
+static const int gip_data_class_mtu[8] = { 64, 64, 64, 2048, 0, 0, 0, 0 };
+
+struct gip_audio_format {
+ uint16_t rate;
+ uint8_t channels;
+};
+
+static const struct gip_audio_format gip_audio_format_table[MAX_GIP_AUDIO_FORMAT + 1] = {
+ [GIP_AUDIO_FORMAT_8000HZ_1CH] = { .rate = 8000, .channels = 1 },
+ [GIP_AUDIO_FORMAT_8000HZ_2CH] = { .rate = 8000, .channels = 2 },
+ [GIP_AUDIO_FORMAT_12000HZ_1CH] = { .rate = 12000, .channels = 1 },
+ [GIP_AUDIO_FORMAT_12000HZ_2CH] = { .rate = 12000, .channels = 2 },
+ [GIP_AUDIO_FORMAT_16000HZ_1CH] = { .rate = 16000, .channels = 1 },
+ [GIP_AUDIO_FORMAT_16000HZ_2CH] = { .rate = 16000, .channels = 2 },
+ [GIP_AUDIO_FORMAT_20000HZ_1CH] = { .rate = 20000, .channels = 1 },
+ [GIP_AUDIO_FORMAT_20000HZ_2CH] = { .rate = 20000, .channels = 2 },
+ [GIP_AUDIO_FORMAT_24000HZ_1CH] = { .rate = 24000, .channels = 1 },
+ [GIP_AUDIO_FORMAT_24000HZ_2CH] = { .rate = 24000, .channels = 2 },
+ [GIP_AUDIO_FORMAT_32000HZ_1CH] = { .rate = 32000, .channels = 1 },
+ [GIP_AUDIO_FORMAT_32000HZ_2CH] = { .rate = 32000, .channels = 2 },
+ [GIP_AUDIO_FORMAT_40000HZ_1CH] = { .rate = 40000, .channels = 1 },
+ [GIP_AUDIO_FORMAT_40000HZ_2CH] = { .rate = 40000, .channels = 2 },
+ [GIP_AUDIO_FORMAT_48000HZ_1CH] = { .rate = 48000, .channels = 1 },
+ [GIP_AUDIO_FORMAT_48000HZ_2CH] = { .rate = 48000, .channels = 2 },
+ [GIP_AUDIO_FORMAT_48000HZ_6CH] = { .rate = 48000, .channels = 6 },
+ [GIP_AUDIO_FORMAT_48000HZ_8CH] = { .rate = 48000, .channels = 8 },
+};
+
+
+static const struct gip_quirks base_quirks[] = {
+ { GIP_VID_PDP, GIP_PID_PDP_ROCK_CANDY, 0, .quirks = GIP_QUIRK_NO_HELLO },
+
+ {0},
+};
+
+struct gip_audio_format_pair {
+ uint8_t inbound;
+ uint8_t outbound;
+};
+static_assert(sizeof(struct gip_audio_format_pair) == 2);
+
+struct gip_protocol_control_ack {
+ uint8_t control_code;
+ uint8_t message_type;
+ uint8_t flags;
+ __le32 fragment_offset;
+ __le16 remaining_buffer;
+} __packed;
+static_assert(sizeof(struct gip_protocol_control_ack) == 9);
+
+struct gip_hello_device {
+ __le64 device_id;
+ __le16 vendor_id;
+ __le16 product_id;
+ __le16 firmware_major_version;
+ __le16 firmware_minor_version;
+ __le16 firmware_build_version;
+ __le16 firmware_revision;
+ uint8_t hardware_major_version;
+ uint8_t hardware_minor_version;
+ uint8_t rf_proto_major_version;
+ uint8_t rf_proto_minor_version;
+ uint8_t security_major_version;
+ uint8_t security_minor_version;
+ uint8_t gip_major_version;
+ uint8_t gip_minor_version;
+} __packed;
+static_assert(sizeof(struct gip_hello_device) == 28);
+
+struct gip_device_metadata_header {
+ __le16 length;
+ uint8_t supports_device_firmware_versions_major;
+ uint8_t supports_device_firmware_versions_minor;
+ __le16 audio_offset;
+ __le16 supported_upstream_system_message_offset;
+ __le16 supported_downstream_system_message_offset;
+ __le16 preferred_types_offset;
+ __le16 supported_interfaces_offset;
+ __le16 hid_descriptor_offset;
+} __packed;
+
+struct gip_direct_motor {
+ uint8_t command;
+ uint8_t motor_bitmap;
+ uint8_t left_impulse_level;
+ uint8_t right_impulse_level;
+ uint8_t left_vibration_level;
+ uint8_t right_vibration_level;
+ uint8_t duration;
+ uint8_t delay;
+ uint8_t repeat;
+};
+
+static const struct gip_driver *base_drivers[] = {
+ &gip_driver_navigation,
+ &gip_driver_gamepad,
+ NULL /* Sentinel */
+};
+
+static int gip_decode_length(uint64_t *length, const uint8_t *bytes, int num_bytes)
+{
+ *length = 0;
+ int offset;
+
+ for (offset = 0; offset < num_bytes; offset++) {
+ uint8_t byte = bytes[offset];
+
+ *length |= (byte & 0x7full) << (offset * 7);
+ if (!(byte & 0x80)) {
+ offset++;
+ break;
+ }
+ }
+ return offset;
+}
+
+static int gip_encode_length(uint64_t length, uint8_t *bytes, int num_bytes)
+{
+ int offset;
+
+ for (offset = 0; offset < num_bytes; offset++) {
+ uint8_t byte = length & 0x7f;
+
+ length >>= 7;
+ if (length)
+ byte |= 0x80;
+ bytes[offset] = byte;
+ if (!length) {
+ offset++;
+ break;
+ }
+ }
+ return offset;
+}
+
+static bool gip_supports_system_message(struct gip_attachment *attachment,
+ uint8_t command, bool upstream)
+{
+ if (upstream)
+ return attachment->metadata.device
+ .in_system_messages[command >> 5] & BIT(command);
+ else
+ return attachment->metadata.device
+ .out_system_messages[command >> 5] & BIT(command);
+}
+
+bool gip_supports_vendor_message(struct gip_attachment *attachment,
+ uint8_t command, bool upstream)
+{
+ size_t i;
+
+ for (i = 0; i < attachment->metadata.num_messages; i++) {
+ struct gip_message_metadata *metadata =
+ &attachment->metadata.message_metadata[i];
+
+ if (metadata->type != command)
+ continue;
+ if (metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE)
+ return true;
+
+ if (upstream)
+ return metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM;
+ else
+ return metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM;
+ }
+ return false;
+}
+
+static uint8_t gip_sequence_next(struct gip_attachment *attachment,
+ uint8_t command, bool system)
+{
+ uint8_t seq;
+
+ if (system) {
+ switch (command) {
+ case GIP_CMD_SECURITY:
+ seq = attachment->seq_security++;
+ if (!seq)
+ seq = attachment->seq_security++;
+ break;
+ case GIP_CMD_EXTENDED:
+ seq = attachment->seq_extended++;
+ if (!seq)
+ seq = attachment->seq_extended++;
+ break;
+ case GIP_AUDIO_DATA:
+ seq = attachment->seq_audio++;
+ if (!seq)
+ seq = attachment->seq_audio++;
+ break;
+ default:
+ seq = attachment->seq_system++;
+ if (!seq)
+ seq = attachment->seq_system++;
+ break;
+ }
+ } else {
+ seq = attachment->seq_vendor++;
+ if (!seq)
+ seq = attachment->seq_vendor++;
+ }
+ return seq;
+}
+
+static void gip_handle_quirks_array(struct gip_attachment *attachment,
+ const struct gip_quirks *quirks)
+{
+ size_t i, j;
+
+ for (i = 0; quirks[i].vendor_id; i++) {
+ if (quirks[i].vendor_id != attachment->vendor_id)
+ continue;
+ if (quirks[i].product_id != attachment->product_id)
+ continue;
+ if (quirks[i].attachment_index != attachment->attachment_index)
+ continue;
+
+ attachment->features |= quirks[i].added_features;
+ attachment->features &= ~quirks[i].filtered_features;
+ attachment->quirks |= quirks[i].quirks;
+
+ if (quirks[i].override_name)
+ attachment->name = quirks[i].override_name;
+
+ for (j = 0; j < 8; ++j) {
+ struct gip_device_metadata *metadata = &attachment->metadata.device;
+
+ metadata->in_system_messages[j] |= quirks[i].extra_in_system[j];
+ metadata->out_system_messages[j] |= quirks[i].extra_out_system[j];
+ }
+
+ attachment->extra_buttons = quirks[i].extra_buttons;
+ attachment->extra_axes = quirks[i].extra_axes;
+ break;
+ }
+
+}
+
+static void gip_handle_quirks(struct gip_attachment *attachment)
+{
+ gip_handle_quirks_array(attachment, base_quirks);
+
+ if (attachment->driver && attachment->driver->quirks)
+ gip_handle_quirks_array(attachment, attachment->driver->quirks);
+}
+
+static int gip_send_raw_message(struct gip_attachment *attachment,
+ uint8_t message_type, uint8_t flags, uint8_t seq, const uint8_t *bytes,
+ int num_bytes)
+{
+ struct gip_interface *intf;
+ int offset = 3;
+ struct gip_urb *urb = NULL;
+ int i;
+ int rc = 0;
+
+ if (num_bytes < 0) {
+ gip_warn(attachment, "Invalid message length %d\n", num_bytes);
+ return -EINVAL;
+ }
+
+ if (num_bytes + 6 > gip_data_class_mtu[message_type >> GIP_DATA_CLASS_SHIFT]) {
+ gip_err(attachment,
+ "Attempted to send a message that requires fragmenting, which is not yet supported.\n");
+ return -EOPNOTSUPP;
+ }
+
+ if ((message_type & GIP_DATA_CLASS_MASK) == GIP_DATA_CLASS_AUDIO)
+ intf = &attachment->device->audio;
+ else
+ intf = &attachment->device->data;
+
+ if (intf->isoc_messages) {
+ /* TODO: Needed for audio support */
+ gip_warn(intf, "Unimplemented isochronous message output\n");
+ return -EOPNOTSUPP;
+ }
+
+ guard(spinlock_irqsave)(&attachment->device->message_lock);
+ for (i = 0; i < MAX_OUT_MESSAGES && !urb; i++) {
+ if (!intf->out_queue[i].urb)
+ continue;
+ if (!intf->out_queue[i].urb->anchor)
+ urb = &intf->out_queue[i];
+ }
+ if (!urb) {
+ gip_err(attachment, "Output queue is full; dropping message\n");
+ return -ENOSPC;
+ }
+ urb->data[0] = message_type;
+ urb->data[1] = flags;
+ urb->data[2] = seq;
+ offset += gip_encode_length(num_bytes, &urb->data[offset],
+ sizeof(urb->data) - offset);
+
+ if (num_bytes > 0)
+ memcpy(&urb->data[offset], bytes, num_bytes);
+
+ num_bytes += offset;
+ urb->urb->transfer_buffer_length = num_bytes;
+
+ print_hex_dump_debug(KBUILD_MODNAME ": Sending message: ",
+ DUMP_PREFIX_OFFSET, 16, 1, urb->data, num_bytes, false);
+
+ usb_anchor_urb(urb->urb, &intf->out_anchor);
+ rc = usb_submit_urb(urb->urb, GFP_ATOMIC);
+ if (rc) {
+ dev_err(&intf->intf->dev,
+ "%s - usb_submit_urb failed with result %d\n",
+ __func__, rc);
+ usb_unanchor_urb(urb->urb);
+ rc = -EIO;
+ }
+
+ return rc;
+}
+
+int gip_send_system_message(struct gip_attachment *attachment,
+ uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes)
+{
+ return gip_send_raw_message(attachment, message_type,
+ GIP_FLAG_SYSTEM | attachment->attachment_index | flags,
+ gip_sequence_next(attachment, message_type, true),
+ bytes, num_bytes);
+}
+
+int gip_send_vendor_message(struct gip_attachment *attachment,
+ uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes)
+{
+ return gip_send_raw_message(attachment, message_type, flags,
+ gip_sequence_next(attachment, message_type, false),
+ bytes, num_bytes);
+}
+
+static void gip_metadata_free(struct device *dev, struct gip_metadata *metadata)
+{
+ devm_kfree(dev, metadata->device.audio_formats);
+
+ if (metadata->device.preferred_types) {
+ int i;
+
+ for (i = 0; i < metadata->device.num_preferred_types; i++)
+ devm_kfree(dev, metadata->device.preferred_types[i]);
+ devm_kfree(dev, metadata->device.preferred_types);
+ }
+ devm_kfree(dev, metadata->device.supported_interfaces);
+ devm_kfree(dev, metadata->device.hid_descriptor);
+ devm_kfree(dev, metadata->message_metadata);
+
+ memset(metadata, 0, sizeof(*metadata));
+}
+
+static int gip_parse_audio_format_metadata(struct device *dev,
+ struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
+ int length, int buffer_offset)
+{
+ unsigned int i;
+
+ dev_metadata->num_audio_formats = bytes[buffer_offset];
+ if (buffer_offset + dev_metadata->num_audio_formats * 2 + 1 > length)
+ return -EINVAL;
+ dev_metadata->audio_formats = devm_kmalloc_array(dev,
+ dev_metadata->num_audio_formats, 2, GFP_KERNEL);
+ if (!dev_metadata->audio_formats)
+ return -ENOMEM;
+ memcpy(dev_metadata->audio_formats, &bytes[buffer_offset + 1],
+ dev_metadata->num_audio_formats * 2);
+
+ for (i = 0; i < dev_metadata->num_audio_formats; i++) {
+ const struct gip_audio_format_pair *pair = &dev_metadata->audio_formats[i];
+ const struct gip_audio_format *inbound = NULL;
+ const struct gip_audio_format *outbound = NULL;
+
+ if (pair->inbound <= MAX_GIP_AUDIO_FORMAT) {
+ inbound = &gip_audio_format_table[pair->inbound];
+ if (pair->inbound != GIP_AUDIO_FORMAT_NULL && inbound->rate == 0)
+ inbound = NULL;
+ }
+ if (!inbound)
+ dev_warn(dev, "Unknown audio format %u\n", pair->inbound);
+
+ if (pair->outbound <= MAX_GIP_AUDIO_FORMAT) {
+ outbound = &gip_audio_format_table[pair->outbound];
+ if (pair->outbound != GIP_AUDIO_FORMAT_NULL && outbound->rate == 0)
+ outbound = NULL;
+ }
+ if (!outbound)
+ dev_warn(dev, "Unknown audio format %u\n", pair->outbound);
+
+ if (inbound && outbound)
+ dev_dbg(dev,
+ "Supported audio format: %uHz %uch inbound, %uHz %uch outbound\n",
+ inbound->rate,
+ inbound->channels,
+ outbound->rate,
+ outbound->channels);
+ }
+ return 0;
+}
+
+static int gip_parse_preferred_types_metadata(struct device *dev,
+ struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
+ int length, int buffer_offset)
+{
+ int i;
+ int count;
+
+ dev_metadata->num_preferred_types = bytes[buffer_offset];
+ dev_metadata->preferred_types = devm_kcalloc(dev,
+ dev_metadata->num_preferred_types, sizeof(char *), GFP_KERNEL);
+ if (!dev_metadata->preferred_types)
+ return -ENOMEM;
+
+ buffer_offset++;
+ for (i = 0; i < dev_metadata->num_preferred_types; i++) {
+ if (buffer_offset + 2 >= length)
+ return -EINVAL;
+
+ count = bytes[buffer_offset];
+ count |= bytes[buffer_offset];
+ buffer_offset += 2;
+ if (buffer_offset + count > length)
+ return -EINVAL;
+
+ dev_metadata->preferred_types[i] = devm_kcalloc(dev, count + 1,
+ sizeof(char), GFP_KERNEL);
+ if (!dev_metadata->preferred_types[i])
+ return -ENOMEM;
+ memcpy(dev_metadata->preferred_types[i], &bytes[buffer_offset], count);
+ buffer_offset += count;
+ }
+
+ return 0;
+}
+
+static int gip_parse_supported_interfaces_metadata(struct device *dev,
+ struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
+ int length, int buffer_offset)
+{
+ dev_metadata->num_supported_interfaces = bytes[buffer_offset];
+ if (buffer_offset + 1 +
+ (int32_t) (dev_metadata->num_supported_interfaces * sizeof(guid_t)) > length)
+ return -EINVAL;
+
+ dev_metadata->supported_interfaces = devm_kmalloc_array(dev,
+ dev_metadata->num_supported_interfaces, sizeof(guid_t), GFP_KERNEL);
+ if (!dev_metadata->supported_interfaces)
+ return -ENOMEM;
+
+ memcpy(dev_metadata->supported_interfaces, &bytes[buffer_offset + 1],
+ sizeof(guid_t) * dev_metadata->num_supported_interfaces);
+
+ return 0;
+}
+
+static int gip_parse_hid_descriptor_metadata(struct device *dev,
+ struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
+ int length, int buffer_offset)
+{
+ dev_metadata->hid_descriptor_size = bytes[buffer_offset];
+ if (buffer_offset + 1 + dev_metadata->hid_descriptor_size > length)
+ return -EINVAL;
+
+ dev_metadata->hid_descriptor = devm_kmalloc(dev,
+ dev_metadata->hid_descriptor_size, GFP_KERNEL);
+ if (!dev_metadata->hid_descriptor)
+ return -ENOMEM;
+
+ memcpy(dev_metadata->hid_descriptor, &bytes[buffer_offset + 1],
+ dev_metadata->hid_descriptor_size);
+ print_hex_dump_debug(KBUILD_MODNAME ": Received HID descriptor: ",
+ DUMP_PREFIX_OFFSET, 16, 1, dev_metadata->hid_descriptor,
+ dev_metadata->hid_descriptor_size, false);
+
+ return 0;
+}
+
+static int gip_parse_device_metadata(struct device *dev,
+ struct gip_metadata *metadata, const uint8_t *bytes, int num_bytes,
+ int *offset)
+{
+ struct gip_device_metadata *dev_metadata = &metadata->device;
+ int buffer_offset;
+ int count;
+ int length;
+ int i;
+ int rc;
+ const struct gip_device_metadata_header *header;
+
+ bytes = &bytes[*offset];
+ num_bytes -= *offset;
+ if (num_bytes < sizeof(*header))
+ return -EINVAL;
+
+ header = (const struct gip_device_metadata_header *)bytes;
+ length = le16_to_cpu(header->length);
+ if (num_bytes < length)
+ return -EINVAL;
+
+ /* Skip supported firmware versions for now */
+
+ buffer_offset = le16_to_cpu(header->audio_offset);
+ if (buffer_offset >= length)
+ return -EINVAL;
+
+ if (buffer_offset > 0) {
+ rc = gip_parse_audio_format_metadata(dev, dev_metadata,
+ bytes, length, buffer_offset);
+ if (rc)
+ return rc;
+ }
+
+ buffer_offset = le16_to_cpu(header->supported_upstream_system_message_offset);
+ if (buffer_offset >= length)
+ return -EINVAL;
+
+ if (buffer_offset > 0) {
+ count = bytes[buffer_offset];
+ if (buffer_offset + count + 1 > length)
+ return -EINVAL;
+
+ for (i = 0; i < count; i++) {
+ uint8_t message = bytes[buffer_offset + 1 + i];
+
+ dev_dbg(dev,
+ "Supported upstream system message %02x\n",
+ message);
+ dev_metadata->in_system_messages[message >> 5] |=
+ BIT(message & 0x1F);
+ }
+ }
+
+ buffer_offset = le16_to_cpu(header->supported_downstream_system_message_offset);
+ if (buffer_offset >= length)
+ return -EINVAL;
+
+ if (buffer_offset > 0) {
+ count = bytes[buffer_offset];
+ if (buffer_offset + count + 1 > length)
+ return -EINVAL;
+
+ for (i = 0; i < count; i++) {
+ uint8_t message = bytes[buffer_offset + 1 + i];
+
+ dev_dbg(dev,
+ "Supported downstream system message %02x\n",
+ message);
+ dev_metadata->out_system_messages[message >> 5] |=
+ BIT(message & 0x1F);
+ }
+ }
+
+ buffer_offset = le16_to_cpu(header->preferred_types_offset);
+ if (buffer_offset >= length)
+ return -EINVAL;
+
+ if (buffer_offset > 0) {
+ rc = gip_parse_preferred_types_metadata(dev, dev_metadata,
+ bytes, length, buffer_offset);
+ if (rc)
+ return rc;
+ }
+
+ buffer_offset = le16_to_cpu(header->supported_interfaces_offset);
+ if (buffer_offset >= length)
+ return -EINVAL;
+
+ if (buffer_offset > 0) {
+ rc = gip_parse_supported_interfaces_metadata(dev,
+ dev_metadata, bytes, length, buffer_offset);
+ if (rc)
+ return rc;
+ }
+
+ if (metadata->version_major > 1 || metadata->version_minor >= 1) {
+ /* HID descriptor support added in metadata version 1.1 */
+ buffer_offset = le16_to_cpu(header->hid_descriptor_offset);
+ if (buffer_offset >= length)
+ return -EINVAL;
+
+ if (buffer_offset > 0) {
+ rc = gip_parse_hid_descriptor_metadata(dev,
+ dev_metadata, bytes, length, buffer_offset);
+ if (rc)
+ return rc;
+ }
+ }
+
+ *offset += length;
+ return 0;
+}
+
+static int gip_parse_message_metadata(struct device *dev,
+ struct gip_message_metadata *metadata, const uint8_t *bytes,
+ int num_bytes, int *offset)
+{
+ uint16_t length;
+
+ bytes = &bytes[*offset];
+ num_bytes -= *offset;
+
+ if (num_bytes < 2)
+ return -EINVAL;
+
+ length = get_unaligned_le16(&bytes[0]);
+ if (num_bytes < length)
+ return -EINVAL;
+
+ if (length < 15)
+ return -EINVAL;
+
+ metadata->type = bytes[2];
+ metadata->length = get_unaligned_le16(&bytes[3]);
+ metadata->data_type = get_unaligned_le16(&bytes[5]);
+ metadata->flags = get_unaligned_le32(&bytes[7]);
+ metadata->period = get_unaligned_le16(&bytes[11]);
+ metadata->persistence_timeout = get_unaligned_le16(&bytes[13]);
+
+ dev_dbg(dev,
+ "Supported vendor message type %02x of length %d, %s, %s, %s\n",
+ metadata->type, metadata->length,
+ metadata->flags & GIP_MESSAGE_FLAG_UPSTREAM ?
+ (metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "bidirectional" : "upstream") :
+ metadata->flags & GIP_MESSAGE_FLAG_DOWNSTREAM ? "downstream" :
+ metadata->flags & GIP_MESSAGE_FLAG_DS_REQUEST_RESPONSE ? "downstream request response" :
+ "unknown direction",
+ metadata->flags & GIP_MESSAGE_FLAG_SEQUENCED ? "sequenced" : "not sequenced",
+ metadata->flags & GIP_MESSAGE_FLAG_RELIABLE ? "reliable" : "unreliable");
+
+ *offset += length;
+ return 0;
+}
+
+static bool gip_parse_metadata(struct device *dev,
+ struct gip_metadata *metadata, const uint8_t *bytes, int num_bytes)
+{
+ int header_size;
+ int metadata_size;
+ int offset = 0;
+ int i;
+ int rc;
+
+ if (num_bytes < 16)
+ return -EINVAL;
+
+ print_hex_dump_debug(KBUILD_MODNAME ": Received metadata: ",
+ DUMP_PREFIX_OFFSET, 16, 1, bytes, num_bytes, false);
+
+ header_size = get_unaligned_le16(&bytes[0]);
+ if (num_bytes < header_size || header_size < 16)
+ return -EINVAL;
+
+ metadata->version_major = get_unaligned_le16(&bytes[2]);
+ metadata->version_minor = get_unaligned_le16(&bytes[4]);
+ /* Middle bytes are reserved */
+ metadata_size = get_unaligned_le16(&bytes[14]);
+
+ if (num_bytes < metadata_size || metadata_size < header_size)
+ return -EINVAL;
+
+ offset = header_size;
+
+ rc = gip_parse_device_metadata(dev, metadata, bytes, num_bytes, &offset);
+ if (rc)
+ goto parse_err;
+
+ if (offset >= num_bytes)
+ goto parse_err;
+
+ metadata->num_messages = bytes[offset];
+ offset++;
+ if (metadata->num_messages > 0) {
+ metadata->message_metadata = devm_kcalloc(dev,
+ metadata->num_messages,
+ sizeof(*metadata->message_metadata), GFP_KERNEL);
+ if (!metadata->message_metadata)
+ return -ENOMEM;
+
+ for (i = 0; i < metadata->num_messages; i++) {
+ rc = gip_parse_message_metadata(dev,
+ &metadata->message_metadata[i], bytes,
+ num_bytes, &offset);
+ if (rc)
+ goto parse_err;
+ }
+ }
+
+ return 0;
+
+parse_err:
+ gip_metadata_free(dev, metadata);
+ return rc;
+}
+
+static int gip_acknowledge(struct gip_attachment *attachment,
+ const struct gip_header *header, uint32_t fragment_offset,
+ uint16_t bytes_remaining)
+{
+ struct gip_protocol_control_ack message;
+
+ message.control_code = GIP_CONTROL_CODE_ACK;
+ message.message_type = header->message_type;
+ message.flags = header->flags & GIP_FLAG_SYSTEM;
+ message.fragment_offset = cpu_to_le32(fragment_offset);
+ message.remaining_buffer = cpu_to_le16(bytes_remaining);
+
+ return gip_send_raw_message(attachment, GIP_CMD_PROTO_CONTROL,
+ GIP_FLAG_SYSTEM | attachment->attachment_index,
+ header->sequence_id, (uint8_t *)&message, sizeof(message));
+}
+
+static int gip_fragment_failed(struct gip_attachment *attachment,
+ const struct gip_header *header)
+{
+ attachment->in_fragment_retries++;
+ if (attachment->in_fragment_retries > 8) {
+ devm_kfree(to_gip_device(attachment), attachment->in_fragment_data);
+ attachment->in_fragment_data = NULL;
+ attachment->in_fragment_message = -1;
+ return 0;
+ }
+ return gip_acknowledge(attachment, header,
+ attachment->in_fragment_offset,
+ attachment->in_total_length - attachment->in_fragment_offset);
+}
+
+static int gip_bind_driver(struct gip_attachment *attachment, const struct gip_driver *driver)
+{
+ if (driver->probe) {
+ int rc = driver->probe(attachment);
+
+ if (rc)
+ return rc;
+ }
+
+ attachment->driver = driver;
+ memcpy(attachment->vendor_handlers, driver->vendor_handlers,
+ sizeof(attachment->vendor_handlers));
+ return 0;
+}
+
+static int gip_enable_elite_buttons(struct gip_attachment *attachment)
+{
+ if (attachment->vendor_id == GIP_VID_MICROSOFT) {
+ if (attachment->product_id == GIP_PID_XBOX_ELITE) {
+ attachment->xbe_format = GIP_BTN_FMT_XBE1;
+ } else if (attachment->product_id == GIP_PID_XBOX_ELITE_2) {
+ if (attachment->firmware_major_version == 4) {
+ attachment->xbe_format = GIP_BTN_FMT_XBE2_4;
+ } else if (attachment->firmware_major_version == 5) {
+ /*
+ * The exact range for this being necessary is
+ * unknown, but it starts at 5.11 and at either
+ * 5.16 or 5.17. This approach still works on
+ * 5.21, even if it's not necessary, so having
+ * a loose upper limit is fine.
+ */
+ if (attachment->firmware_minor_version >= 11 &&
+ attachment->firmware_minor_version < 17)
+ attachment->xbe_format = GIP_BTN_FMT_XBE2_RAW;
+ else
+ attachment->xbe_format = GIP_BTN_FMT_XBE2_5;
+ }
+ }
+ }
+
+ if (attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
+ /*
+ * The meaning of this packet is unknown and not documented, but
+ * it's needed for the Elite 2 controller to send raw reports
+ */
+ static const uint8_t enable_raw_report[] = { 7, 0 };
+
+ return gip_send_vendor_message(attachment, GIP_SL_ELITE_CONFIG,
+ 0, enable_raw_report, sizeof(enable_raw_report));
+ }
+
+ return 0;
+}
+
+#ifdef CONFIG_JOYSTICK_XBOX_GIP_FF
+static int gip_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
+{
+ struct gip_attachment *attachment = input_get_drvdata(dev);
+ struct gip_direct_motor control = {
+ .motor_bitmap = GIP_MOTOR_LEFT_VIBRATION | GIP_MOTOR_RIGHT_VIBRATION
+ };
+
+ if (effect->type != FF_RUMBLE)
+ return 0;
+
+ control.left_vibration_level = effect->u.rumble.strong_magnitude * 100 / 0xFFFF;
+ control.right_vibration_level = effect->u.rumble.weak_magnitude * 100 / 0xFFFF;
+ control.duration = 255;
+
+ return gip_send_vendor_message(attachment, GIP_CMD_DIRECT_MOTOR,
+ 0, &control, sizeof(control));
+}
+#endif
+
+static int gip_send_guide_button_led(struct gip_attachment *attachment,
+ uint8_t pattern, uint8_t intensity)
+{
+ uint8_t buffer[] = {
+ GIP_LED_GUIDE,
+ pattern,
+ intensity,
+ };
+
+ if (!gip_supports_system_message(attachment, GIP_CMD_LED, false))
+ return 0;
+
+ return gip_send_system_message(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer));
+}
+
+static bool gip_send_set_device_state(struct gip_attachment *attachment, uint8_t state)
+{
+ uint8_t buffer[] = { state };
+
+ return gip_send_system_message(attachment, GIP_CMD_SET_DEVICE_STATE,
+ attachment->attachment_index, buffer, sizeof(buffer));
+}
+
+static int gip_handle_command_raw_report(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ struct input_dev *input;
+
+ if (num_bytes < 17) {
+ gip_dbg(attachment, "Discarding too-short raw report\n");
+ return -EINVAL;
+ }
+ guard(rcu)();
+ input = rcu_dereference(attachment->input);
+ if (!input)
+ return -ENODEV;
+
+ if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS)
+ && attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
+ input_report_abs(input, ABS_PROFILE, bytes[15] & 3);
+ if (bytes[15] & 3) {
+ input_report_key(input, BTN_GRIPL, 0);
+ input_report_key(input, BTN_GRIPR, 0);
+ input_report_key(input, BTN_GRIPL2, 0);
+ input_report_key(input, BTN_GRIPR2, 0);
+ } else {
+ input_report_key(input, BTN_GRIPL,
+ bytes[GIP_BTN_OFFSET_XBE2] & BIT(2));
+ input_report_key(input, BTN_GRIPR,
+ bytes[GIP_BTN_OFFSET_XBE2] & BIT(0));
+ input_report_key(input, BTN_GRIPL2,
+ bytes[GIP_BTN_OFFSET_XBE2] & BIT(3));
+ input_report_key(input, BTN_GRIPR2,
+ bytes[GIP_BTN_OFFSET_XBE2] & BIT(1));
+ }
+
+ input_sync(input);
+ }
+ return 0;
+}
+
+static int gip_setup_input_device(struct gip_attachment *attachment)
+{
+ struct input_dev *input;
+ int rc;
+
+ if (!attachment->driver || !attachment->driver->setup_input)
+ return -ENODEV;
+
+ rcu_read_lock();
+ input = rcu_dereference(attachment->input);
+ rcu_read_unlock();
+ if (input)
+ return 0;
+
+ input = input_allocate_device();
+ if (!input)
+ return -ENOMEM;
+ input->id.bustype = BUS_USB;
+ input->id.vendor = attachment->vendor_id;
+ input->id.product = attachment->product_id;
+ input->uniq = attachment->uniq;
+ if (attachment->name)
+ input->name = attachment->name;
+ else if (attachment->attachment_index == 0)
+ input->name = attachment->device->udev->product;
+ input->phys = attachment->phys;
+
+ input_set_capability(input, EV_KEY, BTN_MODE);
+
+ rc = attachment->driver->setup_input(attachment, input);
+ if (rc < 0)
+ goto err_free_device;
+
+ if (attachment->features &
+ (GIP_FEATURE_CONSOLE_FUNCTION_MAP | GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW))
+ input_set_capability(input, EV_KEY, KEY_RECORD);
+
+ if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
+ input_set_capability(input, EV_KEY, BTN_GRIPL);
+ input_set_capability(input, EV_KEY, BTN_GRIPR);
+ input_set_capability(input, EV_KEY, BTN_GRIPL2);
+ input_set_capability(input, EV_KEY, BTN_GRIPR2);
+ if (attachment->xbe_format == GIP_BTN_FMT_XBE1)
+ input_set_abs_params(input, ABS_PROFILE, 0, 1, 0, 0);
+ else
+ input_set_abs_params(input, ABS_PROFILE, 0, 3, 0, 0);
+
+ attachment->vendor_handlers[GIP_CMD_RAW_REPORT] = gip_handle_command_raw_report;
+ }
+
+#ifdef CONFIG_JOYSTICK_XBOX_GIP_FF
+ if (attachment->features & GIP_FEATURE_MOTOR_CONTROL) {
+ input_set_capability(input, EV_FF, FF_RUMBLE);
+ input_ff_create_memless(input, NULL, gip_play_effect);
+ }
+#endif
+
+ input_set_drvdata(input, attachment);
+ rcu_assign_pointer(attachment->input, input);
+ rc = input_register_device(input);
+ if (rc)
+ goto err_free_device;
+
+ return 0;
+
+err_free_device:
+ input_free_device(input);
+ return rc;
+}
+
+static int gip_init_input_device(struct gip_attachment *attachment)
+{
+ struct input_dev *input;
+ int rc = 0;
+
+ rcu_read_lock();
+ input = rcu_dereference(attachment->input);
+ rcu_read_unlock();
+ if (input)
+ return 0;
+
+ if (attachment->driver && attachment->driver->init) {
+ rc = attachment->driver->init(attachment);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (rc != GIP_INIT_NO_INPUT && (attachment->features & GIP_FEATURE_CONTROLLER)) {
+ rc = gip_setup_input_device(attachment);
+ if (rc == -ENODEV)
+ return 0;
+ if (rc)
+ return rc;
+ }
+
+ return rc;
+}
+
+static int gip_send_init_sequence(struct gip_attachment *attachment)
+{
+ int rc = 0;
+ size_t len;
+
+ if (attachment->features & GIP_FEATURE_EXTENDED_SET_DEVICE_STATE) {
+ /*
+ * The meaning of this packet is unknown and not documented, but it's
+ * needed for the Elite 2 controller to start up on older firmwares
+ */
+ static const uint8_t set_device_state[] = {
+ GIP_STATE_UNK6, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x55, 0x53, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0
+ };
+
+ rc = gip_send_system_message(attachment,
+ GIP_CMD_SET_DEVICE_STATE, 0, set_device_state,
+ sizeof(set_device_state));
+ if (rc)
+ return rc;
+ }
+ rc = gip_enable_elite_buttons(attachment);
+ if (rc)
+ return rc;
+ if (!gip_supports_system_message(attachment, GIP_CMD_AUDIO_CONTROL, false)) {
+ rc = gip_send_set_device_state(attachment, GIP_STATE_START);
+ if (rc)
+ return rc;
+ attachment->device_state = GIP_STATE_START;
+ } else {
+ rc = gip_send_set_device_state(attachment, GIP_STATE_STOP);
+ if (rc)
+ return rc;
+ attachment->device_state = GIP_STATE_STOP;
+ }
+
+ rc = gip_send_guide_button_led(attachment,
+ GIP_LED_GUIDE_ON,
+ GIP_LED_GUIDE_INIT_BRIGHTNESS);
+ if (rc)
+ return rc;
+
+ if (gip_supports_system_message(attachment, GIP_CMD_SECURITY, false)
+ && !(attachment->features & GIP_FEATURE_SECURITY_OPT_OUT)) {
+ /* TODO: Implement Security command property */
+ uint8_t buffer[] = { 0x1, 0x0 };
+
+ rc = gip_send_system_message(attachment, GIP_CMD_SECURITY, 0,
+ buffer, sizeof(buffer));
+ if (rc)
+ return rc;
+ }
+
+ usb_make_path(attachment->device->udev, attachment->phys,
+ sizeof(attachment->phys));
+ len = strlen(attachment->phys);
+ if (len < sizeof(attachment->phys) - 1)
+ snprintf(attachment->phys + len,
+ sizeof(attachment->phys) - len, "/input%d",
+ attachment->attachment_index);
+
+ rc = gip_init_input_device(attachment);
+ if (rc)
+ return rc;
+
+ return 0;
+}
+
+static void gip_fragment_timeout(struct work_struct *work)
+{
+ struct gip_attachment *attachment = container_of(to_delayed_work(work),
+ struct gip_attachment, in_fragment_timeout);
+
+ guard(mutex)(&attachment->lock);
+ devm_kfree(to_gip_device(attachment), attachment->in_fragment_data);
+ attachment->in_fragment_data = NULL;
+ attachment->in_fragment_message = -1;
+}
+
+static void gip_reset_metadata(struct gip_attachment *attachment)
+{
+ struct input_dev *input;
+
+ if (attachment->got_metadata != GIP_METADATA_GOT)
+ return;
+
+ gip_metadata_free(to_gip_device(attachment), &attachment->metadata);
+ rcu_read_lock();
+ input = rcu_dereference(attachment->input);
+ rcu_read_unlock();
+ if (input) {
+ rcu_assign_pointer(attachment->input, NULL);
+ synchronize_rcu();
+ input_unregister_device(input);
+ }
+ attachment->got_metadata = GIP_METADATA_NONE;
+}
+
+static void gip_retry_metadata(struct work_struct *work)
+{
+ struct gip_attachment *attachment = container_of(to_delayed_work(work),
+ struct gip_attachment, metadata_next);
+
+ guard(mutex)(&attachment->lock);
+ if (attachment->metadata_retries < 4) {
+ attachment->metadata_retries++;
+ schedule_delayed_work(&attachment->metadata_next, HZ / 2);
+ gip_send_system_message(attachment, GIP_CMD_METADATA, 0, NULL, 0);
+ } else {
+ gip_info(attachment,
+ "Unable to obtain metadata, attempting to reset device\n");
+ gip_send_set_device_state(attachment, GIP_STATE_RESET);
+ }
+}
+
+static int gip_ensure_metadata(struct gip_attachment *attachment)
+{
+ switch (attachment->got_metadata) {
+ case GIP_METADATA_GOT:
+ case GIP_METADATA_FAKED:
+ return 0;
+ case GIP_METADATA_NONE:
+ attachment->got_metadata = GIP_METADATA_PENDING;
+ cancel_delayed_work_sync(&attachment->metadata_next);
+ schedule_delayed_work(&attachment->metadata_next, HZ / 2);
+ attachment->metadata_retries = 0;
+ return gip_send_system_message(attachment, GIP_CMD_METADATA, 0, NULL, 0);
+ default:
+ return 0;
+ }
+}
+
+static void gip_set_metadata_defaults(struct gip_attachment *attachment)
+{
+ if (attachment->got_metadata != GIP_METADATA_NONE)
+ gip_reset_metadata(attachment);
+
+ attachment->metadata.device.in_system_messages[0] =
+ GIP_DEFAULT_IN_SYSTEM_MESSAGES;
+ attachment->metadata.device.out_system_messages[0] =
+ GIP_DEFAULT_OUT_SYSTEM_MESSAGES;
+ if (attachment->attachment_index == 0) {
+ /* Some decent default settings */
+ attachment->features |= GIP_FEATURE_CONTROLLER;
+ attachment->metadata.device.in_system_messages[0] |= BIT(GIP_CMD_GUIDE_BUTTON);
+ }
+
+ gip_handle_quirks(attachment);
+ if (attachment->quirks & GIP_QUIRK_NO_HELLO)
+ gip_ensure_metadata(attachment);
+
+ attachment->got_metadata = GIP_METADATA_FAKED;
+}
+
+static void gip_reset_attachment(struct gip_attachment *attachment)
+{
+ devm_kfree(to_gip_device(attachment), attachment->in_fragment_data);
+ attachment->in_fragment_data = NULL;
+ attachment->in_fragment_message = -1;
+
+ gip_reset_metadata(attachment);
+}
+
+static int gip_handle_command_protocol_control(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ /* TODO */
+ gip_warn(attachment, "Unimplemented Protocol Control message\n");
+ return -EOPNOTSUPP;
+}
+
+static bool gip_handle_command_hello_device(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ const struct gip_hello_device *message;
+
+ if (num_bytes != sizeof(*message))
+ return -EINVAL;
+
+ message = (const struct gip_hello_device *)bytes;
+
+ gip_dbg(attachment, "Device hello from %llx (%04x:%04x)\n",
+ le64_to_cpu(message->device_id),
+ le16_to_cpu(message->vendor_id),
+ le16_to_cpu(message->product_id));
+ gip_dbg(attachment, "Firmware version %d.%d.%d rev %d\n",
+ le16_to_cpu(message->firmware_major_version),
+ le16_to_cpu(message->firmware_minor_version),
+ le16_to_cpu(message->firmware_build_version),
+ le16_to_cpu(message->firmware_revision));
+
+ /*
+ * The GIP spec specifies that the host should reject the device if any of these are wrong.
+ * I don't know if Windows or an Xbox do, however, so let's just log warnings instead.
+ */
+ if (message->rf_proto_major_version != 1 && message->rf_proto_minor_version != 0)
+ gip_warn(attachment, "Invalid RF protocol version %d.%d, expected 1.0\n",
+ message->rf_proto_major_version, message->rf_proto_minor_version);
+
+ if (message->security_major_version != 1 && message->security_minor_version != 0)
+ gip_warn(attachment, "Invalid security protocol version %d.%d, expected 1.0\n",
+ message->security_major_version, message->security_minor_version);
+
+ if (message->gip_major_version != 1 && message->gip_minor_version != 0)
+ gip_warn(attachment, "Invalid GIP version %d.%d, expected 1.0\n",
+ message->gip_major_version, message->gip_minor_version);
+
+ attachment->firmware_major_version = le16_to_cpu(message->firmware_major_version);
+ attachment->firmware_minor_version = le16_to_cpu(message->firmware_minor_version);
+ attachment->vendor_id = le16_to_cpu(message->vendor_id);
+ attachment->product_id = le16_to_cpu(message->product_id);
+ attachment->uniq = devm_kasprintf(to_gip_device(attachment),
+ GFP_KERNEL, "%llx", le64_to_cpu(message->device_id));
+
+ if (header->flags & GIP_FLAG_ATTACHMENT_MASK)
+ return gip_send_system_message(attachment, GIP_CMD_METADATA, 0, NULL, 0);
+ if (attachment->got_metadata == GIP_METADATA_FAKED)
+ attachment->got_metadata = GIP_METADATA_NONE;
+ if (attachment->got_metadata == GIP_METADATA_GOT)
+ gip_set_metadata_defaults(attachment);
+
+ gip_reset_attachment(attachment);
+ return gip_ensure_metadata(attachment);
+}
+
+static int gip_handle_command_status_device(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ int rc;
+ int i;
+
+ if (num_bytes < 1)
+ return -EINVAL;
+
+ attachment->status.base.battery_level = bytes[0] & 3;
+ attachment->status.base.battery_type = (bytes[0] >> 2) & 3;
+ attachment->status.base.charge = (bytes[0] >> 4) & 3;
+ attachment->status.base.power_level = (bytes[0] >> 6) & 3;
+
+ if (num_bytes >= 4) {
+ attachment->status.device_active = bytes[1] & 1;
+ if (bytes[1] & 2) {
+ /* Events present */
+ if (num_bytes < 5)
+ return -EINVAL;
+
+ attachment->status.num_events = bytes[4];
+ if (attachment->status.num_events > 5) {
+ gip_info(attachment,
+ "Device reported too many events, %d > 5\n",
+ attachment->status.num_events);
+ return -EINVAL;
+ }
+ if (5 + attachment->status.num_events * 10 > num_bytes)
+ return -EINVAL;
+
+ for (i = 0; i < attachment->status.num_events; i++) {
+ struct gip_status_event *event = &attachment->status.events[i];
+
+ event->event_type = get_unaligned_le16(&bytes[i * 10 + 5]);
+ event->fault_tag = get_unaligned_le32(&bytes[i * 10 + 7]);
+ event->fault_address = get_unaligned_le32(&bytes[i * 10 + 11]);
+
+ gip_info(attachment,
+ "Attachment %i event type %i, tag %i address %x\n",
+ attachment->attachment_index,
+ event->event_type,
+ event->fault_tag,
+ event->fault_address);
+ }
+ }
+ }
+
+ rc = gip_init_input_device(attachment);
+ if (rc)
+ return rc;
+
+ return gip_ensure_metadata(attachment);
+}
+
+static int gip_handle_command_metadata_respose(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ struct gip_metadata metadata = {0};
+ const guid_t *expected_guid = NULL;
+ bool found_expected_guid;
+ bool found_controller_guid = false;
+ int i, j, k;
+ int rc;
+
+ rc = gip_parse_metadata(to_gip_device(attachment), &metadata, bytes, num_bytes);
+ if (rc)
+ return rc;
+
+ gip_reset_metadata(attachment);
+
+ attachment->metadata = metadata;
+ attachment->got_metadata = GIP_METADATA_GOT;
+ attachment->features = 0;
+ cancel_delayed_work_sync(&attachment->metadata_next);
+
+ for (i = 0; i < metadata.device.num_preferred_types; i++) {
+ const char *type = metadata.device.preferred_types[i];
+
+ gip_dbg(attachment, "Device preferred type: %s\n", type);
+ }
+ for (i = 0; i < metadata.device.num_preferred_types; i++) {
+ const char *type = metadata.device.preferred_types[i];
+
+ for (j = 0; base_drivers[j] && !expected_guid; j++) {
+ for (k = 0; base_drivers[j]->types[k] && !expected_guid; k++) {
+ if (strcmp(type, base_drivers[j]->types[k]) == 0) {
+ gip_dbg(attachment, "Matched type %s\n", type);
+ rc = gip_bind_driver(attachment, base_drivers[j]);
+ if (rc == 0)
+ expected_guid = &base_drivers[j]->guid;
+ else if (rc != -ENODEV)
+ return rc;
+ }
+ }
+ }
+ if (expected_guid)
+ break;
+
+ if (strcmp(type, "Windows.Xbox.Input.Chatpad") == 0)
+ break;
+
+ if (strcmp(type, "Windows.Xbox.Input.Headset") == 0) {
+ expected_guid = &guid_headset;
+ break;
+ }
+ }
+
+ found_expected_guid = !expected_guid;
+ for (i = 0; i < metadata.device.num_supported_interfaces; i++) {
+ const guid_t *guid = &metadata.device.supported_interfaces[i];
+
+ gip_dbg(attachment, "Supported interface: %pUl\n", guid);
+ if (expected_guid && guid_equal(expected_guid, guid))
+ found_expected_guid = true;
+
+ if (guid_equal(&guid_controller, guid)) {
+ found_controller_guid = true;
+ continue;
+ }
+ if (guid_equal(&gip_driver_navigation.guid, guid)) {
+ attachment->features |= GIP_FEATURE_CONTROLLER;
+ continue;
+ }
+ if (guid_equal(&guid_dev_auth_pc_opt_out, guid)) {
+ attachment->features |= GIP_FEATURE_SECURITY_OPT_OUT;
+ continue;
+ }
+ if (guid_equal(&guid_console_function_map, guid)) {
+ attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP;
+ continue;
+ }
+ if (guid_equal(&guid_console_function_map_overflow, guid)) {
+ attachment->features |= GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW;
+ continue;
+ }
+ if (guid_equal(&guid_elite_buttons, guid)) {
+ attachment->features |= GIP_FEATURE_ELITE_BUTTONS;
+ continue;
+ }
+ if (guid_equal(&guid_dynamic_latency_input, guid)) {
+ attachment->features |= GIP_FEATURE_DYNAMIC_LATENCY_INPUT;
+ continue;
+ }
+ }
+
+ for (i = 0; i < metadata.num_messages; i++) {
+ struct gip_message_metadata *message = &metadata.message_metadata[i];
+
+ if (message->type == GIP_CMD_DIRECT_MOTOR && message->length >= 9
+ && (message->flags & GIP_MESSAGE_FLAG_DOWNSTREAM))
+ attachment->features |= GIP_FEATURE_MOTOR_CONTROL;
+ }
+
+ if (!found_expected_guid || !found_controller_guid)
+ gip_dbg(attachment, "Controller was missing expected GUID. "
+ "This controller probably won't work on an actual Xbox.\n");
+
+ gip_handle_quirks(attachment);
+
+ if ((attachment->features & GIP_FEATURE_GUIDE_COLOR)
+ && !gip_supports_vendor_message(attachment,
+ GIP_CMD_GUIDE_COLOR, false))
+ attachment->features &= ~GIP_FEATURE_GUIDE_COLOR;
+
+ gip_dbg(attachment, "Attachment %i has features: %02x\n",
+ attachment->attachment_index, attachment->features);
+
+ return gip_send_init_sequence(attachment);
+}
+
+static int gip_handle_command_security(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ /* TODO: Needed for controllers that connect via dongles */
+ gip_warn(attachment, "Unimplemented Security message\n");
+ return -EOPNOTSUPP;
+}
+
+static int gip_handle_command_guide_button_status(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ struct input_dev *input;
+
+ if (num_bytes < 2)
+ return -EINVAL;
+
+ guard(rcu)();
+ input = rcu_dereference(attachment->input);
+ if (!input)
+ return -ENODEV;
+
+ if (bytes[1] == VK_LWIN) {
+ input_report_key(input, BTN_MODE, bytes[0] & 3);
+ input_sync(input);
+ }
+
+ return 0;
+}
+
+static int gip_handle_command_audio_control(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ /* TODO: Needed for audio */
+ gip_warn(attachment, "Unimplemented Audio Control message\n");
+ return -EOPNOTSUPP;
+}
+
+static int gip_handle_command_firmware(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ if (num_bytes < 1)
+ return -EINVAL;
+
+ if (bytes[0] == 1) {
+ uint16_t major, minor, build, rev;
+
+ if (num_bytes < 14) {
+ gip_dbg(attachment, "Discarding too-short firmware message\n");
+
+ return -EINVAL;
+ }
+ major = get_unaligned_le16(&bytes[6]);
+ minor = get_unaligned_le16(&bytes[8]);
+ build = get_unaligned_le16(&bytes[10]);
+ rev = get_unaligned_le16(&bytes[12]);
+
+ gip_dbg(attachment,
+ "Firmware version: %d.%d.%d rev %d\n", major, minor, build, rev);
+
+ attachment->firmware_major_version = major;
+ attachment->firmware_minor_version = minor;
+
+ if (attachment->vendor_id == GIP_VID_MICROSOFT
+ && attachment->product_id == GIP_PID_XBOX_ELITE_2)
+ return gip_enable_elite_buttons(attachment);
+
+ return 0;
+ }
+
+ gip_warn(attachment, "Unimplemented Firmware message\n");
+
+ return -EOPNOTSUPP;
+}
+
+static int gip_handle_command_hid_report(struct gip_attachment *attachment,
+ const struct gip_header *header, uint8_t *bytes, int num_bytes)
+{
+ gip_warn(attachment, "Unimplemented HID report message\n");
+
+ return -EOPNOTSUPP;
+}
+
+static int gip_handle_command_extended(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ if (num_bytes < 2)
+ return -EINVAL;
+
+ if (bytes[1] != GIP_EXTENDED_STATUS_OK) {
+ gip_dbg(attachment,
+ "Extended message type %02x failed with status %i\n",
+ bytes[0], bytes[1]);
+ return -EPROTO;
+ }
+
+ switch (bytes[0]) {
+ case GIP_EXTCMD_GET_SERIAL_NUMBER:
+ memcpy(attachment->serial, &bytes[2],
+ min(sizeof(attachment->serial), (size_t)(num_bytes - 2)));
+ break;
+ default:
+ /* TODO */
+ gip_dbg(attachment, "Unimplemented extended message type %02x\n",
+ bytes[0]);
+ return -EOPNOTSUPP;
+ }
+
+ return 0;
+}
+
+static int gip_handle_elite_buttons(struct gip_attachment *attachment,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes)
+{
+ bool grip[4] = { 0, 0, 0, 0 };
+ int profile = -1;
+
+ if (attachment->xbe_format == GIP_BTN_FMT_XBE1
+ && num_bytes > GIP_BTN_OFFSET_XBE1) {
+ profile = bytes[GIP_BTN_OFFSET_XBE1] >> 4;
+ if (profile) {
+ grip[0] = bytes[GIP_BTN_OFFSET_XBE1] & BIT(0);
+ grip[1] = bytes[GIP_BTN_OFFSET_XBE1] & BIT(1);
+ grip[2] = bytes[GIP_BTN_OFFSET_XBE1] & BIT(2);
+ grip[3] = bytes[GIP_BTN_OFFSET_XBE1] & BIT(3);
+ }
+ } else if ((attachment->xbe_format == GIP_BTN_FMT_XBE2_4
+ || attachment->xbe_format == GIP_BTN_FMT_XBE2_5)
+ && num_bytes > GIP_BTN_OFFSET_XBE2) {
+ int profile_offset;
+
+ if (attachment->xbe_format == GIP_BTN_FMT_XBE2_4)
+ profile_offset = 15;
+ else
+ profile_offset = 20;
+ profile = bytes[profile_offset] & 3;
+
+ if (!profile) {
+ grip[0] = bytes[GIP_BTN_OFFSET_XBE2] & BIT(2);
+ grip[1] = bytes[GIP_BTN_OFFSET_XBE2] & BIT(0);
+ grip[2] = bytes[GIP_BTN_OFFSET_XBE2] & BIT(3);
+ grip[3] = bytes[GIP_BTN_OFFSET_XBE2] & BIT(1);
+ }
+ }
+ if (profile >= 0) {
+ input_report_key(input, BTN_GRIPL, grip[0]);
+ input_report_key(input, BTN_GRIPR, grip[1]);
+ input_report_key(input, BTN_GRIPL2, grip[2]);
+ input_report_key(input, BTN_GRIPR2, grip[3]);
+ input_report_abs(input, ABS_PROFILE, profile);
+ }
+ return 0;
+}
+
+static int gip_handle_console_map(struct gip_attachment *attachment,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes)
+{
+ int function_map_offset = -1;
+
+ if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) {
+ /* The dynamic latency input bytes are after the console function map */
+ if (num_bytes >= 26)
+ function_map_offset = num_bytes - 26;
+ } else {
+ function_map_offset = num_bytes - 18;
+ }
+ if (function_map_offset >= 0) {
+ input_report_key(input, KEY_RECORD,
+ bytes[function_map_offset] & BIT(0));
+ }
+ return 0;
+}
+
+static int gip_handle_ll_input_report(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ struct input_dev *input;
+ int rc = 0;
+
+ guard(rcu)();
+ input = rcu_dereference(attachment->input);
+ if (!input)
+ return -ENODEV;
+
+ if (attachment->device_state != GIP_STATE_START) {
+ gip_dbg(attachment, "Discarding early input report\n");
+ attachment->device_state = GIP_STATE_START;
+ return 0;
+ }
+
+ if (attachment->driver && attachment->driver->handle_input_report) {
+ rc = attachment->driver->handle_input_report(attachment, input, bytes, num_bytes);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
+ rc = gip_handle_elite_buttons(attachment, input, bytes, num_bytes);
+ if (rc < 0)
+ goto exit;
+ }
+
+ if (num_bytes >= 32 && (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP))
+ rc = gip_handle_console_map(attachment, input, bytes, num_bytes);
+
+exit:
+ input_sync(input);
+
+ return rc;
+}
+
+static int gip_handle_ll_overflow_input_report(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ struct input_dev *input;
+ int rc = 0;
+
+ guard(rcu)();
+ input = rcu_dereference(attachment->input);
+ if (!input)
+ return -ENODEV;
+
+ if (attachment->device_state != GIP_STATE_START) {
+ gip_dbg(attachment, "Discarding early input report\n");
+ attachment->device_state = GIP_STATE_START;
+ return 0;
+ }
+
+ if (attachment->driver && attachment->driver->handle_overflow_input_report) {
+ rc = attachment->driver->handle_overflow_input_report(attachment,
+ input, bytes, num_bytes);
+ if (rc < 0)
+ return rc;
+ }
+
+ if (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW)
+ rc = gip_handle_console_map(attachment, input, bytes, num_bytes);
+
+ input_sync(input);
+
+ return rc;
+}
+
+static int gip_handle_audio_data(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ /* TODO: Needed for audio support */
+ gip_dbg(attachment, "Unimplemented Audio Data message\n");
+ return -EOPNOTSUPP;
+}
+
+static int gip_handle_system_message(struct gip_attachment *attachment,
+ const struct gip_header *header, uint8_t *bytes, int num_bytes)
+{
+ if (!gip_supports_system_message(attachment, header->message_type, true)) {
+ gip_warn(attachment,
+ "Received claimed-unsupported system message type %02x\n",
+ header->message_type);
+ return -EPROTO;
+ }
+ switch (header->message_type) {
+ case GIP_CMD_PROTO_CONTROL:
+ return gip_handle_command_protocol_control(attachment, header,
+ bytes, num_bytes);
+ case GIP_CMD_HELLO_DEVICE:
+ return gip_handle_command_hello_device(attachment, header,
+ bytes, num_bytes);
+ case GIP_CMD_STATUS_DEVICE:
+ return gip_handle_command_status_device(attachment, header,
+ bytes, num_bytes);
+ case GIP_CMD_METADATA:
+ return gip_handle_command_metadata_respose(attachment, header,
+ bytes, num_bytes);
+ case GIP_CMD_SECURITY:
+ return gip_handle_command_security(attachment, header, bytes,
+ num_bytes);
+ case GIP_CMD_GUIDE_BUTTON:
+ return gip_handle_command_guide_button_status(attachment,
+ header, bytes, num_bytes);
+ case GIP_CMD_AUDIO_CONTROL:
+ return gip_handle_command_audio_control(attachment, header,
+ bytes, num_bytes);
+ case GIP_CMD_FIRMWARE:
+ return gip_handle_command_firmware(attachment, header, bytes,
+ num_bytes);
+ case GIP_CMD_HID_REPORT:
+ return gip_handle_command_hid_report(attachment, header,
+ bytes, num_bytes);
+ case GIP_CMD_EXTENDED:
+ return gip_handle_command_extended(attachment, header, bytes,
+ num_bytes);
+ case GIP_AUDIO_DATA:
+ return gip_handle_audio_data(attachment, header, bytes,
+ num_bytes);
+ default:
+ gip_warn(attachment,
+ "Received unknown system message type %02x\n",
+ header->message_type);
+ return -EPROTO;
+ }
+}
+
+static struct gip_attachment *gip_ensure_attachment(struct gip_device *device,
+ uint8_t attachment_index)
+{
+ struct gip_attachment *attachment = device->attachments[attachment_index];
+
+ if (!attachment) {
+ attachment = devm_kzalloc(to_gip_device(device), sizeof(*attachment), GFP_KERNEL);
+ if (!attachment)
+ return ERR_PTR(-ENOMEM);
+
+ attachment->attachment_index = attachment_index;
+ attachment->device = device;
+ attachment->in_fragment_message = -1;
+
+ if (attachment_index == 0) {
+ attachment->vendor_id = device->udev->descriptor.idVendor;
+ attachment->product_id = device->udev->descriptor.idProduct;
+ }
+
+ device->attachments[attachment_index] = attachment;
+
+ mutex_init(&attachment->lock);
+ INIT_DELAYED_WORK(&attachment->in_fragment_timeout, gip_fragment_timeout);
+ INIT_DELAYED_WORK(&attachment->metadata_next, gip_retry_metadata);
+
+ gip_set_metadata_defaults(attachment);
+ }
+ return attachment;
+}
+
+static int gip_handle_message(struct gip_attachment *attachment,
+ const struct gip_header *header, uint8_t *bytes, int num_bytes)
+{
+ if (header->flags & GIP_FLAG_SYSTEM)
+ return gip_handle_system_message(attachment, header, bytes,
+ num_bytes);
+
+ if (header->message_type < MAX_GIP_CMD && attachment->vendor_handlers[header->message_type])
+ return attachment->vendor_handlers[header->message_type](attachment,
+ header, bytes, num_bytes);
+
+ switch (header->message_type) {
+ case GIP_LL_INPUT_REPORT:
+ return gip_handle_ll_input_report(attachment, header, bytes,
+ num_bytes);
+ case GIP_LL_OVERFLOW_INPUT_REPORT:
+ return gip_handle_ll_overflow_input_report(attachment, header,
+ bytes, num_bytes);
+ }
+ gip_info(attachment,
+ "Received unknown vendor message type %02x\n",
+ header->message_type);
+ return -EOPNOTSUPP;
+}
+
+static int gip_receive_fragment(struct gip_attachment *attachment,
+ const struct gip_header *header, int offset,
+ uint64_t *fragment_offset, uint16_t *bytes_remaining, uint8_t *bytes,
+ int num_bytes)
+{
+ int rc = 0;
+
+ if (header->flags & GIP_FLAG_INIT_FRAG) {
+ uint64_t total_length;
+
+ if (attachment->in_fragment_message >= 0) {
+ /*
+ * Reset fragment buffer if we get a new initial
+ * fragment before finishing the last message.
+ * TODO: Is this the correct behavior?
+ */
+ devm_kfree(to_gip_device(attachment), attachment->in_fragment_data);
+ attachment->in_fragment_data = NULL;
+ }
+ offset += gip_decode_length(&total_length, &bytes[offset],
+ num_bytes - offset);
+ if (total_length > MAX_MESSAGE_LENGTH)
+ return -EINVAL;
+
+ attachment->in_total_length = total_length;
+ attachment->in_fragment_message = header->message_type;
+ if (header->length > num_bytes - offset) {
+ gip_warn(attachment,
+ "Received fragment that claims to be %llu bytes, expected %i\n",
+ header->length, num_bytes - offset);
+ return -EPROTO;
+ }
+ if (header->length > total_length) {
+ gip_warn(attachment,
+ "Received too long fragment, %llu bytes, exceeds %d\n",
+ header->length, attachment->in_total_length);
+ return -EINVAL;
+ }
+ attachment->in_fragment_data = devm_kmalloc(to_gip_device(attachment),
+ attachment->in_total_length, GFP_KERNEL);
+ if (!attachment->in_fragment_data)
+ return -ENOMEM;
+ memcpy(attachment->in_fragment_data, &bytes[offset],
+ header->length);
+ *fragment_offset = header->length;
+ attachment->in_fragment_offset = header->length;
+ *bytes_remaining = attachment->in_total_length - header->length;
+ if (header->flags & GIP_FLAG_ACME)
+ gip_acknowledge(attachment, header, *fragment_offset, *bytes_remaining);
+ } else {
+ if (attachment->in_fragment_message < 0) {
+ gip_warn_ratelimited(attachment,
+ "Received unexpected fragment of message type %02x\n",
+ header->message_type);
+ return -EPROTO;
+ }
+ if (header->message_type != attachment->in_fragment_message) {
+ gip_warn_ratelimited(attachment,
+ "Received out of sequence message type %02x, expected %02x\n",
+ header->message_type, attachment->in_fragment_message);
+ gip_fragment_failed(attachment, header);
+ return -EPROTO;
+ }
+
+ offset += gip_decode_length(fragment_offset, &bytes[offset],
+ num_bytes - offset);
+ if (*fragment_offset != attachment->in_fragment_offset) {
+ /*
+ * While this is theoretically always an issue, for
+ * whatever reason a bunch of controllers will send the
+ * wrong final fragment offset. Presumably, that means
+ * the official driver won't reject it, so I guess we
+ * shouldn't either. Feels bad.
+ */
+ if (header->length && *fragment_offset != attachment->in_total_length) {
+ gip_warn_ratelimited(attachment,
+ "Received out of sequence fragment: claimed %llu, expected %d\n",
+ *fragment_offset, attachment->in_fragment_offset);
+ gip_acknowledge(attachment, header,
+ attachment->in_fragment_offset,
+ attachment->in_total_length - attachment->in_fragment_offset);
+ return -EPROTO;
+ }
+ gip_dbg_ratelimited(attachment,
+ "Received erroneous final fragment offset, proceeding anyway\n");
+ *bytes_remaining = 0;
+ } else if (*fragment_offset + header->length > attachment->in_total_length) {
+ gip_warn(attachment,
+ "Received too long fragment, %llu exceeds %d\n",
+ *fragment_offset + header->length, attachment->in_total_length);
+ gip_fragment_failed(attachment, header);
+ return -EINVAL;
+ } else {
+ *bytes_remaining = attachment->in_total_length -
+ (*fragment_offset + header->length);
+ }
+ if (header->length != 0)
+ memcpy(&attachment->in_fragment_data[*fragment_offset],
+ &bytes[offset], header->length);
+ *fragment_offset += header->length;
+ attachment->in_fragment_offset = *fragment_offset;
+
+ if (header->flags & GIP_FLAG_ACME)
+ gip_acknowledge(attachment, header, *fragment_offset, *bytes_remaining);
+ if (header->length == 0 && !*bytes_remaining) {
+ rc = gip_handle_message(attachment, header,
+ attachment->in_fragment_data,
+ attachment->in_total_length);
+ devm_kfree(to_gip_device(attachment), attachment->in_fragment_data);
+ attachment->in_fragment_data = NULL;
+ attachment->in_fragment_message = -1;
+ }
+ }
+ cancel_delayed_work_sync(&attachment->in_fragment_timeout);
+ schedule_delayed_work(&attachment->in_fragment_timeout, HZ);
+
+ return rc;
+}
+
+static int gip_receive_message(struct gip_device *device, uint8_t *bytes,
+ int num_bytes)
+{
+ struct gip_header header;
+ int offset = 3;
+ int rc = 0;
+ uint64_t fragment_offset = 0;
+ uint16_t bytes_remaining = 0;
+ bool is_fragment;
+ uint8_t attachment_index;
+ struct gip_attachment *attachment;
+
+ if (num_bytes < 5)
+ return -EINVAL;
+
+ header.message_type = bytes[0];
+ header.flags = bytes[1];
+ header.sequence_id = bytes[2];
+ offset += gip_decode_length(&header.length, &bytes[offset], num_bytes - offset);
+
+ is_fragment = header.flags & GIP_FLAG_FRAGMENT;
+ attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK;
+ attachment = gip_ensure_attachment(device, attachment_index);
+
+ print_hex_dump_debug(KBUILD_MODNAME ": Received message: ",
+ DUMP_PREFIX_OFFSET, 16, 1, bytes, num_bytes, false);
+
+ guard(mutex)(&attachment->lock);
+ /* Handle coalescing fragmented messages */
+ if (is_fragment) {
+ rc = gip_receive_fragment(attachment, &header, offset,
+ &fragment_offset, &bytes_remaining, bytes, num_bytes);
+ } else if (header.length + offset > num_bytes) {
+ gip_warn(device,
+ "Received message with erroneous length (claimed %llu, actual %d), discarding\n",
+ header.length + offset, num_bytes);
+ rc = -EPROTO;
+ } else {
+ num_bytes -= offset;
+ bytes += offset;
+ fragment_offset = header.length;
+ if (header.flags & GIP_FLAG_ACME)
+ gip_acknowledge(attachment, &header, fragment_offset, bytes_remaining);
+ rc = gip_handle_message(attachment, &header, bytes, num_bytes);
+ }
+
+ return rc;
+}
+
+static void gip_receive_work(struct work_struct *work)
+{
+ struct gip_device *device = container_of(work, struct gip_device,
+ receive_message);
+ unsigned long flags;
+
+ spin_lock_irqsave(&device->message_lock, flags);
+ while (device->pending_in_messages) {
+ struct gip_raw_message *message = &device->in_queue[device->next_in_message];
+
+ spin_unlock_irqrestore(&device->message_lock, flags);
+
+ gip_receive_message(device, message->bytes, message->num_bytes);
+
+ spin_lock_irqsave(&device->message_lock, flags);
+ device->next_in_message = (device->next_in_message + 1) % MAX_IN_MESSAGES;
+ device->pending_in_messages--;
+ }
+ spin_unlock_irqrestore(&device->message_lock, flags);
+}
+
+static void gip_urb_in(struct urb *urb)
+{
+ struct gip_interface *intf = urb->context;
+ struct gip_device *gip = intf->device;
+ struct device *dev = &intf->intf->dev;
+ int status = urb->status;
+ int message_id;
+ struct gip_raw_message *message;
+ unsigned long flags;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ return;
+ default:
+ dev_dbg(dev, "%s - urb has status of: %d\n",
+ __func__, status);
+ goto exit;
+ }
+ if (intf->isoc_messages) {
+ /* TODO: Needed for audio support */
+ gip_warn(gip, "Unimplemented isochronous message input\n");
+ goto exit;
+ }
+
+ spin_lock_irqsave(&gip->message_lock, flags);
+ if (gip->pending_in_messages >= MAX_IN_MESSAGES) {
+ gip_err(gip, "Input queue is full; dropping message\n");
+ } else {
+ message_id = (gip->next_in_message + gip->pending_in_messages) % MAX_IN_MESSAGES;
+ message = &gip->in_queue[message_id];
+ gip->pending_in_messages++;
+ memcpy(message->bytes, intf->in_data, urb->actual_length);
+ message->num_bytes = urb->actual_length;
+ }
+ spin_unlock_irqrestore(&gip->message_lock, flags);
+ schedule_work(&gip->receive_message);
+
+exit:
+ status = usb_submit_urb(urb, GFP_ATOMIC);
+ if (status)
+ dev_err(dev, "%s - usb_submit_urb failed with result %d\n",
+ __func__, status);
+}
+
+static void gip_urb_out(struct urb *urb)
+{
+ struct gip_interface *intf = urb->context;
+ struct device *dev = &intf->intf->dev;
+ int status = urb->status;
+
+ switch (status) {
+ case 0:
+ /* success */
+ break;
+
+ case -ECONNRESET:
+ case -ENOENT:
+ case -ESHUTDOWN:
+ /* this urb is terminated, clean up */
+ dev_dbg(dev, "%s - urb shutting down with status: %d\n",
+ __func__, status);
+ break;
+
+ default:
+ dev_dbg(dev, "%s - nonzero urb status received: %d\n",
+ __func__, status);
+ break;
+ }
+}
+
+static int gip_init_input(struct gip_interface *intf,
+ struct usb_endpoint_descriptor *ep_in)
+{
+ int error;
+ struct usb_device *udev = interface_to_usbdev(intf->intf);
+
+ intf->urb_in = usb_alloc_urb(intf->isoc_messages, GFP_KERNEL);
+ if (!intf->urb_in)
+ return -ENOMEM;
+
+ intf->in_data = usb_alloc_coherent(udev, intf->mtu, GFP_KERNEL,
+ &intf->urb_in->transfer_dma);
+
+ if (!intf->in_data) {
+ return -ENOMEM;
+ goto err_free_urb;
+ }
+
+ usb_fill_int_urb(intf->urb_in, udev,
+ usb_rcvintpipe(udev, ep_in->bEndpointAddress),
+ intf->in_data, intf->mtu, gip_urb_in, intf,
+ ep_in->bInterval);
+ intf->urb_in->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ if (intf->isoc_messages)
+ intf->urb_in->transfer_flags |= URB_ISO_ASAP;
+
+ return 0;
+
+err_free_urb:
+ usb_free_urb(intf->urb_in);
+ intf->urb_in = NULL;
+
+ return error;
+}
+
+static int gip_init_output(struct gip_interface *intf,
+ struct usb_endpoint_descriptor *ep_out)
+{
+ int error;
+ struct usb_device *udev = interface_to_usbdev(intf->intf);
+ int i;
+
+ if (usb_ifnum_to_if(udev, GIP_WIRED_INTF_AUDIO)) {
+ /*
+ * Explicitly disable the audio interface. This is needed
+ * for some controllers, such as the PowerA Enhanced Wired
+ * Controller for Series X|S (0x20d6:0x200e) to report the
+ * guide button.
+ */
+ error = usb_set_interface(udev, GIP_WIRED_INTF_AUDIO, 0);
+ if (error)
+ gip_warn(intf,
+ "unable to disable audio interface: %d\n",
+ error);
+ }
+
+ init_usb_anchor(&intf->out_anchor);
+
+ for (i = 0; i < MAX_OUT_MESSAGES; i++) {
+ intf->out_queue[i].urb = usb_alloc_urb(intf->isoc_messages, GFP_KERNEL);
+ if (!intf->out_queue[i].urb) {
+ error = -ENOMEM;
+ goto err_free_urbs;
+ }
+
+ intf->out_queue[i].data = usb_alloc_coherent(udev, intf->mtu, GFP_KERNEL,
+ &intf->out_queue[i].urb->transfer_dma);
+
+ if (!intf->out_queue[i].data) {
+ return -ENOMEM;
+ goto err_free_urbs;
+ }
+
+ usb_fill_int_urb(intf->out_queue[i].urb, udev,
+ usb_sndintpipe(udev, ep_out->bEndpointAddress),
+ intf->out_queue[i].data, intf->mtu, gip_urb_out, intf,
+ ep_out->bInterval);
+ intf->out_queue[i].urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
+
+ if (intf->isoc_messages)
+ intf->out_queue[i].urb->transfer_flags |= URB_ISO_ASAP;
+ }
+
+ return 0;
+
+err_free_urbs:
+ for (i = 0; i < MAX_OUT_MESSAGES; i++) {
+ if (intf->out_queue[i].data)
+ usb_free_coherent(udev, intf->mtu, intf->out_queue[i].data,
+ intf->out_queue[i].urb->transfer_dma);
+ usb_free_urb(intf->out_queue[i].urb);
+ intf->out_queue[i].urb = NULL;
+ }
+ return error;
+}
+
+static void gip_deinit_output(struct gip_interface *intf)
+{
+ int i;
+
+ for (i = 0; i < MAX_OUT_MESSAGES; i++) {
+ if (!intf->out_queue[i].urb)
+ continue;
+ usb_free_coherent(interface_to_usbdev(intf->intf), intf->mtu,
+ intf->out_queue[i].data, intf->out_queue[i].urb->transfer_dma);
+ usb_free_urb(intf->out_queue[i].urb);
+ intf->out_queue[i].data = NULL;
+ intf->out_queue[i].urb = NULL;
+ }
+}
+
+static void gip_deinit_input(struct gip_interface *intf)
+{
+ usb_free_coherent(interface_to_usbdev(intf->intf), intf->mtu,
+ intf->in_data, intf->urb_in->transfer_dma);
+ usb_free_urb(intf->urb_in);
+ intf->urb_in = NULL;
+}
+
+static int gip_interface_init(struct gip_interface *intf)
+{
+ struct usb_endpoint_descriptor *ep_in = NULL;
+ struct usb_endpoint_descriptor *ep_out = NULL;
+ int error = usb_find_common_endpoints(intf->intf->cur_altsetting,
+ NULL, NULL, &ep_in, &ep_out);
+
+ if (error)
+ return error;
+
+ if (!ep_in || !ep_out)
+ return -ENODEV;
+
+ error = gip_init_input(intf, ep_in);
+ if (error)
+ return error;
+
+ error = gip_init_output(intf, ep_out);
+ if (error)
+ goto err_free_input;
+
+ if (usb_submit_urb(intf->urb_in, GFP_KERNEL)) {
+ error = -EIO;
+ goto err_free_output;
+ }
+
+ return 0;
+
+err_free_output:
+ gip_deinit_output(intf);
+err_free_input:
+ gip_deinit_input(intf);
+ return error;
+}
+
+static int gip_probe(struct usb_interface *intf, const struct usb_device_id *id)
+{
+ struct usb_device *udev = interface_to_usbdev(intf);
+ struct gip_device *gip = NULL;
+ struct gip_attachment *attachment;
+ int rc;
+
+ if (intf->cur_altsetting->desc.bInterfaceNumber != GIP_WIRED_INTF_DATA) {
+ /*
+ * The Xbox One controller lists three interfaces all with the
+ * same interface class, subclass and protocol. Differentiate by
+ * interface number.
+ */
+ return 0;
+ }
+
+ gip = devm_kzalloc(&udev->dev, sizeof(*gip), GFP_KERNEL);
+ if (!gip)
+ return -ENOMEM;
+
+ gip->udev = udev;
+ gip->data.device = gip;
+ gip->data.intf = intf;
+ gip->data.mtu = BASE_GIP_MTU;
+ gip->audio.device = gip;
+ gip->audio.mtu = MAX_GIP_MTU;
+ gip->audio.isoc_messages = MAX_AUDIO_MESSAGES;
+
+ INIT_WORK(&gip->receive_message, gip_receive_work);
+ spin_lock_init(&gip->message_lock);
+
+ rc = gip_interface_init(&gip->data);
+ if (rc) {
+ devm_kfree(to_gip_device(gip), gip);
+ return rc;
+ }
+ /* Don't init audio interface -- we aren't using it yet */
+
+ usb_set_intfdata(intf, gip);
+
+ /* Pre-create the first attachment, as it should always exist */
+ attachment = gip_ensure_attachment(gip, 0);
+ if (IS_ERR(attachment))
+ return PTR_ERR(attachment);
+
+ return 0;
+}
+
+static int gip_shutdown(struct gip_device *device)
+{
+ int i;
+
+ cancel_work_sync(&device->receive_message);
+
+ for (i = 0; i < MAX_ATTACHMENTS; i++) {
+ struct gip_attachment *attachment = device->attachments[i];
+ struct input_dev *input;
+
+ if (!attachment)
+ continue;
+
+ scoped_guard (mutex, &attachment->lock) {
+ cancel_delayed_work_sync(&attachment->metadata_next);
+ cancel_delayed_work_sync(&attachment->in_fragment_timeout);
+
+ rcu_read_lock();
+ input = rcu_dereference(attachment->input);
+ rcu_read_unlock();
+
+ rcu_assign_pointer(attachment->input, NULL);
+ synchronize_rcu();
+ }
+
+ if (input)
+ input_unregister_device(input);
+ }
+
+ return 0;
+}
+
+static void gip_disconnect(struct usb_interface *intf)
+{
+ struct gip_device *gip = usb_get_intfdata(intf);
+ unsigned long flags;
+ int i;
+
+ if (!gip)
+ return;
+
+ usb_kill_urb(gip->data.urb_in);
+ if (gip->audio.intf)
+ usb_kill_urb(gip->audio.urb_in);
+
+ gip_shutdown(gip);
+
+ spin_lock_irqsave(&gip->message_lock, flags);
+ gip_deinit_input(&gip->data);
+ gip_deinit_output(&gip->data);
+ if (gip->audio.intf) {
+ gip_deinit_input(&gip->audio);
+ gip_deinit_output(&gip->audio);
+ }
+ spin_unlock_irqrestore(&gip->message_lock, flags);
+
+ usb_set_intfdata(intf, NULL);
+
+ for (i = 0; i < MAX_ATTACHMENTS; i++) {
+ struct gip_attachment *attachment = gip->attachments[i];
+
+ if (!attachment)
+ continue;
+
+ gip_reset_attachment(attachment);
+
+ devm_kfree(to_gip_device(attachment), attachment->uniq);
+ devm_kfree(to_gip_device(attachment), attachment);
+ }
+
+ devm_kfree(to_gip_device(gip), gip);
+}
+
+static int gip_suspend(struct usb_interface *intf, pm_message_t message)
+{
+ struct gip_device *gip = usb_get_intfdata(intf);
+
+ if (!gip)
+ return 0;
+
+ usb_kill_urb(gip->data.urb_in);
+ if (gip->audio.intf)
+ usb_kill_urb(gip->audio.urb_in);
+
+ if (gip->attachments[0]) {
+ struct gip_attachment *attachment = gip->attachments[0];
+
+ guard(mutex)(&attachment->lock);
+ gip_send_set_device_state(attachment, GIP_STATE_OFF);
+ attachment->device_state = GIP_STATE_OFF;
+ }
+
+ return gip_shutdown(gip);
+}
+
+static int gip_resume(struct usb_interface *intf)
+{
+ struct gip_device *gip = usb_get_intfdata(intf);
+
+ if (!gip)
+ return 0;
+
+ if (usb_submit_urb(gip->data.urb_in, GFP_KERNEL))
+ return -EIO;
+
+ return 0;
+}
+
+/* The Xbox One controller uses subclass 71 and protocol 208. */
+#define GIP_VENDOR(vend) \
+ { \
+ .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \
+ .idVendor = (vend), \
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \
+ .bInterfaceSubClass = 71, \
+ .bInterfaceProtocol = 208 \
+ }
+
+static const struct usb_device_id gip_table[] = {
+ /*
+ * Please keep this list sorted by vendor ID.
+ */
+ GIP_VENDOR(0x03f0), /* HP/HyperX */
+ GIP_VENDOR(0x044f), /* ThrustMaster */
+ GIP_VENDOR(0x045e), /* Microsoft */
+ GIP_VENDOR(0x046d), /* Logitech */
+ GIP_VENDOR(0x0738), /* Mad Catz */
+ GIP_VENDOR(0x0b05), /* ASUS */
+ GIP_VENDOR(0x0e6f), /* PDP */
+ GIP_VENDOR(0x0f0d), /* Hori */
+ GIP_VENDOR(0x10f5), /* Turtle Beach */
+ GIP_VENDOR(0x1532), /* Razer */
+ GIP_VENDOR(0x20d6), /* PowerA/BDA */
+ GIP_VENDOR(0x24c6), /* PowerA/BDA/ThrustMaster */
+ GIP_VENDOR(0x294b), /* Snakebyte */
+ GIP_VENDOR(0x2dc8), /* 8BitDo */
+ GIP_VENDOR(0x2e24), /* Hyperkin */
+ GIP_VENDOR(0x2e95), /* SCUF Gaming */
+ GIP_VENDOR(0x3285), /* Nacon */
+ GIP_VENDOR(0x3537), /* GameSir */
+ GIP_VENDOR(0x366c), /* ByoWave */
+ { }
+};
+
+MODULE_DEVICE_TABLE(usb, gip_table);
+
+static struct usb_driver gip_driver = {
+ .name = "xbox-gip",
+ .probe = gip_probe,
+ .disconnect = gip_disconnect,
+ .suspend = gip_suspend,
+ .resume = gip_resume,
+ .id_table = gip_table,
+};
+
+module_usb_driver(gip_driver);
+
+MODULE_AUTHOR("Vicki Pfau <vi@endrift.com>");
+MODULE_DESCRIPTION("Xbox Gaming Input Protocol driver");
+MODULE_LICENSE("GPL");
diff --git a/drivers/input/joystick/gip/gip-drivers.c b/drivers/input/joystick/gip/gip-drivers.c
new file mode 100644
index 000000000000..2c2297b1fed8
--- /dev/null
+++ b/drivers/input/joystick/gip/gip-drivers.c
@@ -0,0 +1,204 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Base drivers for common GIP devices
+ *
+ * Copyright (c) 2025 Valve Software
+ *
+ * This driver is based on the Microsoft GIP spec at:
+ * https://aka.ms/gipdocs
+ * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc
+ */
+
+#include <linux/unaligned.h>
+#include "gip.h"
+
+struct gip_device_capabilities_response {
+ uint8_t extra_button_count;
+ uint8_t extra_axis_count;
+ uint8_t led_count;
+ uint8_t max_global_led_gain;
+};
+
+static bool dpad_as_buttons;
+
+static int gip_setup_gamepad_input(struct gip_attachment *attachment, struct input_dev *input)
+{
+ int ret = gip_driver_navigation.setup_input(attachment, input);
+
+ if (ret < 0)
+ return ret;
+ input_set_capability(input, EV_KEY, BTN_THUMBR);
+ input_set_capability(input, EV_KEY, BTN_THUMBL);
+ input_set_abs_params(input, ABS_X, -32768, 32767, 16, 128);
+ input_set_abs_params(input, ABS_Y, -32768, 32767, 16, 128);
+ input_set_abs_params(input, ABS_RX, -32768, 32767, 16, 128);
+ input_set_abs_params(input, ABS_RY, -32768, 32767, 16, 128);
+ input_set_abs_params(input, ABS_Z, 0, 1023, 0, 0);
+ input_set_abs_params(input, ABS_RZ, 0, 1023, 0, 0);
+
+ /* Xbox Adaptive Controller */
+ if (attachment->vendor_id == 0x045e && attachment->product_id == 0x0b0a)
+ input_set_abs_params(input, ABS_PROFILE, 0, 3, 0, 0);
+ return 0;
+}
+
+static int gip_handle_gamepad_report(struct gip_attachment *attachment,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes)
+{
+ int ret = gip_driver_navigation.handle_input_report(attachment, input, bytes, num_bytes);
+
+ if (ret < 0)
+ return ret;
+
+ if (num_bytes < 14) {
+ gip_dbg(attachment, "Discarding too-short input report\n");
+ return -EINVAL;
+ }
+
+ input_report_key(input, BTN_THUMBL, bytes[1] & BIT(6));
+ input_report_key(input, BTN_THUMBR, bytes[1] & BIT(7));
+
+ input_report_abs(input, ABS_Z, (int16_t)get_unaligned_le16(&bytes[2]));
+ input_report_abs(input, ABS_RZ, (int16_t)get_unaligned_le16(&bytes[4]));
+
+ input_report_abs(input, ABS_X, (int16_t)get_unaligned_le16(&bytes[6]));
+ input_report_abs(input, ABS_Y, ~(int16_t)get_unaligned_le16(&bytes[8]));
+
+ input_report_abs(input, ABS_RX, (int16_t)get_unaligned_le16(&bytes[10]));
+ input_report_abs(input, ABS_RY, ~(int16_t)get_unaligned_le16(&bytes[12]));
+
+ if (attachment->vendor_id == GIP_VID_MICROSOFT &&
+ attachment->product_id == GIP_PID_XBOX_ADAPTIVE_CONTROLLER &&
+ num_bytes >= 31)
+ input_report_abs(input, ABS_PROFILE, bytes[30] & 3);
+
+ return 0;
+}
+
+const struct gip_driver gip_driver_gamepad = {
+ .types = (const char* const[]) { "Windows.Xbox.Input.Gamepad", NULL },
+ .guid = GUID_INIT(0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab,
+ 0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5),
+
+ .quirks = (const struct gip_quirks[]) {
+ /* Xbox One Controller (model 1573) */
+ { GIP_VID_MICROSOFT, GIP_PID_XBOX_ONE_1573, 0,
+ .override_name = "Xbox One Controller" },
+
+ /* Xbox One Controller (model 1697) */
+ { GIP_VID_MICROSOFT, GIP_PID_XBOX_ONE_1697, 0,
+ .override_name = "Xbox One Controller" },
+
+ /* Xbox Elite */
+ { GIP_VID_MICROSOFT, GIP_PID_XBOX_ELITE, 0,
+ .override_name = "Xbox Elite Controller",
+ .added_features = GIP_FEATURE_ELITE_BUTTONS,
+ .filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP },
+
+ /* Xbox One Controller (model 1708) */
+ { GIP_VID_MICROSOFT, GIP_PID_XBOX_ONE_1708, 0,
+ .override_name = "Xbox One Controller" },
+
+ /* Xbox Elite 2 */
+ { GIP_VID_MICROSOFT, GIP_PID_XBOX_ELITE_2, 0,
+ .override_name = "Xbox Elite Series 2 Controller",
+ .added_features = GIP_FEATURE_GUIDE_COLOR |
+ GIP_FEATURE_EXTENDED_SET_DEVICE_STATE },
+
+ /* Xbox Adaptive Controller */
+ { GIP_VID_MICROSOFT, GIP_PID_XBOX_ADAPTIVE_CONTROLLER, 0,
+ .override_name = "Xbox Adaptive Controller" },
+
+ /* Xbox Wireless Controller */
+ { GIP_VID_MICROSOFT, GIP_PID_XBOX_WIRELESS, 0,
+ .override_name = "Xbox Wireless Controller" },
+
+ {0},
+ },
+
+ .probe = NULL,
+ .remove = NULL,
+ .init = NULL,
+ .setup_input = gip_setup_gamepad_input,
+ .handle_input_report = gip_handle_gamepad_report,
+};
+
+static int gip_setup_navigation_input(struct gip_attachment *attachment, struct input_dev *input)
+{
+ input_set_capability(input, EV_KEY, BTN_Y);
+ input_set_capability(input, EV_KEY, BTN_B);
+ input_set_capability(input, EV_KEY, BTN_X);
+ input_set_capability(input, EV_KEY, BTN_A);
+ input_set_capability(input, EV_KEY, BTN_SELECT);
+ input_set_capability(input, EV_KEY, BTN_START);
+ input_set_capability(input, EV_KEY, BTN_TR);
+ input_set_capability(input, EV_KEY, BTN_TL);
+
+ attachment->dpad_as_buttons = dpad_as_buttons;
+ if (attachment->dpad_as_buttons) {
+ input_set_capability(input, EV_KEY, BTN_DPAD_UP);
+ input_set_capability(input, EV_KEY, BTN_DPAD_RIGHT);
+ input_set_capability(input, EV_KEY, BTN_DPAD_LEFT);
+ input_set_capability(input, EV_KEY, BTN_DPAD_DOWN);
+ } else {
+ input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
+ input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
+ }
+
+ return 0;
+}
+
+static int gip_handle_navigation_report(struct gip_attachment *attachment,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes)
+{
+ if (num_bytes < 2) {
+ gip_dbg(attachment, "Discarding too-short input report\n");
+ return -EINVAL;
+ }
+
+ input_report_key(input, BTN_START, bytes[0] & BIT(2));
+ input_report_key(input, BTN_SELECT, bytes[0] & BIT(3));
+ input_report_key(input, BTN_A, bytes[0] & BIT(4));
+ input_report_key(input, BTN_B, bytes[0] & BIT(5));
+ input_report_key(input, BTN_X, bytes[0] & BIT(6));
+ input_report_key(input, BTN_Y, bytes[0] & BIT(7));
+
+ if (attachment->dpad_as_buttons) {
+ input_report_key(input, BTN_DPAD_UP, bytes[1] & BIT(0));
+ input_report_key(input, BTN_DPAD_DOWN, bytes[1] & BIT(1));
+ input_report_key(input, BTN_DPAD_LEFT, bytes[1] & BIT(2));
+ input_report_key(input, BTN_DPAD_RIGHT, bytes[1] & BIT(3));
+ } else {
+ input_report_abs(input, ABS_HAT0X,
+ !!(bytes[1] & BIT(3)) - !!(bytes[1] & BIT(2)));
+ input_report_abs(input, ABS_HAT0Y,
+ !!(bytes[1] & BIT(1)) - !!(bytes[1] & BIT(0)));
+ }
+
+ if (attachment->quirks & GIP_QUIRK_SWAP_LB_RB) {
+ /* Previous */
+ input_report_key(input, BTN_TR, bytes[1] & BIT(4));
+ /* Next */
+ input_report_key(input, BTN_TL, bytes[1] & BIT(5));
+ } else {
+ input_report_key(input, BTN_TL, bytes[1] & BIT(4));
+ input_report_key(input, BTN_TR, bytes[1] & BIT(5));
+ }
+
+ return 0;
+}
+
+const struct gip_driver gip_driver_navigation = {
+ .types = (const char* const[]) { "Windows.Xbox.Input.NavigationController", NULL },
+ .guid = GUID_INIT(0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8,
+ 0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7),
+
+ .probe = NULL,
+ .remove = NULL,
+ .init = NULL,
+ .setup_input = gip_setup_navigation_input,
+ .handle_input_report = gip_handle_navigation_report,
+};
+
+module_param(dpad_as_buttons, bool, 0444);
+MODULE_PARM_DESC(dpad_as_buttons, "Map the D-Pad as buttons instead of axes");
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
new file mode 100644
index 000000000000..d48ab64ab2eb
--- /dev/null
+++ b/drivers/input/joystick/gip/gip.h
@@ -0,0 +1,344 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Gaming Input Protocol driver for Xbox One/Series controllers
+ *
+ * Copyright (c) 2025 Valve Software
+ *
+ * This driver is based on the Microsoft GIP spec at:
+ * https://aka.ms/gipdocs
+ * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc
+ */
+
+#ifndef _GIP_H
+#define _GIP_H
+
+#include <linux/rcupdate.h>
+#include <linux/usb/input.h>
+
+#define BASE_GIP_MTU 64
+#define MAX_GIP_MTU 2048
+
+#define MAX_ATTACHMENTS 8
+
+#define MAX_IN_MESSAGES 8
+#define MAX_OUT_MESSAGES 8
+
+#define GIP_VID_MICROSOFT 0x045e
+#define GIP_VID_PDP 0x0e6f
+
+#define GIP_PID_XBOX_ONE_1573 0x02d1
+#define GIP_PID_XBOX_ONE_1697 0x02dd
+#define GIP_PID_XBOX_ELITE 0x02e3
+#define GIP_PID_XBOX_ONE_1708 0x02ea
+#define GIP_PID_XBOX_ELITE_2 0x0b00
+#define GIP_PID_XBOX_ADAPTIVE_CONTROLLER 0x0b0a
+#define GIP_PID_XBOX_WIRELESS 0x0b12
+
+#define GIP_PID_PDP_ROCK_CANDY 0x0246
+
+#define GIP_QUIRK_NO_HELLO BIT(0)
+#define GIP_QUIRK_NO_IMPULSE_VIBRATION BIT(1)
+#define GIP_QUIRK_SWAP_LB_RB BIT(2)
+/*
+ * Driver-specific quirks should start from 31 and go downwards to avoid
+ * conflicts with newly-added core quirks
+ */
+
+#define GIP_FEATURE_CONTROLLER BIT(0)
+#define GIP_FEATURE_CONSOLE_FUNCTION_MAP BIT(1)
+#define GIP_FEATURE_CONSOLE_FUNCTION_MAP_OVERFLOW BIT(2)
+#define GIP_FEATURE_ELITE_BUTTONS BIT(3)
+#define GIP_FEATURE_DYNAMIC_LATENCY_INPUT BIT(4)
+#define GIP_FEATURE_SECURITY_OPT_OUT BIT(5)
+#define GIP_FEATURE_MOTOR_CONTROL BIT(6)
+#define GIP_FEATURE_GUIDE_COLOR BIT(7)
+#define GIP_FEATURE_EXTENDED_SET_DEVICE_STATE BIT(8)
+
+#define GIP_FLAG_FRAGMENT BIT(7)
+#define GIP_FLAG_INIT_FRAG BIT(6)
+#define GIP_FLAG_SYSTEM BIT(5)
+#define GIP_FLAG_ACME BIT(4)
+#define GIP_FLAG_ATTACHMENT_MASK 0x7
+
+/* System messages */
+#define GIP_CMD_PROTO_CONTROL 0x01
+#define GIP_CMD_HELLO_DEVICE 0x02
+#define GIP_CMD_STATUS_DEVICE 0x03
+#define GIP_CMD_METADATA 0x04
+#define GIP_CMD_SET_DEVICE_STATE 0x05
+#define GIP_CMD_SECURITY 0x06
+#define GIP_CMD_GUIDE_BUTTON 0x07
+#define GIP_CMD_AUDIO_CONTROL 0x08
+#define GIP_CMD_LED 0x0a
+#define GIP_CMD_HID_REPORT 0x0b
+#define GIP_CMD_FIRMWARE 0x0c
+#define GIP_CMD_EXTENDED 0x1e
+#define GIP_CMD_DEBUG 0x1f
+#define GIP_AUDIO_DATA 0x60
+
+/* Navigation vendor messages */
+#define GIP_CMD_DIRECT_MOTOR 0x09
+#define GIP_LL_INPUT_REPORT 0x20
+#define GIP_LL_OVERFLOW_INPUT_REPORT 0x26
+
+/* Wheel and ArcadeStick vendor messages */
+#define GIP_CMD_INITIAL_REPORTS_REQUEST 0x0a
+#define GIP_LL_STATIC_CONFIGURATION 0x21
+#define GIP_LL_BUTTON_INFO_REPORT 0x22
+
+#define MAX_GIP_CMD 0x80
+
+#define to_gip_device(p) \
+ _Generic((p), \
+ struct gip_attachment * : gip_attachment_dev, \
+ struct gip_interface * : gip_interface_dev, \
+ struct gip_device * : gip_device_dev)(p)
+
+#define gip_dbg(dev, ...) dev_dbg(to_gip_device(dev), __VA_ARGS__)
+#define gip_info(dev, ...) dev_info(to_gip_device(dev), __VA_ARGS__)
+#define gip_warn(dev, ...) dev_warn(to_gip_device(dev), __VA_ARGS__)
+#define gip_err(dev, ...) dev_err(to_gip_device(dev), __VA_ARGS__)
+
+#define gip_dbg_ratelimited(dev, ...) dev_dbg_ratelimited(to_gip_device(dev), __VA_ARGS__)
+#define gip_info_ratelimited(dev, ...) dev_info_ratelimited(to_gip_device(dev), __VA_ARGS__)
+#define gip_warn_ratelimited(dev, ...) dev_warn_ratelimited(to_gip_device(dev), __VA_ARGS__)
+#define gip_err_ratelimited(dev, ...) dev_err_ratelimited(to_gip_device(dev), __VA_ARGS__)
+
+enum gip_init_status {
+ GIP_INIT_OK = 0,
+ GIP_INIT_NO_INPUT = 1,
+};
+
+enum gip_metadata_status {
+ GIP_METADATA_NONE = 0,
+ GIP_METADATA_GOT = 1,
+ GIP_METADATA_FAKED = 2,
+ GIP_METADATA_PENDING = 3,
+};
+
+enum gip_elite_button_format {
+ GIP_BTN_FMT_UNKNOWN,
+ GIP_BTN_FMT_XBE1,
+ GIP_BTN_FMT_XBE2_RAW,
+ GIP_BTN_FMT_XBE2_4,
+ GIP_BTN_FMT_XBE2_5,
+};
+
+struct gip_header {
+ uint8_t message_type;
+ uint8_t flags;
+ uint8_t sequence_id;
+ uint64_t length;
+};
+
+struct gip_raw_message {
+ uint16_t num_bytes;
+ uint8_t bytes[BASE_GIP_MTU];
+};
+
+struct gip_device_metadata {
+ uint8_t num_audio_formats;
+ uint8_t num_preferred_types;
+ uint8_t num_supported_interfaces;
+ uint8_t hid_descriptor_size;
+
+ uint32_t in_system_messages[8];
+ uint32_t out_system_messages[8];
+
+ struct gip_audio_format_pair *audio_formats;
+ char **preferred_types;
+ guid_t *supported_interfaces;
+ uint8_t *hid_descriptor;
+};
+
+struct gip_message_metadata {
+ uint16_t length;
+ uint8_t type;
+ uint16_t data_type;
+ uint32_t flags;
+ uint16_t period;
+ uint16_t persistence_timeout;
+};
+
+struct gip_metadata {
+ uint16_t version_major;
+ uint16_t version_minor;
+
+ struct gip_device_metadata device;
+
+ uint8_t num_messages;
+ struct gip_message_metadata *message_metadata;
+};
+
+struct gip_status {
+ int power_level;
+ int charge;
+ int battery_type;
+ int battery_level;
+};
+
+struct gip_status_event {
+ uint16_t event_type;
+ uint32_t fault_tag;
+ uint32_t fault_address;
+};
+
+struct gip_extended_status {
+ struct gip_status base;
+ bool device_active;
+
+ int num_events;
+ struct gip_status_event events[5];
+};
+
+struct gip_attachment;
+typedef int (*gip_command_handler)(struct gip_attachment *a, const struct gip_header *header,
+ const uint8_t *bytes, int num_bytes);
+
+struct gip_device;
+struct gip_attachment {
+ const struct gip_driver *driver;
+ struct gip_device *device;
+ void *driver_data;
+ gip_command_handler vendor_handlers[MAX_GIP_CMD];
+
+ uint8_t attachment_index;
+ struct input_dev __rcu *input;
+ uint16_t vendor_id;
+ uint16_t product_id;
+ char *uniq;
+ const char *name;
+ char phys[32];
+ char serial[32];
+ struct mutex lock;
+
+ int16_t in_fragment_message;
+ uint16_t in_total_length;
+ uint8_t *in_fragment_data;
+ uint32_t in_fragment_offset;
+ struct delayed_work in_fragment_timeout;
+ int in_fragment_retries;
+
+ uint16_t firmware_major_version;
+ uint16_t firmware_minor_version;
+
+ enum gip_metadata_status got_metadata;
+ struct delayed_work metadata_next;
+ int metadata_retries;
+ struct gip_metadata metadata;
+
+ uint8_t seq_system;
+ uint8_t seq_security;
+ uint8_t seq_extended;
+ uint8_t seq_audio;
+ uint8_t seq_vendor;
+
+ uint8_t device_state;
+
+ struct gip_extended_status status;
+
+ enum gip_elite_button_format xbe_format;
+ uint32_t features;
+ uint32_t quirks;
+
+ int extra_buttons;
+ int extra_axes;
+
+ bool dpad_as_buttons;
+ struct hid_device __rcu *hdev;
+};
+
+struct gip_urb {
+ struct urb *urb;
+ uint8_t *data;
+ unsigned int offset;
+};
+
+struct gip_interface {
+ struct gip_device *device;
+ struct usb_interface *intf;
+ uint32_t mtu;
+ int isoc_messages;
+
+ struct urb *urb_in;
+ uint8_t *in_data;
+
+ struct usb_anchor out_anchor;
+ struct gip_urb out_queue[MAX_OUT_MESSAGES];
+};
+
+struct gip_device {
+ struct usb_device *udev;
+
+ struct gip_interface data;
+ struct gip_interface audio;
+
+ struct gip_raw_message in_queue[MAX_IN_MESSAGES];
+ int pending_in_messages;
+ int next_in_message;
+
+ struct work_struct receive_message;
+ spinlock_t message_lock;
+
+ struct gip_attachment *attachments[MAX_ATTACHMENTS];
+};
+
+struct gip_quirks {
+ uint16_t vendor_id;
+ uint16_t product_id;
+ uint8_t attachment_index;
+ const char *override_name;
+ uint32_t added_features;
+ uint32_t filtered_features;
+ uint32_t quirks;
+ uint32_t extra_in_system[8];
+ uint32_t extra_out_system[8];
+ uint8_t extra_buttons;
+ uint8_t extra_axes;
+};
+
+struct gip_driver {
+ const char *const *types;
+ guid_t guid;
+
+ const struct gip_quirks *quirks;
+
+ int (*probe)(struct gip_attachment *a);
+ void (*remove)(struct gip_attachment *a);
+ int (*init)(struct gip_attachment *a);
+ int (*setup_input)(struct gip_attachment *a, struct input_dev *input);
+ int (*handle_input_report)(struct gip_attachment *a,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes);
+ int (*handle_overflow_input_report)(struct gip_attachment *a,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes);
+ gip_command_handler vendor_handlers[MAX_GIP_CMD];
+};
+
+static inline struct device *gip_attachment_dev(struct gip_attachment *attachment)
+{
+ return &attachment->device->udev->dev;
+}
+
+static inline struct device *gip_interface_dev(struct gip_interface *intf)
+{
+ return &intf->device->udev->dev;
+}
+
+static inline struct device *gip_device_dev(struct gip_device *device)
+{
+ return &device->udev->dev;
+}
+
+bool gip_supports_vendor_message(struct gip_attachment *attachment, uint8_t command, bool upstream);
+
+int gip_send_system_message(struct gip_attachment *attachment,
+ uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes);
+int gip_send_vendor_message(struct gip_attachment *attachment,
+ uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes);
+
+extern const struct gip_driver gip_driver_navigation;
+extern const struct gip_driver gip_driver_gamepad;
+extern const struct gip_driver gip_driver_arcade_stick;
+extern const struct gip_driver gip_driver_wheel;
+extern const struct gip_driver gip_driver_flight_stick;
+#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 02/12] Input: xpad - Remove Xbox One support
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 01/12] " Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:41 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 03/12] Input: xbox_gip - Add controllable LED support Vicki Pfau
` (9 subsequent siblings)
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
It has been superseded by xbox_gip. As the new driver is already at feature
parity, removing the fairly rough Xbox One support from xpad is safe and
will prevent any potential conflicts.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
Documentation/input/devices/xpad.rst | 17 +-
drivers/input/joystick/xpad.c | 636 +--------------------------
2 files changed, 11 insertions(+), 642 deletions(-)
diff --git a/Documentation/input/devices/xpad.rst b/Documentation/input/devices/xpad.rst
index a480bc781565..fd2afdd7b405 100644
--- a/Documentation/input/devices/xpad.rst
+++ b/Documentation/input/devices/xpad.rst
@@ -2,19 +2,19 @@
xpad - Linux USB driver for Xbox compatible controllers
=======================================================
-This driver exposes all first-party and third-party Xbox compatible
-controllers. It has a long history and has enjoyed considerable usage
-as Windows' xinput library caused most PC games to focus on Xbox
-controller compatibility.
+This driver exposes all first-party and third-party Xbox and Xbox 360
+compatible controllers. It has a long history and has enjoyed considerable
+usage as Windows' xinput library caused most PC games to focus on Xbox
+controller compatibility. Xbox One/Series controller support has been
+superseded by the xbox_gip driver, which specializes in the Gaming Input
+Protocl that is introduced on the Xbox One.
Due to backwards compatibility all buttons are reported as digital.
This only affects Original Xbox controllers. All later controller models
have only digital face buttons.
Rumble is supported on some models of Xbox 360 controllers but not of
-Original Xbox controllers nor on Xbox One controllers. As of writing
-the Xbox One's rumble protocol has not been reverse-engineered but in
-the future could be supported.
+Original Xbox controllers.
Notes
@@ -98,9 +98,6 @@ All generations of Xbox controllers speak USB over the wire.
- Wireless Xbox 360 controllers require a 'Xbox 360 Wireless Gaming Receiver
for Windows'
- Wired Xbox 360 controllers use standard USB connectors.
-- Xbox One controllers can be wireless but speak Wi-Fi Direct and are not
- yet supported.
-- Xbox One controllers can be wired and use standard Micro-USB connectors.
diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index feb8f368f834..72d773280402 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -17,7 +17,6 @@
* - the iForce driver drivers/char/joystick/iforce.c
* - the skeleton-driver drivers/usb/usb-skeleton.c
* - Xbox 360 information http://www.free60.org/wiki/Gamepad
- * - Xbox One information https://github.com/quantus/xbox-one-controller-protocol
*
* Thanks to:
* - ITO Takayuki for providing essential xpad information on his website
@@ -46,10 +45,6 @@
#define MAP_DPAD_TO_BUTTONS BIT(0)
#define MAP_TRIGGERS_TO_BUTTONS BIT(1)
#define MAP_STICKS_TO_NULL BIT(2)
-#define MAP_SHARE_BUTTON BIT(3)
-#define MAP_PADDLES BIT(4)
-#define MAP_PROFILE_BUTTON BIT(5)
-#define MAP_SHARE_OFFSET BIT(6)
#define DANCEPAD_MAP_CONFIG (MAP_DPAD_TO_BUTTONS | \
MAP_TRIGGERS_TO_BUTTONS | MAP_STICKS_TO_NULL)
@@ -57,8 +52,7 @@
#define XTYPE_XBOX 0
#define XTYPE_XBOX360 1
#define XTYPE_XBOX360W 2
-#define XTYPE_XBOXONE 3
-#define XTYPE_UNKNOWN 4
+#define XTYPE_UNKNOWN 3
/* Send power-off packet to xpad360w after holding the mode button for this many
* seconds
@@ -105,16 +99,11 @@ static const struct xpad_device {
{ 0x03eb, 0xff02, "Wooting Two (Legacy)", 0, XTYPE_XBOX360 },
{ 0x03f0, 0x038D, "HyperX Clutch", 0, XTYPE_XBOX360 }, /* wired */
{ 0x03f0, 0x048D, "HyperX Clutch", 0, XTYPE_XBOX360 }, /* wireless */
- { 0x03f0, 0x0495, "HyperX Clutch Gladiate", 0, XTYPE_XBOXONE },
- { 0x03f0, 0x07A0, "HyperX Clutch Gladiate RGB", 0, XTYPE_XBOXONE },
- { 0x03f0, 0x08B6, "HyperX Clutch Gladiate", MAP_SHARE_BUTTON, XTYPE_XBOXONE }, /* v2 */
- { 0x03f0, 0x09B4, "HyperX Clutch Tanto", 0, XTYPE_XBOXONE },
{ 0x044f, 0x0f00, "Thrustmaster Wheel", 0, XTYPE_XBOX },
{ 0x044f, 0x0f03, "Thrustmaster Wheel", 0, XTYPE_XBOX },
{ 0x044f, 0x0f07, "Thrustmaster, Inc. Controller", 0, XTYPE_XBOX },
{ 0x044f, 0x0f10, "Thrustmaster Modena GT Wheel", 0, XTYPE_XBOX },
{ 0x044f, 0xb326, "Thrustmaster Gamepad GP XID", 0, XTYPE_XBOX360 },
- { 0x044f, 0xd01e, "ThrustMaster, Inc. ESWAP X 2 ELDEN RING EDITION", 0, XTYPE_XBOXONE },
{ 0x045e, 0x0202, "Microsoft X-Box pad v1 (US)", 0, XTYPE_XBOX },
{ 0x045e, 0x0285, "Microsoft X-Box pad (Japan)", 0, XTYPE_XBOX },
{ 0x045e, 0x0287, "Microsoft Xbox Controller S", 0, XTYPE_XBOX },
@@ -124,14 +113,7 @@ static const struct xpad_device {
{ 0x045e, 0x028f, "Microsoft X-Box 360 pad v2", 0, XTYPE_XBOX360 },
{ 0x045e, 0x0291, "Xbox 360 Wireless Receiver (XBOX)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
{ 0x045e, 0x02a9, "Xbox 360 Wireless Receiver (Unofficial)", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
- { 0x045e, 0x02d1, "Microsoft X-Box One pad", 0, XTYPE_XBOXONE },
- { 0x045e, 0x02dd, "Microsoft X-Box One pad (Firmware 2015)", 0, XTYPE_XBOXONE },
- { 0x045e, 0x02e3, "Microsoft X-Box One Elite pad", MAP_PADDLES, XTYPE_XBOXONE },
- { 0x045e, 0x02ea, "Microsoft X-Box One S pad", 0, XTYPE_XBOXONE },
{ 0x045e, 0x0719, "Xbox 360 Wireless Receiver", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX360W },
- { 0x045e, 0x0b00, "Microsoft X-Box One Elite 2 pad", MAP_PADDLES, XTYPE_XBOXONE },
- { 0x045e, 0x0b0a, "Microsoft X-Box Adaptive Controller", MAP_PROFILE_BUTTON, XTYPE_XBOXONE },
- { 0x045e, 0x0b12, "Microsoft Xbox Series S|X Controller", MAP_SHARE_BUTTON | MAP_SHARE_OFFSET, XTYPE_XBOXONE },
{ 0x046d, 0xc21d, "Logitech Gamepad F310", 0, XTYPE_XBOX360 },
{ 0x046d, 0xc21e, "Logitech Gamepad F510", 0, XTYPE_XBOX360 },
{ 0x046d, 0xc21f, "Logitech Gamepad F710", 0, XTYPE_XBOX360 },
@@ -151,7 +133,6 @@ static const struct xpad_device {
{ 0x06a3, 0x0200, "Saitek Racing Wheel", 0, XTYPE_XBOX },
{ 0x06a3, 0x0201, "Saitek Adrenalin", 0, XTYPE_XBOX },
{ 0x06a3, 0xf51a, "Saitek P3600", 0, XTYPE_XBOX360 },
- { 0x0738, 0x4503, "Mad Catz Racing Wheel", 0, XTYPE_XBOXONE },
{ 0x0738, 0x4506, "Mad Catz 4506 Wireless Controller", 0, XTYPE_XBOX },
{ 0x0738, 0x4516, "Mad Catz Control Pad", 0, XTYPE_XBOX },
{ 0x0738, 0x4520, "Mad Catz Control Pad Pro", 0, XTYPE_XBOX },
@@ -173,7 +154,6 @@ static const struct xpad_device {
{ 0x0738, 0x4740, "Mad Catz Beat Pad", 0, XTYPE_XBOX360 },
{ 0x0738, 0x4743, "Mad Catz Beat Pad Pro", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
{ 0x0738, 0x4758, "Mad Catz Arcade Game Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x0738, 0x4a01, "Mad Catz FightStick TE 2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
{ 0x0738, 0x6040, "Mad Catz Beat Pad Pro", MAP_DPAD_TO_BUTTONS, XTYPE_XBOX },
{ 0x0738, 0x9871, "Mad Catz Portable Drum", 0, XTYPE_XBOX360 },
{ 0x0738, 0xb726, "Mad Catz Xbox controller - MW2", 0, XTYPE_XBOX360 },
@@ -184,12 +164,8 @@ static const struct xpad_device {
{ 0x0738, 0xcb29, "Saitek Aviator Stick AV8R02", 0, XTYPE_XBOX360 },
{ 0x0738, 0xf738, "Super SFIV FightStick TE S", 0, XTYPE_XBOX360 },
{ 0x07ff, 0xffff, "Mad Catz GamePad", 0, XTYPE_XBOX360 },
- { 0x0b05, 0x1a38, "ASUS ROG RAIKIRI", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x0b05, 0x1abb, "ASUS ROG RAIKIRI PRO", 0, XTYPE_XBOXONE },
{ 0x0b05, 0x1c91, "ASUS ROG RAIKIRI II", 0, XTYPE_XBOX360 },
{ 0x0b05, 0x1c92, "ASUS ROG RAIKIRI II WIRELESS", 0, XTYPE_XBOX360 },
- { 0x0b05, 0x1c96, "ASUS ROG RAIKIRI II XBOX", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x0b05, 0x1d04, "ASUS ROG RAIKIRI II XBOX WIRELESS", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x0c12, 0x0005, "Intec wireless", 0, XTYPE_XBOX },
{ 0x0c12, 0x8801, "Nyko Xbox Controller", 0, XTYPE_XBOX },
{ 0x0c12, 0x8802, "Zeroplus Xbox Controller", 0, XTYPE_XBOX },
@@ -212,34 +188,10 @@ static const struct xpad_device {
{ 0x0e6f, 0x011f, "Rock Candy Gamepad Wired Controller", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0131, "PDP EA Sports Controller", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0133, "Xbox 360 Wired Controller", 0, XTYPE_XBOX360 },
- { 0x0e6f, 0x0139, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x013a, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0146, "Rock Candy Wired Controller for Xbox One", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0147, "PDP Marvel Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x015c, "PDP Xbox One Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
- { 0x0e6f, 0x015d, "PDP Mirror's Edge Official Wired Controller for Xbox One", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0161, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0162, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0163, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0164, "PDP Battlefield One", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x0165, "PDP Titanfall 2", 0, XTYPE_XBOXONE },
{ 0x0e6f, 0x0201, "Pelican PL-3601 'TSZ' Wired Xbox 360 Controller", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0213, "Afterglow Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x021f, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
- { 0x0e6f, 0x0246, "Rock Candy Gamepad for Xbox One 2015", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a0, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a1, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a2, "PDP Wired Controller for Xbox One - Crimson Red", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a4, "PDP Wired Controller for Xbox One - Stealth Series", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a6, "PDP Wired Controller for Xbox One - Camo Series", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a7, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02a8, "PDP Xbox One Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02ab, "PDP Controller for Xbox One", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02ad, "PDP Wired Controller for Xbox One - Stealth Series", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02b3, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE },
- { 0x0e6f, 0x02b8, "Afterglow Prismatic Wired Controller", 0, XTYPE_XBOXONE },
{ 0x0e6f, 0x0301, "Logic3 Controller", 0, XTYPE_XBOX360 },
- { 0x0e6f, 0x0346, "Rock Candy Gamepad for Xbox One 2016", 0, XTYPE_XBOXONE },
{ 0x0e6f, 0x0401, "Logic3 Controller", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0413, "Afterglow AX.1 Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
{ 0x0e6f, 0x0501, "PDP Xbox 360 Controller", 0, XTYPE_XBOX360 },
@@ -251,23 +203,13 @@ static const struct xpad_device {
{ 0x0f0d, 0x000d, "Hori Fighting Stick EX2", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x0f0d, 0x0016, "Hori Real Arcade Pro.EX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x0f0d, 0x001b, "Hori Real Arcade Pro VX", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x0f0d, 0x0063, "Hori Real Arcade Pro Hayabusa (USA) Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
- { 0x0f0d, 0x0067, "HORIPAD ONE", 0, XTYPE_XBOXONE },
- { 0x0f0d, 0x0078, "Hori Real Arcade Pro V Kai Xbox One", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
- { 0x0f0d, 0x00c5, "Hori Fighting Commander ONE", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
{ 0x0f0d, 0x00dc, "HORIPAD FPS for Nintendo Switch", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x0f0d, 0x0151, "Hori Racing Wheel Overdrive for Xbox Series X", 0, XTYPE_XBOXONE },
- { 0x0f0d, 0x0152, "Hori Racing Wheel Overdrive for Xbox Series X", 0, XTYPE_XBOXONE },
- { 0x0f0d, 0x01b2, "HORI Taiko No Tatsujin Drum Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x0f30, 0x010b, "Philips Recoil", 0, XTYPE_XBOX },
{ 0x0f30, 0x0202, "Joytech Advanced Controller", 0, XTYPE_XBOX },
{ 0x0f30, 0x8888, "BigBen XBMiniPad Controller", 0, XTYPE_XBOX },
{ 0x102c, 0xff0c, "Joytech Wireless Advanced Controller", 0, XTYPE_XBOX },
{ 0x1038, 0x1430, "SteelSeries Stratus Duo", 0, XTYPE_XBOX360 },
{ 0x1038, 0x1431, "SteelSeries Stratus Duo", 0, XTYPE_XBOX360 },
- { 0x10f5, 0x7005, "Turtle Beach Recon Controller", 0, XTYPE_XBOXONE },
- { 0x10f5, 0x7008, "Turtle Beach Recon Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x10f5, 0x7073, "Turtle Beach Stealth Ultra Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x11c9, 0x55f0, "Nacon GC-100XF", 0, XTYPE_XBOX360 },
{ 0x11ff, 0x0511, "PXN V900", 0, XTYPE_XBOX360 },
{ 0x1209, 0x2882, "Ardwiino Controller", 0, XTYPE_XBOX360 },
@@ -280,9 +222,6 @@ static const struct xpad_device {
{ 0x1430, 0xf801, "RedOctane Controller", 0, XTYPE_XBOX360 },
{ 0x146b, 0x0601, "BigBen Interactive XBOX 360 Controller", 0, XTYPE_XBOX360 },
{ 0x146b, 0x0604, "Bigben Interactive DAIJA Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x1532, 0x0a00, "Razer Atrox Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOXONE },
- { 0x1532, 0x0a03, "Razer Wildcat", 0, XTYPE_XBOXONE },
- { 0x1532, 0x0a29, "Razer Wolverine V2", 0, XTYPE_XBOXONE },
{ 0x1532, 0x0a57, "Razer Wolverine V3 Pro (Wired)", 0, XTYPE_XBOX360 },
{ 0x1532, 0x0a59, "Razer Wolverine V3 Pro (2.4 GHz Dongle)", 0, XTYPE_XBOX360 },
{ 0x15e4, 0x3f00, "Power A Mini Pro Elite", 0, XTYPE_XBOX360 },
@@ -334,12 +273,7 @@ static const struct xpad_device {
{ 0x1ee9, 0x1590, "ZOTAC Gaming Zone", 0, XTYPE_XBOX360 },
{ 0x20bc, 0x5134, "BETOP BTP-KP50B Xinput Dongle", 0, XTYPE_XBOX360 },
{ 0x20bc, 0x514a, "BETOP BTP-KP50C Xinput Dongle", 0, XTYPE_XBOX360 },
- { 0x20d6, 0x2001, "BDA Xbox Series X Wired Controller", 0, XTYPE_XBOXONE },
- { 0x20d6, 0x2009, "PowerA Enhanced Wired Controller for Xbox Series X|S", 0, XTYPE_XBOXONE },
- { 0x20d6, 0x2064, "PowerA Wired Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x20d6, 0x281f, "PowerA Wired Controller For Xbox 360", 0, XTYPE_XBOX360 },
- { 0x20d6, 0x400b, "PowerA FUSION Pro 4 Wired Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x20d6, 0x890b, "PowerA MOGA XP-Ultra Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x2345, 0xe00b, "Machenike G5 Pro Controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5000, "Razer Atrox Arcade Stick", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x24c6, 0x5300, "PowerA MINI PROEX Controller", 0, XTYPE_XBOX360 },
@@ -347,9 +281,6 @@ static const struct xpad_device {
{ 0x24c6, 0x530a, "Xbox 360 Pro EX Controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x531a, "PowerA Pro Ex", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5397, "FUS1ON Tournament Controller", 0, XTYPE_XBOX360 },
- { 0x24c6, 0x541a, "PowerA Xbox One Mini Wired Controller", 0, XTYPE_XBOXONE },
- { 0x24c6, 0x542a, "Xbox ONE spectra", 0, XTYPE_XBOXONE },
- { 0x24c6, 0x543a, "PowerA Xbox One wired controller", 0, XTYPE_XBOXONE },
{ 0x24c6, 0x5500, "Hori XBOX 360 EX 2 with Turbo", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5501, "Hori Real Arcade Pro VX-SA", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5502, "Hori Fighting Stick VX Alt", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
@@ -358,29 +289,18 @@ static const struct xpad_device {
{ 0x24c6, 0x550d, "Hori GEM Xbox controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x550e, "Hori Real Arcade Pro V Kai 360", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
{ 0x24c6, 0x5510, "Hori Fighting Commander ONE (Xbox 360/PC Mode)", MAP_TRIGGERS_TO_BUTTONS, XTYPE_XBOX360 },
- { 0x24c6, 0x551a, "PowerA FUSION Pro Controller", 0, XTYPE_XBOXONE },
- { 0x24c6, 0x561a, "PowerA FUSION Controller", 0, XTYPE_XBOXONE },
- { 0x24c6, 0x581a, "ThrustMaster XB1 Classic Controller", 0, XTYPE_XBOXONE },
{ 0x24c6, 0x5b00, "ThrustMaster Ferrari 458 Racing Wheel", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5b02, "Thrustmaster, Inc. GPX Controller", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5b03, "Thrustmaster Ferrari 458 Racing Wheel", 0, XTYPE_XBOX360 },
{ 0x24c6, 0x5d04, "Razer Sabertooth", 0, XTYPE_XBOX360 },
{ 0x24c6, 0xfafe, "Rock Candy Gamepad for Xbox 360", 0, XTYPE_XBOX360 },
{ 0x2563, 0x058d, "OneXPlayer Gamepad", 0, XTYPE_XBOX360 },
- { 0x294b, 0x3303, "Snakebyte GAMEPAD BASE X", 0, XTYPE_XBOXONE },
- { 0x294b, 0x3404, "Snakebyte GAMEPAD RGB X", 0, XTYPE_XBOXONE },
{ 0x2993, 0x2001, "TECNO Pocket Go", 0, XTYPE_XBOX360 },
- { 0x2dc8, 0x2000, "8BitDo Pro 2 Wired Controller fox Xbox", 0, XTYPE_XBOXONE },
- { 0x2dc8, 0x200f, "8BitDo Ultimate 3-mode Controller for Xbox", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x2dc8, 0x3106, "8BitDo Ultimate Wireless / Pro 2 Wired Controller", 0, XTYPE_XBOX360 },
{ 0x2dc8, 0x3109, "8BitDo Ultimate Wireless Bluetooth", 0, XTYPE_XBOX360 },
{ 0x2dc8, 0x310a, "8BitDo Ultimate 2C Wireless Controller", 0, XTYPE_XBOX360 },
{ 0x2dc8, 0x310b, "8BitDo Ultimate 2 Wireless Controller", 0, XTYPE_XBOX360 },
{ 0x2dc8, 0x6001, "8BitDo SN30 Pro", 0, XTYPE_XBOX360 },
- { 0x2e24, 0x0423, "Hyperkin DuchesS Xbox One pad", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
- { 0x2e24, 0x0652, "Hyperkin Duke X-Box One pad", 0, XTYPE_XBOXONE },
- { 0x2e24, 0x1688, "Hyperkin X91 X-Box One pad", 0, XTYPE_XBOXONE },
- { 0x2e95, 0x0504, "SCUF Gaming Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE },
{ 0x31e3, 0x1100, "Wooting One", 0, XTYPE_XBOX360 },
{ 0x31e3, 0x1200, "Wooting Two", 0, XTYPE_XBOX360 },
{ 0x31e3, 0x1210, "Wooting Lekker", 0, XTYPE_XBOX360 },
@@ -388,17 +308,11 @@ static const struct xpad_device {
{ 0x31e3, 0x1230, "Wooting Two HE (ARM)", 0, XTYPE_XBOX360 },
{ 0x31e3, 0x1300, "Wooting 60HE (AVR)", 0, XTYPE_XBOX360 },
{ 0x31e3, 0x1310, "Wooting 60HE (ARM)", 0, XTYPE_XBOX360 },
- { 0x3285, 0x0603, "Nacon Pro Compact controller for Xbox", 0, XTYPE_XBOXONE },
{ 0x3285, 0x0607, "Nacon GC-100", 0, XTYPE_XBOX360 },
- { 0x3285, 0x0614, "Nacon Pro Compact", 0, XTYPE_XBOXONE },
- { 0x3285, 0x0646, "Nacon Pro Compact", 0, XTYPE_XBOXONE },
{ 0x3285, 0x0662, "Nacon Revolution5 Pro", 0, XTYPE_XBOX360 },
- { 0x3285, 0x0663, "Nacon Evol-X", 0, XTYPE_XBOXONE },
{ 0x3537, 0x1004, "GameSir T4 Kaleid", 0, XTYPE_XBOX360 },
{ 0x3537, 0x100f, "GameSir Nova 2 Lite", 0, XTYPE_XBOX360 },
- { 0x3537, 0x1010, "GameSir G7 SE", 0, XTYPE_XBOXONE },
{ 0x3651, 0x1000, "CRKD SG", 0, XTYPE_XBOX360 },
- { 0x366c, 0x0005, "ByoWave Proteus Controller", MAP_SHARE_BUTTON, XTYPE_XBOXONE, FLAG_DELAY_INIT },
{ 0x3767, 0x0101, "Fanatec Speedster 3 Forceshock Wheel", 0, XTYPE_XBOX },
{ 0x37d7, 0x2501, "Flydigi Apex 5", 0, XTYPE_XBOX360 },
{ 0x413d, 0x2104, "Black Shark Green Ghost Gamepad", 0, XTYPE_XBOX360 },
@@ -456,13 +370,6 @@ static const signed short xpad_abs_triggers[] = {
-1
};
-/* used when the controller has extra paddle buttons */
-static const signed short xpad_btn_paddles[] = {
- BTN_GRIPR, BTN_GRIPR2, /* paddle upper right, lower right */
- BTN_GRIPL, BTN_GRIPL2, /* paddle upper left, lower left */
- -1 /* terminating entry */
-};
-
/*
* Xbox 360 has a vendor-specific class, so we cannot match it with only
* USB_INTERFACE_INFO (also specifically refused by USB subsystem), so we
@@ -479,49 +386,30 @@ static const signed short xpad_btn_paddles[] = {
{ XPAD_XBOX360_VENDOR_PROTOCOL((vend), 1) }, \
{ XPAD_XBOX360_VENDOR_PROTOCOL((vend), 129) }
-/* The Xbox One controller uses subclass 71 and protocol 208. */
-#define XPAD_XBOXONE_VENDOR_PROTOCOL(vend, pr) \
- .match_flags = USB_DEVICE_ID_MATCH_VENDOR | USB_DEVICE_ID_MATCH_INT_INFO, \
- .idVendor = (vend), \
- .bInterfaceClass = USB_CLASS_VENDOR_SPEC, \
- .bInterfaceSubClass = 71, \
- .bInterfaceProtocol = (pr)
-#define XPAD_XBOXONE_VENDOR(vend) \
- { XPAD_XBOXONE_VENDOR_PROTOCOL((vend), 208) }
-
static const struct usb_device_id xpad_table[] = {
/*
- * Please keep this list sorted by vendor ID. Note that there are 2
- * macros - XPAD_XBOX360_VENDOR and XPAD_XBOXONE_VENDOR.
+ * Please keep this list sorted by vendor ID
*/
{ USB_INTERFACE_INFO('X', 'B', 0) }, /* Xbox USB-IF not-approved class */
XPAD_XBOX360_VENDOR(0x0079), /* GPD Win 2 controller */
XPAD_XBOX360_VENDOR(0x0351), /* CRKD Controllers */
XPAD_XBOX360_VENDOR(0x03eb), /* Wooting Keyboards (Legacy) */
XPAD_XBOX360_VENDOR(0x03f0), /* HP HyperX Xbox 360 controllers */
- XPAD_XBOXONE_VENDOR(0x03f0), /* HP HyperX Xbox One controllers */
XPAD_XBOX360_VENDOR(0x044f), /* Thrustmaster Xbox 360 controllers */
- XPAD_XBOXONE_VENDOR(0x044f), /* Thrustmaster Xbox One controllers */
XPAD_XBOX360_VENDOR(0x045e), /* Microsoft Xbox 360 controllers */
- XPAD_XBOXONE_VENDOR(0x045e), /* Microsoft Xbox One controllers */
XPAD_XBOX360_VENDOR(0x046d), /* Logitech Xbox 360-style controllers */
XPAD_XBOX360_VENDOR(0x0502), /* Acer Inc. Xbox 360 style controllers */
XPAD_XBOX360_VENDOR(0x056e), /* Elecom JC-U3613M */
XPAD_XBOX360_VENDOR(0x06a3), /* Saitek P3600 */
XPAD_XBOX360_VENDOR(0x0738), /* Mad Catz Xbox 360 controllers */
{ USB_DEVICE(0x0738, 0x4540) }, /* Mad Catz Beat Pad */
- XPAD_XBOXONE_VENDOR(0x0738), /* Mad Catz FightStick TE 2 */
XPAD_XBOX360_VENDOR(0x07ff), /* Mad Catz Gamepad */
XPAD_XBOX360_VENDOR(0x0b05), /* ASUS controllers */
- XPAD_XBOXONE_VENDOR(0x0b05), /* ASUS controllers */
XPAD_XBOX360_VENDOR(0x0c12), /* Zeroplus X-Box 360 controllers */
XPAD_XBOX360_VENDOR(0x0db0), /* Micro Star International X-Box 360 controllers */
XPAD_XBOX360_VENDOR(0x0e6f), /* 0x0e6f Xbox 360 controllers */
- XPAD_XBOXONE_VENDOR(0x0e6f), /* 0x0e6f Xbox One controllers */
XPAD_XBOX360_VENDOR(0x0f0d), /* Hori controllers */
- XPAD_XBOXONE_VENDOR(0x0f0d), /* Hori controllers */
XPAD_XBOX360_VENDOR(0x1038), /* SteelSeries controllers */
- XPAD_XBOXONE_VENDOR(0x10f5), /* Turtle Beach Controllers */
XPAD_XBOX360_VENDOR(0x11c9), /* Nacon GC100XF */
XPAD_XBOX360_VENDOR(0x11ff), /* PXN V900 */
XPAD_XBOX360_VENDOR(0x1209), /* Ardwiino Controllers */
@@ -529,7 +417,6 @@ static const struct usb_device_id xpad_table[] = {
XPAD_XBOX360_VENDOR(0x1430), /* RedOctane Xbox 360 controllers */
XPAD_XBOX360_VENDOR(0x146b), /* Bigben Interactive controllers */
XPAD_XBOX360_VENDOR(0x1532), /* Razer Sabertooth */
- XPAD_XBOXONE_VENDOR(0x1532), /* Razer Wildcat */
XPAD_XBOX360_VENDOR(0x15e4), /* Numark Xbox 360 controllers */
XPAD_XBOX360_VENDOR(0x162e), /* Joytech Xbox 360 controllers */
XPAD_XBOX360_VENDOR(0x1689), /* Razer Onza */
@@ -540,27 +427,18 @@ static const struct usb_device_id xpad_table[] = {
XPAD_XBOX360_VENDOR(0x1ee9), /* ZOTAC Technology Limited */
XPAD_XBOX360_VENDOR(0x20bc), /* BETOP wireless dongles */
XPAD_XBOX360_VENDOR(0x20d6), /* PowerA controllers */
- XPAD_XBOXONE_VENDOR(0x20d6), /* PowerA controllers */
XPAD_XBOX360_VENDOR(0x2345), /* Machenike Controllers */
XPAD_XBOX360_VENDOR(0x24c6), /* PowerA controllers */
- XPAD_XBOXONE_VENDOR(0x24c6), /* PowerA controllers */
XPAD_XBOX360_VENDOR(0x2563), /* OneXPlayer Gamepad */
XPAD_XBOX360_VENDOR(0x260d), /* Dareu H101 */
- XPAD_XBOXONE_VENDOR(0x294b), /* Snakebyte */
XPAD_XBOX360_VENDOR(0x2993), /* TECNO Mobile */
XPAD_XBOX360_VENDOR(0x2c22), /* Qanba Controllers */
XPAD_XBOX360_VENDOR(0x2dc8), /* 8BitDo Controllers */
- XPAD_XBOXONE_VENDOR(0x2dc8), /* 8BitDo Controllers */
- XPAD_XBOXONE_VENDOR(0x2e24), /* Hyperkin Controllers */
XPAD_XBOX360_VENDOR(0x2f24), /* GameSir Controllers */
- XPAD_XBOXONE_VENDOR(0x2e95), /* SCUF Gaming Controller */
XPAD_XBOX360_VENDOR(0x31e3), /* Wooting Keyboards */
XPAD_XBOX360_VENDOR(0x3285), /* Nacon GC-100 */
- XPAD_XBOXONE_VENDOR(0x3285), /* Nacon Evol-X */
XPAD_XBOX360_VENDOR(0x3537), /* GameSir Controllers */
- XPAD_XBOXONE_VENDOR(0x3537), /* GameSir Controllers */
XPAD_XBOX360_VENDOR(0x3651), /* CRKD Controllers */
- XPAD_XBOXONE_VENDOR(0x366c), /* ByoWave controllers */
XPAD_XBOX360_VENDOR(0x37d7), /* Flydigi Controllers */
XPAD_XBOX360_VENDOR(0x3958), /* RedOctane Games Controllers */
XPAD_XBOX360_VENDOR(0x413d), /* Black Shark Green Ghost Controller */
@@ -569,162 +447,6 @@ static const struct usb_device_id xpad_table[] = {
MODULE_DEVICE_TABLE(usb, xpad_table);
-struct xboxone_init_packet {
- u16 idVendor;
- u16 idProduct;
- const u8 *data;
- u8 len;
-};
-
-#define XBOXONE_INIT_PKT(_vid, _pid, _data) \
- { \
- .idVendor = (_vid), \
- .idProduct = (_pid), \
- .data = (_data), \
- .len = ARRAY_SIZE(_data), \
- }
-
-/*
- * starting with xbox one, the game input protocol is used
- * magic numbers are taken from
- * - https://github.com/xpadneo/gip-dissector/blob/main/src/gip-dissector.lua
- * - https://github.com/medusalix/xone/blob/master/bus/protocol.c
- */
-#define GIP_CMD_ACK 0x01
-#define GIP_CMD_ANNOUNCE 0x02
-#define GIP_CMD_IDENTIFY 0x04
-#define GIP_CMD_POWER 0x05
-#define GIP_CMD_AUTHENTICATE 0x06
-#define GIP_CMD_VIRTUAL_KEY 0x07
-#define GIP_CMD_RUMBLE 0x09
-#define GIP_CMD_LED 0x0a
-#define GIP_CMD_FIRMWARE 0x0c
-#define GIP_CMD_INPUT 0x20
-
-#define GIP_SEQ0 0x00
-
-#define GIP_OPT_ACK 0x10
-#define GIP_OPT_INTERNAL 0x20
-
-/*
- * length of the command payload encoded with
- * https://en.wikipedia.org/wiki/LEB128
- * which is a no-op for N < 128
- */
-#define GIP_PL_LEN(N) (N)
-
-/*
- * payload specific defines
- */
-#define GIP_PWR_ON 0x00
-#define GIP_LED_ON 0x01
-
-#define GIP_MOTOR_R BIT(0)
-#define GIP_MOTOR_L BIT(1)
-#define GIP_MOTOR_RT BIT(2)
-#define GIP_MOTOR_LT BIT(3)
-#define GIP_MOTOR_ALL (GIP_MOTOR_R | GIP_MOTOR_L | GIP_MOTOR_RT | GIP_MOTOR_LT)
-
-#define GIP_WIRED_INTF_DATA 0
-#define GIP_WIRED_INTF_AUDIO 1
-
-/*
- * This packet is required for all Xbox One pads with 2015
- * or later firmware installed (or present from the factory).
- */
-static const u8 xboxone_power_on[] = {
- GIP_CMD_POWER, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(1), GIP_PWR_ON
-};
-
-/*
- * This packet is required for Xbox One S (0x045e:0x02ea)
- * and Xbox One Elite Series 2 (0x045e:0x0b00) pads to
- * initialize the controller that was previously used in
- * Bluetooth mode.
- */
-static const u8 xboxone_s_init[] = {
- GIP_CMD_POWER, GIP_OPT_INTERNAL, GIP_SEQ0, 0x0f, 0x06
-};
-
-/*
- * This packet is required to get additional input data
- * from Xbox One Elite Series 2 (0x045e:0x0b00) pads.
- * We mostly do this right now to get paddle data
- */
-static const u8 extra_input_packet_init[] = {
- 0x4d, 0x10, 0x01, 0x02, 0x07, 0x00
-};
-
-/*
- * This packet is required for the Titanfall 2 Xbox One pads
- * (0x0e6f:0x0165) to finish initialization and for Hori pads
- * (0x0f0d:0x0067) to make the analog sticks work.
- */
-static const u8 xboxone_hori_ack_id[] = {
- GIP_CMD_ACK, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(9),
- 0x00, GIP_CMD_IDENTIFY, GIP_OPT_INTERNAL, 0x3a, 0x00, 0x00, 0x00, 0x80, 0x00
-};
-
-/*
- * This packet is sent by default on Windows, and is required for some pads to
- * start sending input reports, including most (all?) of the PDP. These pads
- * include: (0x0e6f:0x02ab), (0x0e6f:0x02a4), (0x0e6f:0x02a6).
- */
-static const u8 xboxone_led_on[] = { GIP_CMD_LED, GIP_OPT_INTERNAL, GIP_SEQ0,
-GIP_PL_LEN(3), 0x00, GIP_LED_ON, 0x14 };
-
-/*
- * This packet is required for most (all?) of the PDP pads to start
- * sending input reports. These pads include: (0x0e6f:0x02ab),
- * (0x0e6f:0x02a4), (0x0e6f:0x02a6).
- */
-static const u8 xboxone_auth_done[] = {
- GIP_CMD_AUTHENTICATE, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(2), 0x01, 0x00
-};
-
-/*
- * A specific rumble packet is required for some PowerA pads to start
- * sending input reports. One of those pads is (0x24c6:0x543a).
- */
-static const u8 xboxone_rumblebegin_init[] = {
- GIP_CMD_RUMBLE, 0x00, GIP_SEQ0, GIP_PL_LEN(9),
- 0x00, GIP_MOTOR_ALL, 0x00, 0x00, 0x1D, 0x1D, 0xFF, 0x00, 0x00
-};
-
-/*
- * A rumble packet with zero FF intensity will immediately
- * terminate the rumbling required to init PowerA pads.
- * This should happen fast enough that the motors don't
- * spin up to enough speed to actually vibrate the gamepad.
- */
-static const u8 xboxone_rumbleend_init[] = {
- GIP_CMD_RUMBLE, 0x00, GIP_SEQ0, GIP_PL_LEN(9),
- 0x00, GIP_MOTOR_ALL, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
-};
-
-/*
- * This specifies the selection of init packets that a gamepad
- * will be sent on init *and* the order in which they will be
- * sent. The correct sequence number will be added when the
- * packet is going to be sent.
- */
-static const struct xboxone_init_packet xboxone_init_packets[] = {
- XBOXONE_INIT_PKT(0x0e6f, 0x0165, xboxone_hori_ack_id),
- XBOXONE_INIT_PKT(0x0f0d, 0x0067, xboxone_hori_ack_id),
- XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_power_on),
- XBOXONE_INIT_PKT(0x045e, 0x02ea, xboxone_s_init),
- XBOXONE_INIT_PKT(0x045e, 0x0b00, xboxone_s_init),
- XBOXONE_INIT_PKT(0x045e, 0x0b00, extra_input_packet_init),
- XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_led_on),
- XBOXONE_INIT_PKT(0x0000, 0x0000, xboxone_auth_done),
- XBOXONE_INIT_PKT(0x24c6, 0x541a, xboxone_rumblebegin_init),
- XBOXONE_INIT_PKT(0x24c6, 0x542a, xboxone_rumblebegin_init),
- XBOXONE_INIT_PKT(0x24c6, 0x543a, xboxone_rumblebegin_init),
- XBOXONE_INIT_PKT(0x24c6, 0x541a, xboxone_rumbleend_init),
- XBOXONE_INIT_PKT(0x24c6, 0x542a, xboxone_rumbleend_init),
- XBOXONE_INIT_PKT(0x24c6, 0x543a, xboxone_rumbleend_init),
-};
-
struct xpad_output_packet {
u8 data[XPAD_PKT_LEN];
u8 len;
@@ -754,7 +476,6 @@ struct usb_xpad {
struct urb *irq_out; /* urb for interrupt out report */
struct usb_anchor irq_out_anchor;
bool irq_out_active; /* we must not use an active URB */
- u8 odata_serial; /* serial number for xbox one protocol */
unsigned char *odata; /* output data */
dma_addr_t odata_dma;
spinlock_t odata_lock;
@@ -782,8 +503,6 @@ struct usb_xpad {
static int xpad_init_input(struct usb_xpad *xpad);
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);
/*
@@ -1021,187 +740,6 @@ static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned cha
rcu_read_unlock();
}
-/*
- * xpadone_process_packet
- *
- * Completes a request by converting the data into events for the
- * input subsystem. This version is for the Xbox One controller.
- *
- * The report format was gleaned from
- * https://github.com/kylelemons/xbox/blob/master/xbox.go
- */
-static void xpadone_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned char *data, u32 len)
-{
- struct input_dev *dev = xpad->dev;
- bool do_sync = false;
-
- /* the xbox button has its own special report */
- if (data[0] == GIP_CMD_VIRTUAL_KEY) {
- /*
- * The Xbox One S controller requires these reports to be
- * acked otherwise it continues sending them forever and
- * won't report further mode button events.
- */
- if (data[1] == (GIP_OPT_ACK | GIP_OPT_INTERNAL))
- xpadone_ack_mode_report(xpad, data[2]);
-
- input_report_key(dev, BTN_MODE, data[4] & GENMASK(1, 0));
- input_sync(dev);
-
- do_sync = true;
- } else if (data[0] == GIP_CMD_FIRMWARE) {
- /* Some packet formats force us to use this separate to poll paddle inputs */
- if (xpad->packet_type == PKT_XBE2_FW_5_11) {
- /* Mute paddles if controller is in a custom profile slot
- * Checked by looking at the active profile slot to
- * verify it's the default slot
- */
- if (data[19] != 0)
- data[18] = 0;
-
- /* Elite Series 2 split packet paddle bits */
- input_report_key(dev, BTN_GRIPR, data[18] & BIT(0));
- input_report_key(dev, BTN_GRIPR2, data[18] & BIT(1));
- input_report_key(dev, BTN_GRIPL, data[18] & BIT(2));
- input_report_key(dev, BTN_GRIPL2, data[18] & BIT(3));
-
- do_sync = true;
- }
- } else if (data[0] == GIP_CMD_ANNOUNCE) {
- int error;
-
- if (xpad->delay_init && !xpad->delayed_init_done) {
- xpad->delayed_init_done = true;
- error = xpad_start_input(xpad);
- if (error)
- dev_warn(&xpad->dev->dev,
- "unable to start delayed input: %d\n",
- error);
- }
- } else if (data[0] == GIP_CMD_INPUT) { /* The main valid packet type for inputs */
- /* menu/view buttons */
- input_report_key(dev, BTN_START, data[4] & BIT(2));
- input_report_key(dev, BTN_SELECT, data[4] & BIT(3));
- if (xpad->mapping & MAP_SHARE_BUTTON) {
- u32 offset = (xpad->mapping & MAP_SHARE_OFFSET) ? 26 : 18;
-
- if (len >= offset)
- input_report_key(dev, KEY_RECORD, data[len - offset] & BIT(0));
- }
-
- /* buttons A,B,X,Y */
- input_report_key(dev, BTN_A, data[4] & BIT(4));
- input_report_key(dev, BTN_B, data[4] & BIT(5));
- input_report_key(dev, BTN_X, data[4] & BIT(6));
- input_report_key(dev, BTN_Y, data[4] & BIT(7));
-
- /* digital pad */
- if (xpad->mapping & MAP_DPAD_TO_BUTTONS) {
- /* dpad as buttons (left, right, up, down) */
- input_report_key(dev, BTN_DPAD_LEFT, data[5] & BIT(2));
- input_report_key(dev, BTN_DPAD_RIGHT, data[5] & BIT(3));
- input_report_key(dev, BTN_DPAD_UP, data[5] & BIT(0));
- input_report_key(dev, BTN_DPAD_DOWN, data[5] & BIT(1));
- } else {
- input_report_abs(dev, ABS_HAT0X,
- !!(data[5] & 0x08) - !!(data[5] & 0x04));
- input_report_abs(dev, ABS_HAT0Y,
- !!(data[5] & 0x02) - !!(data[5] & 0x01));
- }
-
- /* TL/TR */
- input_report_key(dev, BTN_TL, data[5] & BIT(4));
- input_report_key(dev, BTN_TR, data[5] & BIT(5));
-
- /* stick press left/right */
- input_report_key(dev, BTN_THUMBL, data[5] & BIT(6));
- input_report_key(dev, BTN_THUMBR, data[5] & BIT(7));
-
- if (!(xpad->mapping & MAP_STICKS_TO_NULL)) {
- /* left stick */
- input_report_abs(dev, ABS_X,
- (__s16) le16_to_cpup((__le16 *)(data + 10)));
- input_report_abs(dev, ABS_Y,
- ~(__s16) le16_to_cpup((__le16 *)(data + 12)));
-
- /* right stick */
- input_report_abs(dev, ABS_RX,
- (__s16) le16_to_cpup((__le16 *)(data + 14)));
- input_report_abs(dev, ABS_RY,
- ~(__s16) le16_to_cpup((__le16 *)(data + 16)));
- }
-
- /* triggers left/right */
- if (xpad->mapping & MAP_TRIGGERS_TO_BUTTONS) {
- input_report_key(dev, BTN_TL2,
- (__u16) le16_to_cpup((__le16 *)(data + 6)));
- input_report_key(dev, BTN_TR2,
- (__u16) le16_to_cpup((__le16 *)(data + 8)));
- } else {
- input_report_abs(dev, ABS_Z,
- (__u16) le16_to_cpup((__le16 *)(data + 6)));
- input_report_abs(dev, ABS_RZ,
- (__u16) le16_to_cpup((__le16 *)(data + 8)));
- }
-
- /* Profile button has a value of 0-3, so it is reported as an axis */
- if (xpad->mapping & MAP_PROFILE_BUTTON)
- input_report_abs(dev, ABS_PROFILE, data[34]);
-
- /* paddle handling */
- /* based on SDL's SDL_hidapi_xboxone.c */
- if (xpad->mapping & MAP_PADDLES) {
- if (xpad->packet_type == PKT_XBE1) {
- /* Mute paddles if controller has a custom mapping applied.
- * Checked by comparing the current mapping
- * config against the factory mapping config
- */
- if (memcmp(&data[4], &data[18], 2) != 0)
- data[32] = 0;
-
- /* OG Elite Series Controller paddle bits */
- input_report_key(dev, BTN_GRIPR, data[32] & BIT(1));
- input_report_key(dev, BTN_GRIPR2, data[32] & BIT(3));
- input_report_key(dev, BTN_GRIPL, data[32] & BIT(0));
- input_report_key(dev, BTN_GRIPL2, data[32] & BIT(2));
- } else if (xpad->packet_type == PKT_XBE2_FW_OLD) {
- /* Mute paddles if controller has a custom mapping applied.
- * Checked by comparing the current mapping
- * config against the factory mapping config
- */
- if (data[19] != 0)
- data[18] = 0;
-
- /* Elite Series 2 4.x firmware paddle bits */
- input_report_key(dev, BTN_GRIPR, data[18] & BIT(0));
- input_report_key(dev, BTN_GRIPR2, data[18] & BIT(1));
- input_report_key(dev, BTN_GRIPL, data[18] & BIT(2));
- input_report_key(dev, BTN_GRIPL2, data[18] & BIT(3));
- } else if (xpad->packet_type == PKT_XBE2_FW_5_EARLY) {
- /* Mute paddles if controller has a custom mapping applied.
- * Checked by comparing the current mapping
- * config against the factory mapping config
- */
- if (data[23] != 0)
- data[22] = 0;
-
- /* Elite Series 2 5.x firmware paddle bits
- * (before the packet was split)
- */
- input_report_key(dev, BTN_GRIPR, data[22] & BIT(0));
- input_report_key(dev, BTN_GRIPR2, data[22] & BIT(1));
- input_report_key(dev, BTN_GRIPL, data[22] & BIT(2));
- input_report_key(dev, BTN_GRIPL2, data[22] & BIT(3));
- }
- }
-
- do_sync = true;
- }
-
- if (do_sync)
- input_sync(dev);
-}
-
static void xpad_irq_in(struct urb *urb)
{
struct usb_xpad *xpad = urb->context;
@@ -1234,9 +772,6 @@ static void xpad_irq_in(struct urb *urb)
case XTYPE_XBOX360W:
xpad360w_process_packet(xpad, 0, xpad->idata);
break;
- case XTYPE_XBOXONE:
- xpadone_process_packet(xpad, 0, xpad->idata, urb->actual_length);
- break;
default:
xpad_process_packet(xpad, 0, xpad->idata);
}
@@ -1248,56 +783,12 @@ static void xpad_irq_in(struct urb *urb)
__func__, retval);
}
-/* Callers must hold xpad->odata_lock spinlock */
-static bool xpad_prepare_next_init_packet(struct usb_xpad *xpad)
-{
- const struct xboxone_init_packet *init_packet;
-
- if (xpad->xtype != XTYPE_XBOXONE)
- return false;
-
- /*
- * Some dongles will discard init packets if they're sent before the
- * controller connects. In these cases, we need to wait until we get
- * an announce packet from them to send the init packet sequence.
- */
- if (xpad->delay_init && !xpad->delayed_init_done)
- return false;
-
- /* Perform initialization sequence for Xbox One pads that require it */
- while (xpad->init_seq < ARRAY_SIZE(xboxone_init_packets)) {
- init_packet = &xboxone_init_packets[xpad->init_seq++];
-
- if (init_packet->idVendor != 0 &&
- init_packet->idVendor != xpad->dev->id.vendor)
- continue;
-
- if (init_packet->idProduct != 0 &&
- init_packet->idProduct != xpad->dev->id.product)
- continue;
-
- /* This packet applies to our device, so prepare to send it */
- memcpy(xpad->odata, init_packet->data, init_packet->len);
- xpad->irq_out->transfer_buffer_length = init_packet->len;
-
- /* Update packet with current sequence number */
- xpad->odata[2] = xpad->odata_serial++;
- return true;
- }
-
- return false;
-}
-
/* Callers must hold xpad->odata_lock spinlock */
static bool xpad_prepare_next_out_packet(struct usb_xpad *xpad)
{
struct xpad_output_packet *pkt, *packet = NULL;
int i;
- /* We may have init packets to send before we can send user commands */
- if (xpad_prepare_next_init_packet(xpad))
- return true;
-
for (i = 0; i < XPAD_NUM_OUT_PACKETS; i++) {
if (++xpad->last_out_packet >= XPAD_NUM_OUT_PACKETS)
xpad->last_out_packet = 0;
@@ -1473,57 +964,6 @@ static int xpad_inquiry_pad_presence(struct usb_xpad *xpad)
return xpad_try_sending_next_out_packet(xpad);
}
-static int xpad_start_xbox_one(struct usb_xpad *xpad)
-{
- int error;
-
- if (usb_ifnum_to_if(xpad->udev, GIP_WIRED_INTF_AUDIO)) {
- /*
- * Explicitly disable the audio interface. This is needed
- * for some controllers, such as the PowerA Enhanced Wired
- * Controller for Series X|S (0x20d6:0x200e) to report the
- * guide button.
- */
- error = usb_set_interface(xpad->udev,
- GIP_WIRED_INTF_AUDIO, 0);
- if (error)
- dev_warn(&xpad->dev->dev,
- "unable to disable audio interface: %d\n",
- error);
- }
-
- guard(spinlock_irqsave)(&xpad->odata_lock);
-
- /*
- * Begin the init sequence by attempting to send a packet.
- * We will cycle through the init packet sequence before
- * sending any packets from the output ring.
- */
- xpad->init_seq = 0;
- return xpad_try_sending_next_out_packet(xpad);
-}
-
-static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num)
-{
- struct xpad_output_packet *packet =
- &xpad->out_packets[XPAD_OUT_CMD_IDX];
- static const u8 mode_report_ack[] = {
- GIP_CMD_ACK, GIP_OPT_INTERNAL, GIP_SEQ0, GIP_PL_LEN(9),
- 0x00, GIP_CMD_VIRTUAL_KEY, GIP_OPT_INTERNAL, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00
- };
-
- guard(spinlock_irqsave)(&xpad->odata_lock);
-
- packet->len = sizeof(mode_report_ack);
- memcpy(packet->data, mode_report_ack, packet->len);
- packet->data[2] = seq_num;
- packet->pending = true;
-
- /* Reset the sequence so we send out the ack now */
- xpad->last_out_packet = -1;
- xpad_try_sending_next_out_packet(xpad);
-}
-
#ifdef CONFIG_JOYSTICK_XPAD_FF
static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect *effect)
{
@@ -1582,24 +1022,6 @@ static int xpad_play_effect(struct input_dev *dev, void *data, struct ff_effect
packet->pending = true;
break;
- case XTYPE_XBOXONE:
- packet->data[0] = GIP_CMD_RUMBLE; /* activate rumble */
- packet->data[1] = 0x00;
- packet->data[2] = xpad->odata_serial++;
- packet->data[3] = GIP_PL_LEN(9);
- packet->data[4] = 0x00;
- packet->data[5] = GIP_MOTOR_ALL;
- packet->data[6] = 0x00; /* left trigger */
- packet->data[7] = 0x00; /* right trigger */
- packet->data[8] = strong / 512; /* left actuator */
- packet->data[9] = weak / 512; /* right actuator */
- packet->data[10] = 0xFF; /* on period */
- packet->data[11] = 0x00; /* off period */
- packet->data[12] = 0xFF; /* repeat count */
- packet->len = 13;
- packet->pending = true;
- break;
-
default:
dev_dbg(&xpad->dev->dev,
"%s - rumble command sent to unsupported xpad type: %d\n",
@@ -1778,13 +1200,6 @@ static int xpad_start_input(struct usb_xpad *xpad)
if (usb_submit_urb(xpad->irq_in, GFP_KERNEL))
return -EIO;
- if (xpad->xtype == XTYPE_XBOXONE) {
- error = xpad_start_xbox_one(xpad);
- if (error) {
- usb_kill_urb(xpad->irq_in);
- return error;
- }
- }
if (xpad->xtype == XTYPE_XBOX360) {
/*
* Some third-party controllers Xbox 360-style controllers
@@ -1890,8 +1305,6 @@ static void xpad_close(struct input_dev *dev)
static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs)
{
- struct usb_xpad *xpad = input_get_drvdata(input_dev);
-
switch (abs) {
case ABS_X:
case ABS_Y:
@@ -1901,18 +1314,12 @@ static void xpad_set_up_abs(struct input_dev *input_dev, signed short abs)
break;
case ABS_Z:
case ABS_RZ: /* the triggers (if mapped to axes) */
- if (xpad->xtype == XTYPE_XBOXONE)
- input_set_abs_params(input_dev, abs, 0, 1023, 0, 0);
- else
- input_set_abs_params(input_dev, abs, 0, 255, 0, 0);
+ input_set_abs_params(input_dev, abs, 0, 255, 0, 0);
break;
case ABS_HAT0X:
case ABS_HAT0Y: /* the d-pad (only if dpad is mapped to axes */
input_set_abs_params(input_dev, abs, -1, 1, 0, 0);
break;
- case ABS_PROFILE: /* 4 value profile button (such as on XAC) */
- input_set_abs_params(input_dev, abs, 0, 4, 0, 0);
- break;
default:
input_set_abs_params(input_dev, abs, 0, 0, 0, 0);
break;
@@ -1967,12 +1374,9 @@ static int xpad_init_input(struct usb_xpad *xpad)
input_set_capability(input_dev, EV_KEY, xpad_common_btn[i]);
/* set up model-specific ones */
- if (xpad->xtype == XTYPE_XBOX360 || xpad->xtype == XTYPE_XBOX360W ||
- xpad->xtype == XTYPE_XBOXONE) {
+ if (xpad->xtype == XTYPE_XBOX360 || xpad->xtype == XTYPE_XBOX360W) {
for (i = 0; xpad360_btn[i] >= 0; i++)
input_set_capability(input_dev, EV_KEY, xpad360_btn[i]);
- if (xpad->mapping & MAP_SHARE_BUTTON)
- input_set_capability(input_dev, EV_KEY, KEY_RECORD);
} else {
for (i = 0; xpad_btn[i] >= 0; i++)
input_set_capability(input_dev, EV_KEY, xpad_btn[i]);
@@ -1984,12 +1388,6 @@ static int xpad_init_input(struct usb_xpad *xpad)
xpad_btn_pad[i]);
}
- /* set up paddles if the controller has them */
- if (xpad->mapping & MAP_PADDLES) {
- for (i = 0; xpad_btn_paddles[i] >= 0; i++)
- input_set_capability(input_dev, EV_KEY, xpad_btn_paddles[i]);
- }
-
/*
* This should be a simple else block. However historically
* xbox360w has mapped DPAD to buttons while xbox360 did not. This
@@ -2011,10 +1409,6 @@ static int xpad_init_input(struct usb_xpad *xpad)
xpad_set_up_abs(input_dev, xpad_abs_triggers[i]);
}
- /* setup profile button as an axis with 4 possible values */
- if (xpad->mapping & MAP_PROFILE_BUTTON)
- xpad_set_up_abs(input_dev, ABS_PROFILE);
-
error = xpad_init_ff(xpad);
if (error)
goto err_free_input;
@@ -2090,8 +1484,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {
if (intf->cur_altsetting->desc.bInterfaceProtocol == 129)
xpad->xtype = XTYPE_XBOX360W;
- else if (intf->cur_altsetting->desc.bInterfaceProtocol == 208)
- xpad->xtype = XTYPE_XBOXONE;
else
xpad->xtype = XTYPE_XBOX360;
} else {
@@ -2106,17 +1498,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
xpad->mapping |= MAP_STICKS_TO_NULL;
}
- if (xpad->xtype == XTYPE_XBOXONE &&
- intf->cur_altsetting->desc.bInterfaceNumber != GIP_WIRED_INTF_DATA) {
- /*
- * The Xbox One controller lists three interfaces all with the
- * same interface class, subclass and protocol. Differentiate by
- * interface number.
- */
- error = -ENODEV;
- goto err_free_in_urb;
- }
-
ep_irq_in = ep_irq_out = NULL;
for (i = 0; i < 2; i++) {
@@ -2291,15 +1672,6 @@ static int xpad_resume(struct usb_interface *intf)
if (input_device_enabled(input))
return xpad_start_input(xpad);
- if (xpad->xtype == XTYPE_XBOXONE) {
- /*
- * Even if there are no users, we'll send Xbox One pads
- * the startup sequence so they don't sit there and
- * blink until somebody opens the input device again.
- */
- return xpad_start_xbox_one(xpad);
- }
-
return 0;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 03/12] Input: xbox_gip - Add controllable LED support
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 01/12] " Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 02/12] Input: xpad - Remove Xbox One support Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:37 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 04/12] Input: xbox_gip - Add HID relaying Vicki Pfau
` (8 subsequent siblings)
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
Xbox One controllers have two different types of controllable LED support:
- Monochrome white, which most controllers have.
- RGBW addressible, which the Elite 2 controllers have.
This exposes both types as led cdevs.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/gip/Kconfig | 2 +-
drivers/input/joystick/gip/gip-core.c | 111 ++++++++++++++++++++++++++
drivers/input/joystick/gip/gip.h | 6 +-
3 files changed, 117 insertions(+), 2 deletions(-)
diff --git a/drivers/input/joystick/gip/Kconfig b/drivers/input/joystick/gip/Kconfig
index 2ce789d23178..3422bf84cb89 100644
--- a/drivers/input/joystick/gip/Kconfig
+++ b/drivers/input/joystick/gip/Kconfig
@@ -4,7 +4,7 @@
#
config JOYSTICK_XBOX_GIP
tristate "Xbox One/Series controller support"
- depends on USB && USB_ARCH_HAS_HCD
+ depends on USB && USB_ARCH_HAS_HCD && LEDS_CLASS_MULTICOLOR
help
Say Y here if you want to use Xbox One and Series controllers with your
computer. Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
index 12a1fb285bfc..15f8ed210711 100644
--- a/drivers/input/joystick/gip/gip-core.c
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -1038,9 +1038,116 @@ static int gip_send_guide_button_led(struct gip_attachment *attachment,
if (!gip_supports_system_message(attachment, GIP_CMD_LED, false))
return 0;
+ if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR))
+ attachment->guide_led.standard.brightness = intensity;
+
return gip_send_system_message(attachment, GIP_CMD_LED, 0, buffer, sizeof(buffer));
}
+static int gip_send_guide_button_color_led(struct gip_attachment *attachment,
+ uint8_t r, uint8_t g, uint8_t b, uint8_t w)
+{
+ uint8_t buffer[] = { 0x00, w, r, g, b };
+
+ if (!(attachment->features & GIP_FEATURE_GUIDE_COLOR))
+ return -EINVAL;
+
+ attachment->guide_led.color.subled_info[0].brightness = r;
+ attachment->guide_led.color.subled_info[1].brightness = g;
+ attachment->guide_led.color.subled_info[2].brightness = b;
+ attachment->guide_led.color.subled_info[3].brightness = w;
+
+ return gip_send_vendor_message(attachment, GIP_CMD_GUIDE_COLOR, 0, buffer, sizeof(buffer));
+}
+
+static int gip_guide_led_set(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct gip_attachment *attachment = container_of(led,
+ struct gip_attachment, guide_led.standard);
+
+ guard(mutex)(&attachment->lock);
+ return gip_send_guide_button_led(attachment, GIP_LED_GUIDE_ON, value);
+}
+
+static int gip_guide_color_led_set(struct led_classdev *led,
+ enum led_brightness value)
+{
+ struct led_classdev_mc *mc_cdev = container_of(led,
+ struct led_classdev_mc, led_cdev);
+ struct gip_attachment *attachment = container_of(mc_cdev,
+ struct gip_attachment, guide_led.color);
+
+ led_mc_calc_color_components(mc_cdev, value);
+ guard(mutex)(&attachment->lock);
+ return gip_send_guide_button_color_led(attachment,
+ mc_cdev->subled_info[0].brightness,
+ mc_cdev->subled_info[1].brightness,
+ mc_cdev->subled_info[2].brightness,
+ mc_cdev->subled_info[3].brightness);
+}
+
+static int gip_guide_led_probe(struct gip_attachment *attachment, struct device *dev)
+{
+ int rc = 0;
+
+ if (!gip_supports_system_message(attachment, GIP_CMD_LED, false))
+ return 0;
+
+ if (attachment->features & GIP_FEATURE_GUIDE_COLOR) {
+ struct mc_subled *mc_led_info;
+ struct led_classdev_mc *mc_cdev = &attachment->guide_led.color;
+ struct led_classdev *cdev = &mc_cdev->led_cdev;
+
+ mc_led_info = devm_kcalloc(dev, 4,
+ sizeof(*mc_led_info), GFP_KERNEL);
+ if (!mc_led_info)
+ return -ENOMEM;
+
+ mc_led_info[0].color_index = LED_COLOR_ID_RED;
+ mc_led_info[1].color_index = LED_COLOR_ID_GREEN;
+ mc_led_info[2].color_index = LED_COLOR_ID_BLUE;
+ mc_led_info[3].color_index = LED_COLOR_ID_WHITE;
+
+ mc_cdev->subled_info = mc_led_info;
+ mc_cdev->num_colors = 4;
+
+ cdev->brightness = 51;
+ cdev->max_brightness = 255;
+ cdev->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ cdev->brightness_set_blocking = gip_guide_color_led_set;
+ cdev->name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s:rgb:power", dev_name(dev));
+ if (!cdev->name)
+ rc = -ENOMEM;
+
+ if (!rc)
+ rc = devm_led_classdev_multicolor_register(dev,
+ mc_cdev);
+
+ if (rc)
+ devm_kfree(dev, mc_led_info);
+ } else {
+ struct led_classdev *cdev = &attachment->guide_led.standard;
+
+ cdev->max_brightness = GIP_LED_GUIDE_MAX_BRIGHTNESS;
+ cdev->brightness = GIP_LED_GUIDE_INIT_BRIGHTNESS;
+ cdev->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
+ cdev->brightness_set_blocking = gip_guide_led_set;
+ cdev->name = devm_kasprintf(dev, GFP_KERNEL,
+ "%s:white:power", dev_name(dev));
+ if (!cdev->name)
+ return -ENOMEM;
+
+ rc = devm_led_classdev_register(dev, cdev);
+
+ if (rc)
+ devm_kfree(dev, cdev->name);
+ }
+
+ return rc;
+}
+
static bool gip_send_set_device_state(struct gip_attachment *attachment, uint8_t state)
{
uint8_t buffer[] = { state };
@@ -1150,6 +1257,10 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
if (rc)
goto err_free_device;
+ rc = gip_guide_led_probe(attachment, &input->dev);
+ if (rc)
+ gip_err(attachment, "Failed to register LEDs: %d\n", rc);
+
return 0;
err_free_device:
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
index d48ab64ab2eb..2e78aad508c3 100644
--- a/drivers/input/joystick/gip/gip.h
+++ b/drivers/input/joystick/gip/gip.h
@@ -12,6 +12,7 @@
#ifndef _GIP_H
#define _GIP_H
+#include <linux/led-class-multicolor.h>
#include <linux/rcupdate.h>
#include <linux/usb/input.h>
@@ -234,6 +235,10 @@ struct gip_attachment {
uint8_t seq_vendor;
uint8_t device_state;
+ union {
+ struct led_classdev standard;
+ struct led_classdev_mc color;
+ } guide_led;
struct gip_extended_status status;
@@ -245,7 +250,6 @@ struct gip_attachment {
int extra_axes;
bool dpad_as_buttons;
- struct hid_device __rcu *hdev;
};
struct gip_urb {
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 04/12] Input: xbox_gip - Add HID relaying
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (2 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 03/12] Input: xbox_gip - Add controllable LED support Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 05/12] Input: xbox_gip - Add battery support Vicki Pfau
` (7 subsequent siblings)
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
GIP allows tunneling of HID packets, with the HID descriptor embedded in
the GIP metadata exchanged during the initial handshake. This patch creates
a hid_device for this HID descriptor if found, as well as relaying the HID
packets.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/gip/gip-core.c | 94 ++++++++++++++++++++++++++-
drivers/input/joystick/gip/gip.h | 2 +
2 files changed, 93 insertions(+), 3 deletions(-)
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
index 15f8ed210711..c833b10259ab 100644
--- a/drivers/input/joystick/gip/gip-core.c
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -560,6 +560,54 @@ int gip_send_vendor_message(struct gip_attachment *attachment,
bytes, num_bytes);
}
+static int gip_hid_ll_parse(struct hid_device *hdev)
+{
+ struct gip_attachment *attachment = hdev->driver_data;
+
+ return hid_parse_report(hdev,
+ attachment->metadata.device.hid_descriptor,
+ attachment->metadata.device.hid_descriptor_size);
+}
+
+static int gip_hid_ll_start(struct hid_device *hdev)
+{
+ return 0;
+}
+
+static void gip_hid_ll_stop(struct hid_device *hdev)
+{
+}
+
+static int gip_hid_ll_open(struct hid_device *hdev)
+{
+ return 0;
+}
+
+static void gip_hid_ll_close(struct hid_device *hdev)
+{
+}
+
+static int gip_hid_ll_raw_request(struct hid_device *hdev,
+ unsigned char reportnum, uint8_t *buf, size_t count,
+ unsigned char report_type, int reqtype)
+{
+ /*
+ * TODO: Based on the metadata, output reports appear to be possible,
+ * but the chatpad doesn't have the LEDs it claims to support, so
+ * it's not clear how to test we're sending them properly.
+ */
+ return 0;
+}
+
+static const struct hid_ll_driver gip_hid_ll_driver = {
+ .parse = gip_hid_ll_parse,
+ .start = gip_hid_ll_start,
+ .stop = gip_hid_ll_stop,
+ .open = gip_hid_ll_open,
+ .close = gip_hid_ll_close,
+ .raw_request = gip_hid_ll_raw_request,
+};
+
static void gip_metadata_free(struct device *dev, struct gip_metadata *metadata)
{
devm_kfree(dev, metadata->device.audio_formats);
@@ -1361,7 +1409,34 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
if (rc)
return rc;
- return 0;
+ if (attachment->metadata.device.hid_descriptor) {
+ struct hid_device *hdev = hid_allocate_device();
+
+ if (IS_ERR(hdev))
+ return PTR_ERR(hdev);
+
+ hdev->ll_driver = &gip_hid_ll_driver;
+ hdev->bus = BUS_USB;
+ hdev->vendor = attachment->vendor_id;
+ hdev->product = attachment->product_id;
+ hdev->dev.parent = to_gip_device(attachment);
+ hdev->driver_data = attachment;
+ if (attachment->name)
+ strscpy(hdev->name, attachment->name);
+ else
+ strscpy(hdev->name, "Xbox Chatpad");
+ strscpy(hdev->phys, attachment->phys);
+ rc = hid_add_device(hdev);
+ if (rc) {
+ gip_err(attachment, "HID device add failed: %d\n", rc);
+ hid_destroy_device(hdev);
+ } else {
+ rcu_assign_pointer(attachment->hdev, hdev);
+ synchronize_rcu();
+ }
+ }
+
+ return rc;
}
static void gip_fragment_timeout(struct work_struct *work)
@@ -1769,9 +1844,16 @@ static int gip_handle_command_firmware(struct gip_attachment *attachment,
static int gip_handle_command_hid_report(struct gip_attachment *attachment,
const struct gip_header *header, uint8_t *bytes, int num_bytes)
{
- gip_warn(attachment, "Unimplemented HID report message\n");
+ struct hid_device *hdev;
- return -EOPNOTSUPP;
+ guard(rcu)();
+ hdev = rcu_dereference(attachment->hdev);
+ if (hdev)
+ return hid_input_report(hdev, HID_INPUT_REPORT, bytes, num_bytes, true);
+
+ gip_warn(attachment, "Got HID report with no HID descriptor\n");
+
+ return -EPROTO;
}
static int gip_handle_command_extended(struct gip_attachment *attachment,
@@ -2527,6 +2609,7 @@ static int gip_shutdown(struct gip_device *device)
for (i = 0; i < MAX_ATTACHMENTS; i++) {
struct gip_attachment *attachment = device->attachments[i];
struct input_dev *input;
+ struct hid_device *hdev;
if (!attachment)
continue;
@@ -2537,14 +2620,19 @@ static int gip_shutdown(struct gip_device *device)
rcu_read_lock();
input = rcu_dereference(attachment->input);
+ hdev = rcu_dereference(attachment->hdev);
rcu_read_unlock();
rcu_assign_pointer(attachment->input, NULL);
+ rcu_assign_pointer(attachment->hdev, NULL);
synchronize_rcu();
}
if (input)
input_unregister_device(input);
+
+ if (hdev)
+ hid_destroy_device(hdev);
}
return 0;
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
index 2e78aad508c3..fbcf66e18158 100644
--- a/drivers/input/joystick/gip/gip.h
+++ b/drivers/input/joystick/gip/gip.h
@@ -12,6 +12,7 @@
#ifndef _GIP_H
#define _GIP_H
+#include <linux/hid.h>
#include <linux/led-class-multicolor.h>
#include <linux/rcupdate.h>
#include <linux/usb/input.h>
@@ -250,6 +251,7 @@ struct gip_attachment {
int extra_axes;
bool dpad_as_buttons;
+ struct hid_device __rcu *hdev;
};
struct gip_urb {
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 05/12] Input: xbox_gip - Add battery support
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (3 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 04/12] Input: xbox_gip - Add HID relaying Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages Vicki Pfau
` (6 subsequent siblings)
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
Controllers are required to give information about the battery in the
controller, if present. This patch exposes that information as a
power_supply device.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/gip/gip-core.c | 97 +++++++++++++++++++++++++++
drivers/input/joystick/gip/gip.h | 3 +
2 files changed, 100 insertions(+)
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
index c833b10259ab..d694ac551639 100644
--- a/drivers/input/joystick/gip/gip-core.c
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -255,6 +255,13 @@ static const struct gip_audio_format gip_audio_format_table[MAX_GIP_AUDIO_FORMAT
};
+static enum power_supply_property gip_battery_props[] = {
+ POWER_SUPPLY_PROP_PRESENT,
+ POWER_SUPPLY_PROP_CAPACITY_LEVEL,
+ POWER_SUPPLY_PROP_SCOPE,
+ POWER_SUPPLY_PROP_STATUS,
+};
+
static const struct gip_quirks base_quirks[] = {
{ GIP_VID_PDP, GIP_PID_PDP_ROCK_CANDY, 0, .quirks = GIP_QUIRK_NO_HELLO },
@@ -1196,6 +1203,92 @@ static int gip_guide_led_probe(struct gip_attachment *attachment, struct device
return rc;
}
+static int gip_battery_get_property(struct power_supply *psy,
+ enum power_supply_property psp, union power_supply_propval *val)
+{
+ struct gip_attachment *attachment = power_supply_get_drvdata(psy);
+
+ guard(mutex)(&attachment->lock);
+ switch (psp) {
+ case POWER_SUPPLY_PROP_PRESENT:
+ val->intval = attachment->status.base.battery_type != GIP_BATTERY_ABSENT;
+ break;
+ case POWER_SUPPLY_PROP_SCOPE:
+ val->intval = POWER_SUPPLY_SCOPE_DEVICE;
+ break;
+ case POWER_SUPPLY_PROP_STATUS:
+ if (attachment->status.base.battery_type == GIP_BATTERY_ABSENT) {
+ val->intval = POWER_SUPPLY_STATUS_NOT_CHARGING;
+ } else {
+ switch (attachment->status.base.charge) {
+ case GIP_CHARGING:
+ if (attachment->status.base.battery_level == GIP_BATTERY_FULL)
+ val->intval = POWER_SUPPLY_STATUS_FULL;
+ else
+ val->intval = POWER_SUPPLY_STATUS_CHARGING;
+ break;
+ case GIP_NOT_CHARGING:
+ val->intval = POWER_SUPPLY_STATUS_DISCHARGING;
+ break;
+ case GIP_CHARGE_ERROR:
+ default:
+ val->intval = POWER_SUPPLY_STATUS_UNKNOWN;
+ break;
+ }
+ }
+ break;
+ case POWER_SUPPLY_PROP_CAPACITY_LEVEL:
+ if (attachment->status.base.battery_type == GIP_BATTERY_ABSENT) {
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ } else {
+ switch (attachment->status.base.battery_level) {
+ case GIP_BATTERY_CRITICAL:
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_CRITICAL;
+ break;
+ case GIP_BATTERY_LOW:
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_LOW;
+ break;
+ case GIP_BATTERY_MEDIUM:
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_NORMAL;
+ break;
+ case GIP_BATTERY_FULL:
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_FULL;
+ break;
+ default:
+ val->intval = POWER_SUPPLY_CAPACITY_LEVEL_UNKNOWN;
+ break;
+ }
+ break;
+ }
+ break;
+ default:
+ return -EINVAL;
+ }
+ return 0;
+}
+
+static int gip_battery_create(struct gip_attachment *attachment, struct device *dev)
+{
+ struct power_supply_config supply_config = { .drv_data = attachment, };
+
+ attachment->battery_desc.properties = gip_battery_props;
+ attachment->battery_desc.num_properties = ARRAY_SIZE(gip_battery_props);
+ attachment->battery_desc.get_property = gip_battery_get_property;
+ attachment->battery_desc.type = POWER_SUPPLY_TYPE_BATTERY;
+ attachment->battery_desc.name = devm_kasprintf(dev, GFP_KERNEL,
+ "gip-battery-%s", dev_name(dev));
+
+ if (!attachment->battery_desc.name)
+ return -ENOMEM;
+
+ attachment->battery = devm_power_supply_register(dev,
+ &attachment->battery_desc, &supply_config);
+ if (IS_ERR(attachment->battery))
+ return PTR_ERR(attachment->battery);
+
+ return power_supply_powers(attachment->battery, dev);
+}
+
static bool gip_send_set_device_state(struct gip_attachment *attachment, uint8_t state)
{
uint8_t buffer[] = { state };
@@ -1308,6 +1401,9 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
rc = gip_guide_led_probe(attachment, &input->dev);
if (rc)
gip_err(attachment, "Failed to register LEDs: %d\n", rc);
+ rc = gip_battery_create(attachment, &input->dev);
+ if (rc)
+ gip_err(attachment, "Failed to register battery: %d\n", rc);
return 0;
@@ -2623,6 +2719,7 @@ static int gip_shutdown(struct gip_device *device)
hdev = rcu_dereference(attachment->hdev);
rcu_read_unlock();
+ attachment->battery = NULL;
rcu_assign_pointer(attachment->input, NULL);
rcu_assign_pointer(attachment->hdev, NULL);
synchronize_rcu();
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
index fbcf66e18158..e7953c683c51 100644
--- a/drivers/input/joystick/gip/gip.h
+++ b/drivers/input/joystick/gip/gip.h
@@ -14,6 +14,7 @@
#include <linux/hid.h>
#include <linux/led-class-multicolor.h>
+#include <linux/power_supply.h>
#include <linux/rcupdate.h>
#include <linux/usb/input.h>
@@ -241,6 +242,8 @@ struct gip_attachment {
struct led_classdev_mc color;
} guide_led;
+ struct power_supply *battery;
+ struct power_supply_desc battery_desc;
struct gip_extended_status status;
enum gip_elite_button_format xbe_format;
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (4 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 05/12] Input: xbox_gip - Add battery support Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:39 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone Vicki Pfau
` (5 subsequent siblings)
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
GIP supports sending messages that exceed the length of the MTU using a
fragmented message flow. It sits on top of a reliable message transmission
flow, which uses the ACME flag to signal that a message must be ACKed if it
is received properly. We already supported receiving and coalescing these
messages, but the ability to send them wasn't yet present due to not
needing it for basic usage. This patch adds it for future use with the
security flow.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/gip/gip-core.c | 330 ++++++++++++++++++++++++--
drivers/input/joystick/gip/gip.h | 17 ++
2 files changed, 331 insertions(+), 16 deletions(-)
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
index d694ac551639..a75fbcb1c02d 100644
--- a/drivers/input/joystick/gip/gip-core.c
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -8,7 +8,6 @@
* - Audio device support
* - Security packet handshake
* - Event logging
- * - Sending fragmented messages
* - Raw character device
* - Wheel support
* - Flight stick support
@@ -28,6 +27,7 @@
#define GIP_WIRED_INTF_DATA 0
#define GIP_WIRED_INTF_AUDIO 1
+#define GIP_DUAL_LENGTH 6
#define MAX_MESSAGE_LENGTH 0x4000
#define MAX_AUDIO_MESSAGES 9
@@ -437,6 +437,13 @@ static uint8_t gip_sequence_next(struct gip_attachment *attachment,
return seq;
}
+static unsigned int gip_fragment_id(const struct gip_attachment *attachment,
+ const struct gip_out_fragment *fragment)
+{
+ return BIT(attachment->attachment_index * MAX_OUT_FRAGMENTS +
+ (unsigned) (fragment - attachment->out_fragments));
+}
+
static void gip_handle_quirks_array(struct gip_attachment *attachment,
const struct gip_quirks *quirks)
{
@@ -479,6 +486,48 @@ static void gip_handle_quirks(struct gip_attachment *attachment)
gip_handle_quirks_array(attachment, attachment->driver->quirks);
}
+static struct gip_out_fragment *gip_find_fragment(struct gip_attachment *attachment,
+ uint8_t message_type, uint8_t flags, uint8_t seq, bool strict)
+{
+ int i;
+
+ for (i = 0; i < MAX_OUT_FRAGMENTS; i++) {
+ if (!attachment->out_fragments[i].active)
+ continue;
+ if (attachment->out_fragments[i].message != message_type)
+ continue;
+ if ((attachment->out_fragments[i].flags ^ flags) & GIP_FLAG_SYSTEM)
+ continue;
+ if (attachment->out_fragments[i].seq != seq) {
+ if (strict)
+ continue;
+
+ /*
+ * Found one with the wrong sequence number. For some
+ * reason the official driver allows this.
+ */
+ gip_warn(attachment,
+ "Found fragment with different sequence number %02x, wanted %02x\n",
+ attachment->out_fragments[i].seq, seq);
+ }
+
+ return &attachment->out_fragments[i];
+ }
+
+ for (i = 0; i < MAX_OUT_FRAGMENTS; i++) {
+ if (!attachment->out_fragments[i].active)
+ continue;
+ if (attachment->out_fragments[i].message != message_type)
+ continue;
+ if ((attachment->out_fragments[i].flags ^ flags) & GIP_FLAG_SYSTEM)
+ continue;
+
+
+ return &attachment->out_fragments[i];
+ }
+ return NULL;
+}
+
static int gip_send_raw_message(struct gip_attachment *attachment,
uint8_t message_type, uint8_t flags, uint8_t seq, const uint8_t *bytes,
int num_bytes)
@@ -486,20 +535,25 @@ static int gip_send_raw_message(struct gip_attachment *attachment,
struct gip_interface *intf;
int offset = 3;
struct gip_urb *urb = NULL;
+ struct gip_out_fragment *fragment = NULL;
int i;
int rc = 0;
+ int mtu = gip_data_class_mtu[message_type >> GIP_DATA_CLASS_SHIFT];
+ int reduced_mtu = mtu - GIP_DUAL_LENGTH;
+ /*
+ * This buffer size shouldn't need to be more than 6 (there should never
+ * be more than 3 length bytes), but add enough extra to fit fully
+ * extended bignums for 32 bit (5 bytes) + 16 bit (3 bytes) values to be
+ * safe and then WARN_ON if we go over. If this ever happens it would be
+ * due to driver bugs.
+ */
+ uint8_t header[11];
if (num_bytes < 0) {
gip_warn(attachment, "Invalid message length %d\n", num_bytes);
return -EINVAL;
}
- if (num_bytes + 6 > gip_data_class_mtu[message_type >> GIP_DATA_CLASS_SHIFT]) {
- gip_err(attachment,
- "Attempted to send a message that requires fragmenting, which is not yet supported.\n");
- return -EOPNOTSUPP;
- }
-
if ((message_type & GIP_DATA_CLASS_MASK) == GIP_DATA_CLASS_AUDIO)
intf = &attachment->device->audio;
else
@@ -511,6 +565,89 @@ static int gip_send_raw_message(struct gip_attachment *attachment,
return -EOPNOTSUPP;
}
+ header[0] = message_type;
+ header[2] = seq;
+
+ if (num_bytes > reduced_mtu ||
+ (flags & (GIP_FLAG_ACME | GIP_FLAG_FRAGMENT)) == GIP_FLAG_ACME) {
+ for (i = 0; i < MAX_OUT_FRAGMENTS; i++) {
+ if (attachment->out_fragments[i].active)
+ continue;
+
+ fragment = &attachment->out_fragments[i];
+ break;
+ }
+
+ if (!fragment) {
+ gip_err(attachment, "Fragmented message queue is full; dropping message\n");
+ return -EALREADY;
+ }
+
+ fragment->active = true;
+ fragment->message = message_type;
+ fragment->flags = flags & GIP_FLAG_SYSTEM;
+ fragment->acked = false;
+ fragment->seq = seq;
+ fragment->total_length = num_bytes;
+ fragment->data = devm_kmalloc(to_gip_device(attachment),
+ fragment->total_length, GFP_ATOMIC);
+ fragment->fragment_offset = 0;
+ memcpy(fragment->data, bytes, num_bytes);
+ bytes = fragment->data;
+
+ if (num_bytes > reduced_mtu) {
+ gip_dbg(attachment, "Starting new reliable message: %02x%02x%02x, total length %u\n",
+ message_type, flags, seq, num_bytes);
+ flags |= GIP_FLAG_INIT_FRAG | GIP_FLAG_FRAGMENT | GIP_FLAG_ACME;
+ fragment->flags |= GIP_FLAG_FRAGMENT;
+ /* The spec says to extend the value to fit in 3 bytes */
+ offset += gip_encode_length(reduced_mtu, &header[offset], sizeof(header));
+ if (offset == 4 && reduced_mtu < 0x80 && num_bytes < 0x80) {
+ header[3] |= 0x80;
+ header[4] = 0;
+ offset = 5;
+ }
+ offset += gip_encode_length(num_bytes, &header[offset],
+ sizeof(header) - offset);
+ WARN_ON(offset != GIP_DUAL_LENGTH);
+ num_bytes = mtu - offset;
+ } else {
+ offset += gip_encode_length(num_bytes, &header[offset],
+ sizeof(header) - offset);
+ }
+ } else if (flags & GIP_FLAG_FRAGMENT) {
+ fragment = gip_find_fragment(attachment, message_type, flags, seq, true);
+ if (!fragment) {
+ gip_err(attachment,
+ "Attempted to send message fragment with no associated message\n");
+ return -EINVAL;
+ }
+
+ num_bytes = min(reduced_mtu, fragment->total_length - fragment->fragment_offset);
+ offset += gip_encode_length(num_bytes, &header[offset],
+ sizeof(header) - offset);
+ if (offset == 4 && num_bytes < 0x80 && fragment->fragment_offset < 0x80) {
+ header[3] |= 0x80;
+ header[4] = 0;
+ offset = 5;
+ }
+ offset += gip_encode_length(fragment->fragment_offset, &header[offset],
+ sizeof(header) - offset);
+ WARN_ON(offset != GIP_DUAL_LENGTH);
+
+ if (num_bytes && fragment->fragment_offset + num_bytes == fragment->total_length) {
+ /* The final fragment in a fragmented message must be ACKed */
+ flags |= GIP_FLAG_ACME;
+ fragment->acked = false;
+ }
+ bytes = &fragment->data[fragment->fragment_offset];
+ } else if (num_bytes >= 0) {
+ offset += gip_encode_length(num_bytes, &header[offset],
+ sizeof(header) - offset);
+ }
+
+ header[1] = flags;
+
guard(spinlock_irqsave)(&attachment->device->message_lock);
for (i = 0; i < MAX_OUT_MESSAGES && !urb; i++) {
if (!intf->out_queue[i].urb)
@@ -522,15 +659,10 @@ static int gip_send_raw_message(struct gip_attachment *attachment,
gip_err(attachment, "Output queue is full; dropping message\n");
return -ENOSPC;
}
- urb->data[0] = message_type;
- urb->data[1] = flags;
- urb->data[2] = seq;
- offset += gip_encode_length(num_bytes, &urb->data[offset],
- sizeof(urb->data) - offset);
+ memcpy(urb->data, header, offset);
if (num_bytes > 0)
memcpy(&urb->data[offset], bytes, num_bytes);
-
num_bytes += offset;
urb->urb->transfer_buffer_length = num_bytes;
@@ -547,9 +679,97 @@ static int gip_send_raw_message(struct gip_attachment *attachment,
rc = -EIO;
}
+ if (fragment) {
+ /*
+ * Sending further fragments is handled in gip_urb_out if this fragment doesn't need
+ * ACKing or in gip_handle_command_protocol_control if it does. The flag should also
+ * be cleared if we're done sending this packet.
+ */
+ if (fragment->total_length > reduced_mtu && !(flags & GIP_FLAG_ACME))
+ intf->has_pending_out |= gip_fragment_id(attachment, fragment);
+ else
+ intf->has_pending_out &= ~gip_fragment_id(attachment, fragment);
+ }
+
return rc;
}
+static void gip_free_fragment(struct device *dev, struct gip_out_fragment *fragment)
+{
+ devm_kfree(dev, fragment->data);
+ memset(fragment, 0, sizeof(*fragment));
+}
+
+static int gip_send_next_fragment(struct gip_attachment *attachment,
+ struct gip_out_fragment *fragment)
+{
+ struct gip_interface *intf;
+ int next_bytes;
+ int flags = GIP_FLAG_FRAGMENT | fragment->flags | attachment->attachment_index;
+ int mtu;
+ int rc;
+
+ mtu = gip_data_class_mtu[fragment->message >> GIP_DATA_CLASS_SHIFT] - GIP_DUAL_LENGTH;
+ if (fragment->fragment_offset == fragment->total_length) {
+ if ((fragment->message & GIP_DATA_CLASS_MASK) == GIP_DATA_CLASS_AUDIO)
+ intf = &attachment->device->audio;
+ else
+ intf = &attachment->device->data;
+
+ next_bytes = 0;
+ intf->has_pending_out &= ~gip_fragment_id(attachment, fragment);
+ } else if (fragment->fragment_offset + mtu >= fragment->total_length) {
+ next_bytes = fragment->total_length - fragment->fragment_offset;
+ flags |= GIP_FLAG_ACME;
+ } else {
+ next_bytes = mtu;
+ }
+
+ rc = gip_send_raw_message(attachment, fragment->message, flags,
+ fragment->seq, &fragment->data[fragment->fragment_offset],
+ next_bytes);
+
+ if (rc < 0)
+ return rc;
+
+ if (fragment->acked)
+ fragment->fragment_offset += next_bytes;
+ if (next_bytes == 0 && !(fragment->flags & GIP_FLAG_ACME))
+ /* Finished sending the message */
+ gip_free_fragment(to_gip_device(attachment), fragment);
+ return next_bytes;
+}
+
+static void gip_send_fragment_work(struct work_struct *work)
+{
+ struct gip_interface *intf = container_of(work, struct gip_interface, send_fragment);
+ struct gip_out_fragment *fragment;
+ unsigned int has_pending_out;
+ unsigned long flags;
+ int i, j;
+
+ spin_lock_irqsave(&intf->device->message_lock, flags);
+ has_pending_out = intf->has_pending_out;
+ spin_unlock_irqrestore(&intf->device->message_lock, flags);
+ for (i = 0; i < MAX_ATTACHMENTS; i++) {
+ struct gip_attachment *attachment = intf->device->attachments[i];
+
+ for (j = 0; j < MAX_OUT_FRAGMENTS; j++) {
+ /* Check for pending output fragments */
+ if (!(has_pending_out & BIT(i * MAX_OUT_FRAGMENTS + j)))
+ continue;
+
+ guard(mutex)(&attachment->lock);
+ fragment = &attachment->out_fragments[j];
+ if (!fragment->active)
+ continue;
+ if (!(fragment->flags & GIP_FLAG_FRAGMENT))
+ continue;
+ gip_send_next_fragment(attachment, fragment);
+ }
+ }
+}
+
int gip_send_system_message(struct gip_attachment *attachment,
uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes)
{
@@ -1623,19 +1843,90 @@ static void gip_set_metadata_defaults(struct gip_attachment *attachment)
static void gip_reset_attachment(struct gip_attachment *attachment)
{
+ int i;
+
devm_kfree(to_gip_device(attachment), attachment->in_fragment_data);
attachment->in_fragment_data = NULL;
attachment->in_fragment_message = -1;
+ for (i = 0; i < MAX_OUT_FRAGMENTS; i++) {
+ if (attachment->out_fragments[i].active)
+ gip_free_fragment(to_gip_device(attachment),
+ &attachment->out_fragments[i]);
+ }
+
gip_reset_metadata(attachment);
}
static int gip_handle_command_protocol_control(struct gip_attachment *attachment,
const struct gip_header *header, const uint8_t *bytes, int num_bytes)
{
- /* TODO */
- gip_warn(attachment, "Unimplemented Protocol Control message\n");
- return -EOPNOTSUPP;
+ const struct gip_protocol_control_ack *ack;
+ struct gip_out_fragment *fragment = NULL;
+ uint32_t fragment_offset;
+ uint16_t remaining_buffer;
+ bool ok = true;
+ int mtu;
+ int rc;
+
+ if (num_bytes < 1)
+ return -EINVAL;
+
+ if (bytes[0] != GIP_CONTROL_CODE_ACK) {
+ gip_warn(attachment, "Unimplemented Protocol Control code %i message\n", bytes[0]);
+ return -EOPNOTSUPP;
+ }
+
+ if (num_bytes < sizeof(*ack))
+ return -EINVAL;
+
+ ack = (const struct gip_protocol_control_ack *)bytes;
+
+ fragment = gip_find_fragment(attachment, ack->message_type, ack->flags,
+ header->sequence_id, false);
+ if (!fragment) {
+ gip_warn(attachment, "Received ACK for unknown message\n");
+ rc = -EINVAL;
+ goto resend;
+ }
+
+
+ fragment_offset = le32_to_cpu(ack->fragment_offset);
+ remaining_buffer = le16_to_cpu(ack->remaining_buffer);
+
+ if (fragment_offset > fragment->total_length)
+ ok = false;
+ mtu = gip_data_class_mtu[fragment->message >> GIP_DATA_CLASS_SHIFT] - GIP_DUAL_LENGTH;
+ if (!ok) {
+ gip_warn(attachment, "Received invalid buffer offset in ACK, "
+ "got offset=%u + remaining=%u, expected offset=%u + remaining=%u\n",
+ fragment_offset, remaining_buffer,
+ fragment->fragment_offset, fragment->total_length - fragment->fragment_offset);
+ rc = -EINVAL;
+ goto resend;
+ } else if ((fragment->flags & GIP_FLAG_FRAGMENT) &&
+ (fragment->fragment_offset > fragment_offset ||
+ fragment->fragment_offset + mtu < fragment_offset)) {
+ gip_warn(attachment, "Received unexpected buffer offset in ACK, "
+ "got offset=%u, expected offset=%u\n",
+ fragment_offset, fragment->fragment_offset + mtu);
+ }
+
+ fragment->acked = true;
+ if (fragment->flags & GIP_FLAG_FRAGMENT) {
+ fragment->fragment_offset = fragment_offset;
+ rc = gip_send_next_fragment(attachment, fragment);
+ if (rc < 0)
+ return rc;
+ } else {
+ gip_free_fragment(to_gip_device(attachment), fragment);
+ }
+
+ return 0;
+
+resend:
+ // TODO
+ return rc;
}
static bool gip_handle_command_hello_device(struct gip_attachment *attachment,
@@ -2175,6 +2466,7 @@ static struct gip_attachment *gip_ensure_attachment(struct gip_device *device,
uint8_t attachment_index)
{
struct gip_attachment *attachment = device->attachments[attachment_index];
+ int i;
if (!attachment) {
attachment = devm_kzalloc(to_gip_device(device), sizeof(*attachment), GFP_KERNEL);
@@ -2190,6 +2482,9 @@ static struct gip_attachment *gip_ensure_attachment(struct gip_device *device,
attachment->product_id = device->udev->descriptor.idProduct;
}
+ for (i = 0; i < MAX_OUT_FRAGMENTS; i++)
+ attachment->out_fragments[i].message = -1;
+
device->attachments[attachment_index] = attachment;
mutex_init(&attachment->lock);
@@ -2476,6 +2771,7 @@ static void gip_urb_out(struct urb *urb)
switch (status) {
case 0:
/* success */
+ schedule_work(&intf->send_fragment);
break;
case -ECONNRESET:
@@ -2672,9 +2968,11 @@ static int gip_probe(struct usb_interface *intf, const struct usb_device_id *id)
gip->data.device = gip;
gip->data.intf = intf;
gip->data.mtu = BASE_GIP_MTU;
+ INIT_WORK(&gip->data.send_fragment, gip_send_fragment_work);
gip->audio.device = gip;
gip->audio.mtu = MAX_GIP_MTU;
gip->audio.isoc_messages = MAX_AUDIO_MESSAGES;
+ INIT_WORK(&gip->audio.send_fragment, gip_send_fragment_work);
INIT_WORK(&gip->receive_message, gip_receive_work);
spin_lock_init(&gip->message_lock);
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
index e7953c683c51..97c848b96e3d 100644
--- a/drivers/input/joystick/gip/gip.h
+++ b/drivers/input/joystick/gip/gip.h
@@ -25,6 +25,7 @@
#define MAX_IN_MESSAGES 8
#define MAX_OUT_MESSAGES 8
+#define MAX_OUT_FRAGMENTS 2
#define GIP_VID_MICROSOFT 0x045e
#define GIP_VID_PDP 0x0e6f
@@ -194,6 +195,17 @@ struct gip_extended_status {
struct gip_status_event events[5];
};
+struct gip_out_fragment {
+ bool active;
+ bool acked;
+ uint8_t message;
+ uint8_t flags;
+ uint8_t seq;
+ uint16_t total_length;
+ uint32_t fragment_offset;
+ uint8_t *data;
+};
+
struct gip_attachment;
typedef int (*gip_command_handler)(struct gip_attachment *a, const struct gip_header *header,
const uint8_t *bytes, int num_bytes);
@@ -222,6 +234,8 @@ struct gip_attachment {
struct delayed_work in_fragment_timeout;
int in_fragment_retries;
+ struct gip_out_fragment out_fragments[MAX_OUT_FRAGMENTS];
+
uint16_t firmware_major_version;
uint16_t firmware_minor_version;
@@ -274,6 +288,9 @@ struct gip_interface {
struct usb_anchor out_anchor;
struct gip_urb out_queue[MAX_OUT_MESSAGES];
+
+ struct work_struct send_fragment;
+ unsigned int has_pending_out;
};
struct gip_device {
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (5 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:35 ` sashiko-bot
2026-08-24 21:49 ` Eric Biggers
2026-08-22 2:11 ` [PATCH v4 08/12] Input: xbox_gip - Add arcade stick support Vicki Pfau
` (4 subsequent siblings)
11 siblings, 2 replies; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
This patch imports and adapts the security message implementation from the
out-of-tree xone driver, which is licensed under the GPL 2.0. This message
is necessary for most controllers that use wireless dongles. The
implementation is largely unchanged, with minor changes being made to adapt
it to the xbox_gip driver and renaming components as appropriate for the
official terminology from the released spec.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/gip/Kconfig | 2 +-
drivers/input/joystick/gip/Makefile | 2 +-
drivers/input/joystick/gip/gip-core.c | 49 +-
drivers/input/joystick/gip/gip-drivers.c | 8 +
drivers/input/joystick/gip/gip-security.c | 867 ++++++++++++++++++++++
drivers/input/joystick/gip/gip.h | 47 +-
6 files changed, 950 insertions(+), 25 deletions(-)
create mode 100644 drivers/input/joystick/gip/gip-security.c
diff --git a/drivers/input/joystick/gip/Kconfig b/drivers/input/joystick/gip/Kconfig
index 3422bf84cb89..b933583d325f 100644
--- a/drivers/input/joystick/gip/Kconfig
+++ b/drivers/input/joystick/gip/Kconfig
@@ -4,7 +4,7 @@
#
config JOYSTICK_XBOX_GIP
tristate "Xbox One/Series controller support"
- depends on USB && USB_ARCH_HAS_HCD && LEDS_CLASS_MULTICOLOR
+ depends on USB && USB_ARCH_HAS_HCD && LEDS_CLASS_MULTICOLOR && CRYPTO_ECDH
help
Say Y here if you want to use Xbox One and Series controllers with your
computer. Make sure to say Y to "Joystick support" (CONFIG_INPUT_JOYDEV)
diff --git a/drivers/input/joystick/gip/Makefile b/drivers/input/joystick/gip/Makefile
index d39f946db1ae..a5ad301a9063 100644
--- a/drivers/input/joystick/gip/Makefile
+++ b/drivers/input/joystick/gip/Makefile
@@ -1,4 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-or-later
obj-$(CONFIG_JOYSTICK_XBOX_GIP) += xbox-gip.o
# Base driver
-xbox-gip-y := gip-core.o gip-drivers.o
+xbox-gip-y := gip-core.o gip-drivers.o gip-security.o
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
index a75fbcb1c02d..765d70033f20 100644
--- a/drivers/input/joystick/gip/gip-core.c
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -6,7 +6,6 @@
*
* TODO:
* - Audio device support
- * - Security packet handshake
* - Event logging
* - Raw character device
* - Wheel support
@@ -1702,17 +1701,6 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
if (rc)
return rc;
- if (gip_supports_system_message(attachment, GIP_CMD_SECURITY, false)
- && !(attachment->features & GIP_FEATURE_SECURITY_OPT_OUT)) {
- /* TODO: Implement Security command property */
- uint8_t buffer[] = { 0x1, 0x0 };
-
- rc = gip_send_system_message(attachment, GIP_CMD_SECURITY, 0,
- buffer, sizeof(buffer));
- if (rc)
- return rc;
- }
-
usb_make_path(attachment->device->udev, attachment->phys,
sizeof(attachment->phys));
len = strlen(attachment->phys);
@@ -2031,6 +2019,26 @@ static int gip_handle_command_status_device(struct gip_attachment *attachment,
}
}
+ if (!attachment->security_sent) {
+ if (attachment->quirks & GIP_QUIRK_SKIP_SECURITY)
+ /*
+ * Some BDA/PowerA Xbox One controllers have a security
+ * handshake implementation that is very sensitive to
+ * the precise message ordering. It's very hard to get
+ * this just right, so for now we just skip it. Most
+ * wired controllers don't need the security flow to
+ * work anyway.
+ */
+ rc = gip_security_skip_handshake(&attachment->security);
+ else
+ rc = gip_security_start_handshake(&attachment->security);
+
+ if (rc)
+ return rc;
+
+ attachment->security_sent = true;
+ }
+
rc = gip_init_input_device(attachment);
if (rc)
return rc;
@@ -2148,20 +2156,16 @@ static int gip_handle_command_metadata_respose(struct gip_attachment *attachment
GIP_CMD_GUIDE_COLOR, false))
attachment->features &= ~GIP_FEATURE_GUIDE_COLOR;
+ if (!gip_supports_system_message(attachment, GIP_CMD_SECURITY, false) ||
+ (attachment->features & GIP_FEATURE_SECURITY_OPT_OUT))
+ attachment->security_sent = true;
+
gip_dbg(attachment, "Attachment %i has features: %02x\n",
attachment->attachment_index, attachment->features);
return gip_send_init_sequence(attachment);
}
-static int gip_handle_command_security(struct gip_attachment *attachment,
- const struct gip_header *header, const uint8_t *bytes, int num_bytes)
-{
- /* TODO: Needed for controllers that connect via dongles */
- gip_warn(attachment, "Unimplemented Security message\n");
- return -EOPNOTSUPP;
-}
-
static int gip_handle_command_guide_button_status(struct gip_attachment *attachment,
const struct gip_header *header, const uint8_t *bytes, int num_bytes)
{
@@ -2434,8 +2438,7 @@ static int gip_handle_system_message(struct gip_attachment *attachment,
return gip_handle_command_metadata_respose(attachment, header,
bytes, num_bytes);
case GIP_CMD_SECURITY:
- return gip_handle_command_security(attachment, header, bytes,
- num_bytes);
+ return gip_security_handle_message(&attachment->security, bytes, num_bytes);
case GIP_CMD_GUIDE_BUTTON:
return gip_handle_command_guide_button_status(attachment,
header, bytes, num_bytes);
@@ -3023,6 +3026,8 @@ static int gip_shutdown(struct gip_device *device)
synchronize_rcu();
}
+ gip_security_release(&attachment->security);
+
if (input)
input_unregister_device(input);
diff --git a/drivers/input/joystick/gip/gip-drivers.c b/drivers/input/joystick/gip/gip-drivers.c
index 2c2297b1fed8..74bc858e71cc 100644
--- a/drivers/input/joystick/gip/gip-drivers.c
+++ b/drivers/input/joystick/gip/gip-drivers.c
@@ -81,6 +81,14 @@ const struct gip_driver gip_driver_gamepad = {
0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5),
.quirks = (const struct gip_quirks[]) {
+ /* PowerA Xbox One Classic Controller */
+ { GIP_VID_BDA, GIP_PID_BDA_XB1_CLASSIC, 0,
+ .quirks = GIP_QUIRK_SKIP_SECURITY },
+
+ /* PowerA Xbox One Fusion Pro */
+ { GIP_VID_BDA, GIP_PID_BDA_XB1_FUSION_PRO, 0,
+ .quirks = GIP_QUIRK_SKIP_SECURITY },
+
/* Xbox One Controller (model 1573) */
{ GIP_VID_MICROSOFT, GIP_PID_XBOX_ONE_1573, 0,
.override_name = "Xbox One Controller" },
diff --git a/drivers/input/joystick/gip/gip-security.c b/drivers/input/joystick/gip/gip-security.c
new file mode 100644
index 000000000000..71da0833980a
--- /dev/null
+++ b/drivers/input/joystick/gip/gip-security.c
@@ -0,0 +1,867 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Gaming Input Protocol security message driver for Xbox One/Series controllers
+ *
+ * This file is based on files from the xone project
+ * - https://github.com/dlundqvist/xone/blob/master/auth/auth.c
+ * - https://github.com/dlundqvist/xone/blob/master/auth/crypto.c
+ *
+ * Copyright (C) 2023 Severin von Wnuck-Lipinski <severinvonw@outlook.de>
+ * Copyright (c) 2026 Valve Software
+ */
+
+#include <linux/scatterlist.h>
+#include <linux/version.h>
+#include <crypto/hash.h>
+#include <crypto/sha2.h>
+#include <crypto/akcipher.h>
+#include <crypto/kpp.h>
+#include <crypto/ecdh.h>
+
+#include "gip.h"
+
+/* trailer is required for v1 clients */
+#define GIP_SECURITY_TRAILER_LEN 8
+#define GIP_SECURITY_CERTIFICATE_MAX_LEN 1024
+#define GIP_SECURITY_ENCRYPTED_PMS_LEN 256
+#define GIP_SECURITY_TRANSCRIPT_LEN 32
+#define GIP_SECURITY_SESSION_KEY_LEN 16
+
+#define GIP_SECURITY_ECDH_SECRET_LEN 32
+
+enum gip_security_context {
+ GIP_SECURITY_CTX_HANDSHAKE = 0x00,
+ GIP_SECURITY_CTX_CONTROL = 0x01,
+};
+
+enum gip_security_command_handshake {
+ GIP_SECURITY_CMD_HOST_HELLO = 0x01,
+ GIP_SECURITY_CMD_CLIENT_HELLO = 0x02,
+ GIP_SECURITY_CMD_CLIENT_CERTIFICATE = 0x03,
+ GIP_SECURITY_CMD_HOST_SECRET = 0x05,
+ GIP_SECURITY_CMD_HOST_FINISH = 0x07,
+ GIP_SECURITY_CMD_CLIENT_FINISH = 0x08,
+
+ GIP_SECURITY2_CMD_HOST_HELLO = 0x21,
+ GIP_SECURITY2_CMD_CLIENT_HELLO = 0x22,
+ GIP_SECURITY2_CMD_CLIENT_CERTIFICATE = 0x23,
+ GIP_SECURITY2_CMD_CLIENT_PUBKEY = 0x24,
+ GIP_SECURITY2_CMD_HOST_PUBKEY = 0x25,
+ GIP_SECURITY2_CMD_HOST_FINISH = 0x26,
+ GIP_SECURITY2_CMD_CLIENT_FINISH = 0x27,
+};
+
+enum gip_security_command_control {
+ GIP_SECURITY_CTRL_COMPLETE = 0x00,
+ GIP_SECURITY_CTRL_RESET = 0x01,
+};
+
+enum gip_security_option {
+ GIP_SECURITY_OPT_ACKNOWLEDGE = BIT(0),
+ GIP_SECURITY_OPT_REQUEST = BIT(1),
+ GIP_SECURITY_OPT_FROM_HOST = BIT(6),
+ GIP_SECURITY_OPT_FROM_CLIENT = BIT(6) | BIT(7),
+};
+
+struct gip_security_header_handshake {
+ u8 context;
+ u8 options;
+ u8 error;
+ u8 command;
+ __be16 length;
+} __packed;
+
+struct gip_security_header_data {
+ u8 command;
+ u8 version;
+ __be16 length;
+} __packed;
+
+struct gip_security_header_full {
+ struct gip_security_header_handshake handshake;
+ struct gip_security_header_data data;
+} __packed;
+
+struct gip_security_header_control {
+ u8 context;
+ u8 control;
+} __packed;
+
+struct gip_security_request {
+ struct gip_security_header_handshake header;
+
+ u8 trailer[GIP_SECURITY_TRAILER_LEN];
+} __packed;
+
+struct gip_security_host_hello {
+ struct gip_security_header_full header;
+
+ u8 random[GIP_SECURITY_RANDOM_LEN];
+ u8 unknown1[4];
+ u8 unknown2[4];
+
+ u8 trailer[GIP_SECURITY_TRAILER_LEN];
+} __packed;
+
+struct gip_security_host_secret {
+ struct gip_security_header_full header;
+
+ u8 encrypted_pms[GIP_SECURITY_ENCRYPTED_PMS_LEN];
+
+ u8 trailer[GIP_SECURITY_TRAILER_LEN];
+} __packed;
+
+struct gip_security_host_finish {
+ struct gip_security_header_full header;
+
+ u8 transcript[GIP_SECURITY_TRANSCRIPT_LEN];
+
+ u8 trailer[GIP_SECURITY_TRAILER_LEN];
+} __packed;
+
+struct gip_security_client_hello {
+ u8 random[GIP_SECURITY_RANDOM_LEN];
+ u8 unknown[48];
+} __packed;
+
+struct gip_security_client_finish {
+ u8 transcript[GIP_SECURITY_TRANSCRIPT_LEN];
+ u8 unknown[32];
+} __packed;
+
+struct gip_security2_host_hello {
+ struct gip_security_header_full header;
+
+ u8 random[GIP_SECURITY_RANDOM_LEN];
+ u8 unknown[4];
+
+ u8 trailer[GIP_SECURITY_TRAILER_LEN];
+} __packed;
+
+struct gip_security2_host_pubkey {
+ struct gip_security_header_full header;
+
+ u8 pubkey[GIP_SECURITY2_PUBKEY_LEN];
+
+ u8 trailer[GIP_SECURITY_TRAILER_LEN];
+} __packed;
+
+struct gip_security2_host_finish {
+ struct gip_security_header_full header;
+
+ u8 transcript[GIP_SECURITY_TRANSCRIPT_LEN];
+
+ u8 trailer[GIP_SECURITY_TRAILER_LEN];
+} __packed;
+
+struct gip_security2_client_hello {
+ u8 random[GIP_SECURITY_RANDOM_LEN];
+ u8 unknown1[108];
+ u8 unknown2[32];
+} __packed;
+
+struct gip_security2_client_cert {
+ char header[4];
+ u8 unknown1[136];
+ char chip[32];
+ char revision[20];
+ u8 unknown2[576];
+} __packed;
+
+struct gip_security2_client_pubkey {
+ u8 pubkey[GIP_SECURITY2_PUBKEY_LEN];
+ u8 unknown[64];
+} __packed;
+
+struct gip_security2_client_finish {
+ u8 transcript[GIP_SECURITY_TRANSCRIPT_LEN];
+ u8 unknown[32];
+} __packed;
+
+static struct shash_desc *gip_security_alloc_shash(const char *alg)
+{
+ struct crypto_shash *tfm;
+ struct shash_desc *desc;
+
+ tfm = crypto_alloc_shash(alg, 0, 0);
+ if (IS_ERR(tfm))
+ return ERR_CAST(tfm);
+
+ desc = kzalloc(sizeof(*desc) + crypto_shash_descsize(tfm), GFP_KERNEL);
+ if (!desc) {
+ crypto_free_shash(tfm);
+ return ERR_PTR(-ENOMEM);
+ }
+
+ desc->tfm = tfm;
+ crypto_shash_init(desc);
+
+ return desc;
+}
+
+static int gip_security_get_transcript(struct shash_desc *desc, void *transcript)
+{
+ void *state = kzalloc(crypto_shash_descsize(desc->tfm), GFP_KERNEL);
+ int err;
+
+ err = crypto_shash_export(desc, state);
+ if (err)
+ goto get_transcript_error;
+
+ err = crypto_shash_final(desc, transcript);
+ if (err)
+ goto get_transcript_error;
+
+ err = crypto_shash_import(desc, state);
+
+get_transcript_error:
+ kfree(state);
+ return err;
+}
+
+static int gip_security_compute_prf(struct shash_desc *desc, const char *label,
+ u8 *key, int key_len, u8 *seed, int seed_len, u8 *out, int out_len)
+{
+ u8 hash[SHA256_DIGEST_SIZE], hash_out[SHA256_DIGEST_SIZE];
+ int err;
+
+ err = crypto_shash_setkey(desc->tfm, key, key_len);
+ if (err)
+ return err;
+
+ crypto_shash_init(desc);
+ crypto_shash_update(desc, label, strlen(label));
+ crypto_shash_update(desc, seed, seed_len);
+ crypto_shash_final(desc, hash);
+
+ while (out_len > 0) {
+ crypto_shash_init(desc);
+ crypto_shash_update(desc, hash, sizeof(hash));
+ crypto_shash_update(desc, label, strlen(label));
+ crypto_shash_update(desc, seed, seed_len);
+ crypto_shash_final(desc, hash_out);
+
+ memcpy(out, hash_out, min_t(int, out_len, sizeof(hash)));
+ out += sizeof(hash);
+ out_len -= sizeof(hash);
+
+ crypto_shash_digest(desc, hash, sizeof(hash), hash);
+ }
+
+ return 0;
+}
+
+static int gip_security_encrypt_rsa(u8 *key, int key_len, u8 *in, int in_len, u8 *out, int out_len)
+{
+ struct crypto_akcipher *tfm;
+ int err;
+
+ tfm = crypto_alloc_akcipher("pkcs1pad(rsa)", 0, 0);
+ if (IS_ERR(tfm))
+ return PTR_ERR(tfm);
+
+ err = crypto_akcipher_set_pub_key(tfm, key, key_len);
+ if (err)
+ goto err_free_tfm;
+
+ err = crypto_akcipher_sync_encrypt(tfm, in, in_len, out, out_len);
+
+err_free_tfm:
+ crypto_free_akcipher(tfm);
+
+ return err;
+}
+
+static int gip_security_ecdh_get_pubkey(struct crypto_kpp *tfm, u8 *out, int len)
+{
+ struct kpp_request *req;
+ struct scatterlist dest;
+ struct ecdh key = {};
+ DECLARE_CRYPTO_WAIT(wait);
+ void *privkey, *pubkey;
+ unsigned int privkey_len;
+ int err = 0;
+
+ privkey_len = crypto_ecdh_key_len(&key);
+ privkey = kzalloc(privkey_len, GFP_KERNEL);
+ if (!privkey)
+ return -ENOMEM;
+
+ pubkey = kzalloc(len, GFP_KERNEL);
+ if (!pubkey) {
+ err = -ENOMEM;
+ goto err_free_privkey;
+ }
+
+ /* generate private key */
+ err = crypto_ecdh_encode_key(privkey, privkey_len, &key);
+ if (err)
+ goto err_free_pubkey;
+
+ err = crypto_kpp_set_secret(tfm, privkey, privkey_len);
+ if (err)
+ goto err_free_pubkey;
+
+ req = kpp_request_alloc(tfm, GFP_KERNEL);
+ if (!req) {
+ err = -ENOMEM;
+ goto err_free_pubkey;
+ }
+
+ sg_init_one(&dest, pubkey, len);
+
+ kpp_request_set_input(req, NULL, 0);
+ kpp_request_set_output(req, &dest, len);
+ kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+ crypto_req_done, &wait);
+ err = crypto_wait_req(crypto_kpp_generate_public_key(req), &wait);
+ if (!err)
+ memcpy(out, pubkey, len);
+
+ kpp_request_free(req);
+
+err_free_pubkey:
+ kfree(pubkey);
+err_free_privkey:
+ kfree(privkey);
+
+ return err;
+}
+
+static int gip_security_ecdh_get_secret(struct crypto_kpp *tfm, u8 *pubkey,
+ int pubkey_len, u8 *secret, int secret_len)
+{
+ struct kpp_request *req;
+ struct scatterlist src, dest;
+ DECLARE_CRYPTO_WAIT(wait);
+ int err;
+
+ req = kpp_request_alloc(tfm, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ sg_init_one(&src, pubkey, pubkey_len);
+ sg_init_one(&dest, secret, secret_len);
+
+ kpp_request_set_input(req, &src, pubkey_len);
+ kpp_request_set_output(req, &dest, secret_len);
+ kpp_request_set_callback(req, CRYPTO_TFM_REQ_MAY_BACKLOG,
+ crypto_req_done, &wait);
+ err = crypto_wait_req(crypto_kpp_compute_shared_secret(req), &wait);
+
+ kpp_request_free(req);
+
+ return err;
+}
+
+static int gip_security_compute_ecdh(u8 *pubkey_in, u8 *pubkey_out, int pubkey_len, u8 *secret_hash)
+{
+ struct crypto_kpp *tfm_ecdh;
+ struct crypto_shash *tfm_sha;
+ u8 *secret;
+ int err;
+
+ secret = kzalloc(GIP_SECURITY_ECDH_SECRET_LEN, GFP_KERNEL);
+ if (!secret)
+ return -ENOMEM;
+
+ tfm_ecdh = crypto_alloc_kpp("ecdh-nist-p256", 0, 0);
+ if (IS_ERR(tfm_ecdh)) {
+ err = PTR_ERR(tfm_ecdh);
+ goto err_free_secret;
+ }
+
+ tfm_sha = crypto_alloc_shash("sha256", 0, 0);
+ if (IS_ERR(tfm_sha)) {
+ err = PTR_ERR(tfm_sha);
+ goto err_free_ecdh;
+ }
+
+ err = gip_security_ecdh_get_pubkey(tfm_ecdh, pubkey_out, pubkey_len);
+ if (err)
+ goto err_free_sha;
+
+ err = gip_security_ecdh_get_secret(tfm_ecdh, pubkey_in, pubkey_len,
+ secret, GIP_SECURITY_ECDH_SECRET_LEN);
+ if (err)
+ goto err_free_sha;
+
+ crypto_shash_tfm_digest(tfm_sha, secret, GIP_SECURITY_ECDH_SECRET_LEN, secret_hash);
+
+err_free_sha:
+ crypto_free_shash(tfm_sha);
+err_free_ecdh:
+ crypto_free_kpp(tfm_ecdh);
+err_free_secret:
+ kfree(secret);
+
+ return err;
+}
+
+static int gip_security_send_message(struct gip_security *security,
+ enum gip_security_command_handshake cmd, void *message, u16 len)
+{
+ struct gip_attachment *attachment = container_of(security, struct gip_attachment, security);
+ struct gip_security_header_full *hdr = message;
+ u16 data_len = len - sizeof(hdr->handshake) - GIP_SECURITY_TRAILER_LEN;
+
+ hdr->handshake.context = GIP_SECURITY_CTX_HANDSHAKE;
+ hdr->handshake.options = GIP_SECURITY_OPT_ACKNOWLEDGE | GIP_SECURITY_OPT_FROM_HOST;
+ hdr->handshake.command = cmd;
+ hdr->handshake.length = cpu_to_be16(data_len);
+
+ hdr->data.command = cmd;
+ hdr->data.version = cmd >= GIP_SECURITY2_CMD_HOST_HELLO ? 0x02 : 0x01;
+ hdr->data.length = cpu_to_be16(data_len - sizeof(hdr->data));
+
+ security->last_sent_command = cmd;
+ crypto_shash_update(security->shash_transcript, message + sizeof(hdr->handshake), data_len);
+
+ return gip_send_system_message(attachment, GIP_CMD_SECURITY, GIP_FLAG_ACME, message, len);
+}
+
+static int gip_security_send_request(struct gip_security *security,
+ enum gip_security_command_handshake cmd, u16 len)
+{
+ struct gip_attachment *attachment = container_of(security, struct gip_attachment, security);
+ struct gip_security_request req = {};
+ u16 data_len = len + sizeof(struct gip_security_header_data);
+
+ req.header.context = GIP_SECURITY_CTX_HANDSHAKE;
+ req.header.options = GIP_SECURITY_OPT_REQUEST | GIP_SECURITY_OPT_FROM_HOST;
+ req.header.command = cmd;
+ req.header.length = cpu_to_be16(data_len);
+
+ return gip_send_system_message(attachment, GIP_CMD_SECURITY,
+ GIP_FLAG_ACME, &req, sizeof(req));
+}
+
+static int gip_security2_send_hello(struct gip_security *security)
+{
+ struct gip_security2_host_hello message = {};
+
+ get_random_bytes(security->random_host, sizeof(security->random_host));
+ memcpy(message.random, security->random_host, sizeof(message.random));
+
+ return gip_security_send_message(security, GIP_SECURITY2_CMD_HOST_HELLO,
+ &message, sizeof(message));
+}
+
+static int gip_security2_handle_hello(struct gip_security *security, const void *data, u32 len)
+{
+ const struct gip_security2_client_hello *message = data;
+
+ if (len < sizeof(*message))
+ return -EINVAL;
+
+ memcpy(security->random_client, message->random, sizeof(security->random_client));
+
+ return gip_security_send_request(security,
+ GIP_SECURITY2_CMD_CLIENT_CERTIFICATE,
+ sizeof(struct gip_security2_client_cert));
+}
+
+static int gip_security2_handle_certificate(struct gip_security *security,
+ const void *data, u32 len)
+{
+ const struct gip_security2_client_cert *message = data;
+
+ if (len < sizeof(*message))
+ return -EINVAL;
+
+ gip_dbg(security,
+ "%s: header=%.*s, chip=%.*s, revision=%.*s\n", __func__,
+ (int)sizeof(message->header), message->header,
+ (int)sizeof(message->chip), message->chip,
+ (int)sizeof(message->revision), message->revision);
+
+ return gip_security_send_request(security,
+ GIP_SECURITY2_CMD_CLIENT_PUBKEY,
+ sizeof(struct gip_security2_client_pubkey));
+}
+
+static int gip_security2_handle_pubkey(struct gip_security *security,
+ const void *data, u32 len)
+{
+ const struct gip_security2_client_pubkey *message = data;
+
+ if (len < sizeof(*message))
+ return -EINVAL;
+
+ memcpy(security->pubkey_client2, message->pubkey, sizeof(message->pubkey));
+ schedule_work(&security->work_exchange_ecdh);
+
+ return 0;
+}
+
+static void gip_security2_exchange_ecdh(struct work_struct *work)
+{
+ struct gip_security *security = container_of(work, typeof(*security), work_exchange_ecdh);
+ struct gip_security2_host_pubkey message = {};
+ u8 random[GIP_SECURITY_RANDOM_LEN * 2];
+ u8 secret[GIP_SECURITY2_SECRET_LEN];
+ int err;
+
+ memcpy(random, security->random_host, sizeof(security->random_host));
+ memcpy(random + sizeof(security->random_host), security->random_client,
+ sizeof(security->random_client));
+
+ err = gip_security_compute_ecdh(security->pubkey_client2,
+ message.pubkey, sizeof(message.pubkey), secret);
+ if (err) {
+ gip_err(security, "%s: compute ECDH failed: %d\n", __func__, err);
+ return;
+ }
+
+ err = gip_security_compute_prf(security->shash_prf, "Master Secret",
+ secret, sizeof(secret), random, sizeof(random),
+ security->master_secret, sizeof(security->master_secret));
+ if (err) {
+ gip_err(security, "%s: compute PRF failed: %d\n", __func__, err);
+ return;
+ }
+
+ err = gip_security_send_message(security, GIP_SECURITY2_CMD_HOST_PUBKEY,
+ &message, sizeof(message));
+ if (err)
+ gip_err(security, "%s: send message failed: %d\n", __func__, err);
+}
+
+static int gip_security_send_hello(struct gip_security *security)
+{
+ struct gip_security_host_hello message = {};
+
+ get_random_bytes(security->random_host, sizeof(security->random_host));
+ memcpy(message.random, security->random_host, sizeof(message.random));
+
+ return gip_security_send_message(security, GIP_SECURITY_CMD_HOST_HELLO,
+ &message, sizeof(message));
+}
+
+static int gip_security_send_finish(struct gip_security *security,
+ enum gip_security_command_handshake cmd)
+{
+ struct gip_security_host_finish message = {};
+ u8 transcript[GIP_SECURITY_TRANSCRIPT_LEN];
+ int err;
+
+ err = gip_security_get_transcript(security->shash_transcript, transcript);
+ if (err) {
+ gip_err(security, "%s: get transcript failed: %d\n", __func__, err);
+ return err;
+ }
+
+ err = gip_security_compute_prf(security->shash_prf, "Host Finished",
+ security->master_secret, sizeof(security->master_secret),
+ transcript, sizeof(transcript), message.transcript,
+ sizeof(message.transcript));
+ if (err) {
+ gip_err(security, "%s: compute PRF failed: %d\n", __func__, err);
+ return err;
+ }
+
+ return gip_security_send_message(security, cmd, &message, sizeof(message));
+}
+
+static int gip_security_handle_acknowledge(struct gip_security *security)
+{
+ switch (security->last_sent_command) {
+ case GIP_SECURITY2_CMD_HOST_HELLO:
+ return gip_security_send_request(security,
+ GIP_SECURITY2_CMD_CLIENT_HELLO,
+ sizeof(struct gip_security2_client_hello));
+ case GIP_SECURITY2_CMD_HOST_PUBKEY:
+ return gip_security_send_finish(security, GIP_SECURITY2_CMD_HOST_FINISH);
+ case GIP_SECURITY2_CMD_HOST_FINISH:
+ return gip_security_send_request(security,
+ GIP_SECURITY2_CMD_CLIENT_FINISH,
+ sizeof(struct gip_security2_client_finish));
+ case GIP_SECURITY_CMD_HOST_HELLO:
+ return gip_security_send_request(security,
+ GIP_SECURITY_CMD_CLIENT_HELLO,
+ sizeof(struct gip_security_client_hello));
+ case GIP_SECURITY_CMD_HOST_SECRET:
+ return gip_security_send_finish(security, GIP_SECURITY_CMD_HOST_FINISH);
+ case GIP_SECURITY_CMD_HOST_FINISH:
+ return gip_security_send_request(security,
+ GIP_SECURITY_CMD_CLIENT_FINISH,
+ sizeof(struct gip_security_client_finish));
+ default:
+ return -EPROTO;
+ }
+}
+
+static int gip_security_handle_hello(struct gip_security *security, const void *data, u32 len)
+{
+ const struct gip_security_client_hello *message = data;
+
+ if (len < sizeof(*message))
+ return -EINVAL;
+
+ memcpy(security->random_client, message->random, sizeof(message->random));
+
+ return gip_security_send_request(security, GIP_SECURITY_CMD_CLIENT_CERTIFICATE,
+ GIP_SECURITY_CERTIFICATE_MAX_LEN);
+}
+
+static int gip_security_handle_certificate(struct gip_security *security, const void *data, u32 len)
+{
+ /* ASN.1 SEQUENCE (len = 0x04 + 0x010a) */
+ u8 asn1_seq[] = { 0x30, 0x82, 0x01, 0x0a };
+ int i;
+
+ if (len > GIP_SECURITY_CERTIFICATE_MAX_LEN)
+ return -EINVAL;
+
+ /*
+ * Poor way of extracting a pubkey from an X.509 certificate.
+ * The certificates issued by Microsoft do not comply with RFC 5280.
+ * They have an empty subject and no subjectAltName.
+ * This is explicitly forbidden by section 4.2.1.6 of the RFC.
+ * The kernel's ASN.1 parser will fail when using x509_cert_parse.
+ */
+ for (i = 0; i + sizeof(asn1_seq) <= len; i++) {
+ if (memcmp(data + i, asn1_seq, sizeof(asn1_seq)))
+ continue;
+
+ if (i + GIP_SECURITY_PUBKEY_LEN > len)
+ return -EINVAL;
+
+ memcpy(security->pubkey_client, data + i, GIP_SECURITY_PUBKEY_LEN);
+ schedule_work(&security->work_exchange_rsa);
+
+ return 0;
+ }
+
+ return -EPROTO;
+}
+
+static int gip_security_handle_finish(struct gip_security *security, const void *data, u32 len)
+{
+ const struct gip_security_client_finish *message = data;
+ u8 transcript[GIP_SECURITY_TRANSCRIPT_LEN];
+ u8 finished[GIP_SECURITY_TRANSCRIPT_LEN];
+ int err;
+
+ if (len < sizeof(*message))
+ return -EINVAL;
+
+ err = gip_security_get_transcript(security->shash_transcript, transcript);
+ if (err) {
+ gip_err(security, "%s: get transcript failed: %d\n", __func__, err);
+ return err;
+ }
+
+ err = gip_security_compute_prf(security->shash_prf, "Device Finished",
+ security->master_secret, sizeof(security->master_secret),
+ transcript, sizeof(transcript), finished, sizeof(finished));
+ if (err) {
+ gip_err(security, "%s: compute PRF failed: %d\n", __func__, err);
+ return err;
+ }
+
+ if (memcmp(message->transcript, finished, sizeof(finished))) {
+ gip_err(security, "%s: transcript mismatch\n", __func__);
+ return -EPROTO;
+ }
+
+ schedule_work(&security->work_complete);
+
+ return 0;
+}
+
+static void gip_security_exchange_rsa(struct work_struct *work)
+{
+ struct gip_security *security = container_of(work, typeof(*security), work_exchange_rsa);
+ struct gip_security_host_secret message = {};
+ u8 random[GIP_SECURITY_RANDOM_LEN * 2];
+ int err;
+
+ memcpy(random, security->random_host, sizeof(security->random_host));
+ memcpy(random + sizeof(security->random_host), security->random_client,
+ sizeof(security->random_client));
+
+ /* get random premaster secret */
+ get_random_bytes(security->pms, sizeof(security->pms));
+
+ err = gip_security_encrypt_rsa(security->pubkey_client,
+ sizeof(security->pubkey_client), security->pms,
+ sizeof(security->pms), message.encrypted_pms,
+ sizeof(message.encrypted_pms));
+ if (err) {
+ gip_err(security, "%s: encrypt RSA failed: %d\n", __func__, err);
+ return;
+ }
+
+ err = gip_security_compute_prf(security->shash_prf, "Master Secret",
+ security->pms, sizeof(security->pms), random, sizeof(random),
+ security->master_secret, sizeof(security->master_secret));
+ if (err) {
+ gip_err(security, "%s: compute PRF failed: %d\n", __func__, err);
+ return;
+ }
+
+ err = gip_security_send_message(security, GIP_SECURITY_CMD_HOST_SECRET,
+ &message, sizeof(message));
+ if (err)
+ gip_err(security, "%s: send message failed: %d\n", __func__, err);
+}
+
+static void gip_security_complete_handshake(struct work_struct *work)
+{
+ struct gip_security *security = container_of(work, typeof(*security), work_complete);
+ struct gip_attachment *attachment = container_of(security, struct gip_attachment, security);
+ struct gip_security_header_control hdr = {
+ .context = GIP_SECURITY_CTX_CONTROL,
+ .control = GIP_SECURITY_CTRL_COMPLETE,
+ };
+ u8 random[GIP_SECURITY_RANDOM_LEN * 2];
+ u8 key[GIP_SECURITY_SESSION_KEY_LEN];
+ int err;
+
+ memcpy(random, security->random_host, sizeof(security->random_host));
+ memcpy(random + sizeof(security->random_host), security->random_client,
+ sizeof(security->random_client));
+
+ err = gip_security_compute_prf(security->shash_prf,
+ "EXPORTER DAWN data channel session key for controller",
+ security->master_secret, sizeof(security->master_secret),
+ random, sizeof(random), key, sizeof(key));
+ if (err) {
+ gip_err(security, "%s: compute PRF failed: %d\n", __func__, err);
+ return;
+ }
+
+ gip_dbg(security, "%s: key=%*phD\n", __func__, (int)sizeof(key), key);
+
+ err = gip_send_system_message(attachment, GIP_CMD_SECURITY,
+ 0, &hdr, sizeof(hdr));
+ if (err)
+ gip_err(security, "%s: send complete failed: %d\n", __func__, err);
+}
+
+static int gip_security_dispatch_message(struct gip_security *security,
+ enum gip_security_command_handshake cmd, const void *data, u32 len)
+{
+ switch (cmd) {
+ case GIP_SECURITY2_CMD_CLIENT_HELLO:
+ return gip_security2_handle_hello(security, data, len);
+ case GIP_SECURITY2_CMD_CLIENT_CERTIFICATE:
+ return gip_security2_handle_certificate(security, data, len);
+ case GIP_SECURITY2_CMD_CLIENT_PUBKEY:
+ return gip_security2_handle_pubkey(security, data, len);
+ case GIP_SECURITY2_CMD_CLIENT_FINISH:
+ return gip_security_handle_finish(security, data, len);
+ case GIP_SECURITY_CMD_CLIENT_HELLO:
+ return gip_security_handle_hello(security, data, len);
+ case GIP_SECURITY_CMD_CLIENT_CERTIFICATE:
+ return gip_security_handle_certificate(security, data, len);
+ case GIP_SECURITY_CMD_CLIENT_FINISH:
+ return gip_security_handle_finish(security, data, len);
+ default:
+ return -EPROTO;
+ }
+}
+
+int gip_security_handle_message(struct gip_security *security, const void *bytes, int num_bytes)
+{
+ const struct gip_security_header_handshake *handshake = bytes;
+ const struct gip_security_header_full *hdr;
+ int err;
+
+ if (num_bytes < sizeof(*handshake))
+ return -EINVAL;
+
+ if (handshake->error)
+ return -EPROTO;
+
+ if (handshake->options & GIP_SECURITY_OPT_ACKNOWLEDGE) {
+ if (handshake->command == 0x01)
+ return gip_security_handle_acknowledge(security);
+
+ gip_err(security, "%s: handshake failed: 0x%02x\n",
+ __func__, handshake->command);
+ return -EPROTO;
+ }
+
+ if (num_bytes < sizeof(*hdr))
+ return -EINVAL;
+
+ hdr = bytes;
+ /* client uses v2 */
+ if (handshake->command != hdr->data.command) {
+ /* reset transcript hash and restart handshake */
+ gip_dbg(security, "%s: protocol upgrade\n", __func__);
+ crypto_shash_init(security->shash_transcript);
+ return gip_security2_send_hello(security);
+ }
+
+ err = gip_security_dispatch_message(security, hdr->data.command,
+ bytes + sizeof(*hdr), num_bytes - sizeof(*hdr));
+ if (err)
+ return err;
+
+ return crypto_shash_update(security->shash_transcript,
+ bytes + sizeof(hdr->handshake),
+ num_bytes - sizeof(hdr->handshake));
+}
+
+void gip_security_release(struct gip_security *security)
+{
+ if (!security->shash_transcript || !security->shash_prf)
+ return;
+
+ cancel_work_sync(&security->work_exchange_rsa);
+ cancel_work_sync(&security->work_exchange_ecdh);
+ cancel_work_sync(&security->work_complete);
+
+ crypto_free_shash(security->shash_transcript->tfm);
+ crypto_free_shash(security->shash_prf->tfm);
+ kfree(security->shash_transcript);
+ kfree(security->shash_prf);
+
+ security->shash_transcript = NULL;
+ security->shash_prf = NULL;
+}
+
+int gip_security_start_handshake(struct gip_security *security)
+{
+ struct shash_desc *shash_transcript, *shash_prf;
+
+ if (!security->shash_transcript) {
+ shash_transcript = gip_security_alloc_shash("sha256");
+ if (IS_ERR(shash_transcript))
+ return PTR_ERR(shash_transcript);
+
+ security->shash_transcript = shash_transcript;
+ }
+
+ if (!security->shash_prf) {
+ shash_prf = gip_security_alloc_shash("hmac(sha256)");
+ if (IS_ERR(shash_prf)) {
+ crypto_free_shash(shash_transcript->tfm);
+ kfree(shash_transcript);
+ security->shash_transcript = NULL;
+ return PTR_ERR(shash_prf);
+ }
+
+ security->shash_prf = shash_prf;
+ }
+
+ INIT_WORK(&security->work_exchange_rsa, gip_security_exchange_rsa);
+ INIT_WORK(&security->work_exchange_ecdh, gip_security2_exchange_ecdh);
+ INIT_WORK(&security->work_complete, gip_security_complete_handshake);
+
+ return gip_security_send_hello(security);
+}
+
+int gip_security_skip_handshake(struct gip_security *security)
+{
+ struct gip_attachment *attachment = container_of(security, struct gip_attachment, security);
+ struct gip_security_header_control hdr = {
+ .context = GIP_SECURITY_CTX_CONTROL,
+ .control = GIP_SECURITY_CTRL_COMPLETE,
+ };
+
+ return gip_send_system_message(attachment, GIP_CMD_SECURITY, 0, &hdr, sizeof(hdr));
+}
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
index 97c848b96e3d..42fe8aee9f68 100644
--- a/drivers/input/joystick/gip/gip.h
+++ b/drivers/input/joystick/gip/gip.h
@@ -29,6 +29,7 @@
#define GIP_VID_MICROSOFT 0x045e
#define GIP_VID_PDP 0x0e6f
+#define GIP_VID_BDA 0x24c6
#define GIP_PID_XBOX_ONE_1573 0x02d1
#define GIP_PID_XBOX_ONE_1697 0x02dd
@@ -40,9 +41,13 @@
#define GIP_PID_PDP_ROCK_CANDY 0x0246
+#define GIP_PID_BDA_XB1_CLASSIC 0x581a
+#define GIP_PID_BDA_XB1_FUSION_PRO 0x591a
+
#define GIP_QUIRK_NO_HELLO BIT(0)
#define GIP_QUIRK_NO_IMPULSE_VIBRATION BIT(1)
#define GIP_QUIRK_SWAP_LB_RB BIT(2)
+#define GIP_QUIRK_SKIP_SECURITY BIT(3)
/*
* Driver-specific quirks should start from 31 and go downwards to avoid
* conflicts with newly-added core quirks
@@ -90,13 +95,21 @@
#define GIP_LL_STATIC_CONFIGURATION 0x21
#define GIP_LL_BUTTON_INFO_REPORT 0x22
+#define GIP_SECURITY_RANDOM_LEN 32
+#define GIP_SECURITY_PUBKEY_LEN 270
+#define GIP_SECURITY_SECRET_LEN 48
+
+#define GIP_SECURITY2_PUBKEY_LEN 64
+#define GIP_SECURITY2_SECRET_LEN 32
+
#define MAX_GIP_CMD 0x80
#define to_gip_device(p) \
_Generic((p), \
struct gip_attachment * : gip_attachment_dev, \
struct gip_interface * : gip_interface_dev, \
- struct gip_device * : gip_device_dev)(p)
+ struct gip_device * : gip_device_dev, \
+ struct gip_security * : gip_security_dev)(p)
#define gip_dbg(dev, ...) dev_dbg(to_gip_device(dev), __VA_ARGS__)
#define gip_info(dev, ...) dev_info(to_gip_device(dev), __VA_ARGS__)
@@ -206,6 +219,26 @@ struct gip_out_fragment {
uint8_t *data;
};
+struct gip_security {
+ struct shash_desc *shash_transcript;
+ struct shash_desc *shash_prf;
+
+ struct work_struct work_exchange_rsa;
+ struct work_struct work_exchange_ecdh;
+ struct work_struct work_complete;
+
+ uint8_t last_sent_command;
+
+ uint8_t random_host[GIP_SECURITY_RANDOM_LEN];
+ uint8_t random_client[GIP_SECURITY_RANDOM_LEN];
+
+ uint8_t pubkey_client[GIP_SECURITY_PUBKEY_LEN];
+ uint8_t pubkey_client2[GIP_SECURITY2_PUBKEY_LEN];
+
+ uint8_t pms[GIP_SECURITY_SECRET_LEN];
+ uint8_t master_secret[GIP_SECURITY_SECRET_LEN];
+};
+
struct gip_attachment;
typedef int (*gip_command_handler)(struct gip_attachment *a, const struct gip_header *header,
const uint8_t *bytes, int num_bytes);
@@ -243,6 +276,8 @@ struct gip_attachment {
struct delayed_work metadata_next;
int metadata_retries;
struct gip_metadata metadata;
+ struct gip_security security;
+ bool security_sent;
uint8_t seq_system;
uint8_t seq_security;
@@ -355,6 +390,11 @@ static inline struct device *gip_device_dev(struct gip_device *device)
return &device->udev->dev;
}
+static inline struct device *gip_security_dev(struct gip_security *security)
+{
+ return gip_attachment_dev(container_of(security, struct gip_attachment, security));
+}
+
bool gip_supports_vendor_message(struct gip_attachment *attachment, uint8_t command, bool upstream);
int gip_send_system_message(struct gip_attachment *attachment,
@@ -362,6 +402,11 @@ int gip_send_system_message(struct gip_attachment *attachment,
int gip_send_vendor_message(struct gip_attachment *attachment,
uint8_t message_type, uint8_t flags, const void *bytes, int num_bytes);
+int gip_security_handle_message(struct gip_security *security, const void *bytes, int num_bytes);
+int gip_security_start_handshake(struct gip_security *security);
+int gip_security_skip_handshake(struct gip_security *security);
+void gip_security_release(struct gip_security *security);
+
extern const struct gip_driver gip_driver_navigation;
extern const struct gip_driver gip_driver_gamepad;
extern const struct gip_driver gip_driver_arcade_stick;
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 08/12] Input: xbox_gip - Add arcade stick support
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (6 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 09/12] Input: xbox_gip - Add support for PDP guitar controllers Vicki Pfau
` (3 subsequent siblings)
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
This adds support for the arcade stick device type. Currently I'm only
aware of one Xbox controller that uses this device type, the Razer Atrox,
so testing coverage is limited.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/gip/Makefile | 2 +
drivers/input/joystick/gip/gip-arcade-stick.c | 173 ++++++++++++++++++
drivers/input/joystick/gip/gip-core.c | 6 +-
drivers/input/joystick/gip/gip.h | 4 +-
4 files changed, 181 insertions(+), 4 deletions(-)
create mode 100644 drivers/input/joystick/gip/gip-arcade-stick.c
diff --git a/drivers/input/joystick/gip/Makefile b/drivers/input/joystick/gip/Makefile
index a5ad301a9063..9ed59caec4d9 100644
--- a/drivers/input/joystick/gip/Makefile
+++ b/drivers/input/joystick/gip/Makefile
@@ -2,3 +2,5 @@
obj-$(CONFIG_JOYSTICK_XBOX_GIP) += xbox-gip.o
# Base driver
xbox-gip-y := gip-core.o gip-drivers.o gip-security.o
+# Additional device support
+xbox-gip-y += gip-arcade-stick.o
diff --git a/drivers/input/joystick/gip/gip-arcade-stick.c b/drivers/input/joystick/gip/gip-arcade-stick.c
new file mode 100644
index 000000000000..11ededfab855
--- /dev/null
+++ b/drivers/input/joystick/gip/gip-arcade-stick.c
@@ -0,0 +1,173 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Drivers for GIP arcade sticks
+ *
+ * Copyright (c) 2025 Valve Software
+ *
+ * This driver is based on the Microsoft GIP spec at:
+ * https://aka.ms/gipdocs
+ * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc
+ */
+
+#include <linux/unaligned.h>
+#include "gip.h"
+
+enum gip_arcade_stick_vibration_motor_support {
+ GIP_VIBRATION_NO_MOTOR = 0,
+ GIP_VIBRATION_SINGLE_MOTOR = 1,
+ GIP_VIBRATION_DUAL_MOTOR = 2,
+};
+
+struct gip_arcade_stick_info {
+ uint8_t vibration_motor;
+ uint16_t actuator_bias;
+};
+
+struct gip_arcade_stick_static_coniguration {
+ uint8_t button_count;
+ uint8_t report_version;
+ uint8_t vibration_motor_support;
+ uint8_t actuator_bits;
+};
+
+static int gip_arcade_stick_probe(struct gip_attachment *attachment)
+{
+ struct gip_arcade_stick_info *info = kzalloc(sizeof(*info), GFP_KERNEL);
+
+ if (!info)
+ return -ENOMEM;
+
+ attachment->quirks |= GIP_QUIRK_SWAP_LB_RB;
+ attachment->driver_data = info;
+
+ return 0;
+}
+
+static void gip_arcade_stick_remove(struct gip_attachment *attachment)
+{
+ kfree(attachment->driver_data);
+ attachment->driver_data = NULL;
+}
+
+static int gip_init_arcade_stick(struct gip_attachment *attachment)
+{
+ if (gip_supports_vendor_message(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, false)) {
+ uint8_t request = GIP_LL_STATIC_CONFIGURATION;
+ int rc = gip_send_vendor_message(attachment, GIP_CMD_INITIAL_REPORTS_REQUEST, 0,
+ &request, sizeof(request));
+
+ if (rc < 0)
+ return rc;
+
+ return GIP_INIT_NO_INPUT;
+ }
+
+ return 0;
+}
+
+static int gip_setup_arcade_stick_input(struct gip_attachment *attachment, struct input_dev *input)
+{
+ struct gip_arcade_stick_info *info = attachment->driver_data;
+ int rc;
+
+ if (!info)
+ return -ENODEV;
+
+ rc = gip_driver_navigation.setup_input(attachment, input);
+ if (rc < 0)
+ return rc;
+
+ if (info->actuator_bias > 0) {
+ input_set_abs_params(input, ABS_X,
+ -info->actuator_bias, info->actuator_bias - 1, 0, 0);
+ input_set_abs_params(input, ABS_Y,
+ -info->actuator_bias, info->actuator_bias - 1, 0, 0);
+ }
+
+ if (attachment->extra_buttons >= 1)
+ input_set_capability(input, EV_KEY, BTN_TR2);
+
+ if (attachment->extra_buttons >= 2)
+ input_set_capability(input, EV_KEY, BTN_TL2);
+ return 0;
+}
+
+static int gip_handle_arcade_stick_report(struct gip_attachment *attachment,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes)
+{
+ struct gip_arcade_stick_info *info = attachment->driver_data;
+ int rc;
+
+ if (!info)
+ return -ENODEV;
+
+ rc = gip_driver_navigation.handle_input_report(attachment, input, bytes, num_bytes);
+ if (rc < 0)
+ return rc;
+
+ if (num_bytes < 6) {
+ gip_dbg(attachment, "Discarding too-short input report\n");
+ return -EINVAL;
+ }
+
+ if (info->actuator_bias > 0) {
+ input_report_abs(input, ABS_X, get_unaligned_le16(&bytes[2]) - info->actuator_bias);
+ input_report_abs(input, ABS_Y, get_unaligned_le16(&bytes[4]) - info->actuator_bias);
+ }
+
+ if (num_bytes >= 19) {
+ /* Extra button 6 */
+ input_report_key(input, BTN_TR2, bytes[18] & BIT(6));
+ /* Extra button 7 */
+ input_report_key(input, BTN_TL2, bytes[18] & BIT(7));
+ }
+
+ return 0;
+}
+
+static int gip_handle_arcade_stick_ll_static_configuration(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ const struct gip_arcade_stick_static_coniguration *config =
+ (const struct gip_arcade_stick_static_coniguration *)bytes;
+ struct gip_arcade_stick_info *info = attachment->driver_data;
+
+ if (!info)
+ return -ENODEV;
+
+ if (num_bytes < 4)
+ return -EINVAL;
+
+ attachment->extra_buttons = clamp(config->button_count, 6, 38) - 6;
+ if (config->actuator_bits)
+ info->actuator_bias = BIT(min(config->actuator_bits, 16) - 1);
+ else
+ info->actuator_bias = 0;
+
+ if (config->vibration_motor_support == GIP_VIBRATION_NO_MOTOR)
+ attachment->features &= ~GIP_FEATURE_MOTOR_CONTROL;
+
+ gip_dbg(attachment, "Arcade stick with report version %u, %u buttons, %u actuator bits\n",
+ config->report_version, config->button_count, config->actuator_bits);
+
+ return gip_setup_input_device(attachment);
+}
+
+const struct gip_driver gip_driver_arcade_stick = {
+ .types = (const char* const[]) {
+ "Windows.Xbox.Input.ArcadeStick",
+ "Microsoft.Xbox.Input.ArcadeStick",
+ NULL
+ },
+ .guid = GUID_INIT(0x332054cc, 0xa34b, 0x41d5, 0xa3, 0x4a,
+ 0xa6, 0xa6, 0x71, 0x1e, 0xc4, 0xb3),
+
+ .probe = gip_arcade_stick_probe,
+ .remove = gip_arcade_stick_remove,
+ .init = gip_init_arcade_stick,
+ .setup_input = gip_setup_arcade_stick_input,
+ .handle_input_report = gip_handle_arcade_stick_report,
+ .vendor_handlers = {
+ [GIP_LL_STATIC_CONFIGURATION] = gip_handle_arcade_stick_ll_static_configuration,
+ },
+};
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
index 765d70033f20..da22545662e2 100644
--- a/drivers/input/joystick/gip/gip-core.c
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -10,7 +10,8 @@
* - Raw character device
* - Wheel support
* - Flight stick support
- * - Arcade stick support
+ * - More arcade stick testing
+ * - Arcade stick extra buttons
* - Split into driver-per-attachment GIP-as-a-bus approach drivers
*
* This driver is based on the Microsoft GIP spec at:
@@ -328,6 +329,7 @@ struct gip_direct_motor {
static const struct gip_driver *base_drivers[] = {
&gip_driver_navigation,
&gip_driver_gamepad,
+ &gip_driver_arcade_stick,
NULL /* Sentinel */
};
@@ -1554,7 +1556,7 @@ static int gip_handle_command_raw_report(struct gip_attachment *attachment,
return 0;
}
-static int gip_setup_input_device(struct gip_attachment *attachment)
+int gip_setup_input_device(struct gip_attachment *attachment)
{
struct input_dev *input;
int rc;
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
index 42fe8aee9f68..1cc0a98717ba 100644
--- a/drivers/input/joystick/gip/gip.h
+++ b/drivers/input/joystick/gip/gip.h
@@ -395,6 +395,8 @@ static inline struct device *gip_security_dev(struct gip_security *security)
return gip_attachment_dev(container_of(security, struct gip_attachment, security));
}
+int gip_setup_input_device(struct gip_attachment *attachment);
+
bool gip_supports_vendor_message(struct gip_attachment *attachment, uint8_t command, bool upstream);
int gip_send_system_message(struct gip_attachment *attachment,
@@ -410,6 +412,4 @@ void gip_security_release(struct gip_security *security);
extern const struct gip_driver gip_driver_navigation;
extern const struct gip_driver gip_driver_gamepad;
extern const struct gip_driver gip_driver_arcade_stick;
-extern const struct gip_driver gip_driver_wheel;
-extern const struct gip_driver gip_driver_flight_stick;
#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 09/12] Input: xbox_gip - Add support for PDP guitar controllers
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (7 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 08/12] Input: xbox_gip - Add arcade stick support Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:30 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER Vicki Pfau
` (2 subsequent siblings)
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
This patch adds support for the PDP guitar controllers, namely the Fender
Jaguar and RiffMaster models. These use mostly the same protocol, but the
RiffMaster appears t use a slightly newer variant.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/gip/Makefile | 1 +
drivers/input/joystick/gip/gip-core.c | 1 +
drivers/input/joystick/gip/gip-pdp-jaguar.c | 129 ++++++++++++++++++++
drivers/input/joystick/gip/gip.h | 2 +
4 files changed, 133 insertions(+)
create mode 100644 drivers/input/joystick/gip/gip-pdp-jaguar.c
diff --git a/drivers/input/joystick/gip/Makefile b/drivers/input/joystick/gip/Makefile
index 9ed59caec4d9..62c016d6c11e 100644
--- a/drivers/input/joystick/gip/Makefile
+++ b/drivers/input/joystick/gip/Makefile
@@ -4,3 +4,4 @@ obj-$(CONFIG_JOYSTICK_XBOX_GIP) += xbox-gip.o
xbox-gip-y := gip-core.o gip-drivers.o gip-security.o
# Additional device support
xbox-gip-y += gip-arcade-stick.o
+xbox-gip-y += gip-pdp-jaguar.o
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
index da22545662e2..c03d730f2968 100644
--- a/drivers/input/joystick/gip/gip-core.c
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -330,6 +330,7 @@ static const struct gip_driver *base_drivers[] = {
&gip_driver_navigation,
&gip_driver_gamepad,
&gip_driver_arcade_stick,
+ &gip_driver_pdp_jaguar,
NULL /* Sentinel */
};
diff --git a/drivers/input/joystick/gip/gip-pdp-jaguar.c b/drivers/input/joystick/gip/gip-pdp-jaguar.c
new file mode 100644
index 000000000000..ec0e9d811d12
--- /dev/null
+++ b/drivers/input/joystick/gip/gip-pdp-jaguar.c
@@ -0,0 +1,129 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Drivers for GIP PDP Jaguar-style guitars
+ *
+ * Copyright (c) 2026 Valve Software
+ */
+
+#include <linux/unaligned.h>
+#include "gip.h"
+
+#define GIP_QUIRK_PDP_HAS_RIGHT_STICK BIT(31)
+
+static int gip_setup_pdp_jaguar_input(struct gip_attachment *attachment, struct input_dev *input)
+{
+ /*
+ * Despite having the navigation controller GUID, we don't want to use
+ * those mappings. Instead, we use the xone mappings for compatibility
+ * reasons.
+ */
+
+ /* Lower fret */
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY1);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY2);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY3);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY4);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY5);
+ /* Upper fret */
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY6);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY7);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY8);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY9);
+ input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY10);
+
+ input_set_capability(input, EV_KEY, BTN_START);
+ input_set_capability(input, EV_KEY, BTN_SELECT);
+
+ /* Whammy bar */
+ input_set_abs_params(input, ABS_Y, 0, 255, 0, 0);
+ /* Tilt */
+ input_set_abs_params(input, ABS_Z, 0, 255, 0, 0);
+
+ input_set_abs_params(input, ABS_HAT0X, -1, 1, 0, 0);
+ input_set_abs_params(input, ABS_HAT0Y, -1, 1, 0, 0);
+
+ if (attachment->quirks & GIP_QUIRK_PDP_HAS_RIGHT_STICK) {
+ input_set_capability(input, EV_KEY, BTN_THUMBR);
+ input_set_abs_params(input, ABS_RX, -32768, 32767, 16, 128);
+ input_set_abs_params(input, ABS_RY, -32768, 32767, 16, 128);
+ }
+
+ return 0;
+}
+
+static int gip_handle_pdp_jaguar_report(struct gip_attachment *attachment,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes)
+{
+ bool lower;
+
+ if (num_bytes < 4) {
+ gip_dbg(attachment, "Discarding too-short input report\n");
+ return -EINVAL;
+ }
+
+ input_report_key(input, BTN_START, bytes[0] & BIT(2));
+ input_report_key(input, BTN_SELECT, bytes[0] & BIT(3));
+
+ if (num_bytes >= 7 && (bytes[5] || bytes[6])) {
+ /* Newer report version on the RiffMaster */
+ input_report_key(input, BTN_TRIGGER_HAPPY1, bytes[5] & BIT(0));
+ input_report_key(input, BTN_TRIGGER_HAPPY2, bytes[5] & BIT(1));
+ input_report_key(input, BTN_TRIGGER_HAPPY3, bytes[5] & BIT(2));
+ input_report_key(input, BTN_TRIGGER_HAPPY4, bytes[5] & BIT(3));
+ input_report_key(input, BTN_TRIGGER_HAPPY5, bytes[5] & BIT(4));
+
+ input_report_key(input, BTN_TRIGGER_HAPPY6, bytes[6] & BIT(0));
+ input_report_key(input, BTN_TRIGGER_HAPPY7, bytes[6] & BIT(1));
+ input_report_key(input, BTN_TRIGGER_HAPPY8, bytes[6] & BIT(2));
+ input_report_key(input, BTN_TRIGGER_HAPPY9, bytes[6] & BIT(3));
+ input_report_key(input, BTN_TRIGGER_HAPPY10, bytes[6] & BIT(4));
+ } else {
+ lower = bytes[1] & BIT(6);
+ input_report_key(input, BTN_TRIGGER_HAPPY1, !lower && (bytes[0] & BIT(4)));
+ input_report_key(input, BTN_TRIGGER_HAPPY2, !lower && (bytes[0] & BIT(5)));
+ input_report_key(input, BTN_TRIGGER_HAPPY3, !lower && (bytes[0] & BIT(7)));
+ input_report_key(input, BTN_TRIGGER_HAPPY4, !lower && (bytes[0] & BIT(6)));
+ input_report_key(input, BTN_TRIGGER_HAPPY5, !lower && (bytes[1] & BIT(4)));
+
+ input_report_key(input, BTN_TRIGGER_HAPPY6, lower && (bytes[0] & BIT(4)));
+ input_report_key(input, BTN_TRIGGER_HAPPY7, lower && (bytes[0] & BIT(5)));
+ input_report_key(input, BTN_TRIGGER_HAPPY8, lower && (bytes[0] & BIT(7)));
+ input_report_key(input, BTN_TRIGGER_HAPPY9, lower && (bytes[0] & BIT(6)));
+ input_report_key(input, BTN_TRIGGER_HAPPY10, lower && (bytes[1] & BIT(4)));
+ }
+
+ input_report_abs(input, ABS_Y, bytes[2]);
+ input_report_abs(input, ABS_Z, bytes[3]);
+
+ input_report_abs(input, ABS_HAT0X,
+ !!(bytes[1] & BIT(3)) - !!(bytes[1] & BIT(2)));
+ input_report_abs(input, ABS_HAT0Y,
+ !!(bytes[1] & BIT(1)) - !!(bytes[1] & BIT(0)));
+
+ if ((attachment->quirks & GIP_QUIRK_PDP_HAS_RIGHT_STICK) && num_bytes >= 14) {
+ input_report_key(input, BTN_THUMBR, bytes[1] & BIT(6));
+ input_report_abs(input, ABS_RX, (int16_t)get_unaligned_le16(&bytes[10]));
+ input_report_abs(input, ABS_RY, ~(int16_t)get_unaligned_le16(&bytes[12]));
+ }
+
+ return 0;
+}
+
+const struct gip_driver gip_driver_pdp_jaguar = {
+ .types = (const char *const[]) { "PDP.Xbox.Guitar.Jaguar", NULL },
+ .guid = GUID_INIT(0x1a266af6, 0x3a46, 0x45e3, 0xb9, 0xb6,
+ 0x0f, 0x2c, 0x0b, 0x2c, 0x1e, 0xbe),
+
+ .quirks = (const struct gip_quirks[]) {
+ /* PDP RiffMaster */
+ { GIP_VID_PDP, GIP_PID_PDP_RIFFMASTER, 0,
+ .quirks = GIP_QUIRK_PDP_HAS_RIGHT_STICK, },
+
+ {0},
+ },
+ .probe = NULL,
+ .remove = NULL,
+ .init = NULL,
+ .setup_input = gip_setup_pdp_jaguar_input,
+ .handle_input_report = gip_handle_pdp_jaguar_report,
+};
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
index 1cc0a98717ba..23c23982658e 100644
--- a/drivers/input/joystick/gip/gip.h
+++ b/drivers/input/joystick/gip/gip.h
@@ -40,6 +40,7 @@
#define GIP_PID_XBOX_WIRELESS 0x0b12
#define GIP_PID_PDP_ROCK_CANDY 0x0246
+#define GIP_PID_PDP_RIFFMASTER 0x0248
#define GIP_PID_BDA_XB1_CLASSIC 0x581a
#define GIP_PID_BDA_XB1_FUSION_PRO 0x591a
@@ -412,4 +413,5 @@ void gip_security_release(struct gip_security *security);
extern const struct gip_driver gip_driver_navigation;
extern const struct gip_driver gip_driver_gamepad;
extern const struct gip_driver gip_driver_arcade_stick;
+extern const struct gip_driver gip_driver_pdp_jaguar;
#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (8 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 09/12] Input: xbox_gip - Add support for PDP guitar controllers Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 11/12] HID: Map more automobile simulation inputs Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 12/12] Input: xbox_gip - Add wheel support Vicki Pfau
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
Add new absolute axes for racing game controllers
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/hid/hid-debug.c | 16 +++++++++-------
include/uapi/linux/input-event-codes.h | 3 +++
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/drivers/hid/hid-debug.c b/drivers/hid/hid-debug.c
index f44e6e708404..7a1b56311e9b 100644
--- a/drivers/hid/hid-debug.c
+++ b/drivers/hid/hid-debug.c
@@ -3511,13 +3511,15 @@ static const char *absolutes[ABS_CNT] = {
[ABS_RY] = "Ry", [ABS_RZ] = "Rz",
[ABS_THROTTLE] = "Throttle", [ABS_RUDDER] = "Rudder",
[ABS_WHEEL] = "Wheel", [ABS_GAS] = "Gas",
- [ABS_BRAKE] = "Brake", [ABS_HAT0X] = "Hat0X",
- [ABS_HAT0Y] = "Hat0Y", [ABS_HAT1X] = "Hat1X",
- [ABS_HAT1Y] = "Hat1Y", [ABS_HAT2X] = "Hat2X",
- [ABS_HAT2Y] = "Hat2Y", [ABS_HAT3X] = "Hat3X",
- [ABS_HAT3Y] = "Hat 3Y", [ABS_PRESSURE] = "Pressure",
- [ABS_DISTANCE] = "Distance", [ABS_TILT_X] = "XTilt",
- [ABS_TILT_Y] = "YTilt", [ABS_TOOL_WIDTH] = "ToolWidth",
+ [ABS_BRAKE] = "Brake", [ABS_CLUTCH] = "Clutch",
+ [ABS_HANDBRAKE] = "Handbrake", [ABS_SHIFTER] = "Shifter",
+ [ABS_HAT0X] = "Hat0X", [ABS_HAT0Y] = "Hat0Y",
+ [ABS_HAT1X] = "Hat1X", [ABS_HAT1Y] = "Hat1Y",
+ [ABS_HAT2X] = "Hat2X", [ABS_HAT2Y] = "Hat2Y",
+ [ABS_HAT3X] = "Hat3X", [ABS_HAT3Y] = "Hat3Y",
+ [ABS_PRESSURE] = "Pressure", [ABS_DISTANCE] = "Distance",
+ [ABS_TILT_X] = "XTilt", [ABS_TILT_Y] = "YTilt",
+ [ABS_TOOL_WIDTH] = "ToolWidth",
[ABS_VOLUME] = "Volume", [ABS_PROFILE] = "Profile",
[ABS_SND_PROFILE] = "SoundProfile",
[ABS_MISC] = "Misc",
diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
index 3528168f7c6d..5c881ed44177 100644
--- a/include/uapi/linux/input-event-codes.h
+++ b/include/uapi/linux/input-event-codes.h
@@ -879,6 +879,9 @@
#define ABS_WHEEL 0x08
#define ABS_GAS 0x09
#define ABS_BRAKE 0x0a
+#define ABS_CLUTCH 0x0b
+#define ABS_HANDBRAKE 0x0c
+#define ABS_SHIFTER 0x0d
#define ABS_HAT0X 0x10
#define ABS_HAT0Y 0x11
#define ABS_HAT1X 0x12
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 11/12] HID: Map more automobile simulation inputs
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (9 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 12/12] Input: xbox_gip - Add wheel support Vicki Pfau
11 siblings, 0 replies; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau, Jiri Kosina
The HID usage tables section 5.3 specify clutch and shifter values that had
previously been ignored. As the ABS_CLUTCH and ABS_SHIFTER bits now exist,
we should use them appropriately.
Signed-off-by: Vicki Pfau <vi@endrift.com>
Acked-by: Jiri Kosina <jkosina@suse.com>
---
drivers/hid/hid-input.c | 2 ++
1 file changed, 2 insertions(+)
diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c
index 3487600cadb4..6ade969412c6 100644
--- a/drivers/hid/hid-input.c
+++ b/drivers/hid/hid-input.c
@@ -828,6 +828,8 @@ static void hidinput_configure_usage(struct hid_input *hidinput, struct hid_fiel
case 0xbb: map_abs(ABS_THROTTLE); break;
case 0xc4: map_abs(ABS_GAS); break;
case 0xc5: map_abs(ABS_BRAKE); break;
+ case 0xc6: map_abs(ABS_CLUTCH); break;
+ case 0xc7: map_abs(ABS_SHIFTER); break;
case 0xc8: map_abs(ABS_WHEEL); break;
default: goto ignore;
}
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* [PATCH v4 12/12] Input: xbox_gip - Add wheel support
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
` (10 preceding siblings ...)
2026-08-22 2:11 ` [PATCH v4 11/12] HID: Map more automobile simulation inputs Vicki Pfau
@ 2026-08-22 2:11 ` Vicki Pfau
2026-08-22 2:54 ` sashiko-bot
11 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-22 2:11 UTC (permalink / raw)
To: Dmitry Torokhov, linux-input; +Cc: Vicki Pfau
This adds preliminary support for racing wheel support in xbox_gip,
exposing them mapped to the newly added axes.
Signed-off-by: Vicki Pfau <vi@endrift.com>
---
drivers/input/joystick/gip/Makefile | 1 +
drivers/input/joystick/gip/gip-core.c | 4 +-
drivers/input/joystick/gip/gip-drivers.c | 10 +
drivers/input/joystick/gip/gip-wheel.c | 349 +++++++++++++++++++++++
drivers/input/joystick/gip/gip.h | 6 +
5 files changed, 369 insertions(+), 1 deletion(-)
create mode 100644 drivers/input/joystick/gip/gip-wheel.c
diff --git a/drivers/input/joystick/gip/Makefile b/drivers/input/joystick/gip/Makefile
index 62c016d6c11e..7d4209eb32c3 100644
--- a/drivers/input/joystick/gip/Makefile
+++ b/drivers/input/joystick/gip/Makefile
@@ -5,3 +5,4 @@ xbox-gip-y := gip-core.o gip-drivers.o gip-security.o
# Additional device support
xbox-gip-y += gip-arcade-stick.o
xbox-gip-y += gip-pdp-jaguar.o
+xbox-gip-y += gip-wheel.o
diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
index c03d730f2968..82f25929480e 100644
--- a/drivers/input/joystick/gip/gip-core.c
+++ b/drivers/input/joystick/gip/gip-core.c
@@ -8,7 +8,7 @@
* - Audio device support
* - Event logging
* - Raw character device
- * - Wheel support
+ * - Wheel force feedback
* - Flight stick support
* - More arcade stick testing
* - Arcade stick extra buttons
@@ -331,6 +331,8 @@ static const struct gip_driver *base_drivers[] = {
&gip_driver_gamepad,
&gip_driver_arcade_stick,
&gip_driver_pdp_jaguar,
+ &gip_driver_trueforce_wheel,
+ &gip_driver_wheel,
NULL /* Sentinel */
};
diff --git a/drivers/input/joystick/gip/gip-drivers.c b/drivers/input/joystick/gip/gip-drivers.c
index 74bc858e71cc..7ba11b19c8ee 100644
--- a/drivers/input/joystick/gip/gip-drivers.c
+++ b/drivers/input/joystick/gip/gip-drivers.c
@@ -142,6 +142,11 @@ static int gip_setup_navigation_input(struct gip_attachment *attachment, struct
input_set_capability(input, EV_KEY, BTN_TR);
input_set_capability(input, EV_KEY, BTN_TL);
+ if (attachment->quirks & GIP_QUIRK_FORCE_GAMEPAD_SB) {
+ input_set_capability(input, EV_KEY, BTN_THUMBR);
+ input_set_capability(input, EV_KEY, BTN_THUMBL);
+ }
+
attachment->dpad_as_buttons = dpad_as_buttons;
if (attachment->dpad_as_buttons) {
input_set_capability(input, EV_KEY, BTN_DPAD_UP);
@@ -193,6 +198,11 @@ static int gip_handle_navigation_report(struct gip_attachment *attachment,
input_report_key(input, BTN_TR, bytes[1] & BIT(5));
}
+ if (attachment->quirks & GIP_QUIRK_FORCE_GAMEPAD_SB) {
+ input_report_key(input, BTN_THUMBL, bytes[1] & BIT(6));
+ input_report_key(input, BTN_THUMBR, bytes[1] & BIT(7));
+ }
+
return 0;
}
diff --git a/drivers/input/joystick/gip/gip-wheel.c b/drivers/input/joystick/gip/gip-wheel.c
new file mode 100644
index 000000000000..333910448c90
--- /dev/null
+++ b/drivers/input/joystick/gip/gip-wheel.c
@@ -0,0 +1,349 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Drivers for GIP racing wheel devices
+ *
+ * Copyright (c) 2025 Valve Software
+ *
+ * This driver is based on the Microsoft GIP spec at:
+ * https://aka.ms/gipdocs
+ * https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-gipusb/e7c90904-5e21-426e-b9ad-d82adeee0dbc
+ */
+
+#include <linux/unaligned.h>
+#include "gip.h"
+
+#define GIP_QUIRK_WHEEL_FORCE_HANDBRAKE BIT(31)
+
+/* Wheel vendor messages */
+#define GIP_CMD_SET_APPLICATION_MEMORY 0x0b
+#define GIP_CMD_SET_EQUATIONS_STATES 0x0c
+#define GIP_CMD_SET_EQUATION 0x0d
+
+/* Wheel-specific flags */
+#define GIP_WHEEL_HAS_POWER BIT(3)
+#define GIP_WHEEL_HANDBRAKE_CONN BIT(4)
+#define GIP_WHEEL_CLUTCH_CONN BIT(5)
+#define GIP_WHEEL_BRAKE_CONN BIT(6)
+#define GIP_WHEEL_THROTTLE_CONN BIT(7)
+
+#define GIP_HSHIFTER_NONE 0
+#define GIP_HSHIFTER_2POS 1 /* 2 position, no neutral */
+#define GIP_HSHIFTER_2POS_N 2 /* 2 position, neutral */
+#define GIP_HSHIFTER_RTL_1TL 3 /* Reverse top left, first top left */
+#define GIP_HSHIFTER_RTL_1BL 4 /* Reverse top left, first bottom left */
+#define GIP_HSHIFTER_RBL 5 /* Reverse bottom left */
+#define GIP_HSHIFTER_RTR 6 /* Reverse top right */
+#define GIP_HSHIFTER_RBR 7 /* Reverse bottom right */
+
+struct gip_wheel_info {
+ uint8_t shifter_type: 3;
+ uint8_t max_gear: 5;
+ uint16_t angle_setting;
+ uint16_t max_angle;
+ uint16_t max_throttle;
+ uint16_t max_brake;
+ uint16_t max_clutch;
+ uint8_t max_handbrake;
+ int8_t value_retries;
+};
+
+struct gip_initial_reports_request {
+ uint8_t type;
+ uint8_t data[2];
+};
+
+static int gip_wheel_probe(struct gip_attachment *attachment)
+{
+ struct gip_wheel_info *info = kzalloc(sizeof(*info), GFP_KERNEL);
+
+ if (!info)
+ return -ENOMEM;
+ attachment->driver_data = info;
+
+ return 0;
+}
+
+static void gip_wheel_remove(struct gip_attachment *attachment)
+{
+ kfree(attachment->driver_data);
+ attachment->driver_data = NULL;
+}
+
+static int gip_wheel_init(struct gip_attachment *attachment)
+{
+ struct gip_initial_reports_request request = { 0 };
+ int rc = gip_send_vendor_message(attachment,
+ GIP_CMD_INITIAL_REPORTS_REQUEST, 0, &request,
+ sizeof(request));
+
+ if (rc < 0)
+ return rc;
+
+ return GIP_INIT_NO_INPUT;
+}
+
+static int gip_setup_wheel_input(struct gip_attachment *attachment, struct input_dev *input)
+{
+ int rc = gip_driver_navigation.setup_input(attachment, input);
+ struct gip_wheel_info *info = attachment->driver_data;
+
+ if (rc < 0)
+ return rc;
+
+ if (!info)
+ return -ENODEV;
+
+ input_set_abs_params(input, ABS_WHEEL, -info->max_angle - 1, info->max_angle, 0, 0);
+ input_abs_set_res(input, ABS_WHEEL, info->angle_setting);
+ if (info->max_throttle)
+ input_set_abs_params(input, ABS_GAS, 0, info->max_throttle, 0, 0);
+
+ if (info->max_brake)
+ input_set_abs_params(input, ABS_BRAKE, 0, info->max_brake, 0, 0);
+
+ if (info->max_clutch)
+ input_set_abs_params(input, ABS_CLUTCH, 0, info->max_clutch, 0, 0);
+
+ if (info->max_handbrake)
+ input_set_abs_params(input, ABS_HANDBRAKE, 0, info->max_handbrake, 0, 0);
+
+ if (info->shifter_type)
+ input_set_abs_params(input, ABS_SHIFTER, -1, info->max_gear, 0, 0);
+
+ return 0;
+}
+
+static int gip_handle_wheel_ll_input_report(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ int rc = 0;
+ struct gip_wheel_info *info = attachment->driver_data;
+
+ if (num_bytes < 17)
+ return -EINVAL;
+
+ if (!info)
+ return -ENODEV;
+
+ info->max_gear = bytes[11] & 0x1F;
+ info->shifter_type = bytes[11] >> 5;
+ info->angle_setting = get_unaligned_le16(&bytes[13]);
+
+ gip_dbg(attachment,
+ "Racing wheel with %u max gear, %u degree rotation\n",
+ info->max_gear, info->angle_setting);
+
+ if (info->angle_setting && info->max_angle) {
+ if (info->value_retries-- > 0)
+ return 0;
+
+ rc = gip_setup_input_device(attachment);
+ } else {
+ return 0;
+ }
+
+ if (rc < 0)
+ return rc;
+
+ /* Now that we're done configuring, fall back to default handler */
+ attachment->vendor_handlers[GIP_LL_INPUT_REPORT] = NULL;
+
+ return 0;
+}
+
+static int gip_handle_wheel_report(struct gip_attachment *attachment,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes)
+{
+ uint8_t connections;
+ struct gip_wheel_info *info = attachment->driver_data;
+ int rc = gip_driver_navigation.handle_input_report(attachment, input, bytes, num_bytes);
+
+ if (rc < 0)
+ return rc;
+
+ if (!info)
+ return -ENODEV;
+
+ if (num_bytes < 17)
+ return -EINVAL;
+
+ input_report_abs(input, ABS_WHEEL, get_unaligned_le16(&bytes[2]) - 0x8000);
+
+ connections = bytes[16];
+
+ if (attachment->quirks & GIP_QUIRK_WHEEL_FORCE_HANDBRAKE)
+ connections |= GIP_WHEEL_HANDBRAKE_CONN;
+
+ if (connections & GIP_WHEEL_THROTTLE_CONN)
+ input_report_abs(input, ABS_GAS, get_unaligned_le16(&bytes[4]));
+ else
+ input_report_abs(input, ABS_GAS, 0);
+
+ if (connections & GIP_WHEEL_BRAKE_CONN)
+ input_report_abs(input, ABS_BRAKE, get_unaligned_le16(&bytes[6]));
+ else
+ input_report_abs(input, ABS_BRAKE, 0);
+
+ if (connections & GIP_WHEEL_CLUTCH_CONN)
+ input_report_abs(input, ABS_CLUTCH, get_unaligned_le16(&bytes[8]));
+ else
+ input_report_abs(input, ABS_CLUTCH, 0);
+
+ if (connections & GIP_WHEEL_HANDBRAKE_CONN)
+ input_report_abs(input, ABS_HANDBRAKE, bytes[10]);
+ else
+ input_report_abs(input, ABS_HANDBRAKE, 0);
+
+ if (info->shifter_type)
+ input_report_abs(input, ABS_SHIFTER, (int8_t)bytes[12]);
+
+ return 0;
+}
+
+static int gip_handle_wheel_ll_static_configuration(struct gip_attachment *attachment,
+ const struct gip_header *header, const uint8_t *bytes, int num_bytes)
+{
+ struct gip_wheel_info *info = attachment->driver_data;
+
+ if (!info)
+ return -ENODEV;
+
+ if (num_bytes < 11)
+ return -EINVAL;
+
+ info->max_angle = BIT(min(bytes[0], 16)) - 1;
+ info->max_throttle = BIT(min(bytes[1], 16)) - 1;
+ info->max_brake = BIT(min(bytes[2], 16)) - 1;
+ info->max_clutch = BIT(min(bytes[3], 16)) - 1;
+ info->max_handbrake = BIT(min(bytes[4], 8)) - 1;
+
+ gip_dbg(attachment,
+ "Racing wheel with precision: %u bits angle, %u bits throttle, %u bits brake,\n",
+ bytes[0], bytes[1], bytes[2]);
+ gip_dbg(attachment,
+ "%u bits clutch, %u bits handbrake\n", bytes[3], bytes[4]);
+
+ if (info->angle_setting && info->max_angle && info->value_retries <= 0)
+ return gip_setup_input_device(attachment);
+
+ return 0;
+}
+
+const struct gip_driver gip_driver_wheel = {
+ .types = (const char* const[]) {
+ "Windows.Xbox.Input.Wheel",
+ "Microsoft.Xbox.Input.Wheel",
+ NULL
+ },
+ .guid = GUID_INIT(0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9,
+ 0x59, 0xe3, 0x98, 0xd7, 0x42, 0x0c),
+
+ .quirks = (const struct gip_quirks[]) {
+ /* Thrustmaster T128X GIP Racing Wheel */
+ { GIP_VID_THRUSTMASTER, GIP_PID_THRUSTMASTER_T128X, 0,
+ .quirks = GIP_QUIRK_FORCE_GAMEPAD_SB | GIP_QUIRK_WHEEL_FORCE_HANDBRAKE, },
+
+ {0},
+ },
+
+ .probe = gip_wheel_probe,
+ .remove = gip_wheel_remove,
+ .init = gip_wheel_init,
+ .setup_input = gip_setup_wheel_input,
+ .handle_input_report = gip_handle_wheel_report,
+ .vendor_handlers = {
+ [GIP_LL_INPUT_REPORT] = gip_handle_wheel_ll_input_report,
+ [GIP_LL_STATIC_CONFIGURATION] = gip_handle_wheel_ll_static_configuration,
+ },
+};
+
+struct gip_trueforce_wheel_state {
+ struct gip_wheel_info wheel_info; /* This field must be first for type punning */
+ int8_t dial;
+};
+
+static int gip_trueforce_wheel_probe(struct gip_attachment *attachment)
+{
+ struct gip_trueforce_wheel_state *state = kzalloc(sizeof(*state), GFP_KERNEL);
+
+ if (!state)
+ return -ENOMEM;
+ attachment->driver_data = state;
+ /* The shifter won't show in the first few input reports */
+ state->wheel_info.value_retries = 3;
+
+ return 0;
+}
+
+static void gip_trueforce_wheel_remove(struct gip_attachment *attachment)
+{
+ kfree(attachment->driver_data);
+ attachment->driver_data = NULL;
+}
+
+static int gip_setup_trueforce_wheel_input(struct gip_attachment *attachment,
+ struct input_dev *input)
+{
+ int rc = gip_driver_wheel.setup_input(attachment, input);
+
+ if (rc < 0)
+ return rc;
+
+ input_set_capability(input, EV_KEY, BTN_THUMBL);
+ input_set_capability(input, EV_KEY, BTN_THUMBR);
+ input_set_capability(input, EV_KEY, KEY_KPPLUS);
+ input_set_capability(input, EV_KEY, KEY_KPMINUS);
+ input_set_capability(input, EV_KEY, KEY_KPENTER);
+ input_set_capability(input, EV_REL, REL_DIAL);
+
+ return 0;
+}
+
+static int gip_handle_trueforce_wheel_report(struct gip_attachment *attachment,
+ struct input_dev *input, const uint8_t *bytes, int num_bytes)
+{
+ int rc = gip_driver_wheel.handle_input_report(attachment, input, bytes, num_bytes);
+ struct gip_trueforce_wheel_state *state = attachment->driver_data;
+ int dial;
+
+ if (rc < 0)
+ return rc;
+
+ if (num_bytes < 18)
+ return -EINVAL;
+
+ dial = bytes[17] >> 5;
+
+ input_report_key(input, BTN_THUMBL, bytes[17] & BIT(0));
+ input_report_key(input, BTN_THUMBR, bytes[17] & BIT(1));
+ input_report_key(input, KEY_KPPLUS, bytes[17] & BIT(2));
+ input_report_key(input, KEY_KPMINUS, bytes[17] & BIT(3));
+ input_report_key(input, KEY_KPENTER, bytes[17] & BIT(4));
+ if (dial == 0 && state->dial == 7)
+ input_report_rel(input, REL_DIAL, -1);
+ else if (dial == 7 && state->dial == 0)
+ input_report_rel(input, REL_DIAL, 1);
+ else
+ input_report_rel(input, REL_DIAL,
+ state->dial - dial);
+ state->dial = dial;
+
+ return 0;
+}
+
+const struct gip_driver gip_driver_trueforce_wheel = {
+ .types = (const char *const[]) { "Logi.Xbox.Input.TrueForceWheel", NULL },
+ .guid = GUID_INIT(0x6ca319e5, 0x0bc0, 0x41be, 0x83, 0x19,
+ 0x6b, 0xb7, 0x10, 0x81, 0xec, 0x55),
+
+ .probe = gip_trueforce_wheel_probe,
+ .remove = gip_trueforce_wheel_remove,
+ .init = gip_wheel_init,
+ .setup_input = gip_setup_trueforce_wheel_input,
+ .handle_input_report = gip_handle_trueforce_wheel_report,
+ .vendor_handlers = {
+ [GIP_LL_INPUT_REPORT] = gip_handle_wheel_ll_input_report,
+ [GIP_LL_STATIC_CONFIGURATION] = gip_handle_wheel_ll_static_configuration,
+ },
+};
+
diff --git a/drivers/input/joystick/gip/gip.h b/drivers/input/joystick/gip/gip.h
index 23c23982658e..06cc2b3c00c8 100644
--- a/drivers/input/joystick/gip/gip.h
+++ b/drivers/input/joystick/gip/gip.h
@@ -27,10 +27,13 @@
#define MAX_OUT_MESSAGES 8
#define MAX_OUT_FRAGMENTS 2
+#define GIP_VID_THRUSTMASTER 0x044f
#define GIP_VID_MICROSOFT 0x045e
#define GIP_VID_PDP 0x0e6f
#define GIP_VID_BDA 0x24c6
+#define GIP_PID_THRUSTMASTER_T128X 0xb69c
+
#define GIP_PID_XBOX_ONE_1573 0x02d1
#define GIP_PID_XBOX_ONE_1697 0x02dd
#define GIP_PID_XBOX_ELITE 0x02e3
@@ -49,6 +52,7 @@
#define GIP_QUIRK_NO_IMPULSE_VIBRATION BIT(1)
#define GIP_QUIRK_SWAP_LB_RB BIT(2)
#define GIP_QUIRK_SKIP_SECURITY BIT(3)
+#define GIP_QUIRK_FORCE_GAMEPAD_SB BIT(4)
/*
* Driver-specific quirks should start from 31 and go downwards to avoid
* conflicts with newly-added core quirks
@@ -414,4 +418,6 @@ extern const struct gip_driver gip_driver_navigation;
extern const struct gip_driver gip_driver_gamepad;
extern const struct gip_driver gip_driver_arcade_stick;
extern const struct gip_driver gip_driver_pdp_jaguar;
+extern const struct gip_driver gip_driver_wheel;
+extern const struct gip_driver gip_driver_trueforce_wheel;
#endif
--
2.54.0
^ permalink raw reply related [flat|nested] 28+ messages in thread
* Re: [PATCH v4 09/12] Input: xbox_gip - Add support for PDP guitar controllers
2026-08-22 2:11 ` [PATCH v4 09/12] Input: xbox_gip - Add support for PDP guitar controllers Vicki Pfau
@ 2026-08-22 2:30 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:30 UTC (permalink / raw)
To: Vicki Pfau; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [High] Dynamic protocol detection relies on payload contents, causing face buttons and the thumbstick click on the RiffMaster to trigger ghost fret inputs when no frets are held.
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but there is a dangling RCU pointer and synchronous free of `input_dev` on registration failure.
--
--- Patch [9]: [PATCH v4 09/12] Input: xbox_gip - Add support for PDP guitar controllers ---
commit 6b8e97ecc5fa40007c8c5bf1a4aa892abddb06cf
Author: Vicki Pfau <vi@endrift.com>
Input: xbox_gip - Add support for PDP guitar controllers
This patch adds support for the PDP guitar controllers, namely the Fender
Jaguar and RiffMaster models. These use mostly the same protocol, but the
RiffMaster appears t use a slightly newer variant.
> diff --git a/drivers/input/joystick/gip/gip-pdp-jaguar.c b/drivers/input/joystick/gip/gip-pdp-jaguar.c
> new file mode 100644
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-pdp-jaguar.c
[ ... ]
> +static int gip_handle_pdp_jaguar_report(struct gip_attachment *attachment,
> + struct input_dev *input, const uint8_t *bytes, int num_bytes)
> +{
> + bool lower;
> +
> + if (num_bytes < 4) {
> + gip_dbg(attachment, "Discarding too-short input report\n");
> + return -EINVAL;
> + }
> +
> + input_report_key(input, BTN_START, bytes[0] & BIT(2));
> + input_report_key(input, BTN_SELECT, bytes[0] & BIT(3));
> +
> + if (num_bytes >= 7 && (bytes[5] || bytes[6])) {
[Severity: High]
Does this condition cause ghost fret inputs on the RiffMaster when
no frets are held?
If face buttons or the thumbstick click are pressed on the RiffMaster
without pressing any frets, bytes[5] and bytes[6] would be zero. This
causes the check to fall through to the legacy report logic, which seems
like it would misinterpret the standard face button bits as legacy fret
bits.
> + /* Newer report version on the RiffMaster */
> + input_report_key(input, BTN_TRIGGER_HAPPY1, bytes[5] & BIT(0));
[ ... ]
[Severity: High]
This isn't a bug introduced by this patch, but is there a dangling RCU pointer
and synchronous free if input_register_device() fails?
Looking at gip_setup_input_device() in drivers/input/joystick/gip/gip-core.c:
rcu_assign_pointer(attachment->input, input);
rc = input_register_device(input);
if (rc)
goto err_free_device;
...
err_free_device:
input_free_device(input);
The input device pointer is published to RCU readers and subsequently freed
synchronously on the error path without clearing the pointer or waiting for
an RCU grace period. Can concurrent incoming USB packets process and
dereference the dangling attachment->input pointer?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=9
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone
2026-08-22 2:11 ` [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone Vicki Pfau
@ 2026-08-22 2:35 ` sashiko-bot
2026-08-24 21:49 ` Eric Biggers
1 sibling, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:35 UTC (permalink / raw)
To: Vicki Pfau; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 8 potential issue(s) to consider:
- [Critical] Receiving a GIP_CMD_SECURITY message before the handshake is explicitly started causes a NULL pointer dereference.
- [High] Concurrent unprotected calls to crypto_shash_update corrupt the cryptographic transcript.
- [High] Missing NULL check for the state buffer allocation in gip_security_get_transcript leads to a NULL pointer dereference.
- [High] Allocating the transcript state buffer using crypto_shash_descsize instead of crypto_shash_statesize causes a heap buffer overflow.
- [High] Unchecked return values in gip_security_compute_prf lead to an information leak of kernel stack memory.
- [Medium] Missing cancellation of pending workers during protocol upgrade.
- [Medium] Unchecked return value of crypto_shash_tfm_digest() in gip_security_compute_ecdh.
- [Medium] The ECDH shared secret is freed without being securely zeroized.
--
--- Patch [7]: [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone ---
commit 5605e9d7634da03b416418354e348fe0ff787ead
Author: Vicki Pfau <vi@endrift.com>
Subject: Input: xbox_gip - Add security implementation from xone
This patch imports and adapts the security message implementation from the
out-of-tree xone driver. It adds a security message driver for Xbox One and
Series controllers, establishing handshakes needed for wireless dongles.
> diff --git a/drivers/input/joystick/gip/gip-security.c b/drivers/input/joystick/gip/gip-security.c
> new file mode 100644
> index 0000000000000..71da0833980ac
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-security.c
[ ... ]
> +static int gip_security_get_transcript(struct shash_desc *desc, void *transcript)
> +{
> + void *state = kzalloc(crypto_shash_descsize(desc->tfm), GFP_KERNEL);
[Severity: High]
Does this allocate the wrong size? The crypto_shash_export function expects
a buffer sized by crypto_shash_statesize, not crypto_shash_descsize. Since
the state size can be strictly larger than the descriptor size for certain
algorithms, this could cause a heap buffer overflow.
[Severity: High]
Is it possible for kzalloc to fail here? The return value is passed directly
to crypto_shash_export without a null check, which would cause a null pointer
dereference under memory pressure.
> + int err;
> +
> + err = crypto_shash_export(desc, state);
[ ... ]
> +static int gip_security_compute_prf(struct shash_desc *desc, const char *label,
> + u8 *key, int key_len, u8 *seed, int seed_len, u8 *out, int out_len)
> +{
> + u8 hash[SHA256_DIGEST_SIZE], hash_out[SHA256_DIGEST_SIZE];
> + int err;
> +
> + err = crypto_shash_setkey(desc->tfm, key, key_len);
> + if (err)
> + return err;
> +
> + crypto_shash_init(desc);
> + crypto_shash_update(desc, label, strlen(label));
> + crypto_shash_update(desc, seed, seed_len);
> + crypto_shash_final(desc, hash);
> +
> + while (out_len > 0) {
> + crypto_shash_init(desc);
> + crypto_shash_update(desc, hash, sizeof(hash));
> + crypto_shash_update(desc, label, strlen(label));
> + crypto_shash_update(desc, seed, seed_len);
> + crypto_shash_final(desc, hash_out);
> +
> + memcpy(out, hash_out, min_t(int, out_len, sizeof(hash)));
[Severity: High]
Should we check the return values of the crypto operations inside this loop?
If crypto_shash_init, crypto_shash_update, or crypto_shash_final fail, the
hash_out buffer will retain its uninitialized kernel stack data. This data is
then copied into the output buffer and eventually sent over USB, leading to an
information leak.
> + out += sizeof(hash);
> + out_len -= sizeof(hash);
> +
> + crypto_shash_digest(desc, hash, sizeof(hash), hash);
> + }
> +
> + return 0;
> +}
[ ... ]
> +static int gip_security_compute_ecdh(u8 *pubkey_in, u8 *pubkey_out, int pubkey_len, u8 *secret_hash)
> +{
> + struct crypto_kpp *tfm_ecdh;
> + struct crypto_shash *tfm_sha;
> + u8 *secret;
> + int err;
[ ... ]
> + err = gip_security_ecdh_get_pubkey(tfm_ecdh, pubkey_out, pubkey_len);
> + if (err)
> + goto err_free_sha;
> +
> + err = gip_security_ecdh_get_secret(tfm_ecdh, pubkey_in, pubkey_len,
> + secret, GIP_SECURITY_ECDH_SECRET_LEN);
> + if (err)
> + goto err_free_sha;
> +
> + crypto_shash_tfm_digest(tfm_sha, secret, GIP_SECURITY_ECDH_SECRET_LEN, secret_hash);
[Severity: Medium]
Could this leave secret_hash uninitialized if the digest fails? The return
value is ignored, which means the caller might silently proceed to compute the
master secret using uninitialized stack memory.
> +
> +err_free_sha:
> + crypto_free_shash(tfm_sha);
> +err_free_ecdh:
> + crypto_free_kpp(tfm_ecdh);
> +err_free_secret:
> + kfree(secret);
[Severity: Medium]
Since the secret buffer holds raw cryptographic key material, should this use
kfree_sensitive instead of kfree to prevent sensitive data from lingering in
the slab allocator?
> +
> + return err;
> +}
> +
> +static int gip_security_send_message(struct gip_security *security,
> + enum gip_security_command_handshake cmd, void *message, u16 len)
> +{
> + struct gip_attachment *attachment = container_of(security, struct gip_attachment, security);
> + struct gip_security_header_full *hdr = message;
> + u16 data_len = len - sizeof(hdr->handshake) - GIP_SECURITY_TRAILER_LEN;
> +
> + hdr->handshake.context = GIP_SECURITY_CTX_HANDSHAKE;
> + hdr->handshake.options = GIP_SECURITY_OPT_ACKNOWLEDGE | GIP_SECURITY_OPT_FROM_HOST;
> + hdr->handshake.command = cmd;
> + hdr->handshake.length = cpu_to_be16(data_len);
> +
> + hdr->data.command = cmd;
> + hdr->data.version = cmd >= GIP_SECURITY2_CMD_HOST_HELLO ? 0x02 : 0x01;
> + hdr->data.length = cpu_to_be16(data_len - sizeof(hdr->data));
> +
> + security->last_sent_command = cmd;
> + crypto_shash_update(security->shash_transcript, message + sizeof(hdr->handshake), data_len);
[Severity: High]
Can this code execute concurrently with the message handler? The
gip_security_send_message function is called from asynchronous workers, while
gip_security_handle_message can also update the same shash_transcript
descriptor. Since shash_desc is not thread-safe, concurrent modifications
could corrupt the cryptographic transcript.
> +
> + return gip_send_system_message(attachment, GIP_CMD_SECURITY, GIP_FLAG_ACME, message, len);
> +}
[ ... ]
> +int gip_security_handle_message(struct gip_security *security, const void *bytes, int num_bytes)
> +{
> + const struct gip_security_header_handshake *handshake = bytes;
> + const struct gip_security_header_full *hdr;
> + int err;
> +
> + if (num_bytes < sizeof(*handshake))
> + return -EINVAL;
> +
> + if (handshake->error)
> + return -EPROTO;
> +
> + if (handshake->options & GIP_SECURITY_OPT_ACKNOWLEDGE) {
> + if (handshake->command == 0x01)
> + return gip_security_handle_acknowledge(security);
> +
> + gip_err(security, "%s: handshake failed: 0x%02x\n",
> + __func__, handshake->command);
> + return -EPROTO;
> + }
> +
> + if (num_bytes < sizeof(*hdr))
> + return -EINVAL;
> +
> + hdr = bytes;
> + /* client uses v2 */
> + if (handshake->command != hdr->data.command) {
> + /* reset transcript hash and restart handshake */
> + gip_dbg(security, "%s: protocol upgrade\n", __func__);
> + crypto_shash_init(security->shash_transcript);
[Severity: Critical]
If an attacker-controlled USB device sends a security packet before the
handshake is explicitly started, could security->shash_transcript be a null
pointer here and cause a dereference? The cryptographic descriptors are
initialized lazily in gip_security_start_handshake, so early packets routed
to this handler might trigger a crash.
[Severity: Medium]
If we restart the handshake here, do we need to cancel any previously
scheduled v1 workers? If a v1 worker like work_exchange_rsa is pending, it
could execute concurrently, modify the state, and corrupt the new v2 handshake.
> + return gip_security2_send_hello(security);
> + }
> +
> + err = gip_security_dispatch_message(security, hdr->data.command,
> + bytes + sizeof(*hdr), num_bytes - sizeof(*hdr));
> + if (err)
> + return err;
> +
> + return crypto_shash_update(security->shash_transcript,
> + bytes + sizeof(hdr->handshake),
> + num_bytes - sizeof(hdr->handshake));
[Severity: Critical]
Similar to the issue above, could security->shash_transcript be a null pointer
here if a packet is received before the handshake was explicitly started,
resulting in a null pointer dereference in crypto_shash_update?
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=7
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 03/12] Input: xbox_gip - Add controllable LED support
2026-08-22 2:11 ` [PATCH v4 03/12] Input: xbox_gip - Add controllable LED support Vicki Pfau
@ 2026-08-22 2:37 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:37 UTC (permalink / raw)
To: Vicki Pfau; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [High] Lifecycle mismatch causing Use-After-Free and list corruption for the LED class device.
- [Medium] Missing initialization of `intensity` for multicolor LED subleds renders it functionally broken.
- [Medium] Data race on `mc_cdev->subled_info` during concurrent sysfs or trigger updates.
Pre-existing issues:
- [High] Missing bounds check on subsequent message fragments causes out-of-bounds read and kernel memory leak.
- [High] Input device properties `phys` and `uniq` point to freed memory, causing a Use-After-Free and information leak.
--
--- Patch [3]: [PATCH v4 03/12] Input: xbox_gip - Add controllable LED support ---
commit ac5f2a01657c115e9d51066385a4a06622ddd86f
Author: Vicki Pfau <vi@endrift.com>
Input: xbox_gip - Add controllable LED support
This commit adds support for monochrome white and RGBW addressable LEDs
found on Xbox One and Elite 2 controllers. It exposes these controllable
LEDs via the led class device and multicolor led class device subsystems.
> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> index 12a1fb285bfc8..15f8ed2107110 100644
> --- a/drivers/input/joystick/gip/gip-core.c
> +++ b/drivers/input/joystick/gip/gip-core.c
[ ... ]
> @@ -1038,9 +1038,116 @@ static int gip_send_guide_button_led(struct gip_attachment *attachment,
> +static int gip_guide_color_led_set(struct led_classdev *led,
> + enum led_brightness value)
> +{
> + struct led_classdev_mc *mc_cdev = container_of(led,
> + struct led_classdev_mc, led_cdev);
> + struct gip_attachment *attachment = container_of(mc_cdev,
> + struct gip_attachment, guide_led.color);
> +
> + led_mc_calc_color_components(mc_cdev, value);
> + guard(mutex)(&attachment->lock);
[Severity: Medium]
Can this cause a data race on the subled_info array? The function
led_mc_calc_color_components() modifies mc_cdev->subled_info before the
attachment->lock is acquired. Since the LED subsystem does not serialize
concurrent calls to brightness_set_blocking (for instance, from concurrent
sysfs writes and blink triggers), multiple threads could modify the shared
subled_info array simultaneously before acquiring the mutex.
> + return gip_send_guide_button_color_led(attachment,
> + mc_cdev->subled_info[0].brightness,
> + mc_cdev->subled_info[1].brightness,
> + mc_cdev->subled_info[2].brightness,
> + mc_cdev->subled_info[3].brightness);
> +}
> +
> +static int gip_guide_led_probe(struct gip_attachment *attachment, struct device *dev)
> +{
> + int rc = 0;
> +
> + if (!gip_supports_system_message(attachment, GIP_CMD_LED, false))
> + return 0;
> +
> + if (attachment->features & GIP_FEATURE_GUIDE_COLOR) {
> + struct mc_subled *mc_led_info;
> + struct led_classdev_mc *mc_cdev = &attachment->guide_led.color;
> + struct led_classdev *cdev = &mc_cdev->led_cdev;
> +
> + mc_led_info = devm_kcalloc(dev, 4,
> + sizeof(*mc_led_info), GFP_KERNEL);
> + if (!mc_led_info)
> + return -ENOMEM;
> +
> + mc_led_info[0].color_index = LED_COLOR_ID_RED;
> + mc_led_info[1].color_index = LED_COLOR_ID_GREEN;
> + mc_led_info[2].color_index = LED_COLOR_ID_BLUE;
> + mc_led_info[3].color_index = LED_COLOR_ID_WHITE;
[Severity: Medium]
Does this leave the intensity fields set to 0? The mc_led_info array is
allocated with devm_kcalloc, which zero-initializes the memory. When the
LED core later calls led_mc_calc_color_components(), it multiplies the
requested brightness by each channel's intensity. If the intensity is 0,
the computed brightness for all channels will be 0, and the LED will not
turn on unless userspace manually writes to the multi_intensity sysfs file.
> +
> + mc_cdev->subled_info = mc_led_info;
> + mc_cdev->num_colors = 4;
> +
> + cdev->brightness = 51;
> + cdev->max_brightness = 255;
> + cdev->flags = LED_CORE_SUSPENDRESUME | LED_RETAIN_AT_SHUTDOWN;
> + cdev->brightness_set_blocking = gip_guide_color_led_set;
> + cdev->name = devm_kasprintf(dev, GFP_KERNEL,
> + "%s:rgb:power", dev_name(dev));
> + if (!cdev->name)
> + rc = -ENOMEM;
> +
> + if (!rc)
> + rc = devm_led_classdev_multicolor_register(dev,
> + mc_cdev);
[Severity: High]
Can this lead to a use-after-free and list corruption on device disconnect?
The LED is registered using devres tied to the input device (dev), meaning
its unregistration is deferred until the input device is finally released.
However, the mc_cdev is embedded in the attachment structure, which is
explicitly freed during gip_disconnect():
drivers/input/joystick/gip/gip-core.c:gip_disconnect() {
...
devm_kfree(to_gip_device(attachment), attachment);
...
}
If a user-space process holds an open file descriptor to the evdev node
during a USB disconnect, the input_dev outlives the attachment. The
led_classdev remains registered but its backing memory is freed. When the
evdev node is finally closed, the input_dev devres cleanup will invoke
led_classdev_unregister() on the freed memory.
While reviewing this patch, I noticed a pre-existing issue in
gip_receive_fragment() that might need addressing:
drivers/input/joystick/gip/gip-core.c:gip_receive_fragment() {
...
} else {
if (header->length != 0)
memcpy(&attachment->in_fragment_data[*fragment_offset],
&bytes[offset], header->length);
...
}
[Severity: High]
This is a pre-existing issue, but does this lack a bounds check for
subsequent message fragments? The first fragment properly validates that
header->length does not exceed the packet size, but this else branch omits
that validation. A malicious or malfunctioning USB device could send a
small packet with a large header->length, causing the memcpy to read out
of bounds from the 64-byte gip_raw_message buffer into the heap-allocated
in_fragment_data buffer, potentially leaking kernel memory.
I also noticed another pre-existing lifecycle issue in
gip_setup_input_device() and gip_disconnect():
drivers/input/joystick/gip/gip-core.c:gip_setup_input_device() {
...
input->uniq = attachment->uniq;
...
input->phys = attachment->phys;
...
}
drivers/input/joystick/gip/gip-core.c:gip_disconnect() {
...
devm_kfree(to_gip_device(attachment), attachment->uniq);
devm_kfree(to_gip_device(attachment), attachment);
...
}
[Severity: High]
This isn't a bug introduced by this patch, but can this cause a
use-after-free if the input device is held open? The input device
properties phys and uniq are set to point to attachment->phys and
attachment->uniq. When the device is disconnected, these are explicitly
freed. If an evdev file descriptor is open, the input_dev outlives the
attachment. Subsequent calls to EVIOCGPHYS or EVIOCGUNIQ ioctls would
read from these freed memory pointers and copy the contents to user-space.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=3
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 04/12] Input: xbox_gip - Add HID relaying
2026-08-22 2:11 ` [PATCH v4 04/12] Input: xbox_gip - Add HID relaying Vicki Pfau
@ 2026-08-22 2:38 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:38 UTC (permalink / raw)
To: Vicki Pfau; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [High] The tunneled `hid_device` (`attachment->hdev`) is permanently leaked when the controller's metadata is reset, which can lead to a future Use-After-Free.
- [High] The HID device is configured with `BUS_USB` but its parent is set to a `usb_device` rather than a `usb_interface`, causing downstream HID drivers to crash.
- [Medium] The transport driver uses `hid_input_report()` instead of the required `hid_safe_input_report()`, circumventing HID core bounds checking.
- [Low] Unnecessary `synchronize_rcu()` after `rcu_assign_pointer()` when publishing `hdev`.
- [Low] Not all required `hid_device` fields are initialized before `hid_add_device()`.
Pre-existing issues:
- [High] The error path in `gip_setup_input_device()` leaves a dangling pointer in `attachment->input`, which causes a Use-After-Free during shutdown.
- [High] Missing NULL check after allocating memory with `devm_kmalloc` in `gip_send_raw_message()`, leading to a NULL pointer dereference.
--
--- Patch [4]: [PATCH v4 04/12] Input: xbox_gip - Add HID relaying ---
commit a021e64e3205f2caaac1426796abb2e1c1a1a8e6
Author: Vicki Pfau <vi@endrift.com>
Input: xbox_gip - Add HID relaying
GIP allows tunneling of HID packets, with the HID descriptor embedded in
the GIP metadata exchanged during the initial handshake. This patch creates
a hid_device for this HID descriptor if found, as well as relaying the HID
packets.
> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> index 15f8ed2107110..c833b10259ab8 100644
> --- a/drivers/input/joystick/gip/gip-core.c
> +++ b/drivers/input/joystick/gip/gip-core.c
[ ... ]
> @@ -1361,7 +1409,34 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
> if (rc)
> return rc;
>
> - return 0;
> + if (attachment->metadata.device.hid_descriptor) {
> + struct hid_device *hdev = hid_allocate_device();
> +
> + if (IS_ERR(hdev))
> + return PTR_ERR(hdev);
[Severity: High]
Does this leak the previous hdev if called multiple times, or does
gip_reset_metadata() need to be updated to clean up this new device?
When the metadata is reset, gip_reset_metadata() tears down the input
device:
if (input) {
rcu_assign_pointer(attachment->input, NULL);
synchronize_rcu();
input_unregister_device(input);
}
But the newly added attachment->hdev is not destroyed there, which could
leave a stray hid_device and lead to a use-after-free via hidraw.
> +
> + hdev->ll_driver = &gip_hid_ll_driver;
> + hdev->bus = BUS_USB;
> + hdev->vendor = attachment->vendor_id;
> + hdev->product = attachment->product_id;
[Severity: Low]
Are there missing hid_device initializations here?
The HID subsystem guidelines recommend fully initializing the device fields
(such as uniq, version, country, etc.) before calling hid_add_device().
> + hdev->dev.parent = to_gip_device(attachment);
[Severity: High]
Could this parent assignment cause a crash in downstream HID drivers?
The device is configured as a USB bus device, but the parent is being set
to a gip_device (which contains a usb_device) rather than a usb_interface.
Many USB HID drivers assume the parent is a usb_interface and call
to_usb_interface() on it. If they do, they will perform an invalid cast and
crash.
> + hdev->driver_data = attachment;
> + if (attachment->name)
> + strscpy(hdev->name, attachment->name);
> + else
> + strscpy(hdev->name, "Xbox Chatpad");
> + strscpy(hdev->phys, attachment->phys);
> + rc = hid_add_device(hdev);
> + if (rc) {
> + gip_err(attachment, "HID device add failed: %d\n", rc);
> + hid_destroy_device(hdev);
> + } else {
> + rcu_assign_pointer(attachment->hdev, hdev);
> + synchronize_rcu();
[Severity: Low]
Is this synchronize_rcu() call necessary?
Since rcu_assign_pointer() already provides the needed memory barriers to
publish a new pointer, and we aren't waiting for readers of an old pointer
to finish (as there isn't one being freed here), calling synchronize_rcu()
might just unnecessarily block and degrade performance.
> + }
> + }
> +
> + return rc;
> }
[ ... ]
> @@ -1769,9 +1844,16 @@ static int gip_handle_command_firmware(struct gip_attachment *attachment,
> static int gip_handle_command_hid_report(struct gip_attachment *attachment,
> const struct gip_header *header, uint8_t *bytes, int num_bytes)
> {
> - gip_warn(attachment, "Unimplemented HID report message\n");
> + struct hid_device *hdev;
>
> - return -EOPNOTSUPP;
> + guard(rcu)();
> + hdev = rcu_dereference(attachment->hdev);
> + if (hdev)
> + return hid_input_report(hdev, HID_INPUT_REPORT, bytes, num_bytes, true);
[Severity: Medium]
Does this bypass the HID core's bounds checking?
Using hid_input_report() instead of hid_safe_input_report() for incoming
data violates the HID subsystem transport guidelines, as it prevents the
core from validating the size of the buffer.
> +
> + gip_warn(attachment, "Got HID report with no HID descriptor\n");
> +
> + return -EPROTO;
> }
[ ... ]
[Severity: High]
This isn't a bug introduced by this patch, but while reviewing the input
device lifecycle, I noticed a potential use-after-free during shutdown.
In gip_setup_input_device(), if input_register_device() fails, the pointer
is left dangling:
err_free_device:
input_free_device(input);
return rc;
When gip_shutdown() later runs, it sees the remaining attachment->input
pointer and calls input_unregister_device() on an already freed device.
[Severity: High]
This is a pre-existing issue, but does this allocation in
gip_send_raw_message() need a null check?
fragment->data = devm_kmalloc(to_gip_device(attachment),
fragment->total_length, GFP_ATOMIC);
fragment->fragment_offset = 0;
memcpy(fragment->data, bytes, num_bytes);
If the atomic allocation fails and returns a null pointer, the immediate
memcpy() will cause a kernel panic.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=4
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 05/12] Input: xbox_gip - Add battery support
2026-08-22 2:11 ` [PATCH v4 05/12] Input: xbox_gip - Add battery support Vicki Pfau
@ 2026-08-22 2:38 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:38 UTC (permalink / raw)
To: Vicki Pfau; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 7 potential issue(s) to consider:
New issues:
- [High] Use-After-Free of `attachment` via battery sysfs properties due to devres lifecycle mismatch.
- [Medium] Missing `power_supply_changed()` call prevents userspace from receiving battery state updates.
- [Low] Data Race on battery status fields causes torn reads in sysfs.
Pre-existing issues:
- [High] Use-After-Free of `attachment` via LED sysfs properties due to devres lifecycle mismatch.
- [High] Dangling `attachment->input` pointer on `input_register_device` failure causes Use-After-Free.
- [High] AB-BA Deadlock between `gip_shutdown` and `gip_fragment_timeout`.
- [High] Out-of-Bounds Read in `gip_receive_fragment` due to missing bounds check.
--
--- Patch [5]: [PATCH v4 05/12] Input: xbox_gip - Add battery support ---
commit 467fca5f8d0f00e2e7affd3b50a79e158dfc1b7d
Author: Vicki Pfau <vi@endrift.com>
Input: xbox_gip - Add battery support
Controllers are required to give information about the battery in the
controller, if present. This patch exposes that information as a
power_supply device.
Signed-off-by: Vicki Pfau <vi@endrift.com>
> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> index c833b10259ab8..d694ac5516395 100644
> --- a/drivers/input/joystick/gip/gip-core.c
> +++ b/drivers/input/joystick/gip/gip-core.c
[ ... ]
> @@ -1196,6 +1203,92 @@ static int gip_guide_led_probe(struct gip_attachment *attachment, struct device
> return rc;
> }
>
> +static int gip_battery_get_property(struct power_supply *psy,
> + enum power_supply_property psp, union power_supply_propval *val)
> +{
> + struct gip_attachment *attachment = power_supply_get_drvdata(psy);
> +
> + guard(mutex)(&attachment->lock);
[Severity: Low]
Does taking this mutex fully protect the battery status fields?
Looking at gip_handle_command_status_device(), the fields are updated
directly without acquiring attachment->lock:
gip_handle_command_status_device()
attachment->status.base.battery_level = bytes[0] & 3;
attachment->status.base.battery_type = (bytes[0] >> 2) & 3;
attachment->status.base.charge = (bytes[0] >> 4) & 3;
attachment->status.base.power_level = (bytes[0] >> 6) & 3;
Could a concurrent sysfs read observe torn or partially updated state?
[Severity: Medium]
Also, when gip_handle_command_status_device() updates the battery state,
is a call to power_supply_changed(attachment->battery) needed?
Without it, will userspace applications be notified of the updated battery
state, or will they only see changes if they manually poll sysfs?
[ ... ]
> +static int gip_battery_create(struct gip_attachment *attachment, struct device *dev)
> +{
> + struct power_supply_config supply_config = { .drv_data = attachment, };
> +
> + attachment->battery_desc.properties = gip_battery_props;
[ ... ]
> + attachment->battery = devm_power_supply_register(dev,
> + &attachment->battery_desc, &supply_config);
[Severity: High]
Could this lead to a use-after-free when the device is disconnected?
The power supply is registered using devm with the input device as its
parent, so its release is deferred until the input device's reference
count drops to zero.
However, gip_disconnect() immediately frees the attachment structure:
gip_disconnect()
devm_kfree(to_gip_device(attachment), attachment);
If a userspace process holds the input evdev node open, the power supply
sysfs node remains registered. Could a subsequent sysfs read trigger
gip_battery_get_property() and dereference the freed attachment structure?
> + if (IS_ERR(attachment->battery))
> + return PTR_ERR(attachment->battery);
> +
> + return power_supply_powers(attachment->battery, dev);
> +}
[ ... ]
> @@ -1308,6 +1401,9 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
> rc = gip_guide_led_probe(attachment, &input->dev);
[Severity: High]
This isn't a bug introduced by this patch, but does gip_guide_led_probe()
have the same devm lifecycle mismatch as the battery creation?
The LEDs are registered using devm_led_classdev_multicolor_register()
with the input device as their parent.
Since gip_disconnect() frees the attachment structure manually, could writing
to the LED brightness sysfs node trigger callbacks that dereference the
freed attachment if userspace still holds the input device open?
[Severity: High]
This is also a pre-existing issue, but if gip_setup_input_device() fails,
is there a dangling pointer left behind?
The pointer is published before registration:
gip_setup_input_device()
rcu_assign_pointer(attachment->input, input);
rc = input_register_device(input);
if (rc)
goto err_free_device;
But the error path does not clear it:
err_free_device:
input_free_device(input);
return rc;
Could an incoming hardware event trigger gip_handle_ll_input_report()
and read the freed attachment->input pointer?
> if (rc)
> gip_err(attachment, "Failed to register LEDs: %d\n", rc);
> + rc = gip_battery_create(attachment, &input->dev);
> + if (rc)
> + gip_err(attachment, "Failed to register battery: %d\n", rc);
>
> return 0;
[ ... ]
> @@ -2623,6 +2719,7 @@ static int gip_shutdown(struct gip_device *device)
> hdev = rcu_dereference(attachment->hdev);
> rcu_read_unlock();
>
> + attachment->battery = NULL;
[Severity: High]
This isn't a bug introduced by this patch, but can gip_shutdown() deadlock
with gip_fragment_timeout()?
In gip_shutdown(), the lock is acquired before cancelling the work:
gip_shutdown()
scoped_guard (mutex, &attachment->lock) {
cancel_delayed_work_sync(&attachment->metadata_next);
cancel_delayed_work_sync(&attachment->in_fragment_timeout);
If gip_fragment_timeout() runs concurrently, it attempts to acquire the
same mutex:
gip_fragment_timeout()
guard(mutex)(&attachment->lock);
Could cancel_delayed_work_sync() block waiting for the work to finish,
while the work blocks waiting for the mutex?
> rcu_assign_pointer(attachment->input, NULL);
> rcu_assign_pointer(attachment->hdev, NULL);
> synchronize_rcu();
[Severity: High]
This is a pre-existing issue, but does gip_receive_fragment() have an
out-of-bounds read when processing fragments?
When parsing a non-initial message fragment, it uses header->length directly
in memcpy():
gip_receive_fragment()
if (header->length != 0)
memcpy(&attachment->in_fragment_data[*fragment_offset],
&bytes[offset], header->length);
Is there a check to ensure that offset + header->length does not exceed
the actual size of the received USB buffer? Could a malicious USB device
sending a fragmented message with an artificially large length cause an
out-of-bounds read?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=5
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER
2026-08-22 2:11 ` [PATCH v4 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER Vicki Pfau
@ 2026-08-22 2:38 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:38 UTC (permalink / raw)
To: Vicki Pfau; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The `hid-universal-pidff.c` driver contains a hardcoded loop bound (`ABS_BRAKE`) for racing axis initialization, leaving the newly added racing axes without correct fuzz and deadzone configuration.
--
--- Patch [10]: [PATCH v4 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER ---
commit 518c533c53885e9a0bf15581d9e84560c1f27740
Author: Vicki Pfau <vi@endrift.com>
Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER
This commit introduces new absolute axes (ABS_CLUTCH, ABS_HANDBRAKE, and
ABS_SHIFTER) for racing game controllers. It updates the relevant definitions
in input-event-codes.h and the debug string representations in the HID
subsystem.
[ ... ]
> diff --git a/include/uapi/linux/input-event-codes.h b/include/uapi/linux/input-event-codes.h
> index 3528168f7c6d7..5c881ed441771 100644
> --- a/include/uapi/linux/input-event-codes.h
> +++ b/include/uapi/linux/input-event-codes.h
> @@ -879,6 +879,9 @@
> #define ABS_WHEEL 0x08
> #define ABS_GAS 0x09
> #define ABS_BRAKE 0x0a
> +#define ABS_CLUTCH 0x0b
> +#define ABS_HANDBRAKE 0x0c
> +#define ABS_SHIFTER 0x0d
[Severity: Medium]
Will these newly added axes be missing fuzz and deadzone configuration in
some drivers?
Looking at universal_pidff_input_configured() in
drivers/hid/hid-universal-pidff.c, there is a hardcoded loop bound that
explicitly terminates at ABS_BRAKE:
/* Decrease fuzz and deadzone on available axes */
for (axis = ABS_X; axis <= ABS_BRAKE; axis++) {
Does this loop need to be updated to include ABS_CLUTCH, ABS_HANDBRAKE,
and ABS_SHIFTER, since they logically belong in this racing axes group
and might otherwise incorrectly retain default settings?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=10
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 01/12] Input: xbox_gip - Add new driver for Xbox GIP
2026-08-22 2:11 ` [PATCH v4 01/12] " Vicki Pfau
@ 2026-08-22 2:38 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:38 UTC (permalink / raw)
To: Vicki Pfau; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 17 potential issue(s) to consider:
- [High] Deadlock caused by calling `cancel_delayed_work_sync()` while holding a mutex required by the work function.
- [High] Lifecycle mismatch causing devres memory leak due to allocating against the parent USB device instead of the USB interface.
- [High] Active OUT URBs are freed without being killed, causing use-after-free and DMA corruption.
- [High] Lock inversion during devres cleanup triggers an AB-BA deadlock between `attachment->lock` and `cancel_work_sync`.
- [High] Buffer over-read in `gip_receive_fragment` for non-initial fragments due to missing bounds check.
- [High] Probe error path leaves an active IN URB running, leading to a Use-After-Free.
- [High] Dangling pointer left in `attachment->input` upon `input_register_device` failure.
- [High] Blind dereference of potential `ERR_PTR` returned from `gip_ensure_attachment`.
- [High] Undefined behavior from left shifting by 64 or more bits in `gip_decode_length`.
- [High] NULL pointer dereference when `in_fragment_data` allocation fails.
- [High] Missing NULL check after `devm_kmalloc` in `gip_send_raw_message`.
- [High] Use-After-Free due to LED/Battery callbacks operating on explicitly freed `attachment` structure.
- [High] Sleeping in atomic context by calling `usb_free_coherent` while holding a spinlock.
- [High] Memory leak of URBs and DMA buffers due to `return` combined with dead `goto` error handling.
- [High] Heap buffer overflow in `gip_receive_fragment` due to bypassed bounds check on exact offset match.
- [Medium] Bitwise OR logic typo in `gip_parse_preferred_types_metadata` ignores the high byte of the string length.
- [Medium] Memory leak of `attachment->uniq` caused by repeated `HELLO_DEVICE` messages.
--
--- Patch [1]: [PATCH v4 01/12] Input: xbox_gip - Add new driver for Xbox GIP ---
commit 8f3bdd937c628e28382af94b64282af26f6cbd22
Author: Vicki Pfau <vi@endrift.com>
Subject: Input: xbox_gip - Add new driver for Xbox GIP
This commit introduces a new Gaming Input Protocol (GIP) driver for Xbox
One and Series controllers. It supports parsing message lengths, metadata,
and handles basic gamepad input functionality along with controller quirks.
> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-core.c
> @@ -0,0 +1,2569 @@
> +static int gip_decode_length(uint64_t *length, const uint8_t *bytes, int num_bytes)
> +{
> + *length = 0;
> + int offset;
> +
> + for (offset = 0; offset < num_bytes; offset++) {
> + uint8_t byte = bytes[offset];
> +
> + *length |= (byte & 0x7full) << (offset * 7);
[Severity: High]
Does this loop need a bounds check to prevent shifting by 64 or more bits?
If a device sends 10 or more consecutive bytes with the 0x80 bit set,
offset will reach 10, causing the shift to become 70. This results in
undefined behavior in C.
[ ... ]
> +static int gip_send_raw_message(struct gip_attachment *attachment,
> + uint8_t message_type, uint8_t flags, uint8_t seq, const uint8_t *bytes,
> + int num_bytes)
> +{
[ ... ]
> + fragment->total_length = num_bytes;
> + fragment->data = devm_kmalloc(to_gip_device(attachment),
> + fragment->total_length, GFP_ATOMIC);
> + fragment->fragment_offset = 0;
> + memcpy(fragment->data, bytes, num_bytes);
[Severity: High]
Is there a missing NULL check for the devm_kmalloc() return value?
If the atomic memory allocation fails under memory pressure, the subsequent
memcpy() will result in a NULL pointer dereference.
[ ... ]
> +static int gip_parse_preferred_types_metadata(struct device *dev,
> + struct gip_device_metadata *dev_metadata, const uint8_t *bytes,
> + int length, int buffer_offset)
> +{
[ ... ]
> + buffer_offset++;
> + for (i = 0; i < dev_metadata->num_preferred_types; i++) {
> + if (buffer_offset + 2 >= length)
> + return -EINVAL;
> +
> + count = bytes[buffer_offset];
> + count |= bytes[buffer_offset];
[Severity: Medium]
Is there a typo in the bitwise OR assignment for the count?
By OR-ing the exact same byte against itself, the high byte of the 16-bit
length is ignored (it looks like it should read buffer_offset + 1). This
could cause strings larger than 255 bytes to parse incorrectly as 0,
desynchronizing the buffer offset.
[ ... ]
> +static bool gip_handle_command_hello_device(struct gip_attachment *attachment,
> + const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
[ ... ]
> + attachment->firmware_major_version = le16_to_cpu(message->firmware_major_version);
> + attachment->firmware_minor_version = le16_to_cpu(message->firmware_minor_version);
> + attachment->vendor_id = le16_to_cpu(message->vendor_id);
> + attachment->product_id = le16_to_cpu(message->product_id);
> + attachment->uniq = devm_kasprintf(to_gip_device(attachment),
> + GFP_KERNEL, "%llx", le64_to_cpu(message->device_id));
[Severity: Medium]
Could this cause a memory leak if the device repeatedly sends HELLO_DEVICE
messages?
The attachment->uniq pointer is assigned a newly allocated string each time
this command is handled, but any previously allocated string is not freed
prior to being overwritten.
[ ... ]
> +int gip_setup_input_device(struct gip_attachment *attachment)
> +{
[ ... ]
> + rcu_assign_pointer(attachment->input, input);
> + rc = input_register_device(input);
> + if (rc)
> + goto err_free_device;
> +
> + rc = gip_guide_led_probe(attachment, &input->dev);
[ ... ]
> +err_free_device:
> + input_free_device(input);
> + return rc;
> +}
[Severity: High]
Should attachment->input be cleared to NULL in the error path?
If input_register_device() fails, input_free_device() is called, but
attachment->input remains set. If an interrupt arrives afterwards,
gip_handle_ll_input_report() could dereference the freed dangling pointer.
[ ... ]
> +static void gip_reset_metadata(struct gip_attachment *attachment)
> +{
> + struct input_dev *input;
> +
> + if (attachment->got_metadata != GIP_METADATA_GOT)
> + return;
> +
> + gip_metadata_free(to_gip_device(attachment), &attachment->metadata);
> + rcu_read_lock();
> + input = rcu_dereference(attachment->input);
> + rcu_read_unlock();
> + if (input) {
> + rcu_assign_pointer(attachment->input, NULL);
> + synchronize_rcu();
> + input_unregister_device(input);
> + }
[Severity: High]
Could this lead to an AB-BA deadlock between attachment->lock and
the power supply's changed_work cancelation?
When a new message fragment is processed, gip_receive_message() holds
attachment->lock. It might call into gip_reset_metadata(), which triggers
input_unregister_device(). This synchronously releases devres resources,
unregistering the battery power supply and flushing its work queue via
cancel_work_sync().
If the power supply worker thread is simultaneously executing
gip_battery_get_property(), it will attempt to acquire attachment->lock,
creating a deadlock.
[ ... ]
> +static int gip_receive_fragment(struct gip_attachment *attachment,
> + const struct gip_header *header, int offset,
> + uint64_t *fragment_offset, uint16_t *bytes_remaining, uint8_t *bytes,
> + int num_bytes)
> +{
[ ... ]
> + attachment->in_fragment_data = devm_kmalloc(to_gip_device(attachment),
> + attachment->in_total_length, GFP_KERNEL);
> + if (!attachment->in_fragment_data)
> + return -ENOMEM;
[Severity: High]
Does this error path leave the attachment in an inconsistent state?
If the allocation fails, the function returns -ENOMEM but leaves
attachment->in_fragment_message set to a valid message type. A subsequent
non-initial fragment matching the type would pass validation and call
memcpy() into the NULL in_fragment_data pointer.
[ ... ]
> + if (*fragment_offset != attachment->in_fragment_offset) {
[ ... ]
> + if (header->length && *fragment_offset != attachment->in_total_length) {
> + gip_warn_ratelimited(attachment,
> + "Received out of sequence fragment: claimed %llu, expected %d\n",
> + *fragment_offset, attachment->in_fragment_offset);
> + gip_acknowledge(attachment, header,
> + attachment->in_fragment_offset,
> + attachment->in_total_length - attachment->in_fragment_offset);
> + return -EPROTO;
> + }
> + gip_dbg_ratelimited(attachment,
> + "Received erroneous final fragment offset, proceeding anyway\n");
> + *bytes_remaining = 0;
> + } else if (*fragment_offset + header->length > attachment->in_total_length) {
> + gip_warn(attachment,
> + "Received too long fragment, %llu exceeds %d\n",
> + *fragment_offset + header->length, attachment->in_total_length);
> + gip_fragment_failed(attachment, header);
> + return -EINVAL;
> + } else {
> + *bytes_remaining = attachment->in_total_length -
> + (*fragment_offset + header->length);
> + }
> + if (header->length != 0)
> + memcpy(&attachment->in_fragment_data[*fragment_offset],
> + &bytes[offset], header->length);
[Severity: High]
Can this logic result in a heap buffer overflow?
If a device provides *fragment_offset == attachment->in_total_length and
header->length > 0, the first if block is evaluated. Since it exits the
block without returning, it skips the else if bounds check entirely and
proceeds to memcpy(), writing header->length bytes past the end of the
heap allocation.
[Severity: High]
Is there a missing bounds check for non-initial fragments?
For initial fragments (GIP_FLAG_INIT_FRAG), the driver checks if
header->length > num_bytes - offset. However, for non-initial fragments,
this validation appears to be missing before calling memcpy(). This allows
the device to specify a header->length larger than the actual received
packet, reading adjacent kernel heap memory into in_fragment_data.
[ ... ]
> + cancel_delayed_work_sync(&attachment->in_fragment_timeout);
> + schedule_delayed_work(&attachment->in_fragment_timeout, HZ);
> +
> + return rc;
> +}
[Severity: High]
Can this lead to an AB-BA deadlock with the work function?
gip_receive_fragment() is called from gip_receive_message() while holding
attachment->lock. It then calls cancel_delayed_work_sync(). If the delayed
work gip_fragment_timeout() is currently executing, it will attempt to
acquire attachment->lock, deadlocking the system.
[ ... ]
> +static int gip_receive_message(struct gip_device *device, uint8_t *bytes,
> + int num_bytes)
> +{
[ ... ]
> + is_fragment = header.flags & GIP_FLAG_FRAGMENT;
> + attachment_index = header.flags & GIP_FLAG_ATTACHMENT_MASK;
> + attachment = gip_ensure_attachment(device, attachment_index);
> +
> + print_hex_dump_debug(KBUILD_MODNAME ": Received message: ",
> + DUMP_PREFIX_OFFSET, 16, 1, bytes, num_bytes, false);
> +
> + guard(mutex)(&attachment->lock);
[Severity: High]
Does gip_ensure_attachment() need to be checked for an ERR_PTR return?
If memory allocation within gip_ensure_attachment() fails, it returns
ERR_PTR(-ENOMEM). Blindly calling guard(mutex)(&attachment->lock) with
an ERR_PTR will trigger a kernel panic.
[ ... ]
> +static int gip_init_output(struct gip_interface *intf,
> + struct usb_endpoint_descriptor *ep_out)
> +{
[ ... ]
> + intf->out_queue[i].data = usb_alloc_coherent(udev, intf->mtu, GFP_KERNEL,
> + &intf->out_queue[i].urb->transfer_dma);
> +
> + if (!intf->out_queue[i].data) {
> + return -ENOMEM;
> + goto err_free_urbs;
> + }
[Severity: High]
Does the early return cause a memory leak on the error path?
The code executes return -ENOMEM; immediately, bypassing the goto statement.
This bypasses the err_free_urbs cleanup logic, leaking all URBs and buffers
allocated in previous loop iterations.
[ ... ]
> +static int gip_probe(struct usb_interface *intf, const struct usb_device_id *id)
> +{
[ ... ]
> + gip = devm_kzalloc(&udev->dev, sizeof(*gip), GFP_KERNEL);
> + if (!gip)
> + return -ENOMEM;
> +
> + gip->udev = udev;
> + gip->data.device = gip;
[Severity: High]
Are devres allocations incorrectly tied to the parent USB device?
The structure is allocated using devm_kzalloc(&udev->dev, ...). Since a
usb_driver is bound to the usb_interface, its devres allocations must be
tied to &intf->dev. Because they are tied to the parent device, unbinding
the driver via sysfs does not trigger devres cleanup, permanently leaking
memory until the physical device is unplugged.
[ ... ]
> + rc = gip_interface_init(&gip->data);
> + if (rc) {
> + devm_kfree(to_gip_device(gip), gip);
> + return rc;
> + }
> + /* Don't init audio interface -- we aren't using it yet */
> +
> + usb_set_intfdata(intf, gip);
> +
> + /* Pre-create the first attachment, as it should always exist */
> + attachment = gip_ensure_attachment(gip, 0);
> + if (IS_ERR(attachment))
> + return PTR_ERR(attachment);
> +
> + return 0;
> +}
[Severity: High]
Are we leaking the running URB if gip_ensure_attachment() fails?
The call to gip_interface_init() submits urb_in. If ensuring the first
attachment subsequently fails, the function returns without killing the
active URB. When the hardware interrupt fires, the USB core will attempt
to access the freed gip structure.
[ ... ]
> +static int gip_shutdown(struct gip_device *device)
> +{
[ ... ]
> + for (i = 0; i < MAX_ATTACHMENTS; i++) {
> + struct gip_attachment *attachment = device->attachments[i];
> + struct input_dev *input;
> +
> + if (!attachment)
> + continue;
> +
> + scoped_guard (mutex, &attachment->lock) {
> + cancel_delayed_work_sync(&attachment->metadata_next);
> + cancel_delayed_work_sync(&attachment->in_fragment_timeout);
[Severity: High]
Does this scoped_guard introduce a deadlock?
The scoped_guard acquires attachment->lock, and then calls
cancel_delayed_work_sync(). If the work functions (gip_retry_metadata or
gip_fragment_timeout) are running concurrently, they attempt to acquire
this same lock, causing an AB-BA deadlock during driver shutdown.
[ ... ]
> +static void gip_disconnect(struct usb_interface *intf)
> +{
[ ... ]
> + usb_kill_urb(gip->data.urb_in);
> + if (gip->audio.intf)
> + usb_kill_urb(gip->audio.urb_in);
> +
> + gip_shutdown(gip);
> +
> + spin_lock_irqsave(&gip->message_lock, flags);
> + gip_deinit_input(&gip->data);
> + gip_deinit_output(&gip->data);
[Severity: High]
Should the active OUT URBs be killed before de-initializing output?
Only the IN URBs are killed via usb_kill_urb() prior to cleanup.
gip_deinit_output() frees the URBs and their DMA buffers via
usb_free_coherent(). If the host controller is actively transmitting
these buffers, it will cause DMA corruption and crash the USB core.
[Severity: High]
Is it safe to call usb_free_coherent() while holding a spinlock?
gip_deinit_input() and gip_deinit_output() invoke usb_free_coherent()
to free DMA buffers. This function can involve TLB flushes and IOMMU
operations that may sleep. Calling it while holding gip->message_lock
and with interrupts disabled violates atomic context rules.
[ ... ]
> + for (i = 0; i < MAX_ATTACHMENTS; i++) {
> + struct gip_attachment *attachment = gip->attachments[i];
> +
> + if (!attachment)
> + continue;
> +
> + gip_reset_attachment(attachment);
> +
> + devm_kfree(to_gip_device(attachment), attachment->uniq);
> + devm_kfree(to_gip_device(attachment), attachment);
> + }
> +
> + devm_kfree(to_gip_device(gip), gip);
> +}
[Severity: High]
Can explicit freeing of the attachment cause a Use-After-Free?
The attachment memory is explicitly freed here via devm_kfree(). However,
the LED and Battery devices were registered with &input->dev as their
devres parent. If userspace holds the input device open, devres cleanup
is delayed. A subsequent sysfs modification to the LED state will invoke
gip_guide_led_set(), which dereferences the freed attachment structure.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=1
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 08/12] Input: xbox_gip - Add arcade stick support
2026-08-22 2:11 ` [PATCH v4 08/12] Input: xbox_gip - Add arcade stick support Vicki Pfau
@ 2026-08-22 2:38 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:38 UTC (permalink / raw)
To: Vicki Pfau; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
New issues:
- [Medium] Memory leak of `gip_arcade_stick_info` on device disconnect because the core framework never invokes the `.remove()` callback.
- [Medium] Missing event synchronization when an input report is truncated (2 to 5 bytes), leaving spurious events stuck in the input subsystem buffer.
Pre-existing issues:
- [High] Use-After-Free of `attachment->input` if `input_register_device` fails.
- [High] Use-After-Free and DMA corruption in the teardown path due to failing to stop outbound URBs and the `send_fragment` work.
--
--- Patch [8]: [PATCH v4 08/12] Input: xbox_gip - Add arcade stick support ---
commit 0a0bb97c59622a30366ca74123a1a5db969d555d
Author: Vicki Pfau <vi@endrift.com>
Input: xbox_gip - Add arcade stick support
This adds support for the arcade stick device type. Currently I'm only
aware of one Xbox controller that uses this device type, the Razer Atrox,
so testing coverage is limited.
> diff --git a/drivers/input/joystick/gip/gip-arcade-stick.c b/drivers/input/joystick/gip/gip-arcade-stick.c
> new file mode 100644
> index 0000000000000..11ededfab8555
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-arcade-stick.c
> @@ -0,0 +1,173 @@
[ ... ]
> +static int gip_arcade_stick_probe(struct gip_attachment *attachment)
> +{
> + struct gip_arcade_stick_info *info = kzalloc(sizeof(*info), GFP_KERNEL);
> +
> + if (!info)
> + return -ENOMEM;
> +
> + attachment->quirks |= GIP_QUIRK_SWAP_LB_RB;
> + attachment->driver_data = info;
> +
> + return 0;
> +}
> +
> +static void gip_arcade_stick_remove(struct gip_attachment *attachment)
> +{
> + kfree(attachment->driver_data);
> + attachment->driver_data = NULL;
> +}
[Severity: Medium]
Will this leak gip_arcade_stick_info on device disconnect? Looking at the
core framework in gip-core.c, it doesn't appear to invoke the .remove()
callback of any attachment driver during device teardown (gip_disconnect),
so the memory allocated here might never get freed.
[ ... ]
> +static int gip_handle_arcade_stick_report(struct gip_attachment *attachment,
> + struct input_dev *input, const uint8_t *bytes, int num_bytes)
> +{
> + struct gip_arcade_stick_info *info = attachment->driver_data;
> + int rc;
> +
> + if (!info)
> + return -ENODEV;
> +
> + rc = gip_driver_navigation.handle_input_report(attachment, input, bytes, num_bytes);
> + if (rc < 0)
> + return rc;
> +
> + if (num_bytes < 6) {
> + gip_dbg(attachment, "Discarding too-short input report\n");
> + return -EINVAL;
> + }
[Severity: Medium]
Can this leave unsynchronized events in the input buffer if an input report is
truncated (between 2 and 5 bytes)?
The base handler gip_driver_navigation.handle_input_report() may successfully
process the first 2 bytes, report multiple EV_KEY events, and return 0. If
num_bytes is then evaluated as < 6, returning -EINVAL here causes the
core caller to skip input_sync().
[ ... ]
> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> index 765d70033f205..da22545662e21 100644
> --- a/drivers/input/joystick/gip/gip-core.c
> +++ b/drivers/input/joystick/gip/gip-core.c
[ ... ]
> @@ -1554,7 +1556,7 @@ static int gip_handle_command_raw_report(struct gip_attachment *attachment,
> return 0;
> }
>
> -static int gip_setup_input_device(struct gip_attachment *attachment)
> +int gip_setup_input_device(struct gip_attachment *attachment)
> {
[Severity: High]
This isn't a bug introduced by this patch, but can attachment->input become a
dangling pointer if input_register_device() fails?
In gip_setup_input_device(), attachment->input is assigned the newly
allocated input device:
rcu_assign_pointer(attachment->input, input);
rc = input_register_device(input);
if (rc)
goto err_free_device;
...
err_free_device:
input_free_device(input);
return rc;
If registration fails, the error path frees the input device but doesn't set
attachment->input back to NULL. Later disconnects or input packets could
dereference the freed pointer.
> struct input_dev *input;
> int rc;
[Severity: High]
This is a pre-existing issue, but does the teardown path in gip_disconnect()
cause a use-after-free or DMA corruption?
Looking at gip_disconnect() in drivers/input/joystick/gip/gip-core.c:
static void gip_disconnect(struct usb_interface *intf)
{
...
usb_kill_urb(gip->data.urb_in);
if (gip->audio.intf)
usb_kill_urb(gip->audio.urb_in);
gip_shutdown(gip);
spin_lock_irqsave(&gip->message_lock, flags);
gip_deinit_input(&gip->data);
gip_deinit_output(&gip->data);
It calls usb_kill_urb() for inbound URBs, but misses outbound URBs
(intf->out_queue[i].urb). It then calls gip_deinit_output(), which
unconditionally frees the coherent DMA buffers and the URB structures. If an
outbound URB is in flight, the USB host controller might perform DMA reads from
the freed memory.
Additionally, gip_disconnect() doesn't seem to cancel the
&intf->send_fragment work, meaning if an outbound URB completes, it could
schedule the work to access the freed gip device structure.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=8
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages
2026-08-22 2:11 ` [PATCH v4 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages Vicki Pfau
@ 2026-08-22 2:39 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:39 UTC (permalink / raw)
To: Vicki Pfau; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 11 potential issue(s) to consider:
New issues:
- [Critical] Missing cancellation of `send_fragment` work and OUT URBs causes Use-After-Free during driver teardown.
- [High] Missing NULL check for `devm_kmalloc` allocation in `gip_send_raw_message`.
- [High] Unsynchronized allocation of `out_fragments` slots in `gip_send_raw_message` leads to a TOCTOU race condition.
- [High] Fragment slots and memory are permanently leaked on output queue full or URB submission failures.
- [High] Unprotected read-modify-write of the `intf->has_pending_out` bitmask causes data races.
- [High] Broken search logic in `gip_find_fragment` completely ignores exact sequence numbers when `strict` is true.
- [High] Fragment slots for ACME messages permanently leak if the device drops the packet.
- [Medium] Improper use of managed resources (`devm_kmalloc`) for high-frequency, dynamic per-message buffers.
Pre-existing issues:
- [High] AB-BA deadlock in `gip_shutdown` when canceling delayed works.
- [High] gip_receive_fragment() blindly trusts header->length when processing subsequent fragments, leading to an out-of-bounds read from the kernel heap.
- [High] gip_receive_fragment() can dereference a NULL pointer if devm_kmalloc fails and the fragmented message sequence continues.
--
--- Patch [6]: [PATCH v4 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages ---
commit 945e1fae94cb61e149c842cf10c67d06095eaf41
Author: Vicki Pfau <vi@endrift.com>
Subject: Input: xbox_gip - Allow sending fragmented and ACME messages
This patch adds support for sending messages that exceed the length of the MTU
using a fragmented message flow. It also supports sending messages requiring
ACME acknowledgments, which will be necessary for future security flows.
> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> index d694ac5516395..a75fbcb1c02dc 100644
> --- a/drivers/input/joystick/gip/gip-core.c
> +++ b/drivers/input/joystick/gip/gip-core.c
[ ... ]
> @@ -479,6 +486,48 @@ static void gip_handle_quirks(struct gip_attachment *attachment)
> +static struct gip_out_fragment *gip_find_fragment(struct gip_attachment *attachment,
> + uint8_t message_type, uint8_t flags, uint8_t seq, bool strict)
> +{
> + int i;
> +
> + for (i = 0; i < MAX_OUT_FRAGMENTS; i++) {
[ ... ]
> + }
> +
> + return &attachment->out_fragments[i];
> + }
> +
> + for (i = 0; i < MAX_OUT_FRAGMENTS; i++) {
> + if (!attachment->out_fragments[i].active)
> + continue;
> + if (attachment->out_fragments[i].message != message_type)
> + continue;
> + if ((attachment->out_fragments[i].flags ^ flags) & GIP_FLAG_SYSTEM)
> + continue;
> +
> +
> + return &attachment->out_fragments[i];
> + }
[Severity: High]
Does this second loop bypass the sequence number check entirely when strict is
true? If a strict search fails in the first loop, falling through to this one
might unconditionally return a fragment that matches the type but has the wrong
sequence number, potentially misrouting packets.
[ ... ]
> @@ -486,20 +535,25 @@ static int gip_send_raw_message(struct gip_attachment *attachment,
[ ... ]
> + if (num_bytes > reduced_mtu ||
> + (flags & (GIP_FLAG_ACME | GIP_FLAG_FRAGMENT)) == GIP_FLAG_ACME) {
> + for (i = 0; i < MAX_OUT_FRAGMENTS; i++) {
[Severity: High]
Are fragment slots permanently consumed if an ACME message is sent but the
device drops the packet and never ACKs it? Since there doesn't appear to be a
timeout or cleanup mechanism for pending fragments, a couple of dropped packets
might permanently exhaust the queue.
> + if (attachment->out_fragments[i].active)
> + continue;
> +
> + fragment = &attachment->out_fragments[i];
> + break;
> + }
> +
> + if (!fragment) {
> + gip_err(attachment, "Fragmented message queue is full; dropping message\n");
> + return -EALREADY;
> + }
> +
> + fragment->active = true;
[Severity: High]
Can multiple threads race to claim the same fragment slot here? This loop and
the subsequent assignment to fragment->active appear to occur without holding
attachment->lock or device->message_lock.
> + fragment->message = message_type;
[ ... ]
> + fragment->data = devm_kmalloc(to_gip_device(attachment),
> + fragment->total_length, GFP_ATOMIC);
> + fragment->fragment_offset = 0;
> + memcpy(fragment->data, bytes, num_bytes);
[Severity: High]
Could this memcpy dereference a NULL pointer if the devm_kmalloc allocation
fails? The GFP_ATOMIC flag means the allocation can fail under memory
pressure, but there is no check before accessing the returned pointer.
[Severity: Medium]
Is devm_kmalloc the right choice for dynamic, per-message buffers? Since
devm_kfree requires a linear scan of the device's devres linked list under a
spinlock, frequent allocations and frees here might cause noticeable overhead
and list fragmentation. Would standard kmalloc and kfree be better suited?
[ ... ]
> @@ -522,15 +659,10 @@ static int gip_send_raw_message(struct gip_attachment *attachment,
[ ... ]
> for (i = 0; i < MAX_OUT_MESSAGES && !urb; i++) {
> if (!intf->out_queue[i].urb)
> continue;
> if (!intf->out_queue[i].urb->anchor)
> urb = &intf->out_queue[i];
> }
> if (!urb) {
> gip_err(attachment, "Output queue is full; dropping message\n");
> return -ENOSPC;
> }
[ ... ]
> + rc = usb_submit_urb(urb->urb, GFP_ATOMIC);
> + if (rc) {
> + dev_err(&intf->intf->dev,
> + "%s - usb_submit_urb failed with result %d\n",
> + __func__, rc);
> + usb_unanchor_urb(urb->urb);
> + rc = -EIO;
> + }
[Severity: High]
If the function returns early due to a full output queue (-ENOSPC) or a failure
in usb_submit_urb() (-EIO), does the fragment slot remain permanently active?
It looks like the allocated memory and the active slot aren't freed on these
error paths.
[ ... ]
> +static int gip_send_next_fragment(struct gip_attachment *attachment,
> + struct gip_out_fragment *fragment)
> +{
[ ... ]
> + if (fragment->fragment_offset == fragment->total_length) {
> + if ((fragment->message & GIP_DATA_CLASS_MASK) == GIP_DATA_CLASS_AUDIO)
> + intf = &attachment->device->audio;
> + else
> + intf = &attachment->device->data;
> +
> + next_bytes = 0;
> + intf->has_pending_out &= ~gip_fragment_id(attachment, fragment);
[Severity: High]
Is it safe to perform an unprotected read-modify-write on intf->has_pending_out
here? In gip_send_raw_message(), this same bitmask is modified while holding
the message_lock spinlock. Concurrent modifications could lead to a data race
and cause the driver to lose track of pending output.
[ ... ]
> @@ -2476,6 +2771,7 @@ static void gip_urb_out(struct urb *urb)
> switch (status) {
> case 0:
> /* success */
> + schedule_work(&intf->send_fragment);
> break;
[Severity: High]
Could this result in a use-after-free during driver teardown?
If gip_disconnect() frees the out_queue URBs using usb_free_urb() instead of
usb_kill_urb(), in-flight URBs might complete after the device structures are
freed. The completion handler here would then schedule the send_fragment work
using a freed intf context.
[ ... ]
A few additional observations in the surrounding codebase:
> static void gip_shutdown(struct gip_device *gip)
> {
[ ... ]
> scoped_guard (mutex, &attachment->lock) {
> cancel_delayed_work_sync(&attachment->metadata_next);
> cancel_delayed_work_sync(&attachment->in_fragment_timeout);
[Severity: High]
This isn't a bug introduced by this patch, but could this cause an AB-BA
deadlock? gip_shutdown() acquires attachment->lock before waiting synchronously
on these works, but the callbacks for both works (gip_retry_metadata() and
gip_fragment_timeout()) begin by acquiring the same attachment->lock.
> static int gip_receive_fragment(...)
> {
[ ... ]
> if (header->length != 0)
> memcpy(&attachment->in_fragment_data[*fragment_offset],
> &bytes[offset], header->length);
[Severity: High]
This isn't a bug introduced by this patch, but is it safe to blindly trust
header->length here? There doesn't appear to be a check ensuring that
offset + header->length <= num_bytes (the actual size of the received USB
packet). A malicious device could send a short packet with a large length field,
potentially causing an out-of-bounds read from the kernel heap.
> static int gip_receive_fragment(...)
> {
[ ... ]
> attachment->in_fragment_message = header->message_type;
> ...
> attachment->in_fragment_data = devm_kmalloc(to_gip_device(attachment),
> attachment->in_total_length, GFP_KERNEL);
> if (!attachment->in_fragment_data)
> return -ENOMEM;
[Severity: High]
This isn't a bug introduced by this patch, but does an allocation failure leave
the state inconsistent? By updating in_fragment_message before a failing
devm_kmalloc(), a subsequent fragment from the device might pass the message
type check and blindly memcpy into a NULL in_fragment_data pointer.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=6
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 02/12] Input: xpad - Remove Xbox One support
2026-08-22 2:11 ` [PATCH v4 02/12] Input: xpad - Remove Xbox One support Vicki Pfau
@ 2026-08-22 2:41 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:41 UTC (permalink / raw)
To: Vicki Pfau; +Cc: linux-input, dmitry.torokhov
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
New issues:
- [Low] Incomplete feature removal leaves behind dead code related to Xbox One support.
Pre-existing issues:
- [High] Use-after-free due to missing cancel_work_sync() in xpad_probe error path.
--
--- Patch [2]: [PATCH v4 02/12] Input: xpad - Remove Xbox One support ---
commit 5a5aa152af89fbca852a5ae95c5acba0ea842ea5
Author: Vicki Pfau <vi@endrift.com>
Input: xpad - Remove Xbox One support
This commit removes Xbox One controller support from the xpad driver, as
it has been superseded by the xbox_gip driver.
> diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
> --- a/drivers/input/joystick/xpad.c
> +++ b/drivers/input/joystick/xpad.c
[ ... ]
> @@ -754,7 +476,6 @@ struct usb_xpad {
> struct urb *irq_out; /* urb for interrupt out report */
> struct usb_anchor irq_out_anchor;
> bool irq_out_active; /* we must not use an active URB */
> - u8 odata_serial; /* serial number for xbox one protocol */
> unsigned char *odata; /* output data */
> dma_addr_t odata_dma;
> spinlock_t odata_lock;
[Severity: Low]
Since Xbox One support is being removed, is it intentional that fields like
init_seq, packet_type, delay_init, and delayed_init_done were left in struct
usb_xpad? They appear to be completely unused now and could cause confusion.
For example, xpad_probe() still contains a large block of logic detecting
Elite firmware revisions purely to set xpad->packet_type:
xpad_probe() {
...
/* Packet type detection */
if (le16_to_cpu(udev->descriptor.idVendor) == 0x045e) {
if (le16_to_cpu(udev->descriptor.idProduct) == 0x02e3) {
/* The original elite controller always uses the oldest
...
}
Could this dead code be cleaned up as part of the removal?
[ ... ]
> @@ -2090,8 +1484,6 @@ static int xpad_probe(struct usb_interface *intf, const struct usb_device_id *id
> if (intf->cur_altsetting->desc.bInterfaceClass == USB_CLASS_VENDOR_SPEC) {
> if (intf->cur_altsetting->desc.bInterfaceProtocol == 129)
> xpad->xtype = XTYPE_XBOX360W;
> - else if (intf->cur_altsetting->desc.bInterfaceProtocol == 208)
> - xpad->xtype = XTYPE_XBOXONE;
> else
> xpad->xtype = XTYPE_XBOX360;
> } else {
[Severity: High]
This is a pre-existing issue, but does the error path in xpad_probe() contain
a use-after-free risk if xpad360w_start_input() fails?
If xpad360w_start_input() fails after submitting the input URB (for instance,
if xpad_inquiry_pad_presence() fails), it returns an error. The error path in
xpad_probe() then frees the xpad struct:
err_deinit_output:
xpad_deinit_output(xpad);
err_free_in_urb:
usb_free_urb(xpad->irq_in);
err_free_idata:
usb_free_coherent(udev, XPAD_PKT_LEN, xpad->idata, xpad->idata_dma);
err_free_mem:
kfree(xpad);
return error;
However, if the URB completes before being fully killed and
xpad360w_process_packet() handles a presence change, it calls
schedule_work(&xpad->work).
Because the error path never calls cancel_work_sync(&xpad->work), the
workqueue might dereference the freed xpad pointer when xpad_presence_work()
executes. Could a malicious USB device trigger this race by intentionally
failing the presence inquiry while sending a valid URB completion?
[ ... ]
> @@ -2291,15 +1672,6 @@ static int xpad_resume(struct usb_interface *intf)
> if (input_device_enabled(input))
> return xpad_start_input(xpad);
>
> - if (xpad->xtype == XTYPE_XBOXONE) {
> - /*
> - * Even if there are no users, we'll send Xbox One pads
> - * the startup sequence so they don't sit there and
> - * blink until somebody opens the input device again.
> - */
> - return xpad_start_xbox_one(xpad);
> - }
> -
> return 0;
> }
[Severity: Low]
The delayed_init_done flag is still being cleared at the start of
xpad_resume() despite being unused elsewhere. Should this assignment be
removed as well?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=2
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 12/12] Input: xbox_gip - Add wheel support
2026-08-22 2:11 ` [PATCH v4 12/12] Input: xbox_gip - Add wheel support Vicki Pfau
@ 2026-08-22 2:54 ` sashiko-bot
0 siblings, 0 replies; 28+ messages in thread
From: sashiko-bot @ 2026-08-22 2:54 UTC (permalink / raw)
To: Vicki Pfau; +Cc: dmitry.torokhov, linux-input
Thank you for your contribution! Sashiko AI review found 5 potential issue(s) to consider:
New issues:
- [High] Mismatched raw value offset and declared axis bounds for the ABS_WHEEL axis.
- [High] Brittle wraparound logic for the TrueForce wheel hardware rotary dial causes erratic backward spikes.
- [Medium] Packet length validation occurs after partially processing events, causing dropped input_sync() calls and stuck events.
Pre-existing issues:
- [High] Dangling pointer left in attachment->input when input_register_device() fails, leading to Use-After-Free on subsequent input reports.
- [High] AB-BA Deadlock between gip_receive_fragment and gip_fragment_timeout workqueue handler.
--
--- Patch [12]: [PATCH v4 12/12] Input: xbox_gip - Add wheel support ---
commit fd5878269a3ac89add6a8a8b7336791b58d87703
Author: Vicki Pfau <vi@endrift.com>
Input: xbox_gip - Add wheel support
This adds preliminary support for racing wheel support in xbox_gip,
exposing them mapped to the newly added axes.
Signed-off-by: Vicki Pfau <vi@endrift.com>
> diff --git a/drivers/input/joystick/gip/gip-wheel.c b/drivers/input/joystick/gip/gip-wheel.c
> new file mode 100644
> index 0000000000000..333910448c901
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-wheel.c
[ ... ]
> +static int gip_handle_wheel_report(struct gip_attachment *attachment,
> + struct input_dev *input, const uint8_t *bytes, int num_bytes)
> +{
> + uint8_t connections;
> + struct gip_wheel_info *info = attachment->driver_data;
> + int rc = gip_driver_navigation.handle_input_report(attachment, input, bytes, num_bytes);
> +
> + if (rc < 0)
> + return rc;
> +
> + if (!info)
> + return -ENODEV;
> +
> + if (num_bytes < 17)
> + return -EINVAL;
[Severity: Medium]
If the packet length is less than 17,
gip_driver_navigation.handle_input_report() has already processed the first
2 bytes and pushed button states to the input core buffer.
Returning -EINVAL here causes the caller to exit without calling
input_sync(). Will this leave the parsed button events stuck in the input
buffer, leading to desynchronized state in userspace?
Could we validate the total packet length before calling the base handler to
modify device state?
> +
> + input_report_abs(input, ABS_WHEEL, get_unaligned_le16(&bytes[2]) - 0x8000);
[Severity: High]
In gip_handle_wheel_ll_static_configuration(), max_angle is calculated as
65535 for 16-bit precision, setting the ABS_WHEEL bounds to [-65536, 65535].
Here, the raw 16-bit wheel value (0-65535) is shifted by a hardcoded 0x8000
(32768), resulting in physical values ranging strictly from -32768 to 32767.
Does this restrict the reported values to only cover 50% of the declared
logical bounding box?
Additionally, if the device reports a precision other than 16 bits, will
this hardcoded 0x8000 shift push the values completely outside the bounding
box?
[ ... ]
> +static int gip_handle_trueforce_wheel_report(struct gip_attachment *attachment,
> + struct input_dev *input, const uint8_t *bytes, int num_bytes)
> +{
[ ... ]
> + if (dial == 0 && state->dial == 7)
> + input_report_rel(input, REL_DIAL, -1);
> + else if (dial == 7 && state->dial == 0)
> + input_report_rel(input, REL_DIAL, 1);
> + else
> + input_report_rel(input, REL_DIAL,
> + state->dial - dial);
[Severity: High]
If the user spins the hardware dial rapidly, the 3-bit value might jump by
more than one step per report (e.g., from 7 to 1).
Will this bypass the single-step wrap checks and fall back to
state->dial - dial (7 - 1 = 6), erroneously reporting a massive 6-step
backward jump instead of the actual 2-step forward movement?
Can this be updated to use modular arithmetic (like sign-extending the
3-bit difference) instead of checking for exact neighbor boundaries?
[ ... ]
> diff --git a/drivers/input/joystick/gip/gip-core.c b/drivers/input/joystick/gip/gip-core.c
> --- a/drivers/input/joystick/gip/gip-core.c
> +++ b/drivers/input/joystick/gip/gip-core.c
[ ... ]
> int gip_setup_input_device(struct gip_attachment *attachment)
> {
> struct input_dev *input;
[ ... ]
> input_set_drvdata(input, attachment);
> rcu_assign_pointer(attachment->input, input);
> rc = input_register_device(input);
> if (rc)
> goto err_free_device;
[ ... ]
> err_free_device:
> input_free_device(input);
> return rc;
> }
[Severity: High]
This is a pre-existing issue, but in gip_setup_input_device(), the allocated
input_dev is assigned to attachment->input via rcu_assign_pointer().
If input_register_device() fails, we jump to err_free_device and call
input_free_device(input). Does this leave a dangling pointer in
attachment->input that could cause a use-after-free when a subsequent
input report is processed?
[ ... ]
> static void gip_receive_fragment(struct gip_attachment *attachment,
> const uint8_t *bytes, int num_bytes)
> {
[ ... ]
> cancel_delayed_work_sync(&attachment->in_fragment_timeout);
[Severity: High]
This is a pre-existing issue, but I noticed a potential deadlock between
gip_receive_fragment() and gip_fragment_timeout().
gip_receive_message() acquires attachment->lock before calling
gip_receive_fragment(), which then calls
cancel_delayed_work_sync(&attachment->in_fragment_timeout).
However, the delayed work handler, gip_fragment_timeout(), immediately
attempts to acquire attachment->lock:
static void gip_fragment_timeout(struct work_struct *work)
{
...
guard(mutex)(&attachment->lock);
...
}
If the delayed work has already started executing on another CPU, will
cancel_delayed_work_sync() block forever waiting for the work to finish,
while the work handler blocks forever waiting for attachment->lock to
be released?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260822021140.1149546-1-vi@endrift.com?part=12
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone
2026-08-22 2:11 ` [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone Vicki Pfau
2026-08-22 2:35 ` sashiko-bot
@ 2026-08-24 21:49 ` Eric Biggers
2026-08-28 1:40 ` Vicki Pfau
1 sibling, 1 reply; 28+ messages in thread
From: Eric Biggers @ 2026-08-24 21:49 UTC (permalink / raw)
To: Vicki Pfau; +Cc: Dmitry Torokhov, linux-input
On Fri, Aug 21, 2026 at 07:11:31PM -0700, Vicki Pfau wrote:
> +int gip_security_start_handshake(struct gip_security *security)
> +{
> + struct shash_desc *shash_transcript, *shash_prf;
> +
> + if (!security->shash_transcript) {
> + shash_transcript = gip_security_alloc_shash("sha256");
> + if (IS_ERR(shash_transcript))
> + return PTR_ERR(shash_transcript);
> +
> + security->shash_transcript = shash_transcript;
> + }
> +
> + if (!security->shash_prf) {
> + shash_prf = gip_security_alloc_shash("hmac(sha256)");
> + if (IS_ERR(shash_prf)) {
> + crypto_free_shash(shash_transcript->tfm);
> + kfree(shash_transcript);
> + security->shash_transcript = NULL;
> + return PTR_ERR(shash_prf);
> + }
> +
> + security->shash_prf = shash_prf;
> + }
There's no need to use crypto_shash. Just call the SHA-256 and
HMAC-SHA256 functions (<crypto/sha2.h>) directly.
- Eric
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone
2026-08-24 21:49 ` Eric Biggers
@ 2026-08-28 1:40 ` Vicki Pfau
2026-08-28 1:50 ` Eric Biggers
0 siblings, 1 reply; 28+ messages in thread
From: Vicki Pfau @ 2026-08-28 1:40 UTC (permalink / raw)
To: Eric Biggers; +Cc: Dmitry Torokhov, linux-input
Hi Eric,
On 8/24/26 2:49 PM, Eric Biggers wrote:
> On Fri, Aug 21, 2026 at 07:11:31PM -0700, Vicki Pfau wrote:
>> +int gip_security_start_handshake(struct gip_security *security)
>> +{
>> + struct shash_desc *shash_transcript, *shash_prf;
>> +
>> + if (!security->shash_transcript) {
>> + shash_transcript = gip_security_alloc_shash("sha256");
>> + if (IS_ERR(shash_transcript))
>> + return PTR_ERR(shash_transcript);
>> +
>> + security->shash_transcript = shash_transcript;
>> + }
>> +
>> + if (!security->shash_prf) {
>> + shash_prf = gip_security_alloc_shash("hmac(sha256)");
>> + if (IS_ERR(shash_prf)) {
>> + crypto_free_shash(shash_transcript->tfm);
>> + kfree(shash_transcript);
>> + security->shash_transcript = NULL;
>> + return PTR_ERR(shash_prf);
>> + }
>> +
>> + security->shash_prf = shash_prf;
>> + }
>
> There's no need to use crypto_shash. Just call the SHA-256 and
> HMAC-SHA256 functions (<crypto/sha2.h>) directly.
This code was originally written by someone else more familiar with these functions than me, but it looks like shash_transcript gets finalized repeatedly without being reset. See gip_security_get_transcript, which exports the state, finalizes it, then rolls back the state before finalizing it. I don't think this can be done with the basic sha2 functions. I might be able to do it with the HMAC though. I'd need to familiarize myself with the API first.>
> - Eric
Vicki
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone
2026-08-28 1:40 ` Vicki Pfau
@ 2026-08-28 1:50 ` Eric Biggers
2026-08-28 1:51 ` Vicki Pfau
0 siblings, 1 reply; 28+ messages in thread
From: Eric Biggers @ 2026-08-28 1:50 UTC (permalink / raw)
To: Vicki Pfau; +Cc: Dmitry Torokhov, linux-input
On Thu, Aug 27, 2026 at 06:40:01PM -0700, Vicki Pfau wrote:
> >
> > There's no need to use crypto_shash. Just call the SHA-256 and
> > HMAC-SHA256 functions (<crypto/sha2.h>) directly.
>
> This code was originally written by someone else more familiar with
> these functions than me, but it looks like shash_transcript gets
> finalized repeatedly without being reset. See
> gip_security_get_transcript, which exports the state, finalizes it,
> then rolls back the state before finalizing it. I don't think this can
> be done with the basic sha2 functions.
What do you mean? In the library the context is just a plain struct, so
you can just make a copy of it if needed. Like this:
static void gip_security_get_transcript(const struct sha256_ctx *ctx,
u8 transcript[GIP_SECURITY_TRANSCRIPT_LEN])
{
struct sha256_ctx tmp = *ctx;
sha256_final(&tmp, transcript);
}
- Eric
^ permalink raw reply [flat|nested] 28+ messages in thread
* Re: [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone
2026-08-28 1:50 ` Eric Biggers
@ 2026-08-28 1:51 ` Vicki Pfau
0 siblings, 0 replies; 28+ messages in thread
From: Vicki Pfau @ 2026-08-28 1:51 UTC (permalink / raw)
To: Eric Biggers; +Cc: Dmitry Torokhov, linux-input
Hi Eric,
On 8/27/26 6:50 PM, Eric Biggers wrote:
> On Thu, Aug 27, 2026 at 06:40:01PM -0700, Vicki Pfau wrote:
>>>
>>> There's no need to use crypto_shash. Just call the SHA-256 and
>>> HMAC-SHA256 functions (<crypto/sha2.h>) directly.
>>
>> This code was originally written by someone else more familiar with
>> these functions than me, but it looks like shash_transcript gets
>> finalized repeatedly without being reset. See
>> gip_security_get_transcript, which exports the state, finalizes it,
>> then rolls back the state before finalizing it. I don't think this can
>> be done with the basic sha2 functions.
>
> What do you mean? In the library the context is just a plain struct, so
> you can just make a copy of it if needed. Like this:
>
> static void gip_security_get_transcript(const struct sha256_ctx *ctx,
> u8 transcript[GIP_SECURITY_TRANSCRIPT_LEN])
> {
> struct sha256_ctx tmp = *ctx;
>
> sha256_final(&tmp, transcript);
> }
>
I probably should have thought of that. That does make sense. I'm still trying to make sense of this code to fix things sashiko reported.
> - Eric
Vicki
^ permalink raw reply [flat|nested] 28+ messages in thread
end of thread, other threads:[~2026-08-28 1:51 UTC | newest]
Thread overview: 28+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-22 2:11 [PATCH v4 00/12] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 01/12] " Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 02/12] Input: xpad - Remove Xbox One support Vicki Pfau
2026-08-22 2:41 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 03/12] Input: xbox_gip - Add controllable LED support Vicki Pfau
2026-08-22 2:37 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 04/12] Input: xbox_gip - Add HID relaying Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 05/12] Input: xbox_gip - Add battery support Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 06/12] Input: xbox_gip - Allow sending fragmented and ACME messages Vicki Pfau
2026-08-22 2:39 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 07/12] Input: xbox_gip - Add security implementation from xone Vicki Pfau
2026-08-22 2:35 ` sashiko-bot
2026-08-24 21:49 ` Eric Biggers
2026-08-28 1:40 ` Vicki Pfau
2026-08-28 1:50 ` Eric Biggers
2026-08-28 1:51 ` Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 08/12] Input: xbox_gip - Add arcade stick support Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 09/12] Input: xbox_gip - Add support for PDP guitar controllers Vicki Pfau
2026-08-22 2:30 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 10/12] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER Vicki Pfau
2026-08-22 2:38 ` sashiko-bot
2026-08-22 2:11 ` [PATCH v4 11/12] HID: Map more automobile simulation inputs Vicki Pfau
2026-08-22 2:11 ` [PATCH v4 12/12] Input: xbox_gip - Add wheel support Vicki Pfau
2026-08-22 2:54 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox