CDC Badge OS
Firmware for the CDC Badge v1.0 hardware security key
Loading...
Searching...
No Matches
usb_hid.cpp
Go to the documentation of this file.
1
4
5#include "usb_badge/usb_hid.h"
6#include "usb_descriptors.h"
7#include "cdc_log.h"
8
9#include "freertos/FreeRTOS.h"
10#include "freertos/task.h"
11#include "esp_mac.h"
12
13extern "C" {
14#include "tusb.h"
15#include "class/cdc/cdc.h"
16#include "class/hid/hid_device.h"
17}
18
19#include <string.h>
20#include <stdio.h>
21
22static const char* TAG = "USB_HID";
23
27
28static bool s_hid_initialized = false;
29
30static constexpr size_t MAX_INTERFACES = 3;
31static UsbInterfaceDef s_defs[MAX_INTERFACES] = {};
32static size_t s_def_count = 0;
33
34static int8_t s_hid_map[MAX_INTERFACES] = {-1, -1, -1};
35static size_t s_hid_count = 0;
36
37static uint8_t s_config_descriptor[256];
38static uint16_t s_config_descriptor_len = 0;
39
40// When true, an MSC (mass-storage) interface is appended after the HID/CCID
41// interfaces. Toggled by usb_hid_set_msc(); the backing LUN lives in usb_msc.cpp.
42static bool s_msc_active = false;
43
47static const char* s_dynamic_strings[MAX_INTERFACES] = {};
48static size_t s_dynamic_string_count = 0;
49
53
54static tusb_desc_device_t device_descriptor = {
55 .bLength = sizeof(tusb_desc_device_t),
56 .bDescriptorType = TUSB_DESC_DEVICE,
57 .bcdUSB = USB_BCD,
58 .bDeviceClass = TUSB_CLASS_MISC,
59 .bDeviceSubClass = MISC_SUBCLASS_COMMON,
60 .bDeviceProtocol = MISC_PROTOCOL_IAD,
61 .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
62 .idVendor = USB_VID_ESPRESSIF,
63 .idProduct = USB_PID_BADGE,
64 .bcdDevice = 0x0100,
65 .iManufacturer = STR_MANUFACTURER,
66 .iProduct = STR_PRODUCT,
67 .iSerialNumber = STR_SERIAL,
68 .bNumConfigurations = 1
69};
70
74static void build_config_descriptor(void) {
75 uint8_t* p = s_config_descriptor;
77
78 uint8_t itf_count = 2;
79 uint16_t total_len = TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN;
80 bool has_ccid = false;
81 uint16_t pref_vid = 0;
82 uint16_t pref_pid = 0;
83
84 // Reset dynamic strings and HID mapping
85 s_hid_count = 0;
87
88 for (size_t i = 0; i < s_def_count; i++) {
89 const auto& def = s_defs[i];
90 if (def.preferredVid != 0 && def.preferredPid != 0) {
91 pref_vid = def.preferredVid;
92 pref_pid = def.preferredPid;
93 }
94 if (def.cls == UsbInterfaceClass::Hid) {
95 itf_count++;
96 total_len += def.hasOut ? TUD_HID_INOUT_DESC_LEN : TUD_HID_DESC_LEN;
97 s_hid_map[s_hid_count++] = static_cast<int8_t>(i);
98 } else if (def.cls == UsbInterfaceClass::Ccid) {
99 itf_count++;
100 total_len += TUD_CCID_TOTAL_LEN;
101 has_ccid = true;
102 }
103 }
104
105 if (s_msc_active) {
106 itf_count++;
107 total_len += TUD_MSC_DESC_LEN;
108 }
109
110 // A per-interface VID/PID hint (e.g. mod_otphid's OnlyKey IDs) overrides the
111 // default product identity; otherwise CCID selects Gemalto, else Espressif.
112 if (pref_vid != 0 && pref_pid != 0) {
113 device_descriptor.idVendor = pref_vid;
114 device_descriptor.idProduct = pref_pid;
115 } else {
117 device_descriptor.idProduct = has_ccid ? USB_PID_GEMALTO : USB_PID_BADGE;
118 }
119
120 uint8_t cfg_desc[] = {
121 TUD_CONFIG_DESCRIPTOR(1, itf_count, 0, total_len, 0, 100),
122 };
123 memcpy(p, cfg_desc, sizeof(cfg_desc));
124 p += sizeof(cfg_desc);
125
126 uint8_t cdc_desc[] = {
127 TUD_CDC_DESCRIPTOR(0, STR_CDC, EP_CDC_NOTIF, EP_CDC_NOTIF_SIZE,
129 };
130 memcpy(p, cdc_desc, sizeof(cdc_desc));
131 p += sizeof(cdc_desc);
132
133 uint8_t next_itf = 2;
134 uint8_t next_out = 0x03;
135 uint8_t next_in = 0x83;
136
137 for (size_t i = 0; i < s_def_count; i++) {
138 const auto& def = s_defs[i];
139
140 // Register interface name as dynamic string
141 const uint8_t str_idx = static_cast<uint8_t>(STR_DYNAMIC_BASE + s_dynamic_string_count);
142 if (def.name && s_dynamic_string_count < MAX_INTERFACES) {
144 }
145
146 if (def.cls == UsbInterfaceClass::Hid) {
147 if (def.reportDesc == nullptr || def.reportDescLen == 0) {
148 LOG_W(TAG, "HID interface missing report descriptor");
149 continue;
150 }
151
152 const uint16_t ep_size = (def.epOutSize > def.epInSize) ? def.epOutSize : def.epInSize;
153 if (def.hasOut) {
154 uint8_t desc[] = {
155 TUD_HID_INOUT_DESCRIPTOR(next_itf, str_idx, def.protocol,
156 def.reportDescLen,
157 next_out, next_in, ep_size, 5),
158 };
159 memcpy(p, desc, sizeof(desc));
160 p += sizeof(desc);
161 next_out++;
162 next_in++;
163 } else {
164 uint8_t desc[] = {
165 TUD_HID_DESCRIPTOR(next_itf, str_idx, def.protocol,
166 def.reportDescLen, next_in, def.epInSize, 10),
167 };
168 memcpy(p, desc, sizeof(desc));
169 p += sizeof(desc);
170 next_in++;
171 }
172 next_itf++;
173 } else if (def.cls == UsbInterfaceClass::Ccid) {
174 uint8_t desc[] = {
175 TUD_CCID_DESCRIPTOR(next_itf, str_idx, next_out, next_in, EP_CCID_SIZE),
176 };
177 memcpy(p, desc, sizeof(desc));
178 p += sizeof(desc);
179 next_out++;
180 next_in++;
181 next_itf++;
182 }
183 }
184
185 if (s_msc_active) {
186 uint8_t desc[] = {
187 TUD_MSC_DESCRIPTOR(next_itf, STR_MSC, next_out, next_in, 64),
188 };
189 memcpy(p, desc, sizeof(desc));
190 p += sizeof(desc);
191 next_out++;
192 next_in++;
193 next_itf++;
194 }
195
196 s_config_descriptor_len = static_cast<uint16_t>(p - s_config_descriptor);
197}
198
202static char s_serial_number[13] = "000000000000";
203
207static void init_serial_number() {
208 static bool inited = false;
209 if (inited) return;
210 inited = true;
211
212 uint8_t mac[6];
213 if (esp_read_mac(mac, ESP_MAC_WIFI_STA) == ESP_OK) {
214 snprintf(s_serial_number, sizeof(s_serial_number),
215 "%02X%02X%02X%02X%02X%02X",
216 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
217 }
218}
219
223static const char* s_fixed_strings[] = {
224 (const char[]){0x09, 0x04}, // 0: Language (English US)
225 "CDC", // 1: Manufacturer
226 "BadgeV1", // 2: Product
227 s_serial_number, // 3: Serial (derived from MAC)
228 "CDC Serial", // 4: CDC Interface
229 "vFAT Storage", // 5: MSC Interface
230};
231static constexpr size_t FIXED_STRING_COUNT = sizeof(s_fixed_strings) / sizeof(s_fixed_strings[0]);
232
236
237extern "C" {
238
243uint8_t const* tud_descriptor_device_cb(void) {
244 return (uint8_t const*)&device_descriptor;
245}
246
252uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
253 (void)index;
255}
256
263uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
264 (void)langid;
265 static uint16_t str_desc[32];
266
267 // Determine which string to use
268 const char* str = nullptr;
269
270 if (index < FIXED_STRING_COUNT) {
271 str = s_fixed_strings[index];
272 } else if (index >= STR_DYNAMIC_BASE &&
274 str = s_dynamic_strings[index - STR_DYNAMIC_BASE];
275 }
276
277 if (!str) return nullptr;
278
279 // Language descriptor (index 0)
280 if (index == 0) {
281 str_desc[0] = (TUSB_DESC_STRING << 8) | 4;
282 str_desc[1] = 0x0409;
283 return str_desc;
284 }
285
286 // Convert ASCII to UTF-16
287 uint8_t len = strlen(str);
288 if (len > 31) len = 31;
289 str_desc[0] = (TUSB_DESC_STRING << 8) | (2 + 2 * len);
290 for (uint8_t i = 0; i < len; i++) {
291 str_desc[1 + i] = str[i];
292 }
293 return str_desc;
294}
295
301uint8_t const* tud_hid_descriptor_report_cb(uint8_t instance) {
302 if (instance >= s_hid_count) return nullptr;
303 int8_t def_idx = s_hid_map[instance];
304 if (def_idx < 0) return nullptr;
305 const auto& def = s_defs[static_cast<size_t>(def_idx)];
306 return def.reportDesc;
307}
308
318uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id,
319 hid_report_type_t report_type,
320 uint8_t* buffer, uint16_t reqlen) {
321 if (instance >= s_hid_count) return 0;
322 int8_t def_idx = s_hid_map[instance];
323 if (def_idx < 0) return 0;
324 const auto& def = s_defs[static_cast<size_t>(def_idx)];
325 if (!def.callbacks.onGetReport) return 0;
326 return def.callbacks.onGetReport(report_id, static_cast<uint8_t>(report_type), buffer, reqlen);
327}
328
337void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id,
338 hid_report_type_t report_type,
339 uint8_t const* buffer, uint16_t bufsize) {
340 if (instance >= s_hid_count) return;
341 int8_t def_idx = s_hid_map[instance];
342 if (def_idx < 0) return;
343 const auto& def = s_defs[static_cast<size_t>(def_idx)];
344 if (!def.callbacks.onSetReport) return;
345 def.callbacks.onSetReport(report_id, static_cast<uint8_t>(report_type), buffer, bufsize);
346}
347
354void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len) {
355 if (instance >= s_hid_count) return;
356 int8_t def_idx = s_hid_map[instance];
357 if (def_idx < 0) return;
358 const auto& def = s_defs[static_cast<size_t>(def_idx)];
359 if (!def.callbacks.onReportComplete) return;
360 def.callbacks.onReportComplete(report, len);
361}
362
363} // extern "C"
364
368
373extern "C" bool usb_hid_init(void) {
374 if (s_hid_initialized) return true;
377 s_hid_initialized = true;
378 return true;
379}
380
388extern "C" bool usb_hid_apply_config(const UsbInterfaceDef* defs, size_t count, bool* needs_replug) {
389 if (needs_replug) *needs_replug = false;
390 if (!defs && count > 0) return false;
391 if (count > MAX_INTERFACES) return false;
392
393 for (size_t i = 0; i < count; i++) {
394 s_defs[i] = defs[i];
395 }
396 s_def_count = count;
397
399
400 if (tud_inited()) {
401 tud_disconnect();
402 vTaskDelay(pdMS_TO_TICKS(20));
403 tud_connect();
404 if (needs_replug) *needs_replug = true;
405 }
406
407 return true;
408}
409
414extern "C" void usb_hid_set_msc(bool active) {
415 if (s_msc_active == active) return;
416 s_msc_active = active;
417
419
420 if (tud_inited()) {
421 tud_disconnect();
422 vTaskDelay(pdMS_TO_TICKS(20));
423 tud_connect();
424 }
425}
426
431extern "C" bool usb_hid_ready(void) {
432 return tud_ready();
433}
434
440extern "C" bool usb_hid_instance_ready(uint8_t instance) {
441 return tud_hid_n_ready(instance);
442}
443
452extern "C" bool usb_hid_send_report(uint8_t instance, uint8_t report_id, const uint8_t* data, uint16_t len) {
453 if (!data || len == 0) return false;
454 return tud_hid_n_report(instance, report_id, data, len);
455}
static const char * TAG
CDC Log: logging over TinyUSB CDC and UART.
#define LOG_W(tag, fmt,...)
Definition cdc_log.h:146
#define USB_VID_ESPRESSIF
#define EP_CDC_NOTIF
#define TUD_CCID_TOTAL_LEN
#define USB_PID_GEMALTO
#define EP_CDC_NOTIF_SIZE
#define TUD_CCID_DESCRIPTOR(_itfnum, _stridx, _epout, _epin, _epsize)
#define EP_CDC_IN
#define USB_PID_BADGE
#define EP_CDC_OUT
@ STR_PRODUCT
@ STR_MANUFACTURER
@ STR_MSC
@ STR_DYNAMIC_BASE
@ STR_CDC
@ STR_SERIAL
#define EP_CDC_SIZE
#define EP_CCID_SIZE
#define USB_VID_GEMALTO
#define USB_BCD
static constexpr size_t FIXED_STRING_COUNT
Definition usb_hid.cpp:231
bool usb_hid_init(void)
Public USB HID/composite configuration API implementation.
Definition usb_hid.cpp:373
static size_t s_def_count
Definition usb_hid.cpp:32
uint16_t const * tud_descriptor_string_cb(uint8_t index, uint16_t langid)
Returns UTF-16 string descriptors for fixed and dynamic strings.
Definition usb_hid.cpp:263
uint8_t const * tud_descriptor_device_cb(void)
TinyUSB descriptor and HID report callbacks.
Definition usb_hid.cpp:243
static bool s_msc_active
Definition usb_hid.cpp:42
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen)
Handles HID GET_REPORT requests by delegating to interface callbacks.
Definition usb_hid.cpp:318
static const char * s_dynamic_strings[MAX_INTERFACES]
Dynamic module interface names generated during configuration apply.
Definition usb_hid.cpp:47
static int8_t s_hid_map[MAX_INTERFACES]
Definition usb_hid.cpp:34
bool usb_hid_ready(void)
Returns whether TinyUSB stack is ready.
Definition usb_hid.cpp:431
static char s_serial_number[13]
USB serial number derived from ESP32 MAC address.
Definition usb_hid.cpp:202
bool usb_hid_send_report(uint8_t instance, uint8_t report_id, const uint8_t *data, uint16_t len)
Sends one HID report on the selected interface instance.
Definition usb_hid.cpp:452
static constexpr size_t MAX_INTERFACES
Definition usb_hid.cpp:30
static const char * s_fixed_strings[]
Fixed USB string descriptors; dynamic interface strings are appended at runtime.
Definition usb_hid.cpp:223
static size_t s_dynamic_string_count
Definition usb_hid.cpp:48
static uint8_t s_config_descriptor[256]
Definition usb_hid.cpp:37
static size_t s_hid_count
Definition usb_hid.cpp:35
static bool s_hid_initialized
Global descriptor and interface registration state.
Definition usb_hid.cpp:28
void usb_hid_set_msc(bool active)
Adds or removes the MSC interface and re-enumerates the device.
Definition usb_hid.cpp:414
uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance)
Returns the HID report descriptor for a HID instance.
Definition usb_hid.cpp:301
bool usb_hid_apply_config(const UsbInterfaceDef *defs, size_t count, bool *needs_replug)
Applies the runtime interface configuration and optionally triggers re-enumeration.
Definition usb_hid.cpp:388
void tud_hid_report_complete_cb(uint8_t instance, uint8_t const *report, uint16_t len)
Notifies interface callbacks that a HID report transfer completed.
Definition usb_hid.cpp:354
uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
Returns the active configuration descriptor.
Definition usb_hid.cpp:252
static UsbInterfaceDef s_defs[MAX_INTERFACES]
Definition usb_hid.cpp:31
bool usb_hid_instance_ready(uint8_t instance)
Returns whether a specific HID instance endpoint is ready.
Definition usb_hid.cpp:440
static uint16_t s_config_descriptor_len
Definition usb_hid.cpp:38
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
Handles HID SET_REPORT requests by delegating to interface callbacks.
Definition usb_hid.cpp:337
static void build_config_descriptor(void)
Rebuilds the composite USB configuration descriptor from registered interfaces.
Definition usb_hid.cpp:74
static tusb_desc_device_t device_descriptor
Descriptor storage used for runtime-generated TinyUSB configuration descriptors.
Definition usb_hid.cpp:54
static void init_serial_number()
Initializes the serial-number string once from efuse MAC.
Definition usb_hid.cpp:207