[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