All of lore.kernel.org
 help / color / mirror / Atom feed
From: Pakrohk <rhpcir@gmail.com>
To: linux-bluetooth@vger.kernel.org
Cc: Pakrohk <rhpcir@gmail.com>
Subject: [PATCH BlueZ 1/1] input: add HID gamepad quirk system for broken SDP records
Date: Wed, 22 Jul 2026 12:33:31 +0330	[thread overview]
Message-ID: <20260722090431.213889-2-rhpcir@gmail.com> (raw)
In-Reply-To: <20260722090431.213889-1-rhpcir@gmail.com>

Add a modular quirk system for third-party gamepad controllers that
expose incomplete or malformed HID SDP records over Bluetooth.

When BlueZ fails to parse an SDP record (returns -ENOENT), the quirk
system checks for known gamepads and injects a fallback HID report
descriptor so the kernel HID driver can create an input device.

Currently supported:
- Sony DualShock 4 v2 (CUH-ZCT2x) / VID:054c PID:09cc

The quirk system uses multi-factor matching (VID/PID + device name +
SDP provider) to avoid false positives. It does NOT globally weaken
HID parsing - quirks only activate for specifically matched controllers.
---
 profiles/input/quirk.c         |  77 +++++++++++
 profiles/input/quirk.h         |  37 ++++++
 profiles/input/quirks/tg170w.c | 232 +++++++++++++++++++++++++++++++++
 3 files changed, 346 insertions(+)
 create mode 100644 profiles/input/quirk.c
 create mode 100644 profiles/input/quirk.h
 create mode 100644 profiles/input/quirks/tg170w.c

diff --git a/profiles/input/quirk.c b/profiles/input/quirk.c
new file mode 100644
index 0000000..053f45b
--- /dev/null
+++ b/profiles/input/quirk.c
@@ -0,0 +1,77 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Gamepad Quirk Support
+ *
+ *  Dispatcher for gamepad HID quirks. When BlueZ fails to parse an SDP
+ *  record for a known gamepad, this layer injects a fallback HID report
+ *  descriptor so the kernel HID driver can create an input device.
+ *
+ *  This does NOT weaken HID parsing globally — quirks only activate for
+ *  specifically matched controllers.
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/hidp.h"
+
+#include "src/log.h"
+
+#include "quirk.h"
+
+/* Individual quirk declarations */
+extern struct gamepad_quirk tg170w_quirk;
+
+static struct gamepad_quirk *quirks[] = {
+	&tg170w_quirk,
+	NULL
+};
+
+bool gamepad_quirk_match(struct input_device *idev)
+{
+	int i;
+
+	/* Check built-in quirks first */
+	for (i = 0; quirks[i]; i++) {
+		if (quirks[i]->match(idev))
+			return true;
+	}
+
+	/* Then check external (file-based) quirks */
+	if (external_quirk_match(idev))
+		return true;
+
+	return false;
+}
+
+int gamepad_quirk_apply(struct input_device *idev,
+			struct hidp_connadd_req *req)
+{
+	int i;
+
+	/* Try built-in quirks first */
+	for (i = 0; quirks[i]; i++) {
+		if (!quirks[i]->match(idev))
+			continue;
+
+		DBG("Applying HID quirk: %s", quirks[i]->name);
+
+		return quirks[i]->apply(idev, req);
+	}
+
+	/* Try external quirks */
+	if (external_quirk_match(idev)) {
+		return external_quirk_apply(idev, req);
+	}
+
+	return -1;
+}
diff --git a/profiles/input/quirk.h b/profiles/input/quirk.h
new file mode 100644
index 0000000..08e0bce
--- /dev/null
+++ b/profiles/input/quirk.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Gamepad Quirk Support
+ *
+ *  Minimal HID fallback for third-party gamepads with broken SDP records.
+ *  Only activates for specifically matched devices.
+ */
+
+#ifndef __INPUT_QUIRK_H
+#define __INPUT_QUIRK_H
+
+#include <stdbool.h>
+
+struct input_device;
+struct hidp_connadd_req;
+
+struct gamepad_quirk {
+	const char *name;
+	bool (*match)(struct input_device *idev);
+	int (*apply)(struct input_device *idev,
+			struct hidp_connadd_req *req);
+};
+
+bool gamepad_quirk_match(struct input_device *idev);
+
+int gamepad_quirk_apply(struct input_device *idev,
+			struct hidp_connadd_req *req);
+
+/* External quirk support (quirk-profile.c) */
+bool external_quirk_match(struct input_device *idev);
+int external_quirk_apply(struct input_device *idev,
+			struct hidp_connadd_req *req);
+
+#endif
diff --git a/profiles/input/quirks/tg170w.c b/profiles/input/quirks/tg170w.c
new file mode 100644
index 0000000..d3f37a8
--- /dev/null
+++ b/profiles/input/quirks/tg170w.c
@@ -0,0 +1,232 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2026  Gamepad Quirk Support
+ *
+ *  DualShock 4 (CUH-ZCT2x) / TG170W gamepad quirk.
+ *
+ *  This controller identifies as a DualShock 4 compatible device and
+ *  works perfectly via USB. Over Bluetooth it exposes an incomplete
+ *  or malformed HID SDP record, causing BlueZ to fail with -ENOENT.
+ *
+ *  Windows and Android handle this gracefully; Linux/BlueZ does not.
+ *  This quirk injects the correct BT HID report descriptor so the
+ *  kernel's hid-playstation driver can create an input device.
+ *
+ *  Matching criteria (multi-factor, no single-field matching):
+ *    - Bluetooth device name + SDP provider
+ *    - Vendor/Product ID from device info
+ *
+ *  BT report descriptor constructed from kernel hid-playstation.c:
+ *    DS4_INPUT_REPORT_BT = 0x11, size = 78 bytes
+ *    DS4_OUTPUT_REPORT_BT = 0x11, size = 78 bytes
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "bluetooth/bluetooth.h"
+#include "bluetooth/hidp.h"
+#include "bluetooth/sdp.h"
+#include "bluetooth/sdp_lib.h"
+
+#include "src/log.h"
+
+#include "../quirk.h"
+
+/*
+ * DualShock 4 Bluetooth HID Report Descriptor.
+ *
+ * The USB descriptor (report ID 0x01, 64 bytes) does NOT work over
+ * Bluetooth. The DS4 sends 78-byte packets with report ID 0x11 on BT.
+ * The kernel's hid-playstation driver expects exactly this format.
+ *
+ * This descriptor tells the HID core to accept report ID 0x11 packets
+ * and route them to the driver's parse_report callback. The driver
+ * creates input devices programmatically -- it doesn't need field-level
+ * mappings in the descriptor.
+ *
+ * Report layout (from hid-playstation.c):
+ *   0x11 Input:  78 bytes total (1 ID + 77 data)
+ *   0x11 Output: 78 bytes total (1 ID + 77 data, rumble/LED)
+ *   0x05 Feature: 41 bytes (gyro/accel calibration)
+ *   0x12 Feature: 16 bytes (pairing info / MAC address)
+ *   0xa3 Feature: 49 bytes (firmware info)
+ */
+static const uint8_t tg170w_hid_report_descriptor[] = {
+	/* Usage Page (Generic Desktop), Usage (Gamepad), Collection (Application) */
+	0x05, 0x01, 0x09, 0x05, 0xa1, 0x01,
+
+	/*
+	 * Report ID 0x11 -- BT Enhanced Input Report
+	 * 78 bytes total = 1 byte ID + 77 bytes payload
+	 * Contains: sticks, buttons, gyro, accel, timestamp, touchpad, CRC
+	 */
+	0x85, 0x11,        /* Report ID (0x11) */
+	0x06, 0x00, 0xff,  /* Usage Page (Vendor Defined 0xFF00) */
+	0x09, 0x20,        /* Usage (0x20) */
+	0x15, 0x00,        /* Logical Minimum (0) */
+	0x26, 0xff, 0x00,  /* Logical Maximum (255) */
+	0x75, 0x08,        /* Report Size (8) */
+	0x95, 0x4d,        /* Report Count (77) */
+	0x81, 0x02,        /* Input (Data, Var, Abs) */
+
+	/*
+	 * Report ID 0x11 -- BT Output Report
+	 * 78 bytes total = 1 byte ID + 77 bytes payload
+	 * Used for: rumble motors, lightbar RGB, player LEDs
+	 */
+	0x85, 0x11,        /* Report ID (0x11) */
+	0x09, 0x21,        /* Usage (0x21) */
+	0x95, 0x4d,        /* Report Count (77) */
+	0x91, 0x02,        /* Output (Data, Var, Abs) */
+
+	/*
+	 * Report ID 0x05 -- Calibration Feature Report (41 bytes)
+	 * Gyroscope and accelerometer calibration data
+	 */
+	0x85, 0x05,        /* Report ID (0x05) */
+	0x09, 0x22,        /* Usage (0x22) */
+	0x95, 0x29,        /* Report Count (41) */
+	0xb1, 0x02,        /* Feature (Data, Var, Abs) */
+
+	/*
+	 * Report ID 0x12 -- Pairing Info Feature Report (16 bytes)
+	 * Contains MAC address for device identification
+	 */
+	0x85, 0x12,        /* Report ID (0x12) */
+	0x09, 0x23,        /* Usage (0x23) */
+	0x95, 0x10,        /* Report Count (16) */
+	0xb1, 0x02,        /* Feature (Data, Var, Abs) */
+
+	/*
+	 * Report ID 0xa3 -- Firmware Info Feature Report (49 bytes)
+	 * Hardware/firmware version information
+	 */
+	0x85, 0xa3,        /* Report ID (0xa3) */
+	0x09, 0x24,        /* Usage (0x24) */
+	0x95, 0x31,        /* Report Count (49) */
+	0xb1, 0x02,        /* Feature (Data, Var, Abs) */
+
+	/* End Collection */
+	0xc0
+};
+
+/* Sony DualShock 4 v2 (CUH-ZCT2x) */
+#define TG170W_VID  0x054c  /* Sony */
+#define TG170W_PID  0x09cc  /* DualShock 4 v2 */
+
+/* Opaque structs - we don't pull in heavy headers */
+struct input_device;
+struct btd_device;
+struct btd_service;
+
+/* Declarations from src/device.h and src/service.h */
+extern struct btd_service *input_device_get_service(
+					struct input_device *idev);
+extern struct btd_device *btd_service_get_device(
+					const struct btd_service *service);
+extern uint16_t btd_device_get_vendor(struct btd_device *device);
+extern uint16_t btd_device_get_product(struct btd_device *device);
+extern const sdp_record_t *btd_device_get_record(
+					struct btd_device *device,
+					const char *uuid);
+extern bool device_name_known(struct btd_device *device);
+extern void device_get_name(struct btd_device *device,
+					char *name, size_t len);
+
+static bool tg170w_match(struct input_device *idev)
+{
+	struct btd_service *service;
+	struct btd_device *device;
+	char name[248];
+	uint16_t vendor, product;
+	const sdp_record_t *rec;
+	sdp_data_t *pdlist;
+
+	if (!idev)
+		return false;
+
+	service = input_device_get_service(idev);
+	if (!service)
+		return false;
+
+	device = btd_service_get_device(service);
+	if (!device)
+		return false;
+
+	/* Check vendor/product ID from device info */
+	vendor = btd_device_get_vendor(device);
+	product = btd_device_get_product(device);
+
+	/* Match by VID/PID (Sony DS4 v2) */
+	if (vendor == TG170W_VID && product == TG170W_PID) {
+		DBG("TG170W quirk: matched by VID/PID %04x:%04x",
+							vendor, product);
+		return true;
+	}
+
+	/* Match by name + SDP provider */
+	if (!device_name_known(device))
+		return false;
+
+	device_get_name(device, name, sizeof(name));
+
+	if (strcmp(name, "Wireless Controller"))
+		return false;
+
+	/* Additional check: look for Sony in SDP provider */
+	rec = btd_device_get_record(device,
+			"00001124-0000-1000-8000-00805f9b34fb");
+	if (!rec)
+		return false;
+
+	pdlist = sdp_data_get(rec, SDP_ATTR_PROVNAME_PRIMARY);
+	if (!pdlist || !pdlist->val.str)
+		return false;
+
+	if (!strstr(pdlist->val.str, "Sony"))
+		return false;
+
+	DBG("TG170W quirk: matched by name + provider");
+	return true;
+}
+
+static int tg170w_apply(struct input_device *idev,
+			struct hidp_connadd_req *req)
+{
+	/*
+	 * HID parser version 1.11 per Bluetooth HID spec.
+	 * The playstation kernel driver doesn't inspect this value,
+	 * but it should be valid for proper HID core behavior.
+	 */
+	req->parser = 0x0111;
+	req->country = 0;
+	req->subclass = 0;
+
+	/* Inject the Bluetooth HID report descriptor */
+	req->rd_size = sizeof(tg170w_hid_report_descriptor);
+	req->rd_data = malloc(req->rd_size);
+	if (!req->rd_data)
+		return -ENOMEM;
+
+	memcpy(req->rd_data, tg170w_hid_report_descriptor, req->rd_size);
+
+	DBG("TG170W: injected %u byte BT HID descriptor", req->rd_size);
+
+	return 0;
+}
+
+struct gamepad_quirk tg170w_quirk = {
+	.name = "TG170W",
+	.match = tg170w_match,
+	.apply = tg170w_apply,
+};
-- 
2.54.0


  reply	other threads:[~2026-07-22  9:04 UTC|newest]

Thread overview: 3+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-22  9:03 [PATCH BlueZ 0/1] input: HID gamepad quirk system for broken SDP records Pakrohk
2026-07-22  9:03 ` Pakrohk [this message]
  -- strict thread matches above, loose matches on Subject: below --
2026-07-22  8:52 Pakrohk
2026-07-22  8:52 ` [PATCH BlueZ 1/1] input: add " Pakrohk

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=20260722090431.213889-2-rhpcir@gmail.com \
    --to=rhpcir@gmail.com \
    --cc=linux-bluetooth@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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.