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
43static const char* s_dynamic_strings[MAX_INTERFACES] = {};
44static size_t s_dynamic_string_count = 0;
45
49
50static tusb_desc_device_t device_descriptor = {
51 .bLength = sizeof(tusb_desc_device_t),
52 .bDescriptorType = TUSB_DESC_DEVICE,
53 .bcdUSB = USB_BCD,
54 .bDeviceClass = TUSB_CLASS_MISC,
55 .bDeviceSubClass = MISC_SUBCLASS_COMMON,
56 .bDeviceProtocol = MISC_PROTOCOL_IAD,
57 .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
58 .idVendor = USB_VID_ESPRESSIF,
59 .idProduct = USB_PID_BADGE,
60 .bcdDevice = 0x0100,
61 .iManufacturer = STR_MANUFACTURER,
62 .iProduct = STR_PRODUCT,
63 .iSerialNumber = STR_SERIAL,
64 .bNumConfigurations = 1
65};
66
70static void build_config_descriptor(void) {
71 uint8_t* p = s_config_descriptor;
73
74 uint8_t itf_count = 2;
75 uint16_t total_len = TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN;
76 bool has_ccid = false;
77 uint16_t pref_vid = 0;
78 uint16_t pref_pid = 0;
79
80 // Reset dynamic strings and HID mapping
81 s_hid_count = 0;
83
84 for (size_t i = 0; i < s_def_count; i++) {
85 const auto& def = s_defs[i];
86 if (def.preferredVid != 0 && def.preferredPid != 0) {
87 pref_vid = def.preferredVid;
88 pref_pid = def.preferredPid;
89 }
90 if (def.cls == UsbInterfaceClass::Hid) {
91 itf_count++;
92 total_len += def.hasOut ? TUD_HID_INOUT_DESC_LEN : TUD_HID_DESC_LEN;
93 s_hid_map[s_hid_count++] = static_cast<int8_t>(i);
94 } else if (def.cls == UsbInterfaceClass::Ccid) {
95 itf_count++;
96 total_len += TUD_CCID_TOTAL_LEN;
97 has_ccid = true;
98 }
99 }
100
101 // A per-interface VID/PID hint (e.g. mod_otphid's OnlyKey IDs) overrides the
102 // default product identity; otherwise CCID selects Gemalto, else Espressif.
103 if (pref_vid != 0 && pref_pid != 0) {
104 device_descriptor.idVendor = pref_vid;
105 device_descriptor.idProduct = pref_pid;
106 } else {
108 device_descriptor.idProduct = has_ccid ? USB_PID_GEMALTO : USB_PID_BADGE;
109 }
110
111 uint8_t cfg_desc[] = {
112 TUD_CONFIG_DESCRIPTOR(1, itf_count, 0, total_len, 0, 100),
113 };
114 memcpy(p, cfg_desc, sizeof(cfg_desc));
115 p += sizeof(cfg_desc);
116
117 uint8_t cdc_desc[] = {
118 TUD_CDC_DESCRIPTOR(0, STR_CDC, EP_CDC_NOTIF, EP_CDC_NOTIF_SIZE,
120 };
121 memcpy(p, cdc_desc, sizeof(cdc_desc));
122 p += sizeof(cdc_desc);
123
124 uint8_t next_itf = 2;
125 uint8_t next_out = 0x03;
126 uint8_t next_in = 0x83;
127
128 for (size_t i = 0; i < s_def_count; i++) {
129 const auto& def = s_defs[i];
130
131 // Register interface name as dynamic string
132 const uint8_t str_idx = static_cast<uint8_t>(STR_DYNAMIC_BASE + s_dynamic_string_count);
133 if (def.name && s_dynamic_string_count < MAX_INTERFACES) {
135 }
136
137 if (def.cls == UsbInterfaceClass::Hid) {
138 if (def.reportDesc == nullptr || def.reportDescLen == 0) {
139 LOG_W(TAG, "HID interface missing report descriptor");
140 continue;
141 }
142
143 const uint16_t ep_size = (def.epOutSize > def.epInSize) ? def.epOutSize : def.epInSize;
144 if (def.hasOut) {
145 uint8_t desc[] = {
146 TUD_HID_INOUT_DESCRIPTOR(next_itf, str_idx, def.protocol,
147 def.reportDescLen,
148 next_out, next_in, ep_size, 5),
149 };
150 memcpy(p, desc, sizeof(desc));
151 p += sizeof(desc);
152 next_out++;
153 next_in++;
154 } else {
155 uint8_t desc[] = {
156 TUD_HID_DESCRIPTOR(next_itf, str_idx, def.protocol,
157 def.reportDescLen, next_in, def.epInSize, 10),
158 };
159 memcpy(p, desc, sizeof(desc));
160 p += sizeof(desc);
161 next_in++;
162 }
163 next_itf++;
164 } else if (def.cls == UsbInterfaceClass::Ccid) {
165 uint8_t desc[] = {
166 TUD_CCID_DESCRIPTOR(next_itf, str_idx, next_out, next_in, EP_CCID_SIZE),
167 };
168 memcpy(p, desc, sizeof(desc));
169 p += sizeof(desc);
170 next_out++;
171 next_in++;
172 next_itf++;
173 }
174 }
175
176 s_config_descriptor_len = static_cast<uint16_t>(p - s_config_descriptor);
177}
178
182static char s_serial_number[13] = "000000000000";
183
187static void init_serial_number() {
188 static bool inited = false;
189 if (inited) return;
190 inited = true;
191
192 uint8_t mac[6];
193 if (esp_read_mac(mac, ESP_MAC_WIFI_STA) == ESP_OK) {
194 snprintf(s_serial_number, sizeof(s_serial_number),
195 "%02X%02X%02X%02X%02X%02X",
196 mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
197 }
198}
199
203static const char* s_fixed_strings[] = {
204 (const char[]){0x09, 0x04}, // 0: Language (English US)
205 "CDC", // 1: Manufacturer
206 "BadgeV1", // 2: Product
207 s_serial_number, // 3: Serial (derived from MAC)
208 "CDC Serial", // 4: CDC Interface
209};
210static constexpr size_t FIXED_STRING_COUNT = sizeof(s_fixed_strings) / sizeof(s_fixed_strings[0]);
211
215
216extern "C" {
217
222uint8_t const* tud_descriptor_device_cb(void) {
223 return (uint8_t const*)&device_descriptor;
224}
225
231uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
232 (void)index;
234}
235
242uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
243 (void)langid;
244 static uint16_t str_desc[32];
245
246 // Determine which string to use
247 const char* str = nullptr;
248
249 if (index < FIXED_STRING_COUNT) {
250 str = s_fixed_strings[index];
251 } else if (index >= STR_DYNAMIC_BASE &&
253 str = s_dynamic_strings[index - STR_DYNAMIC_BASE];
254 }
255
256 if (!str) return nullptr;
257
258 // Language descriptor (index 0)
259 if (index == 0) {
260 str_desc[0] = (TUSB_DESC_STRING << 8) | 4;
261 str_desc[1] = 0x0409;
262 return str_desc;
263 }
264
265 // Convert ASCII to UTF-16
266 uint8_t len = strlen(str);
267 if (len > 31) len = 31;
268 str_desc[0] = (TUSB_DESC_STRING << 8) | (2 + 2 * len);
269 for (uint8_t i = 0; i < len; i++) {
270 str_desc[1 + i] = str[i];
271 }
272 return str_desc;
273}
274
280uint8_t const* tud_hid_descriptor_report_cb(uint8_t instance) {
281 if (instance >= s_hid_count) return nullptr;
282 int8_t def_idx = s_hid_map[instance];
283 if (def_idx < 0) return nullptr;
284 const auto& def = s_defs[static_cast<size_t>(def_idx)];
285 return def.reportDesc;
286}
287
297uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id,
298 hid_report_type_t report_type,
299 uint8_t* buffer, uint16_t reqlen) {
300 if (instance >= s_hid_count) return 0;
301 int8_t def_idx = s_hid_map[instance];
302 if (def_idx < 0) return 0;
303 const auto& def = s_defs[static_cast<size_t>(def_idx)];
304 if (!def.callbacks.onGetReport) return 0;
305 return def.callbacks.onGetReport(report_id, static_cast<uint8_t>(report_type), buffer, reqlen);
306}
307
316void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id,
317 hid_report_type_t report_type,
318 uint8_t const* buffer, uint16_t bufsize) {
319 if (instance >= s_hid_count) return;
320 int8_t def_idx = s_hid_map[instance];
321 if (def_idx < 0) return;
322 const auto& def = s_defs[static_cast<size_t>(def_idx)];
323 if (!def.callbacks.onSetReport) return;
324 def.callbacks.onSetReport(report_id, static_cast<uint8_t>(report_type), buffer, bufsize);
325}
326
333void tud_hid_report_complete_cb(uint8_t instance, uint8_t const* report, uint16_t len) {
334 if (instance >= s_hid_count) return;
335 int8_t def_idx = s_hid_map[instance];
336 if (def_idx < 0) return;
337 const auto& def = s_defs[static_cast<size_t>(def_idx)];
338 if (!def.callbacks.onReportComplete) return;
339 def.callbacks.onReportComplete(report, len);
340}
341
342} // extern "C"
343
347
352extern "C" bool usb_hid_init(void) {
353 if (s_hid_initialized) return true;
356 s_hid_initialized = true;
357 return true;
358}
359
367extern "C" bool usb_hid_apply_config(const UsbInterfaceDef* defs, size_t count, bool* needs_replug) {
368 if (needs_replug) *needs_replug = false;
369 if (!defs && count > 0) return false;
370 if (count > MAX_INTERFACES) return false;
371
372 for (size_t i = 0; i < count; i++) {
373 s_defs[i] = defs[i];
374 }
375 s_def_count = count;
376
378
379 if (tud_inited()) {
380 tud_disconnect();
381 vTaskDelay(pdMS_TO_TICKS(20));
382 tud_connect();
383 if (needs_replug) *needs_replug = true;
384 }
385
386 return true;
387}
388
393extern "C" bool usb_hid_ready(void) {
394 return tud_ready();
395}
396
402extern "C" bool usb_hid_instance_ready(uint8_t instance) {
403 return tud_hid_n_ready(instance);
404}
405
414extern "C" bool usb_hid_send_report(uint8_t instance, uint8_t report_id, const uint8_t* data, uint16_t len) {
415 if (!data || len == 0) return false;
416 return tud_hid_n_report(instance, report_id, data, len);
417}
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_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:210
bool usb_hid_init(void)
Public USB HID/composite configuration API implementation.
Definition usb_hid.cpp:352
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:242
uint8_t const * tud_descriptor_device_cb(void)
TinyUSB descriptor and HID report callbacks.
Definition usb_hid.cpp:222
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:297
static const char * s_dynamic_strings[MAX_INTERFACES]
Dynamic module interface names generated during configuration apply.
Definition usb_hid.cpp:43
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:393
static char s_serial_number[13]
USB serial number derived from ESP32 MAC address.
Definition usb_hid.cpp:182
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:414
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:203
static size_t s_dynamic_string_count
Definition usb_hid.cpp:44
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
uint8_t const * tud_hid_descriptor_report_cb(uint8_t instance)
Returns the HID report descriptor for a HID instance.
Definition usb_hid.cpp:280
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:367
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:333
uint8_t const * tud_descriptor_configuration_cb(uint8_t index)
Returns the active configuration descriptor.
Definition usb_hid.cpp:231
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:402
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:316
static void build_config_descriptor(void)
Rebuilds the composite USB configuration descriptor from registered interfaces.
Definition usb_hid.cpp:70
static tusb_desc_device_t device_descriptor
Descriptor storage used for runtime-generated TinyUSB configuration descriptors.
Definition usb_hid.cpp:50
static void init_serial_number()
Initializes the serial-number string once from efuse MAC.
Definition usb_hid.cpp:187