mode_ps5.cpp 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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. // USB firmware-information payload from a DualSense, excluding report ID 0x20.
  49. // This emulated identity is independent of OpenPuck's own build version.
  50. static const uint8_t PS5_FIRMWARE_INFO[] = {
  51. 0x4A, 0x75, 0x6C, 0x20, 0x20, 0x34, 0x20, 0x32, 0x30, 0x32, 0x35,
  52. 0x31, 0x30, 0x3A, 0x31, 0x30, 0x3A, 0x33, 0x32, 0x02, 0x00, 0x04,
  53. 0x00, 0x13, 0x04, 0x00, 0x00, 0x2A, 0x00, 0x10, 0x01, 0x50, 0x38,
  54. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30,
  55. 0x06, 0x00, 0x00, 0x2A, 0x00, 0x01, 0x00, 0x0A, 0x00, 0x02, 0x00,
  56. 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
  57. };
  58. static_assert(sizeof PS5_FIRMWARE_INFO == 63, "PS5 firmware report size");
  59. static void initPs5Macs()
  60. {
  61. if (g_ps5MacInit)
  62. return;
  63. for (int s = 0; s < NSLOT; s++) {
  64. memcpy(g_ps5Mac[s], PS5_MAC_BASE, 5);
  65. g_ps5Mac[s][5] = (uint8_t)(0x60 + s); // 0x60, 0x61, 0x62, 0x63
  66. }
  67. g_ps5MacInit = true;
  68. }
  69. // GET_FEATURE handler. Per-slot dispatch via per-instance callback. Sizes per drivers/hid/hid-playstation.c:
  70. // 0x05=41, 0x09=20, 0x20=64. TinyUSB writes the report id itself and hands us the buffer PAST it, so we
  71. // fill only the PAYLOAD and return size-1.
  72. static uint16_t ps5GetCommon(uint8_t slot, uint8_t rid, hid_report_type_t type,
  73. uint8_t *buf, uint16_t reqlen)
  74. {
  75. (void)slot;
  76. if (type != HID_REPORT_TYPE_FEATURE || !buf || reqlen == 0)
  77. return 0;
  78. memset(buf, 0, reqlen);
  79. switch (rid) {
  80. // capabilities: identify as DualSense-capable (SDL-only probe; hid-playstation never reads 0x03)
  81. case 0x03: {
  82. if (reqlen < 47)
  83. return 0;
  84. buf[0] = 0x00;
  85. buf[1] = 0x28;
  86. buf[2] = 0x01;
  87. buf[3] = 0x00;
  88. buf[4] = 0x0E; // sensors + lightbar + vibration capability bits
  89. return 47;
  90. }
  91. case 0x05: // motion calibration (41 incl id)
  92. if (reqlen < 40)
  93. return 0;
  94. psNeutralCalib(buf);
  95. return 40;
  96. case 0x09: // pairing info / MAC (20 incl id)
  97. if (reqlen < 19)
  98. return 0;
  99. // MAC at kernel buf[1..6] = payload[0..5]
  100. memcpy(buf, g_ps5Mac[slot], 6);
  101. return 19;
  102. case 0x20: // firmware info (64 incl id)
  103. if (reqlen < sizeof PS5_FIRMWARE_INFO)
  104. return 0;
  105. memcpy(buf, PS5_FIRMWARE_INFO, sizeof PS5_FIRMWARE_INFO);
  106. return sizeof PS5_FIRMWARE_INFO;
  107. default:
  108. return 0;
  109. }
  110. }
  111. static void ps5SetCommon(uint8_t slot, uint8_t rid, hid_report_type_t type,
  112. uint8_t const *b, uint16_t n)
  113. {
  114. if (type != HID_REPORT_TYPE_OUTPUT || !b || n < 1)
  115. return;
  116. uint8_t id;
  117. const uint8_t *p;
  118. uint16_t pn;
  119. if (rid == 0) {
  120. id = b[0];
  121. p = b + 1;
  122. pn = (uint16_t)(n - 1);
  123. } else {
  124. // TinyUSB already strips the ID from control SET_REPORT data.
  125. // A leading 0x02 here is the haptics flag, not another ID.
  126. id = rid;
  127. p = b;
  128. pn = n;
  129. }
  130. if (id != 0x02 || pn < 4)
  131. return;
  132. // `slot` here is the USB slot the report arrived on -> route rumble to the bond slot it's mapped to.
  133. int bond = (slot < NSLOT) ? g_usbToBond[slot] : -1;
  134. if (bond < 0)
  135. return;
  136. hapticSteamRumble((uint16_t)p[3] * 257u, (uint16_t)p[2] * 257u,
  137. (uint8_t)bond);
  138. // DualSense: left=low, right=high
  139. }
  140. #define PS5CB(N) \
  141. static uint16_t ps5Get##N(uint8_t r, hid_report_type_t t, uint8_t *bf, \
  142. uint16_t rl) \
  143. { \
  144. return ps5GetCommon(N, r, t, bf, rl); \
  145. } \
  146. static void ps5Set##N(uint8_t r, hid_report_type_t t, \
  147. uint8_t const *b, uint16_t n) \
  148. { \
  149. ps5SetCommon(N, r, t, b, n); \
  150. }
  151. // clang-format off
  152. PS5CB(0)
  153. PS5CB(1)
  154. PS5CB(2)
  155. PS5CB(3)
  156. // clang-format on
  157. typedef uint16_t (*ps5_getcb_t)(uint8_t, hid_report_type_t, uint8_t *,
  158. uint16_t);
  159. typedef void (*ps5_setcb_t)(uint8_t, hid_report_type_t, uint8_t const *,
  160. uint16_t);
  161. static ps5_getcb_t const PS5_GETCB[NSLOT] = { ps5Get0, ps5Get1, ps5Get2,
  162. ps5Get3 };
  163. static ps5_setcb_t const PS5_SETCB[NSLOT] = { ps5Set0, ps5Set1, ps5Set2,
  164. ps5Set3 };
  165. // usbSlot drives the per-HID sequence counter; bond is the controller whose decoded input feeds the report.
  166. static void ps5Build(uint8_t usbSlot, uint8_t slot, uint8_t out[63])
  167. {
  168. uint32_t b = psButtonsFromSteam(g_in[slot].buttons);
  169. psPadClickEdge(slot, (b & (TB_LPADC | TB_RPADC)) != 0);
  170. // A pad mapped to a stick must NOT also report as a touchpad contact -- the host would read the same
  171. // finger twice (stick deflection AND a cursor drag).
  172. bool lTouch = g_padStick[0] == PS_OFF &&
  173. ((b & TB_LPADT) || (b & TB_LPADC)),
  174. rTouch = g_padStick[1] == PS_OFF &&
  175. ((b & TB_RPADT) || (b & TB_RPADC));
  176. memset(out, 0, 63);
  177. int16_t lx, ly, rx, ry;
  178. slotSticks(slot, &lx, &ly, &rx, &ry);
  179. out[0] = swStick(lx, false);
  180. out[1] = swStick(ly, true);
  181. out[2] = swStick(rx, false);
  182. out[3] = swStick(ry, true);
  183. out[4] = g_in[slot].lt;
  184. out[5] = g_in[slot].rt;
  185. static uint8_t seq[NSLOT] = { 0 };
  186. out[6] = seq[usbSlot]++;
  187. out[7] = psHatNibble(b) | psFaceNibble(b);
  188. out[8] = psShouldersByte(b, g_in[slot].lt, g_in[slot].rt);
  189. out[9] = ((b & TB_STEAM) ? 0x01 : 0) |
  190. ((b & TB_TOUCH || b & TB_LPADC || b & TB_RPADC) ? 0x02 : 0) |
  191. ((b & TB_MUTE) ? 0x04 : 0);
  192. out[15] = g_in[slot].gx & 0xFF;
  193. out[16] = g_in[slot].gx >> 8;
  194. out[17] = g_in[slot].gz & 0xFF;
  195. out[18] = g_in[slot].gz >> 8;
  196. out[19] = (-g_in[slot].gy) & 0xFF;
  197. out[20] = (-g_in[slot].gy) >> 8;
  198. out[21] = g_in[slot].ax & 0xFF;
  199. out[22] = g_in[slot].ax >> 8;
  200. out[23] = g_in[slot].ay & 0xFF;
  201. out[24] = g_in[slot].ay >> 8;
  202. out[25] = g_in[slot].az & 0xFF;
  203. out[26] = g_in[slot].az >> 8;
  204. // SDL treats the normal DualSense 32-bit sensor timestamp as ~1/3 us ticks.
  205. uint32_t ps5SensorTimestamp = g_in[slot].imuTimestampUs * 3u;
  206. out[27] = (uint8_t)(ps5SensorTimestamp & 0xFF);
  207. out[28] = (uint8_t)((ps5SensorTimestamp >> 8) & 0xFF);
  208. out[29] = (uint8_t)((ps5SensorTimestamp >> 16) & 0xFF);
  209. out[30] = (uint8_t)((ps5SensorTimestamp >> 24) & 0xFF);
  210. uint16_t tlx, tly, trx, trry;
  211. steamPadsToTouch(b, PS5_TOUCH_H, g_in[slot].lpx, g_in[slot].lpy,
  212. g_in[slot].rpx, g_in[slot].rpy, &tlx, &tly, &trx,
  213. &trry);
  214. touchPackPadsStateful(slot, out + 32, lTouch, rTouch, tlx, tly, trx,
  215. trry);
  216. out[52] = PS5_STATUS_USB;
  217. PsImuFrame imu = psImuFromSteam(g_in[slot]);
  218. le16(out + 15, imu.gx);
  219. le16(out + 17, imu.gy);
  220. le16(out + 19, imu.gz);
  221. le16(out + 21, imu.ax);
  222. le16(out + 23, imu.ay);
  223. le16(out + 25, imu.az);
  224. }
  225. // Dynamic-mount mode: begin() is unused (setup() calls beginPool()+usbReenumerate instead).
  226. void Ps5Controller::begin()
  227. {
  228. }
  229. // HID budget: clean PS modes have no wake mouse (CFG_TUD_HID slots); normal PS5 keeps the wake mouse (1 HID).
  230. uint8_t Ps5Controller::maxSlots() const
  231. {
  232. uint8_t cap = modeIsCleanPS(g_usbMode) ? (uint8_t)CFG_TUD_HID :
  233. (uint8_t)(CFG_TUD_HID - 1);
  234. return cap < NSLOT ? cap : (uint8_t)NSLOT;
  235. }
  236. void Ps5Controller::usbIdentity()
  237. {
  238. USBDevice.setID(0x054C, 0x0CE6);
  239. USBDevice.setVersion(0x0200);
  240. USBDevice.setDeviceVersion(0x0110);
  241. USBDevice.setManufacturerDescriptor("Sony Interactive Entertainment");
  242. USBDevice.setProductDescriptor("DualSense Wireless Controller");
  243. }
  244. // One-time: create the DualSense HID pool and lock instance indices (wake mouse, if any, was begun first).
  245. void Ps5Controller::beginPool()
  246. {
  247. initPs5Macs();
  248. uint8_t pool = maxSlots();
  249. for (uint8_t s = 0; s < pool; s++) {
  250. g_ps5[s].enableOutEndpoint(true);
  251. g_ps5[s].setReportCallback(PS5_GETCB[s], PS5_SETCB[s]);
  252. g_ps5[s].setReportDescriptor(PS5_HID_DESC, sizeof PS5_HID_DESC);
  253. g_ps5[s].setPollInterval(1);
  254. g_ps5[s].begin();
  255. }
  256. }
  257. void Ps5Controller::mountSlots(uint8_t k)
  258. {
  259. for (uint8_t u = 0; u < k; u++)
  260. USBDevice.addInterface(g_ps5[u]);
  261. }
  262. void Ps5Controller::task()
  263. {
  264. for (uint8_t u = 0; u < g_usbMountCount; u++) {
  265. if (!g_ps5[u].ready())
  266. continue;
  267. if (millis() - g_ps5LastMs[u] < USB_STREAM_MS)
  268. continue;
  269. int bond = g_usbToBond[u];
  270. if (bond < 0)
  271. continue;
  272. g_ps5LastMs[u] = millis();
  273. uint8_t p[63];
  274. ps5Build(u, (uint8_t)bond, p);
  275. usbTxHid(&g_ps5[u], 0x01, p, sizeof p);
  276. }
  277. }