mode_hidgyro.cpp 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. #include "mode_hidgyro.h"
  2. #include "triton.h"
  3. #include "gamepad_util.h"
  4. #include "config.h"
  5. #include "haptics.h"
  6. #include "bonds.h"
  7. #include "usb_mount.h"
  8. #include "usb_tx.h"
  9. #include <Adafruit_TinyUSB.h>
  10. #include <Arduino.h>
  11. #include <string.h>
  12. HidGyroController g_hidGyroCtl;
  13. static const uint8_t GYRO_HID_DESC[] = {
  14. 0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09,
  15. 0x31, 0x09, 0x32, 0x09, 0x35, 0x15, 0x00, 0x25, 0x07, 0x75, 0x04,
  16. 0x95, 0x01, 0x81, 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29,
  17. 0x0E, 0x15, 0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x0E, 0x81, 0x02,
  18. 0x06, 0x00, 0xFF, 0x09, 0x20, 0x75, 0x06, 0x95, 0x01, 0x15, 0x00,
  19. 0x25, 0x7F, 0x81, 0x02, 0x05, 0x01, 0x09, 0x33, 0x09, 0x34, 0x15,
  20. 0x00, 0x26, 0xFF, 0x00, 0x75, 0x08, 0x95, 0x02, 0x81, 0x02, 0x06,
  21. 0x00, 0xFF, 0x09, 0x21, 0x95, 0x36, 0x81, 0x02, 0x85, 0x05, 0x09,
  22. 0x22, 0x95, 0x1F, 0x91, 0x02, 0x85, 0x04, 0x09, 0x23, 0x95, 0x24,
  23. 0xB1, 0x02, 0x85, 0x02, 0x09, 0x24, 0x95, 0x24, 0xB1, 0x02, 0xC0
  24. };
  25. #define DS4_TOUCH_H 942
  26. #define DS4_STATUS_USB 0x1B // cable + level 11 (full)
  27. static unsigned long g_gyroLastMs[NSLOT] = { 0 };
  28. // NSLOT HID instances: 4 DS4 gamepads on the wire, one per bond slot. The host enumerates each as a
  29. // separate input device (hid-playstation, hid-sony, SDL, Steam Input).
  30. static Adafruit_USBD_HID g_hidGyro[NSLOT];
  31. // Per-slot MAC base: 4 distinct NICs so the host sees 4 different devices (real DS4s have unique MACs).
  32. // OUI 0x001BDC is Sony's; vary the last byte per slot.
  33. static const uint8_t DS4_MAC_BASE[5] = { 0x00, 0x1B, 0xDC, 0x4F, 0x55 };
  34. static uint8_t g_ds4Mac[NSLOT][6];
  35. static bool g_ds4MacInit = false;
  36. static void initDs4Macs()
  37. {
  38. if (g_ds4MacInit)
  39. return;
  40. for (int s = 0; s < NSLOT; s++) {
  41. memcpy(g_ds4Mac[s], DS4_MAC_BASE, 5);
  42. g_ds4Mac[s][5] = (uint8_t)(0x50 + s); // 0x50, 0x51, 0x52, 0x53
  43. }
  44. g_ds4MacInit = true;
  45. }
  46. // GET_FEATURE handler. Per-slot dispatch via per-instance callback (ps5Get##N passes the slot index).
  47. // Sizes per drivers/hid/hid-playstation.c: 0x02=37, 0x12=16, 0xA3=49; legacy hid-sony MAC 0x81=7.
  48. static uint16_t hidGyroGetCommon(uint8_t slot, uint8_t rid,
  49. hid_report_type_t type, uint8_t *buf,
  50. uint16_t reqlen)
  51. {
  52. (void)slot;
  53. if (type != HID_REPORT_TYPE_FEATURE || !buf || reqlen == 0)
  54. return 0;
  55. memset(buf, 0, reqlen);
  56. switch (rid) {
  57. case 0x02: // motion calibration (37 incl id)
  58. if (reqlen < 36)
  59. return 0;
  60. psNeutralCalib(buf);
  61. return 36;
  62. case 0x12: // pairing info / MAC, hid-playstation (16 incl id)
  63. if (reqlen < 15)
  64. return 0;
  65. memcpy(buf, g_ds4Mac[slot], 6);
  66. return 15;
  67. case 0x81: // MAC, legacy hid-sony USB (7 incl id)
  68. if (reqlen < 6)
  69. return 0;
  70. memcpy(buf, g_ds4Mac[slot], 6);
  71. return 6;
  72. case 0xA3: // firmware / hardware info (49 incl id)
  73. if (reqlen < 48)
  74. return 0;
  75. // non-zero version (contents not validated over USB)
  76. buf[0] = 0x01;
  77. return 48;
  78. default:
  79. return 0;
  80. }
  81. }
  82. static void hidGyroSetCommon(uint8_t slot, uint8_t rid, hid_report_type_t type,
  83. uint8_t const *b, uint16_t n)
  84. {
  85. if (type != HID_REPORT_TYPE_OUTPUT || n < 1)
  86. return;
  87. uint8_t id;
  88. const uint8_t *p;
  89. uint16_t pn;
  90. if (rid == 0) {
  91. id = b[0];
  92. p = b + 1;
  93. pn = (uint16_t)(n - 1);
  94. } else {
  95. id = rid;
  96. p = b;
  97. pn = n;
  98. }
  99. // DS4 USB effects: report 0x05, magic at byte 0, effects block starts at byte 3.
  100. if (id != 0x05 || pn < 5)
  101. return;
  102. // `slot` is the USB slot the report arrived on -> route rumble to its mapped bond slot.
  103. int bond = (slot < NSLOT) ? g_usbToBond[slot] : -1;
  104. if (bond < 0)
  105. return;
  106. hapticSteamRumble((uint16_t)p[4] * 257u, (uint16_t)p[3] * 257u,
  107. (uint8_t)bond); // DS4: left=low, right=high
  108. }
  109. // Per-slot callback trampolines -- the Adafruit HID class has ONE _get/_set pair shared across instances,
  110. // and the lib's tud_hid_*_cb doesn't pass the interface index to the user callback. Generate a
  111. // per-instance trampoline that closes over the slot index, matching the pattern in puck_hid.cpp.
  112. #define HIDGYROCB(N) \
  113. static uint16_t hidGyroGet##N(uint8_t r, hid_report_type_t t, \
  114. uint8_t *bf, uint16_t rl) \
  115. { \
  116. return hidGyroGetCommon(N, r, t, bf, rl); \
  117. } \
  118. static void hidGyroSet##N(uint8_t r, hid_report_type_t t, \
  119. uint8_t const *b, uint16_t n) \
  120. { \
  121. hidGyroSetCommon(N, r, t, b, n); \
  122. }
  123. // clang-format off
  124. HIDGYROCB(0)
  125. HIDGYROCB(1)
  126. HIDGYROCB(2)
  127. HIDGYROCB(3)
  128. // clang-format on
  129. typedef uint16_t (*ds4_getcb_t)(uint8_t, hid_report_type_t, uint8_t *,
  130. uint16_t);
  131. typedef void (*ds4_setcb_t)(uint8_t, hid_report_type_t, uint8_t const *,
  132. uint16_t);
  133. static ds4_getcb_t const DS4_GETCB[NSLOT] = { hidGyroGet0, hidGyroGet1,
  134. hidGyroGet2, hidGyroGet3 };
  135. static ds4_setcb_t const DS4_SETCB[NSLOT] = { hidGyroSet0, hidGyroSet1,
  136. hidGyroSet2, hidGyroSet3 };
  137. // usbSlot drives the per-HID counters; slot (bond) is the controller whose decoded input feeds the report.
  138. static void hidGyroBuild(uint8_t usbSlot, uint8_t slot, uint8_t out[63])
  139. {
  140. uint32_t b = psButtonsFromSteam(g_in[slot].buttons);
  141. // A pad mapped to a stick must NOT also report as a touchpad contact -- the host would read the same
  142. // finger twice (stick deflection AND a cursor drag).
  143. bool lTouch = g_padStick[0] == PS_OFF &&
  144. ((b & TB_LPADT) || (b & TB_LPADC)),
  145. rTouch = g_padStick[1] == PS_OFF &&
  146. ((b & TB_RPADT) || (b & TB_RPADC));
  147. memset(out, 0, 63);
  148. int16_t lx, ly, rx, ry;
  149. slotSticks(slot, &lx, &ly, &rx, &ry);
  150. out[0] = swStick(lx, false);
  151. out[1] = swStick(ly, true);
  152. out[2] = swStick(rx, false);
  153. out[3] = swStick(ry, true);
  154. out[4] = psHatNibble(b) | psFaceNibble(b);
  155. out[5] = psShouldersByte(b, g_in[slot].lt, g_in[slot].rt);
  156. static uint8_t ctr[NSLOT] = { 0 };
  157. out[6] = ((ctr[usbSlot]++ & 0x0F) << 4) |
  158. ((b & TB_TOUCH || b & TB_LPADC || b & TB_RPADC) ? 0x02 : 0) |
  159. ((b & TB_STEAM) ? 0x01 : 0);
  160. out[7] = g_in[slot].lt;
  161. out[8] = g_in[slot].rt;
  162. out[12] = g_in[slot].gx & 0xFF;
  163. out[13] = g_in[slot].gx >> 8;
  164. out[14] = g_in[slot].gz & 0xFF;
  165. out[15] = g_in[slot].gz >> 8;
  166. out[16] = (-g_in[slot].gy) & 0xFF;
  167. out[17] = (-g_in[slot].gy) >> 8;
  168. out[18] = g_in[slot].ax & 0xFF;
  169. out[19] = g_in[slot].ax >> 8;
  170. out[20] = g_in[slot].ay & 0xFF;
  171. out[21] = g_in[slot].ay >> 8;
  172. out[22] = g_in[slot].az & 0xFF;
  173. out[23] = g_in[slot].az >> 8;
  174. out[29] = DS4_STATUS_USB;
  175. if (lTouch || rTouch) {
  176. uint16_t tlx, tly, trx, trry;
  177. steamPadsToTouch(b, DS4_TOUCH_H, g_in[slot].lpx, g_in[slot].lpy,
  178. g_in[slot].rpx, g_in[slot].rpy, &tlx, &tly,
  179. &trx, &trry);
  180. static uint8_t tstamp[NSLOT] = { 0 };
  181. out[32] = 1;
  182. out[33] = tstamp[usbSlot]++;
  183. touchPackPads(out + 34, lTouch, rTouch, tlx, tly, trx, trry);
  184. } else {
  185. out[32] = 0;
  186. touchPackPads(
  187. out + 34, false, false, 0, 0, 0,
  188. 0); // contact 0x80 -- memset(0) reads as touch @0,0
  189. }
  190. }
  191. // Dynamic-mount mode: begin() is unused (setup() calls beginPool()+usbReenumerate instead).
  192. void HidGyroController::begin()
  193. {
  194. }
  195. // HID budget: clean DS4 (MODE_DS4_GAME) has no wake mouse; normal HIDGYRO keeps it (1 HID).
  196. uint8_t HidGyroController::maxSlots() const
  197. {
  198. uint8_t cap = modeIsCleanPS(g_usbMode) ? (uint8_t)CFG_TUD_HID :
  199. (uint8_t)(CFG_TUD_HID - 1);
  200. return cap < NSLOT ? cap : (uint8_t)NSLOT;
  201. }
  202. void HidGyroController::usbIdentity()
  203. {
  204. USBDevice.setID(0x054C, 0x05C4);
  205. USBDevice.setVersion(0x0200);
  206. USBDevice.setDeviceVersion(0x0120);
  207. USBDevice.setManufacturerDescriptor("Sony Computer Entertainment");
  208. USBDevice.setProductDescriptor("Wireless Controller");
  209. }
  210. void HidGyroController::beginPool()
  211. {
  212. initDs4Macs();
  213. uint8_t pool = maxSlots();
  214. for (uint8_t s = 0; s < pool; s++) {
  215. g_hidGyro[s].enableOutEndpoint(true);
  216. g_hidGyro[s].setReportCallback(DS4_GETCB[s], DS4_SETCB[s]);
  217. g_hidGyro[s].setReportDescriptor(GYRO_HID_DESC,
  218. sizeof GYRO_HID_DESC);
  219. g_hidGyro[s].setPollInterval(1);
  220. g_hidGyro[s].begin();
  221. }
  222. }
  223. void HidGyroController::mountSlots(uint8_t k)
  224. {
  225. for (uint8_t u = 0; u < k; u++)
  226. USBDevice.addInterface(g_hidGyro[u]);
  227. }
  228. void HidGyroController::task()
  229. {
  230. for (uint8_t u = 0; u < g_usbMountCount; u++) {
  231. if (!g_hidGyro[u].ready())
  232. continue;
  233. if (millis() - g_gyroLastMs[u] < USB_STREAM_MS)
  234. continue;
  235. int bond = g_usbToBond[u];
  236. if (bond < 0)
  237. continue;
  238. g_gyroLastMs[u] = millis();
  239. uint8_t p[63];
  240. hidGyroBuild(u, (uint8_t)bond, p);
  241. usbTxHid(&g_hidGyro[u], 0x01, p, sizeof p);
  242. }
  243. }