public inbox for linux-input@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH] xpad: expose xinput capabilities via sysattr
@ 2026-03-04  1:03 Sanjay Govind
  2026-03-04  1:22 ` Dmitry Torokhov
  0 siblings, 1 reply; 5+ messages in thread
From: Sanjay Govind @ 2026-03-04  1:03 UTC (permalink / raw)
  To: Dmitry Torokhov, Vicki Pfau, Mario Limonciello, Sanjay Govind,
	Nilton Perim Neto
  Cc: Pierre-Loup A. Griffais, linux-input, linux-kernel

Fetch xinput capabilities for x360 wired and wireless
and then expose them via the following attributes:

ATTRS{xpad/flags}=="3"
ATTRS{xpad/gamepad_buttons}=="ffff"
ATTRS{xpad/gamepad_lsx}=="0"
ATTRS{xpad/gamepad_lsy}=="0"
ATTRS{xpad/gamepad_lt}=="3f"
ATTRS{xpad/gamepad_rsx}=="ffc0"
ATTRS{xpad/gamepad_rsy}=="ffc0"
ATTRS{xpad/gamepad_rt}=="ff"
ATTRS{xpad/rumble_l}=="0"
ATTRS{xpad/rumble_r}=="0"
ATTRS{xpad/subtype}=="7"
ATTRS{xpad/type}=="1"

Signed-off-by: Sanjay Govind <sanjay.govind9@gmail.com>
---
 drivers/input/joystick/xpad.c | 197 ++++++++++++++++++++++++++++++++--
 1 file changed, 188 insertions(+), 9 deletions(-)

diff --git a/drivers/input/joystick/xpad.c b/drivers/input/joystick/xpad.c
index bf4accf3f581..70e4a7c85ab5 100644
--- a/drivers/input/joystick/xpad.c
+++ b/drivers/input/joystick/xpad.c
@@ -94,6 +94,12 @@
 #define XTYPE_XBOXONE     3
 #define XTYPE_UNKNOWN     4
 
+#define FLAG_FORCE_FEEDBACK 0x01
+#define FLAG_WIRELESS       0x02
+#define FLAG_VOICE          0x04
+#define FLAG_PLUGIN_MODULES 0x08
+#define FLAG_NO_NAVIGATION  0x10
+
 /* Send power-off packet to xpad360w after holding the mode button for this many
  * seconds
  */
@@ -747,6 +753,47 @@ static const struct xboxone_init_packet xboxone_init_packets[] = {
 	XBOXONE_INIT_PKT(0x24c6, 0x543a, xboxone_rumbleend_init),
 };
 
+struct xpad_x360_gamepad_descriptor {
+	u8 bLength;
+	u8 bDescriptorType;
+	u8 reserved;
+	u8 type;
+	u8 subType;
+	u8 reserved2;
+	u8 bEndpointAddressIn;
+	u8 bMaxDataSizeIn;
+	u8 reserved3[5];
+	u8 bEndpointAddressOut;
+	u8 bMaxDataSizeOut;
+	u8 reserved4[2];
+} __packed;
+
+struct x360_capabilities {
+	u8 type;
+	u8 subType;
+	struct {
+		u8 id;
+		u8 rsize;
+		u16 buttons;
+		u8 leftTrigger;
+		u8 rightTrigger;
+		u16 leftThumbX;
+		u16 leftThumbY;
+		u16 rightThumbX;
+		u16 rightThumbY;
+		u8 reserved[4];
+		u16 flags;
+	} gamepad;
+	struct {
+		u8 id;
+		u8 rsize;
+		u8 padding;
+		u16 leftMotorSpeed;
+		u16 rightMotorSpeed;
+		u8 padding2[3];
+	} vibration;
+} __packed;
+
 struct xpad_output_packet {
 	u8 data[XPAD_PKT_LEN];
 	u8 len;
@@ -795,6 +842,7 @@ struct usb_xpad {
 	int xtype;			/* type of xbox device */
 	int packet_type;		/* type of the extended packet */
 	int pad_nr;			/* the order x360 pads were attached */
+	struct x360_capabilities capabilities; /* capabilities of the device */
 	const char *name;		/* name of the device */
 	struct work_struct work;	/* init/remove device from callback */
 	time64_t mode_btn_down_ts;
@@ -802,11 +850,63 @@ struct usb_xpad {
 	bool delayed_init_done;
 };
 
+#define XPAD_SHOW(name, object) \
+static ssize_t name##_show(struct device *dev,\
+			   struct device_attribute *attr,\
+			   char *buf)\
+{\
+	struct usb_xpad *xpad = input_get_drvdata(to_input_dev(dev));\
+\
+	return sysfs_emit(buf, "%x\n", xpad->capabilities.object);\
+} \
+\
+static DEVICE_ATTR_RO(name)
+
+XPAD_SHOW(subtype, subType);
+XPAD_SHOW(type, type);
+XPAD_SHOW(flags, gamepad.flags);
+XPAD_SHOW(gamepad_buttons, gamepad.buttons);
+XPAD_SHOW(gamepad_lt, gamepad.leftTrigger);
+XPAD_SHOW(gamepad_rt, gamepad.rightTrigger);
+XPAD_SHOW(gamepad_lsx, gamepad.leftThumbX);
+XPAD_SHOW(gamepad_lsy, gamepad.leftThumbY);
+XPAD_SHOW(gamepad_rsx, gamepad.rightThumbX);
+XPAD_SHOW(gamepad_rsy, gamepad.rightThumbY);
+XPAD_SHOW(rumble_l, vibration.leftMotorSpeed);
+XPAD_SHOW(rumble_r, vibration.rightMotorSpeed);
+
+static struct attribute *xpad_attrs[] = {
+	&dev_attr_type.attr,
+	&dev_attr_subtype.attr,
+	&dev_attr_flags.attr,
+	&dev_attr_gamepad_buttons.attr,
+	&dev_attr_gamepad_lt.attr,
+	&dev_attr_gamepad_rt.attr,
+	&dev_attr_gamepad_lsx.attr,
+	&dev_attr_gamepad_lsy.attr,
+	&dev_attr_gamepad_rsx.attr,
+	&dev_attr_gamepad_rsy.attr,
+	&dev_attr_rumble_l.attr,
+	&dev_attr_rumble_r.attr,
+	NULL
+};
+
+static struct attribute_group xpad_group = {
+	.attrs = xpad_attrs,
+	.name = "xpad"
+};
+
+static const struct attribute_group *xpad_groups[] = {
+	&xpad_group,
+	NULL,
+};
+
 static int xpad_init_input(struct usb_xpad *xpad);
 static void xpad_deinit_input(struct usb_xpad *xpad);
 static int xpad_start_input(struct usb_xpad *xpad);
 static void xpadone_ack_mode_report(struct usb_xpad *xpad, u8 seq_num);
 static void xpad360w_poweroff_controller(struct usb_xpad *xpad);
+static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad);
 
 /*
  *	xpad_process_packet
@@ -1032,6 +1132,29 @@ static void xpad360w_process_packet(struct usb_xpad *xpad, u16 cmd, unsigned cha
 		}
 	}
 
+	/* Link report */
+	if (data[0] == 0x00 && data[1] == 0x0F) {
+		xpad->capabilities.subType = data[25] & 0x7f;
+		xpad->capabilities.gamepad.flags = FLAG_WIRELESS;
+		if ((data[25] & 0x80) != 0)
+			xpad->capabilities.gamepad.flags |= FLAG_FORCE_FEEDBACK;
+		xpad_inquiry_pad_capabilities(xpad);
+	}
+
+	/* Capabilities report */
+	if (data[0] == 0x00 && data[1] == 0x05 && data[5] == 0x12) {
+		xpad->capabilities.gamepad.buttons = (data[7] << 8) | data[6];
+		xpad->capabilities.gamepad.leftTrigger = data[8];
+		xpad->capabilities.gamepad.rightTrigger = data[9];
+		xpad->capabilities.gamepad.leftThumbX = (data[11] << 8) | data[10];
+		xpad->capabilities.gamepad.leftThumbY = (data[13] << 8) | data[12];
+		xpad->capabilities.gamepad.rightThumbX = (data[15] << 8) | data[14];
+		xpad->capabilities.gamepad.rightThumbY = (data[17] << 8) | data[16];
+		xpad->capabilities.gamepad.flags |= data[20];
+		xpad->capabilities.vibration.leftMotorSpeed = data[18];
+		xpad->capabilities.vibration.rightMotorSpeed = data[19];
+	}
+
 	/* Valid pad data */
 	if (data[1] != 0x1)
 		return;
@@ -1495,6 +1618,31 @@ static int xpad_inquiry_pad_presence(struct usb_xpad *xpad)
 	return xpad_try_sending_next_out_packet(xpad);
 }
 
+static int xpad_inquiry_pad_capabilities(struct usb_xpad *xpad)
+{
+	struct xpad_output_packet *packet =
+			&xpad->out_packets[XPAD_OUT_CMD_IDX];
+
+	guard(spinlock_irqsave)(&xpad->odata_lock);
+
+	packet->data[0] = 0x00;
+	packet->data[1] = 0x00;
+	packet->data[2] = 0x02;
+	packet->data[3] = 0x80;
+	packet->data[4] = 0x00;
+	packet->data[5] = 0x00;
+	packet->data[6] = 0x00;
+	packet->data[7] = 0x00;
+	packet->data[8] = 0x00;
+	packet->data[9] = 0x00;
+	packet->data[10] = 0x00;
+	packet->data[11] = 0x00;
+	packet->len = 12;
+	packet->pending = true;
+
+	return xpad_try_sending_next_out_packet(xpad);
+}
+
 static int xpad_start_xbox_one(struct usb_xpad *xpad)
 {
 	int error;
@@ -1808,25 +1956,37 @@ static int xpad_start_input(struct usb_xpad *xpad)
 		}
 	}
 	if (xpad->xtype == XTYPE_XBOX360) {
-		/*
-		 * Some third-party controllers Xbox 360-style controllers
-		 * require this message to finish initialization.
-		 */
-		u8 dummy[20];
-
 		error = usb_control_msg_recv(xpad->udev, 0,
 					     /* bRequest */ 0x01,
 					     /* bmRequestType */
 					     USB_TYPE_VENDOR | USB_DIR_IN |
-						USB_RECIP_INTERFACE,
+					     USB_RECIP_INTERFACE,
 					     /* wValue */ 0x100,
 					     /* wIndex */ 0x00,
-					     dummy, sizeof(dummy),
+					     &xpad->capabilities.gamepad,
+					     sizeof(xpad->capabilities.gamepad),
 					     25, GFP_KERNEL);
 		if (error)
 			dev_warn(&xpad->dev->dev,
-				 "unable to receive magic message: %d\n",
+				 "unable to receive input capabilities: %d\n",
 				 error);
+
+		if (xpad->capabilities.gamepad.flags & FLAG_FORCE_FEEDBACK) {
+			error = usb_control_msg_recv(xpad->udev, 0,
+						     /* bRequest */ 0x01,
+						     /* bmRequestType */
+						     USB_TYPE_VENDOR | USB_DIR_IN |
+						     USB_RECIP_INTERFACE,
+						     /* wValue */ 0x00,
+						     /* wIndex */ 0x00,
+						     &xpad->capabilities.vibration,
+						     sizeof(xpad->capabilities.vibration),
+						     25, GFP_KERNEL);
+			if (error)
+				dev_warn(&xpad->dev->dev,
+					 "unable to receive vibration capabilities: %d\n",
+					 error);
+		}
 	}
 
 	return 0;
@@ -1953,6 +2113,7 @@ static void xpad_deinit_input(struct usb_xpad *xpad)
 static int xpad_init_input(struct usb_xpad *xpad)
 {
 	struct input_dev *input_dev;
+	struct xpad_x360_gamepad_descriptor *input_desc;
 	int i, error;
 
 	input_dev = input_allocate_device();
@@ -1962,11 +2123,29 @@ static int xpad_init_input(struct usb_xpad *xpad)
 	xpad->dev = input_dev;
 	input_dev->name = xpad->name;
 	input_dev->phys = xpad->phys;
+	xpad->capabilities.subType = 1;
+	xpad->capabilities.type = 1;
+	xpad->capabilities.gamepad.flags = 0;
+	xpad->capabilities.gamepad.buttons = 0xFFFF;
+	xpad->capabilities.gamepad.leftTrigger = 0xFF;
+	xpad->capabilities.gamepad.rightTrigger = 0xFF;
+	xpad->capabilities.gamepad.leftThumbX = 0xFFC0;
+	xpad->capabilities.gamepad.leftThumbY = 0xFFC0;
+	xpad->capabilities.gamepad.rightThumbX = 0xFFC0;
+	xpad->capabilities.gamepad.rightThumbY = 0xFFC0;
+	xpad->dev->dev.groups = xpad_groups;
 	usb_to_input_id(xpad->udev, &input_dev->id);
 
 	if (xpad->xtype == XTYPE_XBOX360W) {
 		/* x360w controllers and the receiver have different ids */
 		input_dev->id.product = 0x02a1;
+		xpad->capabilities.gamepad.flags = FLAG_WIRELESS;
+	}
+
+	if (xpad->xtype == XTYPE_XBOX360 &&
+	    usb_get_extra_descriptor(xpad->intf->cur_altsetting, 0x21, &input_desc) == 0) {
+		xpad->capabilities.subType = input_desc->subType;
+		xpad->capabilities.type = input_desc->type;
 	}
 
 	input_dev->dev.parent = &xpad->intf->dev;
-- 
2.53.0


^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2026-03-11  5:47 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-03-04  1:03 [PATCH] xpad: expose xinput capabilities via sysattr Sanjay Govind
2026-03-04  1:22 ` Dmitry Torokhov
     [not found]   ` <CALQgdA0PL82yLxWbjcqchWzsf+bA7_Egq9hZFzz7toL4kByAvw@mail.gmail.com>
2026-03-04  1:34     ` Dmitry Torokhov
     [not found]       ` <CALQgdA0q=+6L2mQBqNPUBzo+HE6=AjhSPv5G_dK9g=V2npSsWA@mail.gmail.com>
2026-03-04 19:58         ` Sanjay Govind
2026-03-11  5:47           ` Sanjay Govind

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox