mode_ps5.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. #include "mode_ps5.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. Ps5Controller g_ps5Ctl;
  13. static const uint8_t PS5_HID_DESC[] = {
  14. 0x05, 0x01, 0x09, 0x05, 0xA1, 0x01, 0x85, 0x01, 0x09, 0x30, 0x09, 0x31,
  15. 0x09, 0x32, 0x09, 0x35, 0x09, 0x33, 0x09, 0x34, 0x15, 0x00, 0x26, 0xFF,
  16. 0x00, 0x75, 0x08, 0x95, 0x06, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x20,
  17. 0x95, 0x01, 0x81, 0x02, 0x05, 0x01, 0x09, 0x39, 0x15, 0x00, 0x25, 0x07,
  18. 0x35, 0x00, 0x46, 0x3B, 0x01, 0x65, 0x14, 0x75, 0x04, 0x95, 0x01, 0x81,
  19. 0x42, 0x65, 0x00, 0x05, 0x09, 0x19, 0x01, 0x29, 0x0F, 0x15, 0x00, 0x25,
  20. 0x01, 0x75, 0x01, 0x95, 0x0F, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x21,
  21. 0x95, 0x0D, 0x81, 0x02, 0x06, 0x00, 0xFF, 0x09, 0x22, 0x15, 0x00, 0x26,
  22. 0xFF, 0x00, 0x75, 0x08, 0x95, 0x34, 0x81, 0x02, 0x85, 0x02, 0x09, 0x23,
  23. 0x95, 0x2F, 0x91, 0x02, 0x85, 0x05, 0x09, 0x33, 0x95, 0x28, 0xB1, 0x02,
  24. 0x85, 0x08, 0x09, 0x34, 0x95, 0x2F, 0xB1, 0x02, 0x85, 0x09, 0x09, 0x24,
  25. 0x95, 0x13, 0xB1, 0x02, 0x85, 0x0A, 0x09, 0x25, 0x95, 0x1A, 0xB1, 0x02,
  26. 0x85, 0x20, 0x09, 0x26, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x21, 0x09, 0x27,
  27. 0x95, 0x04, 0xB1, 0x02, 0x85, 0x22, 0x09, 0x40, 0x95, 0x3F, 0xB1, 0x02,
  28. 0x85, 0x80, 0x09, 0x28, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x81, 0x09, 0x29,
  29. 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x82, 0x09, 0x2A, 0x95, 0x09, 0xB1, 0x02,
  30. 0x85, 0x83, 0x09, 0x2B, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x84, 0x09, 0x2C,
  31. 0x95, 0x3F, 0xB1, 0x02, 0x85, 0x85, 0x09, 0x2D, 0x95, 0x02, 0xB1, 0x02,
  32. 0x85, 0xA0, 0x09, 0x2E, 0x95, 0x01, 0xB1, 0x02, 0x85, 0xE0, 0x09, 0x2F,
  33. 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF0, 0x09, 0x30, 0x95, 0x3F, 0xB1, 0x02,
  34. 0x85, 0xF1, 0x09, 0x31, 0x95, 0x3F, 0xB1, 0x02, 0x85, 0xF2, 0x09, 0x32,
  35. 0x95, 0x0F, 0xB1, 0x02, 0x85, 0xF4, 0x09, 0x35, 0x95, 0x3F, 0xB1, 0x02,
  36. 0x85, 0xF5, 0x09, 0x36, 0x95, 0x03, 0xB1, 0x02, 0xC0
  37. };
  38. #define PS5_TOUCH_H 1080
  39. #define PS5_STATUS_USB 0x1A // charging + level 10 (~100%)
  40. static unsigned long g_ps5LastMs[NSLOT] = { 0 };
  41. // NSLOT DualSense HID instances, one per bond slot. The host enumerates each as a separate DualSense
  42. // (hid-playstation on Linux/SteamOS, SDL on Windows).
  43. static Adafruit_USBD_HID g_ps5[NSLOT];
  44. // Per-slot MAC base: 4 distinct NICs. OUI 0x001BDC is Sony's; vary the last byte per slot.
  45. static const uint8_t PS5_MAC_BASE[5] = { 0x00, 0x1B, 0xDC, 0x4F, 0x55 };
  46. static uint8_t g_ps5Mac[NSLOT][6];
  47. static bool g_ps5MacInit = false;
  48. static void initPs5Macs()
  49. {
  50. if (g_ps5MacInit)
  51. return;
  52. for (int s = 0; s < NSLOT; s++) {
  53. memcpy(g_ps5Mac[s], PS5_MAC_BASE, 5);
  54. g_ps5Mac[s][5] = (uint8_t)(0x60 + s); // 0x60, 0x61, 0x62, 0x63
  55. }
  56. g_ps5MacInit = true;
  57. }
  58. // GET_FEATURE handler. Per-slot dispatch via per-instance callback. Sizes per drivers/hid/hid-playstation.c:
  59. // 0x05=41, 0x09=20, 0x20=64. TinyUSB writes the report id itself and hands us the buffer PAST it, so we
  60. // fill only the PAYLOAD and return size-1.
  61. static uint16_t ps5GetCommon(uint8_t slot, uint8_t rid, hid_report_type_t type,
  62. uint8_t *buf, uint16_t reqlen)
  63. {
  64. (void)slot;
  65. if (type != HID_REPORT_TYPE_FEATURE || !buf || reqlen == 0)
  66. return 0;
  67. memset(buf, 0, reqlen);
  68. switch (rid) {
  69. // capabilities: identify as DualSense-capable (SDL-only probe; hid-playstation never reads 0x03)
  70. case 0x03: {
  71. if (reqlen < 47)
  72. return 0;
  73. buf[0] = 0x00;
  74. buf[1] = 0x28;
  75. buf[2] = 0x01;
  76. buf[3] = 0x00;
  77. buf[4] = 0x0E; // sensors + lightbar + vibration capability bits
  78. return 47;
  79. }
  80. case 0x05: // motion calibration (41 incl id)
  81. if (reqlen < 40)
  82. return 0;
  83. psNeutralCalib(buf);
  84. return 40;
  85. case 0x09: // pairing info / MAC (20 incl id)
  86. if (reqlen < 19)
  87. return 0;
  88. // MAC at kernel buf[1..6] = payload[0..5]
  89. memcpy(buf, g_ps5Mac[slot], 6);
  90. return 19;
  91. case 0x20: // firmware info (64 incl id)
  92. if (reqlen < 63)
  93. return 0;
  94. buf[23] = 0x01; // hw_version (le32 @ kernel buf[24]) non-zero
  95. buf[27] = 0x01; // fw_version (le32 @ kernel buf[28]) non-zero
  96. return 63;
  97. default:
  98. return 0;
  99. }
  100. }
  101. static void ps5SetCommon(uint8_t slot, uint8_t rid, hid_report_type_t type,
  102. uint8_t const *b, uint16_t n)
  103. {
  104. if (type != HID_REPORT_TYPE_OUTPUT || n < 1)
  105. return;
  106. uint8_t id;
  107. const uint8_t *p;
  108. uint16_t pn;
  109. if (rid == 0) {
  110. id = b[0];
  111. p = b + 1;
  112. pn = (uint16_t)(n - 1);
  113. } else if (rid == 0x02 && b[0] == 0x02 && n >= 5) {
  114. id = rid;
  115. p = b + 1;
  116. pn = (uint16_t)(n - 1);
  117. } // some paths leave report id in b
  118. else {
  119. id = rid;
  120. p = b;
  121. pn = n;
  122. }
  123. if (id != 0x02 || pn < 4)
  124. return;
  125. // `slot` here is the USB slot the report arrived on -> route rumble to the bond slot it's mapped to.
  126. int bond = (slot < NSLOT) ? g_usbToBond[slot] : -1;
  127. if (bond < 0)
  128. return;
  129. hapticSteamRumble((uint16_t)p[3] * 257u, (uint16_t)p[2] * 257u,
  130. (uint8_t)bond);
  131. // DualSense: left=low, right=high
  132. }
  133. #define PS5CB(N) \
  134. static uint16_t ps5Get##N(uint8_t r, hid_report_type_t t, uint8_t *bf, \
  135. uint16_t rl) \
  136. { \
  137. return ps5GetCommon(N, r, t, bf, rl); \
  138. } \
  139. static void ps5Set##N(uint8_t r, hid_report_type_t t, \
  140. uint8_t const *b, uint16_t n) \
  141. { \
  142. ps5SetCommon(N, r, t, b, n); \
  143. }
  144. // clang-format off
  145. PS5CB(0)
  146. PS5CB(1)
  147. PS5CB(2)
  148. PS5CB(3)
  149. // clang-format on
  150. typedef uint16_t (*ps5_getcb_t)(uint8_t, hid_report_type_t, uint8_t *,
  151. uint16_t);
  152. typedef void (*ps5_setcb_t)(uint8_t, hid_report_type_t, uint8_t const *,
  153. uint16_t);
  154. static ps5_getcb_t const PS5_GETCB[NSLOT] = { ps5Get0, ps5Get1, ps5Get2,
  155. ps5Get3 };
  156. static ps5_setcb_t const PS5_SETCB[NSLOT] = { ps5Set0, ps5Set1, ps5Set2,
  157. ps5Set3 };
  158. // usbSlot drives the per-HID sequence counter; bond is the controller whose decoded input feeds the report.
  159. static void ps5Build(uint8_t usbSlot, uint8_t slot, uint8_t out[63])
  160. {
  161. uint32_t b = psButtonsFromSteam(g_in[slot].buttons);
  162. bool lTouch = (b & TB_LPADT) || (b & TB_LPADC),
  163. rTouch = (b & TB_RPADT) || (b & TB_RPADC);
  164. memset(out, 0, 63);
  165. out[0] = swStick(g_in[slot].lx, false);
  166. out[1] = swStick(g_in[slot].ly, true);
  167. out[2] = swStick(g_in[slot].rx, false);
  168. out[3] = swStick(g_in[slot].ry, true);
  169. out[4] = g_in[slot].lt;
  170. out[5] = g_in[slot].rt;
  171. static uint8_t seq[NSLOT] = { 0 };
  172. out[6] = seq[usbSlot]++;
  173. out[7] = psHatNibble(b) | psFaceNibble(b);
  174. out[8] = psShouldersByte(b, g_in[slot].lt, g_in[slot].rt);
  175. out[9] = ((b & TB_STEAM) ? 0x01 : 0) |
  176. ((b & TB_TOUCH || b & TB_LPADC || b & TB_RPADC) ? 0x02 : 0) |
  177. ((b & TB_MUTE) ? 0x04 : 0);
  178. out[15] = g_in[slot].gx & 0xFF;
  179. out[16] = g_in[slot].gx >> 8;
  180. out[17] = g_in[slot].gz & 0xFF;
  181. out[18] = g_in[slot].gz >> 8;
  182. out[19] = (-g_in[slot].gy) & 0xFF;
  183. out[20] = (-g_in[slot].gy) >> 8;
  184. out[21] = g_in[slot].ax & 0xFF;
  185. out[22] = g_in[slot].ax >> 8;
  186. out[23] = g_in[slot].ay & 0xFF;
  187. out[24] = g_in[slot].ay >> 8;
  188. out[25] = g_in[slot].az & 0xFF;
  189. out[26] = g_in[slot].az >> 8;
  190. uint16_t lx, ly, rx, ry;
  191. steamPadsToTouch(b, PS5_TOUCH_H, g_in[slot].lpx, g_in[slot].lpy,
  192. g_in[slot].rpx, g_in[slot].rpy, &lx, &ly, &rx, &ry);
  193. touchPackPads(out + 32, lTouch, rTouch, lx, ly, rx, ry);
  194. out[52] = PS5_STATUS_USB;
  195. }
  196. // Dynamic-mount mode: begin() is unused (setup() calls beginPool()+usbReenumerate instead).
  197. void Ps5Controller::begin()
  198. {
  199. }
  200. // HID budget: clean PS modes have no wake mouse (CFG_TUD_HID slots); normal PS5 keeps the wake mouse (1 HID).
  201. uint8_t Ps5Controller::maxSlots() const
  202. {
  203. uint8_t cap = modeIsCleanPS(g_usbMode) ? (uint8_t)CFG_TUD_HID :
  204. (uint8_t)(CFG_TUD_HID - 1);
  205. return cap < NSLOT ? cap : (uint8_t)NSLOT;
  206. }
  207. void Ps5Controller::usbIdentity()
  208. {
  209. USBDevice.setID(0x054C, 0x0CE6);
  210. // Clean mode (no wake mouse, no WebUSB) matches the real wired DualSense's
  211. // single-HID layout -> use 0x0100 to match it. Normal MODE_PS5 (with wake
  212. // mouse + WebUSB) uses 0x0110 so Windows re-reads the descriptor on
  213. // a mode switch between the two (per the bcdDevice caching rule).
  214. USBDevice.setDeviceVersion(modeIsCleanPS(g_usbMode) ? 0x0100 : 0x0110);
  215. USBDevice.setManufacturerDescriptor("Sony Interactive Entertainment");
  216. USBDevice.setProductDescriptor("DualSense Wireless Controller");
  217. }
  218. // One-time: create the DualSense HID pool and lock instance indices (wake mouse, if any, was begun first).
  219. void Ps5Controller::beginPool()
  220. {
  221. initPs5Macs();
  222. uint8_t pool = maxSlots();
  223. for (uint8_t s = 0; s < pool; s++) {
  224. g_ps5[s].enableOutEndpoint(true);
  225. g_ps5[s].setReportCallback(PS5_GETCB[s], PS5_SETCB[s]);
  226. g_ps5[s].setReportDescriptor(PS5_HID_DESC, sizeof PS5_HID_DESC);
  227. g_ps5[s].setPollInterval(1);
  228. g_ps5[s].begin();
  229. }
  230. }
  231. void Ps5Controller::mountSlots(uint8_t k)
  232. {
  233. for (uint8_t u = 0; u < k; u++)
  234. USBDevice.addInterface(g_ps5[u]);
  235. }
  236. void Ps5Controller::task()
  237. {
  238. for (uint8_t u = 0; u < g_usbMountCount; u++) {
  239. if (!g_ps5[u].ready())
  240. continue;
  241. if (millis() - g_ps5LastMs[u] < USB_STREAM_MS)
  242. continue;
  243. int bond = g_usbToBond[u];
  244. if (bond < 0)
  245. continue;
  246. g_ps5LastMs[u] = millis();
  247. uint8_t p[63];
  248. ps5Build(u, (uint8_t)bond, p);
  249. usbTxHid(&g_ps5[u], 0x01, p, sizeof p);
  250. }
  251. }