usb_descriptors.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. // usb_descriptors.c — mode-aware TinyUSB descriptors.
  2. //
  3. // Puck modes (Steam/Lizard) present VID/PID 28DE:1304 with four HID slot
  4. // interfaces (the cloned puck descriptor) + a WebUSB vendor interface. Emulated
  5. // modes (Xbox/Switch/PS5/PS3/…) present that mode's controller as ONE HID
  6. // gamepad + the same WebUSB vendor interface (so the panel works in every mode).
  7. // The active mode is read from flash at boot; a mode switch persists + reboots.
  8. //
  9. // SPDX-License-Identifier: AGPL-3.0-or-later
  10. #include <string.h>
  11. #include "tusb.h"
  12. #include "usb/usb_descriptors.h"
  13. #include "usb/puck_desc.h"
  14. #include "puck/identity.h"
  15. #include "puck/emu.h"
  16. #include "config/modes.h"
  17. #include "sys/settings.h"
  18. void webusb_set_connected(bool connected);
  19. #if CFG_TUD_CDC
  20. #define PP_BCD PP_BCD_DEVICE_CDC
  21. #else
  22. #define PP_BCD PP_BCD_DEVICE
  23. #endif
  24. // ---- cached per-boot presentation ------------------------------------------
  25. static uint8_t s_mode;
  26. static const emu_mode_t *s_emu; // NULL for puck/xinput modes
  27. static bool s_xinput; // MODE_XBOX: custom vendor class, not HID
  28. static uint8_t s_emu_ifaces = 1; // # of emulated HID interfaces (one per controller)
  29. static tusb_desc_device_t s_dev;
  30. static uint8_t s_emu_cfg[220]; // PP_NSLOT HID (32 B each) + config + vendor
  31. static uint8_t s_vendor_itf;
  32. uint8_t usb_emu_iface_count(void) { return s_emu_ifaces; }
  33. #define MS_OS_20_DESC_LEN 0xB2
  34. static uint8_t s_msos[MS_OS_20_DESC_LEN];
  35. // Build the emulated-mode config descriptor: `nif` HID gamepad interfaces (one
  36. // per controller slot, each with an IN + OUT endpoint) followed by the WebUSB
  37. // vendor interface. Presenting several HID interfaces is what lets more than one
  38. // connected controller appear to the host at once (each interface = one pad).
  39. // Returns total length. HID endpoints: IN 0x81+i / OUT 0x01+i; vendor 0x85/0x05.
  40. static uint16_t build_emu_config(uint8_t *b, const emu_mode_t *emu, uint8_t nif)
  41. {
  42. uint16_t rlen = emu->report_desc_len;
  43. uint8_t poll = emu->poll_ms ? emu->poll_ms : 1;
  44. if (nif < 1) nif = 1;
  45. if (nif > PP_NSLOT) nif = PP_NSLOT;
  46. const uint16_t total = 9 + nif * (9 + 9 + 7 + 7) + (9 + 7 + 7);
  47. uint8_t *p = b;
  48. // configuration
  49. *p++ = 9; *p++ = TUSB_DESC_CONFIGURATION;
  50. *p++ = (uint8_t)(total & 0xFF); *p++ = (uint8_t)(total >> 8);
  51. *p++ = (uint8_t)(nif + 1); *p++ = 1; *p++ = 0; *p++ = 0x80; *p++ = 250;
  52. for (uint8_t i = 0; i < nif; i++) {
  53. // HID interface i — IN + OUT (rumble / Switch-Pro subcommands ride the
  54. // interrupt OUT pipe; output reports also work via EP0 SET_REPORT).
  55. *p++ = 9; *p++ = TUSB_DESC_INTERFACE; *p++ = i; *p++ = 0; *p++ = 2;
  56. *p++ = TUSB_CLASS_HID; *p++ = 0; *p++ = 0; *p++ = 0;
  57. *p++ = 9; *p++ = HID_DESC_TYPE_HID; *p++ = 0x11; *p++ = 0x01; *p++ = 0;
  58. *p++ = 1; *p++ = HID_DESC_TYPE_REPORT;
  59. *p++ = (uint8_t)(rlen & 0xFF); *p++ = (uint8_t)(rlen >> 8);
  60. *p++ = 7; *p++ = TUSB_DESC_ENDPOINT; *p++ = (uint8_t)(0x81 + i); *p++ = 0x03;
  61. *p++ = 64; *p++ = 0; *p++ = poll;
  62. *p++ = 7; *p++ = TUSB_DESC_ENDPOINT; *p++ = (uint8_t)(0x01 + i); *p++ = 0x03;
  63. *p++ = 64; *p++ = 0; *p++ = poll;
  64. }
  65. // vendor interface (last) — WebUSB
  66. *p++ = 9; *p++ = TUSB_DESC_INTERFACE; *p++ = nif; *p++ = 0; *p++ = 2;
  67. *p++ = TUSB_CLASS_VENDOR_SPECIFIC; *p++ = 0; *p++ = 0; *p++ = 0;
  68. *p++ = 7; *p++ = TUSB_DESC_ENDPOINT; *p++ = 0x05; *p++ = 0x02;
  69. *p++ = 64; *p++ = 0; *p++ = 0;
  70. *p++ = 7; *p++ = TUSB_DESC_ENDPOINT; *p++ = 0x85; *p++ = 0x02;
  71. *p++ = 64; *p++ = 0; *p++ = 0;
  72. return total;
  73. }
  74. // Build the XInput config: the WebUSB vendor interface FIRST (itf 0), so the
  75. // built-in vendor class driver claims it and — with only one vendor instance —
  76. // leaves the XInput interface (itf 1, class 0xFF/0x5D/0x01) for the custom
  77. // XInput app driver. Endpoints: vendor bulk 0x02/0x82; XInput interrupt IN
  78. // 0x83 / OUT 0x03. Returns total length.
  79. static uint16_t build_xinput_config(uint8_t *b)
  80. {
  81. const uint16_t total = 9 + (9 + 7 + 7) + (9 + 17 + 7 + 7); // 72
  82. uint8_t *p = b;
  83. // configuration (2 interfaces)
  84. *p++ = 9; *p++ = TUSB_DESC_CONFIGURATION;
  85. *p++ = (uint8_t)(total & 0xFF); *p++ = (uint8_t)(total >> 8);
  86. *p++ = 2; *p++ = 1; *p++ = 0; *p++ = 0x80; *p++ = 250;
  87. // vendor interface (itf 0) — WebUSB
  88. *p++ = 9; *p++ = TUSB_DESC_INTERFACE; *p++ = 0; *p++ = 0; *p++ = 2;
  89. *p++ = TUSB_CLASS_VENDOR_SPECIFIC; *p++ = 0; *p++ = 0; *p++ = 4;
  90. *p++ = 7; *p++ = TUSB_DESC_ENDPOINT; *p++ = 0x02; *p++ = 0x02;
  91. *p++ = 64; *p++ = 0; *p++ = 0;
  92. *p++ = 7; *p++ = TUSB_DESC_ENDPOINT; *p++ = 0x82; *p++ = 0x02;
  93. *p++ = 64; *p++ = 0; *p++ = 0;
  94. // XInput interface (itf 1) — vendor class 0xFF/0x5D/0x01, 2 interrupt eps
  95. *p++ = 9; *p++ = TUSB_DESC_INTERFACE; *p++ = 1; *p++ = 0; *p++ = 2;
  96. *p++ = 0xFF; *p++ = 0x5D; *p++ = 0x01; *p++ = 0;
  97. // XInput "unknown" 0x21 descriptor (verbatim; bytes 6/13 = IN/OUT ep addr)
  98. *p++ = 0x11; *p++ = 0x21; *p++ = 0x00; *p++ = 0x01; *p++ = 0x01; *p++ = 0x25;
  99. *p++ = 0x83; *p++ = 0x14; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00; *p++ = 0x00;
  100. *p++ = 0x13; *p++ = 0x03; *p++ = 0x08; *p++ = 0x00; *p++ = 0x00;
  101. // XInput IN endpoint (interrupt, 0x83, 1 ms)
  102. *p++ = 7; *p++ = TUSB_DESC_ENDPOINT; *p++ = 0x83; *p++ = 0x03;
  103. *p++ = 0x20; *p++ = 0; *p++ = 1;
  104. // XInput OUT endpoint (interrupt, 0x03, 8 ms)
  105. *p++ = 7; *p++ = TUSB_DESC_ENDPOINT; *p++ = 0x03; *p++ = 0x03;
  106. *p++ = 0x20; *p++ = 0; *p++ = 8;
  107. return total;
  108. }
  109. // MS OS 2.0 template (WINUSB compatible id + DeviceInterfaceGUIDs). The
  110. // function-subset "first interface" byte is patched per mode at init.
  111. static const uint8_t k_msos_template[MS_OS_20_DESC_LEN] = {
  112. U16_TO_U8S_LE(0x000A), U16_TO_U8S_LE(MS_OS_20_SET_HEADER_DESCRIPTOR),
  113. U32_TO_U8S_LE(0x06030000), U16_TO_U8S_LE(MS_OS_20_DESC_LEN),
  114. U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_CONFIGURATION),
  115. 0, 0, U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A),
  116. U16_TO_U8S_LE(0x0008), U16_TO_U8S_LE(MS_OS_20_SUBSET_HEADER_FUNCTION),
  117. 0 /* first interface, patched */, 0,
  118. U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08),
  119. U16_TO_U8S_LE(0x0014), U16_TO_U8S_LE(MS_OS_20_FEATURE_COMPATBLE_ID),
  120. 'W', 'I', 'N', 'U', 'S', 'B', 0x00, 0x00,
  121. 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  122. U16_TO_U8S_LE(MS_OS_20_DESC_LEN - 0x0A - 0x08 - 0x08 - 0x14),
  123. U16_TO_U8S_LE(MS_OS_20_FEATURE_REG_PROPERTY),
  124. U16_TO_U8S_LE(0x0007), U16_TO_U8S_LE(0x002A),
  125. 'D', 0x00, 'e', 0x00, 'v', 0x00, 'i', 0x00, 'c', 0x00, 'e', 0x00, 'I', 0x00,
  126. 'n', 0x00, 't', 0x00, 'e', 0x00, 'r', 0x00, 'f', 0x00, 'a', 0x00, 'c', 0x00,
  127. 'e', 0x00, 'G', 0x00, 'U', 0x00, 'I', 0x00, 'D', 0x00, 's', 0x00, 0x00, 0x00,
  128. U16_TO_U8S_LE(0x0050),
  129. '{', 0x00, '9', 0x00, '7', 0x00, '5', 0x00, 'F', 0x00, '4', 0x00, '4', 0x00,
  130. 'D', 0x00, '9', 0x00, '-', 0x00, '0', 0x00, 'D', 0x00, '0', 0x00, '8', 0x00,
  131. '-', 0x00, '4', 0x00, '3', 0x00, 'F', 0x00, 'D', 0x00, '-', 0x00, '8', 0x00,
  132. 'B', 0x00, '3', 0x00, 'E', 0x00, '-', 0x00, '1', 0x00, '2', 0x00, '7', 0x00,
  133. 'C', 0x00, 'A', 0x00, '8', 0x00, 'A', 0x00, 'F', 0x00, 'F', 0x00, 'F', 0x00,
  134. '9', 0x00, 'D', 0x00, '}', 0x00, 0x00, 0x00, 0x00, 0x00,
  135. };
  136. void usb_descriptors_init(void)
  137. {
  138. s_mode = settings_mode();
  139. s_xinput = mode_is_xinput(s_mode);
  140. s_emu = (mode_is_puck(s_mode) || s_xinput) ? NULL : emu_mode_for(s_mode);
  141. if (!mode_is_puck(s_mode) && !s_xinput && !s_emu)
  142. s_mode = MODE_STEAM; // unknown/unimplemented emu mode → puck fallback
  143. memset(&s_dev, 0, sizeof(s_dev));
  144. s_dev.bLength = sizeof(tusb_desc_device_t);
  145. s_dev.bDescriptorType = TUSB_DESC_DEVICE;
  146. s_dev.bcdUSB = 0x0210;
  147. s_dev.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE;
  148. s_dev.iManufacturer = 0x01;
  149. s_dev.iProduct = 0x02;
  150. s_dev.iSerialNumber = 0x03;
  151. s_dev.bNumConfigurations = 0x01;
  152. if (s_xinput) {
  153. // Xbox 360 wired: Windows xusb / Linux xpad / SDL bind 045E:028E.
  154. s_dev.idVendor = 0x045E;
  155. s_dev.idProduct = 0x028E;
  156. s_dev.bcdDevice = 0x0120;
  157. build_xinput_config(s_emu_cfg);
  158. s_vendor_itf = 0; // WebUSB vendor is itf 0 in the XInput config
  159. } else if (s_emu) {
  160. s_dev.idVendor = s_emu->vid;
  161. s_dev.idProduct = s_emu->pid;
  162. s_dev.bcdDevice = s_emu->bcd;
  163. // One HID interface per paired controller (min 1) so several connected
  164. // controllers each appear as their own pad. Bond count is read from the
  165. // persisted label table (BT isn't up yet at descriptor-build time). New
  166. // pairings made while already in an emulated mode need a replug/reboot to
  167. // gain an interface.
  168. uint8_t nb = 0;
  169. const pp_cfg_t *c = settings();
  170. for (int i = 0; i < 4; i++)
  171. if (c->bond[i].used) nb++;
  172. s_emu_ifaces = nb < 1 ? 1 : (nb > PP_NSLOT ? PP_NSLOT : nb);
  173. build_emu_config(s_emu_cfg, s_emu, s_emu_ifaces);
  174. s_vendor_itf = s_emu_ifaces; // vendor is the interface after the HIDs
  175. } else {
  176. s_dev.idVendor = PP_USB_VID;
  177. s_dev.idProduct = PP_USB_PID;
  178. s_dev.bcdDevice = PP_BCD;
  179. s_vendor_itf = ITF_NUM_VENDOR;
  180. }
  181. memcpy(s_msos, k_msos_template, MS_OS_20_DESC_LEN);
  182. s_msos[22] = s_vendor_itf; // function-subset "first interface"
  183. }
  184. const uint8_t *tud_descriptor_device_cb(void)
  185. {
  186. return (const uint8_t *)&s_dev;
  187. }
  188. const uint8_t *tud_hid_descriptor_report_cb(uint8_t instance)
  189. {
  190. (void)instance;
  191. return s_emu ? s_emu->report_desc : PUCK_HID_DESC;
  192. }
  193. // ---- puck configuration descriptor -----------------------------------------
  194. #define PUCK_CONFIG_TOTAL_LEN \
  195. (TUD_CONFIG_DESC_LEN + 4 * TUD_HID_DESC_LEN + TUD_VENDOR_DESC_LEN + \
  196. CFG_TUD_CDC * TUD_CDC_DESC_LEN)
  197. static const uint8_t desc_configuration_puck[] = {
  198. TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, PUCK_CONFIG_TOTAL_LEN, 0x00, 250),
  199. TUD_HID_DESCRIPTOR(ITF_NUM_HID0, 0, HID_ITF_PROTOCOL_NONE,
  200. PUCK_HID_DESC_SIZE, EPNUM_HID0, CFG_TUD_HID_EP_BUFSIZE, 1),
  201. TUD_HID_DESCRIPTOR(ITF_NUM_HID1, 0, HID_ITF_PROTOCOL_NONE,
  202. PUCK_HID_DESC_SIZE, EPNUM_HID1, CFG_TUD_HID_EP_BUFSIZE, 1),
  203. TUD_HID_DESCRIPTOR(ITF_NUM_HID2, 0, HID_ITF_PROTOCOL_NONE,
  204. PUCK_HID_DESC_SIZE, EPNUM_HID2, CFG_TUD_HID_EP_BUFSIZE, 1),
  205. TUD_HID_DESCRIPTOR(ITF_NUM_HID3, 0, HID_ITF_PROTOCOL_NONE,
  206. PUCK_HID_DESC_SIZE, EPNUM_HID3, CFG_TUD_HID_EP_BUFSIZE, 1),
  207. TUD_VENDOR_DESCRIPTOR(ITF_NUM_VENDOR, 4, EPNUM_VENDOR_OUT, EPNUM_VENDOR_IN, 64),
  208. #if CFG_TUD_CDC
  209. TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 5, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT,
  210. EPNUM_CDC_IN, 64),
  211. #endif
  212. };
  213. const uint8_t *tud_descriptor_configuration_cb(uint8_t index)
  214. {
  215. (void)index;
  216. return (s_emu || s_xinput) ? s_emu_cfg : desc_configuration_puck;
  217. }
  218. // ---- BOS + MS OS 2.0 --------------------------------------------------------
  219. #define BOS_TOTAL_LEN \
  220. (TUD_BOS_DESC_LEN + TUD_BOS_WEBUSB_DESC_LEN + \
  221. TUD_BOS_MICROSOFT_OS_DESC_LEN)
  222. static const uint8_t desc_bos[] = {
  223. TUD_BOS_DESCRIPTOR(BOS_TOTAL_LEN, 2),
  224. TUD_BOS_WEBUSB_DESCRIPTOR(VENDOR_REQUEST_WEBUSB, 0),
  225. TUD_BOS_MS_OS_20_DESCRIPTOR(MS_OS_20_DESC_LEN, VENDOR_REQUEST_MICROSOFT),
  226. };
  227. const uint8_t *tud_descriptor_bos_cb(void)
  228. {
  229. return desc_bos;
  230. }
  231. bool tud_vendor_control_xfer_cb(uint8_t rhport, uint8_t stage,
  232. tusb_control_request_t const *request)
  233. {
  234. if (stage != CONTROL_STAGE_SETUP)
  235. return true;
  236. switch (request->bmRequestType_bit.type) {
  237. case TUSB_REQ_TYPE_VENDOR:
  238. if (request->bRequest == VENDOR_REQUEST_MICROSOFT &&
  239. request->wIndex == 7) {
  240. uint16_t total_len;
  241. memcpy(&total_len, s_msos + 8, 2);
  242. return tud_control_xfer(rhport, request,
  243. (void *)(uintptr_t)s_msos, total_len);
  244. }
  245. return false;
  246. case TUSB_REQ_TYPE_CLASS:
  247. if (request->bRequest == 0x22) {
  248. webusb_set_connected(request->wValue != 0);
  249. return tud_control_status(rhport, request);
  250. }
  251. break;
  252. default:
  253. break;
  254. }
  255. return false;
  256. }
  257. // ---- string descriptors ----------------------------------------------------
  258. enum { STRID_LANGID = 0, STRID_MANUFACTURER, STRID_PRODUCT, STRID_SERIAL, STRID_VENDOR };
  259. static uint16_t desc_str[32];
  260. const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t langid)
  261. {
  262. (void)langid;
  263. size_t chr_count;
  264. if (index == STRID_LANGID) {
  265. desc_str[1] = 0x0409;
  266. chr_count = 1;
  267. } else {
  268. const char *str;
  269. switch (index) {
  270. case STRID_MANUFACTURER:
  271. str = s_emu ? "PicoPuck" : "Valve Software";
  272. break;
  273. case STRID_PRODUCT:
  274. str = s_emu ? s_emu->product : "Steam Controller Puck";
  275. break;
  276. case STRID_SERIAL:
  277. str = g_usb_serial;
  278. break;
  279. case STRID_VENDOR:
  280. str = "PicoPuck WebUSB";
  281. break;
  282. default:
  283. return NULL;
  284. }
  285. if (!str)
  286. return NULL;
  287. chr_count = strlen(str);
  288. if (chr_count > 31)
  289. chr_count = 31;
  290. for (size_t i = 0; i < chr_count; i++)
  291. desc_str[1 + i] = str[i];
  292. }
  293. desc_str[0] = (uint16_t)((TUSB_DESC_STRING << 8) | (2 * chr_count + 2));
  294. return desc_str;
  295. }