[PATCH BlueZ v2 2/3] input: add HID gamepad quirk fallback for broken SDP records
Pakrohk <[email protected]>
| Newsgroups | org.kernel.vger.linux-bluetooth |
|---|---|
| Message-ID | <[email protected]> |
From: Pakrohk <[email protected]> Add a modular gamepad quirk system to profiles/input/ that provides HID report descriptor fallbacks when BlueZ's SDP parser fails. When extract_hid_record() returns -ENOENT for a known gamepad, the quirk system checks registered quirks and injects a fallback HID descriptor so the kernel's HID driver can create an input device. Two tiers of quirk support: Built-in quirks: Hardcoded C entries for known broken controllers. Currently supports DualShock 4 v2 / TG170W (054c:09cc) with a minimal BT HID descriptor matching report ID 0x11 (78 bytes, as expected by hid-playstation). External quirk profiles: JSON files in /var/lib/bluez/quirks/ with HMAC-SHA256 signatures. Loaded at bluetoothd startup. Enables community-maintained support without BlueZ source modifications. Architecture: quirk.h - quirk struct definition and dispatch API quirk.c - quirk registry and dispatch logic quirk-profile.h/c - external JSON profile loader with HMAC verification quirks/tg170w.c - built-in quirk for DualShock 4 v2 The quirk only activates when SDP parsing fails AND the device matches a registered quirk. This does not globally weaken HID parsing. Multi-factor matching avoids false positives: match = (vendor_id == 0x054c && product_id == 0x09cc) || (device_name == "Wireless Controller" && SDP provider contains "Sony") Signed-off-by: Pakrohk <[email protected]> diff --git a/profiles/input/device.c b/profiles/input/device.c index 8017e07..0089c3d 100644 --- a/profiles/input/device.c +++ b/profiles/input/device.c @@ -1089,11 +1089,8 @@ static int hidp_add_connection(struct input_device *idev) err = extract_hid_record(idev, req); if (err < 0) { - /* Try gamepad quirk fallback for known broken devices */ - if (gamepad_quirk_match(idev)) { - DBG("HID SDP failed, trying gamepad quirk"); - err = gamepad_quirk_apply(idev, req); - } + DBG("HID SDP failed, trying gamepad quirk"); + err = gamepad_quirk_apply(idev, req); } if (err < 0) { diff --git a/profiles/input/quirk-profile.c b/profiles/input/quirk-profile.c index 8ec3835..4240154 100644 --- a/profiles/input/quirk-profile.c +++ b/profiles/input/quirk-profile.c @@ -28,22 +28,20 @@ #include "bluetooth/bluetooth.h" #include "bluetooth/hidp.h" +#include "bluetooth/sdp.h" +#include "bluetooth/sdp_lib.h" #include "src/log.h" #include "quirk.h" #include "quirk-profile.h" -/* Accessors 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 bool device_name_known(struct btd_device *device); -extern void device_get_name(struct btd_device *device, - char *name, size_t len); +#include "gdbus/gdbus.h" + +#include "src/device.h" +#include "src/service.h" +#include "src/shared/crypto.h" +#include "device.h" #define MAX_EXTERNAL_QUIRKS 32 #define HMAC_KEY_PATH QUIRK_PROFILE_DIR "/.hmac_key" @@ -67,6 +65,7 @@ struct external_quirk { static struct external_quirk *ext_quirks[MAX_EXTERNAL_QUIRKS + 1]; static int num_ext_quirks; +static struct bt_crypto *crypto; /* * Parse a hex string like "0501 0905" into a byte buffer. @@ -139,77 +138,23 @@ static char *read_file(const char *path, size_t *out_len) } /* - * Compute HMAC-SHA256 using openssl CLI. - */ -static char *hmac_sha256(const uint8_t *key, int key_len, - const void *data, size_t data_len) -{ - char key_hex[HMAC_KEY_SIZE * 2 + 1]; - char *cmd; - char *result = NULL; - FILE *p, *tmpf; - int i; - const char *tmpfile = "/tmp/.bluez_quirk_hmac_input"; - char line[256]; - char *eq, *end; - - for (i = 0; i < key_len; i++) - snprintf(key_hex + i * 2, 3, "%02x", key[i]); - key_hex[key_len * 2] = '\0'; - - tmpf = fopen(tmpfile, "we"); - if (!tmpf) - return NULL; - - fwrite(data, 1, data_len, tmpf); - fclose(tmpf); - - cmd = malloc(strlen(key_hex) + 128); - if (!cmd) { - unlink(tmpfile); - return NULL; - } - - snprintf(cmd, strlen(key_hex) + 128, - "openssl dgst -sha256 -hmac '%s' -hex < %s 2>/dev/null", - key_hex, tmpfile); - - p = popen(cmd, "re"); - free(cmd); - unlink(tmpfile); - - if (!p) - return NULL; - - while (fgets(line, sizeof(line), p)) { - eq = strstr(line, "= "); - if (eq) { - eq += 2; - end = eq + strlen(eq) - 1; - while (end > eq && (*end == '\n' || *end == '\r' - || *end == ' ')) - *end-- = '\0'; - result = strdup(eq); - break; - } - } - - pclose(p); - return result; -} - -/* - * Verify HMAC signature. + * Verify HMAC-SHA256 signature using bt_crypto (kernel AF_ALG). */ static bool verify_signature(const char *json_path, const uint8_t *hmac_key, int key_len) { char sig_path[1024]; size_t sig_len, json_len; - char *sig_hex, *json_data, *expected_sig; + char *sig_hex, *json_data; char *end; + uint8_t computed[32]; + char computed_hex[65]; + int i; bool valid; + if (!crypto) + return false; + snprintf(sig_path, sizeof(sig_path), "%s" SIG_EXT, json_path); sig_hex = read_file(sig_path, &sig_len); @@ -228,21 +173,25 @@ static bool verify_signature(const char *json_path, return false; } - expected_sig = hmac_sha256(hmac_key, key_len, json_data, json_len); - free(json_data); - - if (!expected_sig) { + if (!bt_crypto_hmac_sha256(crypto, hmac_key, key_len, + (const uint8_t *) json_data, json_len, + computed)) { + free(json_data); free(sig_hex); return false; } + free(json_data); + + for (i = 0; i < 32; i++) + snprintf(computed_hex + i * 2, 3, "%02x", computed[i]); + computed_hex[64] = '\0'; - valid = (strcmp(sig_hex, expected_sig) == 0); + valid = (strcmp(sig_hex, computed_hex) == 0); if (!valid) DBG("Signature mismatch for %s", json_path); free(sig_hex); - free(expected_sig); return valid; } @@ -516,6 +465,12 @@ int load_external_quirks(const char *dir) if (!dir) dir = QUIRK_PROFILE_DIR; + if (!crypto) { + crypto = bt_crypto_new(); + if (!crypto) + DBG("quirk-profile: failed to init crypto"); + } + d = opendir(dir); if (!d) { DBG("quirk-profile: cannot open %s: %s", dir, strerror(errno)); @@ -586,4 +541,9 @@ void free_external_quirks(void) ext_quirks[i] = NULL; } num_ext_quirks = 0; + + if (crypto) { + bt_crypto_unref(crypto); + crypto = NULL; + } } diff --git a/profiles/input/quirk.c b/profiles/input/quirk.c index 053f45b..fa5e7e6 100644 --- a/profiles/input/quirk.c +++ b/profiles/input/quirk.c @@ -36,23 +36,6 @@ static struct gamepad_quirk *quirks[] = { 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) { @@ -69,9 +52,5 @@ int gamepad_quirk_apply(struct input_device *idev, } /* Try external quirks */ - if (external_quirk_match(idev)) { - return external_quirk_apply(idev, req); - } - - return -1; + return external_quirk_apply(idev, req); } diff --git a/profiles/input/quirk.h b/profiles/input/quirk.h index 08e0bce..f2fc81b 100644 --- a/profiles/input/quirk.h +++ b/profiles/input/quirk.h @@ -24,13 +24,10 @@ struct gamepad_quirk { 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); diff --git a/profiles/input/quirks/tg170w.c b/profiles/input/quirks/tg170w.c index d3f37a8..d82c3fa 100644 --- a/profiles/input/quirks/tg170w.c +++ b/profiles/input/quirks/tg170w.c @@ -38,9 +38,14 @@ #include "bluetooth/sdp.h" #include "bluetooth/sdp_lib.h" +#include "gdbus/gdbus.h" + #include "src/log.h" +#include "src/device.h" +#include "src/service.h" #include "../quirk.h" +#include "../device.h" /* * DualShock 4 Bluetooth HID Report Descriptor. @@ -124,25 +129,6 @@ static const uint8_t tg170w_hid_report_descriptor[] = { #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;