public inbox for linux-input@vger.kernel.org
 help / color / mirror / Atom feed
From: Vicki Pfau <vi@endrift.com>
To: Dmitry Torokhov <dmitry.torokhov@gmail.com>, linux-input@vger.kernel.org
Subject: Re: [PATCH v3 01/10] Input: xbox_gip - Add new driver for Xbox GIP
Date: Tue, 10 Mar 2026 17:41:31 -0700	[thread overview]
Message-ID: <1bade97e-22b8-401e-9727-99141c754090@endrift.com> (raw)
In-Reply-To: <20260310052017.1289494-2-vi@endrift.com>

Hello,

In my haste, it seems I forgot to run checkpatch before submitting this. There were a bunch of stylistic errors, which I have fixed locally and will have fixed in v4. That said, I would still appreciate a review on things that aren't directly found by checkpatch as I would like v4 to be the last version of the patch submitted before being merged.

On 3/9/26 10:19 PM, Vicki Pfau wrote:
> 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       |   30 +
>  drivers/input/joystick/gip/Makefile      |    3 +
>  drivers/input/joystick/gip/gip-core.c    | 2536 ++++++++++++++++++++++
>  drivers/input/joystick/gip/gip-drivers.c |  210 ++
>  drivers/input/joystick/gip/gip.h         |  309 +++
>  8 files changed, 3097 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 9ed6d11a77466..6c744d0af359d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -27927,6 +27927,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 7755e5b454d2c..d4665c80a3713 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 9976f596a9208..323392921b7dc 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 0000000000000..83293df3b0410
> --- /dev/null
> +++ b/drivers/input/joystick/gip/Kconfig
> @@ -0,0 +1,30 @@
> +# 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_ARCH_HAS_HCD
> +	select USB
> +	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.
> +
> +config JOYSTICK_XBOX_GIP_LEDS
> +	bool "LED Support for the Xbox One/Series controller Guide button"
> +	depends on JOYSTICK_XBOX_GIP && LEDS_CLASS_MULTICOLOR
> +	help
> +	  This option enables support for the LED which surrounds the Big X on
> +	  Xbox One/Series controllers.

This config entry isn't used until a later patch and I have already locally moved it to that patch.

> +
> diff --git a/drivers/input/joystick/gip/Makefile b/drivers/input/joystick/gip/Makefile
> new file mode 100644
> index 0000000000000..a75e0cace0f92
> --- /dev/null
> +++ b/drivers/input/joystick/gip/Makefile
> @@ -0,0 +1,3 @@
> +# SPDX-License-Identifier: GPL-2.0-or-later
> +obj-$(CONFIG_JOYSTICK_XBOX_GIP)	+= xbox-gip.o
> +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 0000000000000..0881797592fea
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-core.c
> @@ -0,0 +1,2536 @@
> +// 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/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_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
> +
> +#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_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_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[] = {
> +	/* PDP Rock Candy */
> +	{ 0x0e6f, 0x0246, 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_hello_device {
> +	uint64_t device_id;
> +	uint16_t vendor_id;
> +	uint16_t product_id;
> +	uint16_t firmware_major_version;
> +	uint16_t firmware_minor_version;
> +	uint16_t firmware_build_version;
> +	uint16_t 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;
> +};
> +
> +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] & (1u << command);
> +	else
> +		return attachment->metadata.device
> +			.out_system_messages[command >> 5] & (1u << 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_device *device,
> +	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) {
> +		dev_warn(GIP_DEV(device), "Invalid message length %d\n", num_bytes);
> +		return -EINVAL;
> +	}
> +
> +	if (num_bytes > gip_data_class_mtu[message_type >> GIP_DATA_CLASS_SHIFT]) {
> +		dev_err(GIP_DEV(device),
> +			"Attempted to send a message that requires fragmenting, which is not yet supported.\n");
> +		return -ENOTSUPP;
> +	}
> +
> +	if ((message_type & GIP_DATA_CLASS_MASK) == GIP_DATA_CLASS_AUDIO)
> +		intf = &device->audio;
> +	else
> +		intf = &device->data;
> +
> +	if (intf->isoc_messages) {
> +		/* TODO: Needed for audio support */
> +		dev_warn(GIP_DEV(intf), "Unimplemented isochronous message output\n");
> +		return -ENOTSUPP;
> +	}
> +
> +	guard(spinlock_irqsave)(&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) {
> +		dev_err(GIP_DEV(device), "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->device, 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->device, 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;
> +
> +	bytes = &bytes[*offset];
> +	num_bytes -= *offset;
> +	if (num_bytes < 16)
> +		return -EINVAL;
> +
> +	length = bytes[0];
> +	length |= bytes[1] << 8;
> +	if (num_bytes < length)
> +		return -EINVAL;
> +
> +	/* Skip supported firmware versions for now */
> +
> +	buffer_offset = bytes[4];
> +	buffer_offset |= bytes[5] << 8;
> +	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 = bytes[6];
> +	buffer_offset |= bytes[7] << 8;
> +	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 = bytes[8];
> +	buffer_offset |= bytes[9] << 8;
> +	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 = bytes[10];
> +	buffer_offset |= bytes[11] << 8;
> +	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 = bytes[12];
> +	buffer_offset |= bytes[13] << 8;
> +	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 = bytes[14];
> +		buffer_offset |= bytes[15] << 8;
> +		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 = bytes[0];
> +	length |= bytes[1] << 8;
> +	if (num_bytes < length)
> +		return -EINVAL;
> +
> +	if (length < 15)
> +		return -EINVAL;
> +
> +	metadata->type = bytes[2];
> +	metadata->length = bytes[3];
> +	metadata->length |= bytes[4] << 8;
> +	metadata->data_type = bytes[5];
> +	metadata->data_type |= bytes[6] << 8;
> +	metadata->flags = bytes[7];
> +	metadata->flags |= bytes[8] << 8;
> +	metadata->flags |= bytes[9] << 16;
> +	metadata->flags |= bytes[10] << 24;
> +	metadata->period = bytes[11];
> +	metadata->period |= bytes[12] << 8;
> +	metadata->persistence_timeout = bytes[13];
> +	metadata->persistence_timeout |= bytes[14] << 8;
> +
> +	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 = bytes[0];
> +	header_size |= bytes[1] << 8;
> +	if (num_bytes < header_size || header_size < 16)
> +		return -EINVAL;
> +
> +	metadata->version_major = bytes[2];
> +	metadata->version_major |= bytes[3] << 8;
> +	metadata->version_minor = bytes[4];
> +	metadata->version_minor |= bytes[5] << 8;
> +	/* Middle bytes are reserved */
> +	metadata_size = bytes[14];
> +	metadata_size |= bytes[15] << 8;
> +
> +	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_device *device,
> +	const struct gip_header *header, uint32_t fragment_offset,
> +	uint16_t bytes_remaining)
> +{
> +	uint8_t buffer[] = {
> +		GIP_CONTROL_CODE_ACK,
> +		header->message_type,
> +		header->flags & GIP_FLAG_SYSTEM,
> +		fragment_offset,
> +		fragment_offset >> 8,
> +		fragment_offset >> 16,
> +		fragment_offset >> 24,
> +		bytes_remaining,
> +		bytes_remaining >> 8,
> +	};
> +
> +	return gip_send_raw_message(device, GIP_CMD_PROTO_CONTROL,
> +		GIP_FLAG_SYSTEM | (header->flags & GIP_FLAG_ATTACHMENT_MASK),
> +		header->sequence_id, buffer, sizeof(buffer));
> +}
> +
> +static int gip_fragment_failed(struct gip_attachment *attachment,
> +	const struct gip_header *header)
> +{
> +	attachment->fragment_retries++;
> +	if (attachment->fragment_retries > 8) {
> +		devm_kfree(GIP_DEV(attachment), attachment->fragment_data);
> +		attachment->fragment_data = NULL;
> +		attachment->fragment_message = 0;
> +	}
> +	return gip_acknowledge(attachment->device, header,
> +		attachment->fragment_offset,
> +		attachment->total_length - attachment->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 == 0x045e) {
> +		if (attachment->product_id == 0x02e3) {
> +			attachment->xbe_format = GIP_BTN_FMT_XBE1;
> +		} else if (attachment->product_id == 0x0b00) {
> +			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) {
> +		dev_dbg(GIP_DEV(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;
> +
> +	rc = attachment->driver->setup_input(attachment, input);
> +	if (rc < 0)
> +		goto err_free_device;
> +
> +	if (attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP)
> +		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_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);
> +
> +	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 0;
> +}
> +
> +static void gip_fragment_timeout(struct work_struct *work)
> +{
> +	struct gip_attachment *attachment = container_of(to_delayed_work(work),
> +		struct gip_attachment, fragment_timeout);
> +
> +	guard(mutex)(&attachment->lock);
> +	devm_kfree(GIP_DEV(attachment), attachment->fragment_data);
> +	attachment->fragment_data = NULL;
> +	attachment->fragment_message = 0;
> +}
> +
> +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 {
> +		dev_info(GIP_DEV(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)
> +		return;
> +
> +	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] |= (1u << 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 bool gip_handle_command_protocol_control(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	/* TODO */
> +	dev_warn(GIP_DEV(attachment), "Unimplemented Protocol Control message\n");
> +	return -ENOTSUPP;
> +}
> +
> +static bool gip_handle_command_hello_device(struct gip_attachment *attachment,
> +	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
> +{
> +	struct gip_hello_device message = {0};
> +
> +	if (num_bytes != 28)
> +		return -EINVAL;
> +
> +	message.device_id = (uint64_t) bytes[0];
> +	message.device_id |= (uint64_t) bytes[1] << 8;
> +	message.device_id |= (uint64_t) bytes[2] << 16;
> +	message.device_id |= (uint64_t) bytes[3] << 24;
> +	message.device_id |= (uint64_t) bytes[4] << 32;
> +	message.device_id |= (uint64_t) bytes[5] << 40;
> +	message.device_id |= (uint64_t) bytes[6] << 48;
> +	message.device_id |= (uint64_t) bytes[7] << 56;
> +
> +	message.vendor_id = bytes[8];
> +	message.vendor_id |= bytes[9] << 8;
> +
> +	message.product_id = bytes[10];
> +	message.product_id |= bytes[11] << 8;
> +
> +	message.firmware_major_version = bytes[12];
> +	message.firmware_major_version |= bytes[13] << 8;
> +
> +	message.firmware_minor_version = bytes[14];
> +	message.firmware_minor_version |= bytes[15] << 8;
> +
> +	message.firmware_build_version = bytes[16];
> +	message.firmware_build_version |= bytes[17] << 8;
> +
> +	message.firmware_revision = bytes[18];
> +	message.firmware_revision |= bytes[19] << 8;
> +
> +	message.hardware_major_version = bytes[20];
> +	message.hardware_minor_version = bytes[21];
> +
> +	message.rf_proto_major_version = bytes[22];
> +	message.rf_proto_minor_version = bytes[23];
> +
> +	message.security_major_version = bytes[24];
> +	message.security_minor_version = bytes[25];
> +
> +	message.gip_major_version = bytes[26];
> +	message.gip_minor_version = bytes[27];
> +
> +	dev_dbg(GIP_DEV(attachment), "Device hello from %llx (%04x:%04x)\n",
> +		message.device_id, message.vendor_id, message.product_id);
> +	dev_dbg(GIP_DEV(attachment), "Firmware version %d.%d.%d rev %d\n",
> +		message.firmware_major_version, message.firmware_minor_version,
> +		message.firmware_build_version, 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)
> +		dev_warn(GIP_DEV(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)
> +		dev_warn(GIP_DEV(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)
> +		dev_warn(GIP_DEV(attachment),
> +			"Invalid GIP version %d.%d, expected 1.0\n",
> +			message.gip_major_version, message.gip_minor_version);
> +
> +	attachment->firmware_major_version = message.firmware_major_version;
> +	attachment->firmware_minor_version = message.firmware_minor_version;
> +	attachment->vendor_id = message.vendor_id;
> +	attachment->product_id = message.product_id;
> +	attachment->uniq = devm_kasprintf(GIP_DEV(attachment),
> +		GFP_KERNEL, "%llx", 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;
> +	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 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) {
> +				dev_info(GIP_DEV(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 = bytes[i * 10 + 5];
> +				event->event_type |= bytes[i * 10 + 6] << 8;
> +				event->fault_tag = bytes[i * 10 + 7];
> +				event->fault_tag |= bytes[i * 10 + 8] << 8;
> +				event->fault_tag |= bytes[i * 10 + 9] << 16;
> +				event->fault_tag |= bytes[i * 10 + 10] << 24;
> +				event->fault_address = bytes[i * 10 + 11];
> +				event->fault_address |= bytes[i * 10 + 12] << 8;
> +				event->fault_address |= bytes[i * 10 + 13] << 16;
> +				event->fault_address |= bytes[i * 10 + 14] << 24;
> +
> +				dev_info(GIP_DEV(attachment),
> +					"Attachment %i event type %i, tag %i address %x\n",
> +					attachment->attachment_index,
> +					event->event_type,
> +					event->fault_tag,
> +					event->fault_address);
> +			}
> +		}
> +	}
> +
> +	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(GIP_DEV(attachment), &metadata, bytes, num_bytes);
> +	if (rc)
> +		return rc;
> +
> +	if (attachment->got_metadata == GIP_METADATA_GOT) {
> +		struct input_dev *input;
> +
> +		gip_metadata_free(GIP_DEV(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->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];
> +
> +		dev_dbg(GIP_DEV(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) {
> +					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;
> +		}

This is one of the aforementioned stylistic errors. I have fixed it locally.

> +		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];
> +
> +		dev_dbg(GIP_DEV(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)
> +		dev_dbg(GIP_DEV(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;
> +
> +	dev_dbg(GIP_DEV(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 */
> +	dev_warn(GIP_DEV(attachment), "Unimplemented Security message\n");
> +	return -ENOTSUPP;
> +}
> +
> +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 */
> +	dev_warn(GIP_DEV(attachment), "Unimplemented Audio Control message\n");
> +	return -ENOTSUPP;
> +}
> +
> +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) {
> +			dev_dbg(GIP_DEV(attachment),
> +				"Discarding too-short firmware message\n");
> +
> +			return -EINVAL;
> +		}
> +		major = bytes[6];
> +		major |= bytes[7] << 8;
> +		minor = bytes[8];
> +		minor |= bytes[9] << 8;
> +		build = bytes[10];
> +		build |= bytes[11] << 8;
> +		rev = bytes[12];
> +		rev |= bytes[13] << 8;
> +
> +		dev_dbg(GIP_DEV(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 == 0x045e
> +			&& attachment->product_id == 0x0b00)
> +			return gip_enable_elite_buttons(attachment);
> +
> +		return 0;
> +	}
> +
> +	dev_warn(GIP_DEV(attachment), "Unimplemented Firmware message\n");
> +
> +	return -ENOTSUPP;
> +}
> +
> +static int gip_handle_command_hid_report(struct gip_attachment *attachment,
> +	const struct gip_header *header, uint8_t *bytes, int num_bytes)
> +{
> +	dev_warn(GIP_DEV(attachment), "Unimplemented HID report message\n");
> +
> +	return -ENOTSUPP;
> +}
> +
> +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) {
> +		dev_dbg(GIP_DEV(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 */
> +		dev_dbg(GIP_DEV(attachment), "Unimplemented extended message type %02x\n",
> +			bytes[0]);
> +		return -ENOTSUPP;
> +	}
> +
> +	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 (num_bytes < 32)
> +		return 0;
> +
> +	if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) {
> +		/* The dynamic latency input bytes are after the console function map */
> +		if (num_bytes >= 40)
> +			function_map_offset = num_bytes - 26;
> +	} else {
> +		function_map_offset = num_bytes - 18;
> +	}
> +	if (function_map_offset >= 14) {
> +		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) {
> +		dev_dbg(GIP_DEV(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;
> +	}
> +
> +	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)
> +{
> +	/* TODO: Unknown if any devices actually use this */
> +	dev_dbg(GIP_DEV(attachment), "Unimplemented Overflow Input Report message\n");
> +	return -ENOTSUPP;
> +}
> +
> +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 */
> +	dev_dbg(GIP_DEV(attachment), "Unimplemented Audio Data message\n");
> +	return -ENOTSUPP;
> +}
> +
> +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)) {
> +		dev_warn(GIP_DEV(attachment),
> +			"Received claimed-unsupported system message type %02x\n",
> +			header->message_type);
> +		return -EINVAL;
> +	}
> +	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:
> +		dev_warn(GIP_DEV(attachment),
> +			"Received unknown system message type %02x\n",
> +			header->message_type);
> +		return -EINVAL;
> +	}
> +}
> +
> +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(GIP_DEV(device),
> +			sizeof(*attachment), GFP_KERNEL);
> +		if (!attachment)
> +			return ERR_PTR(-ENOMEM);
> +
> +		attachment->attachment_index = attachment_index;
> +		attachment->device = device;
> +
> +		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->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);
> +	}
> +	dev_warn(GIP_DEV(attachment),
> +		"Received unknown vendor message type %02x\n",
> +		header->message_type);
> +	return -ENOTSUPP;
> +}
> +
> +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->fragment_message) {
> +			/*
> +			 * Reset fragment buffer if we get a new initial
> +			 * fragment before finishing the last message.
> +			 * TODO: Is this the correct behavior?
> +			 */
> +			devm_kfree(GIP_DEV(attachment), attachment->fragment_data);
> +			attachment->fragment_data = NULL;
> +		}
> +		offset += gip_decode_length(&total_length, &bytes[offset],
> +			num_bytes - offset);
> +		if (total_length > MAX_MESSAGE_LENGTH)
> +			return -EINVAL;
> +
> +		attachment->total_length = total_length;
> +		attachment->fragment_message = header->message_type;
> +		if (header->length > num_bytes - offset) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received fragment that claims to be %llu bytes, expected %i\n",
> +				header->length, num_bytes - offset);
> +			return -EINVAL;
> +		}
> +		if (header->length > total_length) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received too long fragment, %llu bytes, exceeds %d\n",
> +				header->length, attachment->total_length);
> +			return -EINVAL;
> +		}
> +		attachment->fragment_data = devm_kmalloc(GIP_DEV(attachment),
> +			attachment->total_length, GFP_KERNEL);
> +		if (!attachment->fragment_data)
> +			return -ENOMEM;
> +		memcpy(attachment->fragment_data, &bytes[offset],
> +			header->length);
> +		*fragment_offset = header->length;
> +		attachment->fragment_offset = header->length;
> +		*bytes_remaining = attachment->total_length - header->length;
> +	} else {
> +		if (header->message_type != attachment->fragment_message) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received out of sequence message type %02x, expected %02x\n",
> +				header->message_type, attachment->fragment_message);
> +			gip_fragment_failed(attachment, header);
> +			return -EINVAL;
> +		}
> +
> +		offset += gip_decode_length(fragment_offset, &bytes[offset],
> +			num_bytes - offset);
> +		if (*fragment_offset != attachment->fragment_offset) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received out of sequence fragment, (claimed %llu, expected %d)\n",
> +				*fragment_offset, attachment->fragment_offset);
> +			gip_acknowledge(attachment->device, header,
> +				attachment->fragment_offset,
> +				attachment->total_length - attachment->fragment_offset);
> +			return -EINVAL;
> +		} else if (*fragment_offset + header->length > attachment->total_length) {
> +			dev_warn(GIP_DEV(attachment),
> +				"Received too long fragment, %llu exceeds %d\n",
> +				*fragment_offset + header->length, attachment->total_length);
> +			gip_fragment_failed(attachment, header);
> +			return -EINVAL;
> +		}
> +
> +		*bytes_remaining = attachment->total_length -
> +			(*fragment_offset + header->length);
> +		if (header->length != 0) {
> +			memcpy(&attachment->fragment_data[*fragment_offset],
> +				&bytes[offset], header->length);
> +		} else {
> +			rc = gip_handle_message(attachment, header,
> +				attachment->fragment_data,
> +				attachment->total_length);
> +			devm_kfree(GIP_DEV(attachment), attachment->fragment_data);
> +			attachment->fragment_data = NULL;
> +			attachment->fragment_message = 0;
> +		}
> +		*fragment_offset += header->length;
> +		attachment->fragment_offset = *fragment_offset;
> +	}
> +	cancel_delayed_work_sync(&attachment->fragment_timeout);
> +	schedule_delayed_work(&attachment->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) {
> +		dev_warn(GIP_DEV(device),
> +			"Received message with erroneous length (claimed %llu, actual %d), discarding\n",
> +			header.length + offset, num_bytes);
> +		rc = -EINVAL;
> +	} else {
> +		num_bytes -= offset;
> +		bytes += offset;
> +		fragment_offset = header.length;
> +		rc = gip_handle_message(attachment, &header, bytes, num_bytes);
> +	}
> +
> +	if (!rc && (header.flags & GIP_FLAG_ACME))
> +		gip_acknowledge(device, &header, fragment_offset, bytes_remaining);
> +
> +	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 */
> +		dev_warn(GIP_DEV(gip), "Unimplemented isochronous message input\n");
> +		goto exit;
> +	}
> +
> +	spin_lock_irqsave(&gip->message_lock, flags);
> +	if (gip->pending_in_messages >= MAX_IN_MESSAGES) {
> +		dev_err(GIP_DEV(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;
> +
> +	guard(spinlock_irqsave)(&intf->device->message_lock);
> +
> +	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)
> +			dev_warn(GIP_DEV(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);
> +		if (intf->out_queue[i].urb) {
> +			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(GIP_DEV(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->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;
> +		devm_kfree(GIP_DEV(attachment), attachment->uniq);
> +		devm_kfree(GIP_DEV(attachment), attachment);
> +	}
> +
> +	devm_kfree(GIP_DEV(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 0000000000000..f5507e6215a94
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip-drivers.c
> @@ -0,0 +1,210 @@
> +// 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 "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)

This is one of the aforementioned stylistic errors. I have fixed all locations of this in the series.

> +{
> +	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)
> +{
> +	int16_t axis;
> +	int ret = gip_driver_navigation.handle_input_report(attachment, input, bytes, num_bytes);
> +
> +	if (ret < 0)
> +		return ret;
> +
> +	if (num_bytes < 14) {
> +		dev_dbg(GIP_DEV(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));
> +
> +	axis = bytes[2];
> +	axis |= bytes[3] << 8;
> +	input_report_abs(input, ABS_Z, axis);
> +
> +	axis = bytes[4];
> +	axis |= bytes[5] << 8;
> +	input_report_abs(input, ABS_RZ, axis);
> +
> +	axis = bytes[6];
> +	axis |= bytes[7] << 8;
> +	input_report_abs(input, ABS_X, axis);
> +	axis = bytes[8];
> +	axis |= bytes[9] << 8;
> +	input_report_abs(input, ABS_Y, ~axis);
> +	axis = bytes[10];
> +	axis |= bytes[11] << 8;
> +	input_report_abs(input, ABS_RX, axis);
> +	axis = bytes[12];
> +	axis |= bytes[13] << 8;
> +	input_report_abs(input, ABS_RY, ~axis);
> +
> +	/* Xbox Adaptive Controller */
> +	if (attachment->vendor_id == 0x045e && attachment->product_id == 0x0b0a && 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) */
> +		{ 0x045e, 0x02d1, 0, .override_name = "Xbox One Controller" },
> +
> +		/* Xbox One Controller (model 1697) */
> +		{ 0x045e, 0x02dd, 0, .override_name = "Xbox One Controller" },
> +
> +		/* Xbox Elite */
> +		{ 0x045e, 0x02e3, 0,
> +			.override_name = "Xbox Elite Controller",
> +			.added_features = GIP_FEATURE_ELITE_BUTTONS,
> +			.filtered_features = GIP_FEATURE_CONSOLE_FUNCTION_MAP },
> +
> +		/* Xbox One Controller (model 1708) */
> +		{ 0x045e, 0x02ea, 0, .override_name = "Xbox One Controller" },
> +
> +		/* Xbox Elite 2 */
> +		{ 0x045e, 0x0b00, 0,
> +			.override_name = "Xbox Elite Series 2 Controller",
> +			.added_features = GIP_FEATURE_GUIDE_COLOR |
> +				GIP_FEATURE_EXTENDED_SET_DEVICE_STATE },
> +
> +		/* Xbox Adaptive Controller */
> +		{ 0x045e, 0x0b0a, 0, .override_name = "Xbox Adaptive Controller" },
> +
> +		/* Xbox Wireless Controller */
> +		{ 0x045e, 0x0b12, 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_MODE);
> +	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) {
> +		dev_dbg(GIP_DEV(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 0000000000000..2c60430c81590
> --- /dev/null
> +++ b/drivers/input/joystick/gip/gip.h
> @@ -0,0 +1,309 @@
> +// 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_QUIRK_NO_HELLO		BIT(0)
> +#define GIP_QUIRK_NO_IMPULSE_VIBRATION	BIT(1)
> +#define GIP_QUIRK_SWAP_LB_RB		BIT(2)
> +
> +#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)
> +
> +/* 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 GIP_DEV(p) \
> +	_Generic((p), \
> +		struct gip_attachment * : gip_attachment_dev, \
> +		struct gip_interface * : gip_interface_dev, \
> +		struct gip_device * : gip_device_dev)(p)
> +
> +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 {
> +	uint8_t type;
> +	uint16_t length;
> +	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;
> +
> +	uint8_t fragment_message;
> +	uint16_t total_length;
> +	uint8_t *fragment_data;
> +	uint32_t fragment_offset;
> +	struct delayed_work fragment_timeout;
> +	int 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;
> +
> +	int 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);

These are two of the aforementioned stylistic errors. I have fixed all locations of this in the series.

> +	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

Thanks,
Vicki


  reply	other threads:[~2026-03-11  0:41 UTC|newest]

Thread overview: 13+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-03-10  5:19 [PATCH v3 00/10] Input: xbox_gip - Add new driver for Xbox GIP Vicki Pfau
2026-03-10  5:19 ` [PATCH v3 01/10] " Vicki Pfau
2026-03-11  0:41   ` Vicki Pfau [this message]
2026-03-10  5:19 ` [PATCH v3 02/10] Input: xpad - Remove Xbox One support Vicki Pfau
2026-03-10  5:19 ` [PATCH v3 03/10] Input: xbox_gip - Add controllable LED support Vicki Pfau
2026-03-10  5:19 ` [PATCH v3 04/10] Input: xbox_gip - Add HID relaying Vicki Pfau
2026-03-10  5:19 ` [PATCH v3 05/10] Input: xbox_gip - Add battery support Vicki Pfau
2026-03-10  5:20 ` [PATCH v3 06/10] Input: xbox_gip - Add arcade stick support Vicki Pfau
2026-03-10  5:20 ` [PATCH v3 07/10] Input: Add ABS_CLUTCH, HANDBRAKE, and SHIFTER Vicki Pfau
2026-03-10  5:20 ` [PATCH v3 08/10] HID: Map more automobile simulation inputs Vicki Pfau
2026-03-10  5:20 ` [PATCH v3 09/10] Input: xbox_gip - Add wheel support Vicki Pfau
2026-03-10  5:20 ` [PATCH v3 10/10] Input: xbox_gip - Add flight stick support Vicki Pfau
2026-03-10  5:23   ` Vicki Pfau

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1bade97e-22b8-401e-9727-99141c754090@endrift.com \
    --to=vi@endrift.com \
    --cc=dmitry.torokhov@gmail.com \
    --cc=linux-input@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox