Parcourir la source

PS4 and PS5 controllers should enumerate better and gyro axes are fixed (#59)

* PS5 button mapping and gyro fix

* ps5 issues fully resolved

* ps4 enumeration and gyro fixed

* fix comments

* fmt

* fmt
Jari il y a 4 mois
Parent
commit
833c2ac96b
5 fichiers modifiés avec 116 ajouts et 36 suppressions
  1. 20 0
      OpenPuck/gamepad_util.cpp
  2. 14 0
      OpenPuck/gamepad_util.h
  3. 47 5
      OpenPuck/mode_hidgyro.cpp
  4. 28 25
      OpenPuck/mode_ps5.cpp
  5. 7 6
      OpenPuck/triton.h

+ 20 - 0
OpenPuck/gamepad_util.cpp

@@ -2,6 +2,26 @@
 #include "triton.h"
 #include "config.h"
 
+void psNeutralCalib(uint8_t *buf)
+{
+	// Payload offsets = kernel buf[] index minus 1. buf[0..5] gyro bias stays zero (caller memset). Symmetric
+	// ranges -> non-zero divisors on the host.
+	le16(buf + 6, 2844);
+	le16(buf + 8, -2844); // gyro pitch +/-
+	le16(buf + 10, 2844);
+	le16(buf + 12, -2844); // gyro yaw +/-
+	le16(buf + 14, 2844);
+	le16(buf + 16, -2844); // gyro roll +/-
+	le16(buf + 18, 2844);
+	le16(buf + 20, 2844); // gyro speed +/- (sum != 0)
+	le16(buf + 22, 8192);
+	le16(buf + 24, -8192); // accel X +/-
+	le16(buf + 26, 8192);
+	le16(buf + 28, -8192); // accel Y +/-
+	le16(buf + 30, 8192);
+	le16(buf + 32, -8192); // accel Z +/-
+}
+
 uint8_t swStick(int16_t v, bool invert)
 {
 	int32_t a = 0x80 + (invert ? -((int32_t)v >> 8) : ((int32_t)v >> 8));

+ 14 - 0
OpenPuck/gamepad_util.h

@@ -11,6 +11,20 @@
 // int16 stick (center 0) -> uint8 (center 0x80), optional axis invert (HID Y is down-positive).
 uint8_t swStick(int16_t v, bool invert);
 
+// Write a signed 16-bit value little-endian.
+static inline void le16(uint8_t *p, int16_t v)
+{
+	p[0] = (uint8_t)(v & 0xFF);
+	p[1] = (uint8_t)((v >> 8) & 0xFF);
+}
+
+// Fill a DS4/DualSense motion-calibration feature report PAYLOAD. The TinyUSB GET_REPORT path already wrote the
+// report id and hands us the buffer PAST it, so our offsets = the kernel's buf[] index minus one (bias at 0..5,
+// stays zero from the caller's memset). Zero bias + symmetric non-zero ranges so hid-playstation's/hid-sony's
+// divisor (|plus-bias|+|minus-bias|, and gyro speed_plus+speed_minus) is never zero. Shared by report 0x02 (DS4)
+// and 0x05 (DualSense) -- identical layout; the caller returns size-1 and leaves trailing bytes zero.
+void psNeutralCalib(uint8_t *buf);
+
 // Steam trackpad s16 coords -> absolute touch surface. TOUCH_PAD_W is split into left/right halves so both
 // pads can co-exist as two contacts on a single DualSense/DS4 touchpad.
 #define TOUCH_PAD_W 1920u

+ 47 - 5
OpenPuck/mode_hidgyro.cpp

@@ -27,6 +27,48 @@ static const uint8_t GYRO_HID_DESC[] = {
 static unsigned long g_gyroLastMs = 0;
 static Adafruit_USBD_HID g_hidGyro;
 
+static const uint8_t DS4_MAC[6] = { 0x00, 0x1B, 0xDC, 0x4F, 0x55, 0x53 };
+
+// GET_FEATURE handler. SteamOS drives the DS4 (054C:05C4) with hid-playstation (older kernels: hid-sony); both
+// issue GET_FEATURE requests during probe (calibration + MAC + firmware) and ABORT the bind if any STALLs or
+// returns the wrong length. ps_get_report enforces, on every bus: the response is the exact report size with the
+// report id as its first byte (data fields then start at buf[1]); CRC is only checked over Bluetooth. macOS/SDL
+// are lenient (re-derive), which is why the DS4 shows up there but not on a Deck. TinyUSB writes the report id
+// itself and hands us the buffer PAST it, so we fill only the PAYLOAD and return size-1. We satisfy both drivers'
+// report ids. (Sizes per drivers/hid/hid-playstation.c: 0x02=37, 0x12=16, 0xA3=49; legacy hid-sony MAC 0x81=7.)
+static uint16_t hidGyroGet(uint8_t rid, hid_report_type_t type, uint8_t *buf,
+			   uint16_t reqlen)
+{
+	if (type != HID_REPORT_TYPE_FEATURE || !buf || reqlen == 0)
+		return 0;
+	memset(buf, 0, reqlen);
+	switch (rid) {
+	case 0x02: // motion calibration (37 incl id)
+		if (reqlen < 36)
+			return 0;
+		psNeutralCalib(buf);
+		return 36;
+	case 0x12: // pairing info / MAC, hid-playstation (16 incl id)
+		if (reqlen < 15)
+			return 0;
+		memcpy(buf, DS4_MAC, 6);
+		return 15;
+	case 0x81: // MAC, legacy hid-sony USB (7 incl id)
+		if (reqlen < 6)
+			return 0;
+		memcpy(buf, DS4_MAC, 6);
+		return 6;
+	case 0xA3: // firmware / hardware info (49 incl id)
+		if (reqlen < 48)
+			return 0;
+		// non-zero version (contents not validated over USB)
+		buf[0] = 0x01;
+		return 48;
+	default:
+		return 0;
+	}
+}
+
 static void hidGyroSet(uint8_t rid, hid_report_type_t type, uint8_t const *b,
 		       uint16_t n)
 {
@@ -71,10 +113,10 @@ static void hidGyroBuild(uint8_t out[63])
 	out[8] = g_in.rt;
 	out[12] = g_in.gx & 0xFF;
 	out[13] = g_in.gx >> 8;
-	out[14] = g_in.gy & 0xFF;
-	out[15] = g_in.gy >> 8;
-	out[16] = (-g_in.gz) & 0xFF;
-	out[17] = (-g_in.gz) >> 8;
+	out[14] = g_in.gz & 0xFF;
+	out[15] = g_in.gz >> 8;
+	out[16] = (-g_in.gy) & 0xFF;
+	out[17] = (-g_in.gy) >> 8;
 	out[18] = g_in.ax & 0xFF;
 	out[19] = g_in.ax >> 8;
 	out[20] = g_in.ay & 0xFF;
@@ -107,7 +149,7 @@ void HidGyroController::begin()
 	USBDevice.setManufacturerDescriptor("Sony Computer Entertainment");
 	USBDevice.setProductDescriptor("Wireless Controller");
 	g_hidGyro.enableOutEndpoint(true);
-	g_hidGyro.setReportCallback(NULL, hidGyroSet);
+	g_hidGyro.setReportCallback(hidGyroGet, hidGyroSet);
 	g_hidGyro.setReportDescriptor(GYRO_HID_DESC, sizeof GYRO_HID_DESC);
 
 	// 1ms bInterval so the RF rate is the only latency limit (matches Xbox)

+ 28 - 25
OpenPuck/mode_ps5.cpp

@@ -38,7 +38,13 @@ static const uint8_t PS5_HID_DESC[] = {
 #define PS5_STATUS_USB 0x1A // charging + level 10 (~100%)
 static unsigned long g_ps5LastMs = 0;
 static Adafruit_USBD_HID g_ps5;
+static const uint8_t PS5_MAC[6] = { 0x00, 0x1B, 0xDC, 0x4F, 0x55, 0x54 };
 
+// GET_FEATURE handler. SteamOS's hid-playstation reads calibration (0x05), pairing/MAC (0x09) and firmware
+// (0x20) during probe and ABORTS the bind unless each returns its EXACT size with the report id as the first
+// byte; CRC is checked only over Bluetooth. macOS/SDL are lenient, so the DualSense shows up there but not on a
+// Deck. TinyUSB writes the report id itself and hands us the buffer PAST it, so we fill only the PAYLOAD and
+// return size-1. Sizes per drivers/hid/hid-playstation.c: 0x05=41, 0x09=20, 0x20=64.
 static uint16_t ps5Get(uint8_t rid, hid_report_type_t type, uint8_t *buf,
 		       uint16_t reqlen)
 {
@@ -46,7 +52,7 @@ static uint16_t ps5Get(uint8_t rid, hid_report_type_t type, uint8_t *buf,
 		return 0;
 	memset(buf, 0, reqlen);
 	switch (rid) {
-	// capabilities: identify as DualSense-capable (SDL checks byte 2 == 0x28)
+	// capabilities: identify as DualSense-capable (SDL-only probe; hid-playstation never reads 0x03)
 	case 0x03: {
 		if (reqlen < 47)
 			return 0;
@@ -57,26 +63,23 @@ static uint16_t ps5Get(uint8_t rid, hid_report_type_t type, uint8_t *buf,
 		buf[4] = 0x0E; // sensors + lightbar + vibration capability bits
 		return 47;
 	}
-
-	// calibration: neutral-ish non-zero block so SDL's read succeeds
-	case 0x05: {
-		uint16_t n = reqlen < 40 ? reqlen : 40;
-		for (uint16_t i = 0; i < n; i++)
-			buf[i] = 0;
-		return n;
-	}
-	case 0x09: { // serial number feature
-		uint16_t n = reqlen < 20 ? reqlen : 20;
-		for (uint16_t i = 0; i < n && i < 12; i++)
-			buf[i] = (uint8_t)("010203040506"[i]);
-		return n;
-	}
-	case 0x20: { // firmware info
-		uint16_t n = reqlen < 63 ? reqlen : 63;
-		buf[0] = 0x01;
-		buf[1] = 0x00; // minimal non-zero firmware version
-		return n;
-	}
+	case 0x05: // motion calibration (41 incl id)
+		if (reqlen < 40)
+			return 0;
+		psNeutralCalib(buf);
+		return 40;
+	case 0x09: // pairing info / MAC (20 incl id)
+		if (reqlen < 19)
+			return 0;
+		// MAC at kernel buf[1..6] = payload[0..5]
+		memcpy(buf, PS5_MAC, 6);
+		return 19;
+	case 0x20: // firmware info (64 incl id)
+		if (reqlen < 63)
+			return 0;
+		buf[23] = 0x01; // hw_version (le32 @ kernel buf[24]) non-zero
+		buf[27] = 0x01; // fw_version (le32 @ kernel buf[28]) non-zero
+		return 63;
 	default:
 		return 0;
 	}
@@ -131,10 +134,10 @@ static void ps5Build(uint8_t out[63])
 		 ((b & TB_MUTE) ? 0x04 : 0);
 	out[15] = g_in.gx & 0xFF;
 	out[16] = g_in.gx >> 8;
-	out[17] = (-g_in.gz) & 0xFF;
-	out[18] = (-g_in.gz) >> 8;
-	out[19] = g_in.gy & 0xFF;
-	out[20] = g_in.gy >> 8;
+	out[17] = g_in.gz & 0xFF;
+	out[18] = g_in.gz >> 8;
+	out[19] = (-g_in.gy) & 0xFF;
+	out[20] = (-g_in.gy) >> 8;
 	out[21] = g_in.ax & 0xFF;
 	out[22] = g_in.ax >> 8;
 	out[23] = g_in.ay & 0xFF;

+ 7 - 6
OpenPuck/triton.h

@@ -30,15 +30,16 @@
 #define TB_LB 0x80000u
 #define TB_RPADT 0x200000u
 #define TB_RPADC 0x400000u
-
-// virtual: PS Touch Click (back-paddle/QAM target, not a real Triton button)
-#define TB_TOUCH 0x100000u
-
-// virtual: PS5 Mute button (back-paddle/QAM target, not a real Triton button)
-#define TB_MUTE 0x1000000u
 #define TB_LPADT 0x2000000u
 #define TB_LPADC 0x4000000u
 
+// Virtual PS targets, settable ONLY via a back-paddle/QAM remap (psOrBackCode / tritonFromCode), never by the
+// controller itself. They live on bits 30/31 -- the only bits the SC2 0x45 report does NOT use (see PROTOCOL.md
+// §8.1; bits 0..29 are all real). They originally aliased 0x00100000 (right stick touch) and 0x01000000 (left
+// stick touch), so merely RESTING a thumb on a capacitive stick fired the DualSense trackpad-click / mute.
+#define TB_TOUCH 0x40000000u // PS touchpad click
+#define TB_MUTE 0x80000000u // PS5 mute
+
 // all four back paddles held -> mode-switch chord guard
 #define CHORD_BACK4 (TB_R4 | TB_L4 | TB_R5 | TB_L5)