Thread (10 messages) 10 messages, 3 authors, 2025-08-11
STALE314d
Revisions (2)
  1. v1 current
  2. v3 [diff vs current]

[PATCH 6/6] Input: xbox_gip - Add flight stick support

From: Vicki Pfau <hidden>
Date: 2025-08-08 04:32:15
Subsystem: input (keyboard, mouse, joystick, touchscreen) drivers, the rest · Maintainers: Dmitry Torokhov, Linus Torvalds

This adds preliminary flight stick support, with a few caveats:

- Flight sticks support up to 64 extra buttons. This only exposes the first
  50, as there isn't any good place to map the remainder.
- Flight sticks support up to 12 extra axes. This picks a fairly abritrary
  mapping for them, as there's again no good place to map them.

Flight sticks also have addressible LEDs, but I don't have a device that
supports them so I can't test them yet.

Signed-off-by: Vicki Pfau <redacted>
---
 drivers/input/joystick/xbox_gip.c | 126 +++++++++++++++++++++++++++++-
 1 file changed, 123 insertions(+), 3 deletions(-)
diff --git a/drivers/input/joystick/xbox_gip.c b/drivers/input/joystick/xbox_gip.c
index 874705a9b6bfb..36611ba8d038e 100644
--- a/drivers/input/joystick/xbox_gip.c
+++ b/drivers/input/joystick/xbox_gip.c
@@ -11,9 +11,10 @@
  * - Sending fragmented messages
  * - Raw character device
  * - Wheel force feedback
- * - Flight stick support
  * - More arcade stick testing
  * - Arcade stick extra buttons
+ * - More flight stick testing
+ * - Flight stick LEDs
  *
  * This driver is based on the Microsoft GIP spec at:
  * https://aka.ms/gipdocs
@@ -37,6 +38,9 @@
 #define MAX_MESSAGE_LENGTH 0x4000
 #define MAX_ATTACHMENTS 8
 
+#define MAX_GIP_FLIGHT_STICK_BUTTONS 64
+#define MAX_GIP_FLIGHT_STICK_AXES 12
+
 #define MAX_IN_MESSAGES 8
 #define MAX_OUT_MESSAGES 8
 
@@ -377,6 +381,21 @@ static const struct gip_audio_format gip_audio_format_table[MAX_GIP_AUDIO_FORMAT
 	[GIP_AUDIO_FORMAT_48000HZ_8CH] = { .rate = 48000, .channels = 8 },
 };
 
+static const unsigned int gip_flight_stick_extra_axes[MAX_GIP_FLIGHT_STICK_AXES] = {
+	ABS_RUDDER,
+	ABS_WHEEL,
+	ABS_GAS,
+	ABS_BRAKE,
+	ABS_CLUTCH,
+	ABS_HANDBRAKE,
+	ABS_HAT1X,
+	ABS_HAT1Y,
+	ABS_HAT2X,
+	ABS_HAT2Y,
+	ABS_HAT3X,
+	ABS_HAT3Y,
+};
+
 struct gip_wheel_info {
 	uint8_t connections;
 	uint8_t shifter_type: 3;
@@ -1564,6 +1583,7 @@ static bool gip_send_set_device_state(struct gip_attachment *attachment, uint8_t
 static int gip_setup_input_device(struct gip_attachment *attachment)
 {
 	struct input_dev *input;
+	int i;
 	int rc;
 
 	input = input_allocate_device();
@@ -1616,6 +1636,24 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
 		input_set_capability(input, EV_KEY, BTN_THUMBR);
 		input_set_capability(input, EV_KEY, BTN_THUMBL);
 		break;
+	case GIP_TYPE_FLIGHT_STICK:
+		input_set_capability(input, EV_KEY, BTN_TOP);
+		input_set_capability(input, EV_KEY, BTN_TOP2);
+		for (i = 0; i < attachment->extra_buttons && i < 10; i++)
+			input_set_capability(input, EV_KEY, BTN_0 + i);
+		for (i = 10; i < attachment->extra_buttons && i - 10 < 40; i++)
+			input_set_capability(input, EV_KEY, BTN_TRIGGER_HAPPY + i - 10);
+		if (attachment->extra_buttons > 50)
+			dev_info(GIP_DEV(attachment),
+				"Device has too many extra buttons, 51 through %i ignored\n",
+				attachment->extra_buttons);
+		input_set_abs_params(input, ABS_X, -32768, 32767, 0, 0);
+		input_set_abs_params(input, ABS_Y, -32768, 32767, 0, 0);
+		input_set_abs_params(input, ABS_Z, -32768, 32767, 0, 0);
+		input_set_abs_params(input, ABS_THROTTLE, 0, 65535, 0, 0);
+		for (i = 0; i < attachment->extra_axes && i < MAX_GIP_FLIGHT_STICK_AXES; i++)
+			input_set_abs_params(input, gip_flight_stick_extra_axes[i], 0, 65535, 0, 0);
+		break;
 	case GIP_TYPE_WHEEL:
 		input_set_abs_params(input, ABS_WHEEL,
 			-attachment->wheel.max_angle - 1,
@@ -1653,7 +1691,6 @@ static int gip_setup_input_device(struct gip_attachment *attachment)
 		break;
 	case GIP_TYPE_UNKNOWN:
 	case GIP_TYPE_NAVIGATION_CONTROLLER:
-	case GIP_TYPE_FLIGHT_STICK:
 		break;
 	case GIP_TYPE_CHATPAD:
 	case GIP_TYPE_HEADSET:
@@ -1770,6 +1807,12 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
 			sizeof(request));
 	}
 
+	if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK
+		&& gip_supports_vendor_message(attachment,
+		GIP_CMD_DEVICE_CAPABILITIES, false))
+		gip_send_vendor_message(attachment,
+			GIP_CMD_DEVICE_CAPABILITIES, 0, NULL, 0);
+
 	usb_make_path(attachment->device->udev, attachment->phys,
 		sizeof(attachment->phys));
 	len = strlen(attachment->phys);
@@ -1779,7 +1822,8 @@ static int gip_send_init_sequence(struct gip_attachment *attachment)
 			attachment->attachment_index);
 
 	if (gip_attachment_is_controller(attachment) && !attachment->input
-		&& attachment->attachment_type != GIP_TYPE_WHEEL) {
+		&& attachment->attachment_type != GIP_TYPE_WHEEL
+		&& attachment->attachment_type != GIP_TYPE_FLIGHT_STICK) {
 		rc = gip_setup_input_device(attachment);
 		if (rc == -ENODEV)
 			return 0;
@@ -2242,6 +2286,22 @@ static int gip_handle_command_firmware(struct gip_attachment *attachment,
 	return -ENOTSUPP;
 }
 
+static int gip_handle_device_capabilities(struct gip_attachment *attachment,
+	const struct gip_header *header, const void *bytes, int num_bytes)
+{
+	const struct gip_device_capabilities_response *response = bytes;
+
+	if (attachment->input)
+		return 0;
+
+	if (num_bytes < 4)
+		return -EINVAL;
+
+	attachment->extra_axes = response->extra_axis_count;
+	attachment->extra_buttons = response->extra_button_count;
+	return gip_setup_input_device(attachment);
+}
+
 static int gip_handle_command_raw_report(struct gip_attachment *attachment,
 	const struct gip_header *header, const uint8_t *bytes, int num_bytes)
 {
@@ -2405,6 +2465,58 @@ static void gip_handle_arcade_stick_report(struct gip_attachment *attachment,
 	}
 }
 
+static void gip_handle_flight_stick_report(struct gip_attachment *attachment,
+	struct input_dev *dev, const uint8_t *bytes, int num_bytes)
+{
+	int32_t axis;
+	int i;
+
+	if (num_bytes < 19)
+		return;
+
+	/* Fire 1 and 2 */
+	input_report_key(dev, BTN_TOP, bytes[2] & BIT(0));
+	input_report_key(dev, BTN_TOP2, bytes[2] & BIT(1));
+
+	for (i = 0; i < attachment->extra_buttons && i < 10; i++) {
+		input_report_key(dev, BTN_0 + i,
+			bytes[i / 8 + 3] & BIT(i));
+	}
+	for (i = 10; i < attachment->extra_buttons && i - 10 < 40; i++) {
+		input_report_key(dev, BTN_TRIGGER_HAPPY + i - 10,
+			bytes[i / 8 + 3] & BIT(i));
+	}
+
+	/*
+	 * Roll, pitch and yaw are signed. Throttle and any
+	 * extra axes are unsigned. All values are full-range.
+	 */
+	axis = bytes[11];
+	axis |= bytes[12] << 8;
+	input_report_abs(dev, ABS_X, (int16_t) axis);
+
+	axis = bytes[13];
+	axis |= bytes[14] << 8;
+	input_report_abs(dev, ABS_Y, (int16_t) axis);
+
+	axis = bytes[15];
+	axis |= bytes[16] << 8;
+	input_report_abs(dev, ABS_Z, (int16_t) axis);
+
+	axis = bytes[17];
+	axis |= bytes[18] << 8;
+	input_report_abs(dev, ABS_THROTTLE, axis);
+
+	for (i = 0; i < attachment->extra_axes && i < MAX_GIP_FLIGHT_STICK_AXES; i++) {
+		if (20 + i * 2 >= num_bytes)
+			return;
+
+		axis = bytes[19 + i * 2];
+		axis |= bytes[20 + i * 2] << 8;
+		input_report_abs(dev, gip_flight_stick_extra_axes[i], axis);
+	}
+}
+
 static void gip_handle_wheel_report(struct gip_attachment *attachment,
 	struct input_dev *dev, const uint8_t *bytes, int num_bytes)
 {
@@ -2508,6 +2620,9 @@ static int gip_handle_ll_input_report(struct gip_attachment *attachment,
 	case GIP_TYPE_ARCADE_STICK:
 		gip_handle_arcade_stick_report(attachment, dev, bytes, num_bytes);
 		break;
+	case GIP_TYPE_FLIGHT_STICK:
+		gip_handle_flight_stick_report(attachment, dev, bytes, num_bytes);
+		break;
 	case GIP_TYPE_WHEEL:
 		gip_handle_wheel_report(attachment, dev, bytes, num_bytes);
 		break;
@@ -2723,6 +2838,11 @@ static int gip_handle_message(struct gip_attachment *attachment,
 			num_bytes);
 
 	switch (header->message_type) {
+	case GIP_CMD_DEVICE_CAPABILITIES:
+		if (attachment->attachment_type == GIP_TYPE_FLIGHT_STICK)
+			return gip_handle_device_capabilities(attachment,
+				header, bytes, num_bytes);
+		break;
 	case GIP_CMD_RAW_REPORT:
 		if (attachment->features & GIP_FEATURE_ELITE_BUTTONS)
 			return gip_handle_command_raw_report(attachment,
-- 
2.50.1
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help