Skip to content

Commit 8ad4d49

Browse files
Fix USB descriptors for multiple interface devices (#3193)
For simple 1-interface devices, there is a fixed position in the USB interface descriptor where the interface lives and we can hardcode an updater in the USB handler. However, for devices like CDC(Serial) or MIDI there are multiple interfaces and the interface IDs need to be stuffed inside the actual descriptor. To allow this, instead of just taking a fixed set of bytes and blindly patching in USB, make all interfaces do their own memcpy from a local version, updated with the new dynamic interface id, via a callback. Add a simple USBClass callback to handle the normal 1-interface case. Fix MSD (SingleFileDrive, FatFSUSB) to only request the correct, single interface used.
1 parent 748dddb commit 8ad4d49

File tree

6 files changed

+48
-23
lines changed

6 files changed

+48
-23
lines changed

cores/rp2040/SerialUSB.cpp

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ extern void serialEvent() __attribute__((weak));
3939
#define USBD_CDC_IN_OUT_MAX_SIZE (64)
4040

4141

42+
4243
SerialUSB::SerialUSB() {
4344
}
4445

@@ -50,17 +51,26 @@ void SerialUSB::begin(unsigned long baud) {
5051
}
5152

5253
USB.disconnect();
53-
static uint8_t cdc_desc[TUD_CDC_DESC_LEN] = {
54-
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
55-
TUD_CDC_DESCRIPTOR(0 /* placeholder*/, USB.registerString("Pico Serial"), _epIn = USB.registerEndpointIn(), USBD_CDC_CMD_MAX_SIZE, _epOut = USB.registerEndpointOut(), USB.registerEndpointIn(), USBD_CDC_IN_OUT_MAX_SIZE)
56-
};
5754

58-
_id = USB.registerInterface(2, cdc_desc, sizeof(cdc_desc), 1, 0);
55+
_epIn = USB.registerEndpointIn();
56+
_epOut = USB.registerEndpointOut();
57+
_epIn2 = USB.registerEndpointIn();
58+
_strID = USB.registerString("Pico Serial");
59+
60+
_id = USB.registerInterface(2, _cb, (void *)this, TUD_CDC_DESC_LEN, 1, 0);
5961

6062
USB.connect();
6163
_running = true;
6264
}
6365

66+
// Need to define here so we don't have to include tusb.h in global header (causes problemw w/BT redefining things)
67+
void SerialUSB::interfaceCB(int itf, uint8_t *dst, int len) {
68+
uint8_t desc[TUD_CDC_DESC_LEN] = {
69+
TUD_CDC_DESCRIPTOR((uint8_t)itf, _strID, _epIn, USBD_CDC_CMD_MAX_SIZE, _epOut, _epIn2, USBD_CDC_IN_OUT_MAX_SIZE)
70+
};
71+
memcpy(dst, desc, len);
72+
};
73+
6474
void SerialUSB::end() {
6575
if (_running) {
6676
USB.disconnect();

cores/rp2040/SerialUSB.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,16 @@ class SerialUSB : public arduino::HardwareSerial {
5757
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts);
5858
void tud_cdc_line_coding_cb(uint8_t itf, void const *p_line_coding); // Can't use cdc_line_coding_t const* p_line_coding, TinyUSB and BTStack conflict when we include tusb.h + BTStack.h
5959

60+
// USB interface descriptor CB
61+
void interfaceCB(int itf, uint8_t *dst, int len);
62+
6063
private:
6164
bool _running = false;
6265
uint8_t _id;
6366
uint8_t _epIn;
6467
uint8_t _epOut;
68+
uint8_t _epIn2;
69+
uint8_t _strID;
6570

6671
typedef struct {
6772
unsigned int rebooting : 1;
@@ -73,6 +78,10 @@ class SerialUSB : public arduino::HardwareSerial {
7378
SyntheticState _ss = { 0, 0, 0, 0, 115200};
7479

7580
void checkSerialReset();
81+
82+
static void _cb(int itf, uint8_t *dst, int len, void *param) {
83+
((SerialUSB *)param)->interfaceCB(itf, dst, len);
84+
}
7685
};
7786

7887
extern SerialUSB Serial;

cores/rp2040/USB.cpp

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,12 @@ void USBClass::unregisterEndpointOut(int ep) {
9191
_endpointOut |= (1 << (ep - 0x80));
9292
}
9393

94-
uint8_t USBClass::addEntry(Entry **head, int interfaces, const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask) {
94+
uint8_t USBClass::addEntry(Entry **head, int interfaces, void (*cb)(int itf, uint8_t *dst, int len, void *param), const void *param, size_t len, int ordering, uint32_t vidMask) {
9595
static uint8_t id = 1;
9696

9797
Entry *n = (Entry *)malloc(sizeof(Entry));
98-
n->descriptor = descriptor;
98+
n->cb = cb;
99+
n->param = param;
99100
n->len = len;
100101
n->interfaces = interfaces;
101102
n->order = ordering;
@@ -163,16 +164,16 @@ uint8_t USBClass::findInterfaceID(unsigned int localid) {
163164

164165
// Called by a HID device to register a report. Returns the *local* ID which must be mapped to the HID report ID
165166
uint8_t USBClass::registerHIDDevice(const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask) {
166-
return addEntry(&_hids, 0, descriptor, len, ordering, vidMask);
167+
return addEntry(&_hids, 0, nullptr, (const void *)descriptor, len, ordering, vidMask);
167168
}
168169

169170
void USBClass::unregisterHIDDevice(unsigned int localid) {
170171
removeEntry(&_hids, localid);
171172
}
172173

173174
// Called by an object at global init time to add a new interface (non-HID, like CDC or Picotool)
174-
uint8_t USBClass::registerInterface(int interfaces, const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask) {
175-
return addEntry(&_interfaces, interfaces, descriptor, len, ordering, vidMask);
175+
uint8_t USBClass::registerInterface(int interfaces, void (*cb)(int itf, uint8_t *dst, int len, void *), void *param, size_t len, int ordering, uint32_t vidMask) {
176+
return addEntry(&_interfaces, interfaces, cb, param, len, ordering, vidMask);
176177
}
177178

178179
void USBClass::unregisterInterface(unsigned int localid) {
@@ -293,7 +294,7 @@ void USBClass::setupDescHIDReport() {
293294
uint8_t id = 1;
294295
h = _hids;
295296
while (h) {
296-
memcpy(p, h->descriptor, h->len);
297+
memcpy(p, h->param, h->len);
297298
// Need to update the report ID, a 2-byte value
298299
char buff[] = { HID_REPORT_ID(id) };
299300
memcpy(p + 6, buff, sizeof(buff));
@@ -340,12 +341,12 @@ void USBClass::setupUSBDescriptor() {
340341
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
341342
TUD_HID_DESCRIPTOR(1 /* placeholder*/, 0, HID_ITF_PROTOCOL_NONE, hid_report_len, _hid_endpoint = USB.registerEndpointIn(), CFG_TUD_HID_EP_BUFSIZE, (uint8_t)usb_hid_poll_interval)
342343
};
343-
_hid_interface = USB.registerInterface(1, hid_desc, sizeof(hid_desc), 10, 0);
344+
_hid_interface = USB.registerInterface(1, simpleInterface, hid_desc, sizeof(hid_desc), 10, 0);
344345
}
345346

346347
#ifdef ENABLE_PICOTOOL_USB
347348
uint8_t picotool_desc[] = { TUD_RPI_RESET_DESCRIPTOR(1, USB.registerString("Reset")) };
348-
_picotool_itf_num = USB.registerInterface(1, picotool_desc, sizeof(picotool_desc), 100, 0);
349+
_picotool_itf_num = USB.registerInterface(1, simpleInterface, picotool_desc, sizeof(picotool_desc), 100, 0);
349350
#endif
350351

351352
usbd_desc_cfg_len = TUD_CONFIG_DESC_LEN; // Always have a config descriptor
@@ -361,19 +362,18 @@ void USBClass::setupUSBDescriptor() {
361362
TUD_CONFIG_DESCRIPTOR(1, interface_count, USB.registerString(""), usbd_desc_cfg_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA)
362363
};
363364

364-
// Allocate the "real" HID report descriptor
365+
// Allocate the "real" report descriptor
365366
usbd_desc_cfg = (uint8_t *)malloc(usbd_desc_cfg_len);
366367
bzero(usbd_desc_cfg, usbd_desc_cfg_len);
367368

368-
// Now copy the descriptors
369+
// Now copy the interface descriptors
369370
h = _interfaces;
370371
uint8_t *p = usbd_desc_cfg;
371372
memcpy(p, tud_cfg_desc, sizeof(tud_cfg_desc));
372373
p += sizeof(tud_cfg_desc);
373374
int id = 0;
374375
while (h) {
375-
memcpy(p, h->descriptor, h->len);
376-
p[2] = id; // Set the interface
376+
h->cb(id, p, h->len, (void *)h->param);
377377
p += h->len;
378378
id += h->interfaces;
379379
h = h->next;

cores/rp2040/USB.h

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ class USBClass {
3939
void unregisterHIDDevice(unsigned int localid);
4040

4141
// Called by an object at global init time to add a new interface (non-HID, like CDC or Picotool)
42-
uint8_t registerInterface(int interfaces, const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask);
42+
uint8_t registerInterface(int interfaces, void (*cb)(int itf, uint8_t *dst, int len, void *param), void *param, size_t len, int ordering, uint32_t vidMask);
4343

4444
// Remove a USB interface from the USB descriptor. Only call after usbDisconnect or results could be unpredictable!
4545
void unregisterInterface(unsigned int localid);
@@ -94,14 +94,21 @@ class USBClass {
9494
uint8_t usbTaskIRQ;
9595
#endif
9696

97+
// Simple 1-interface updated for "easy" interfaces like Picotool or HIF
98+
static void simpleInterface(int itf, uint8_t *dst, int len, void *data) {
99+
memcpy(dst, data, len);
100+
dst[2] = itf; // Set the interface
101+
}
102+
97103
private:
98104
// We can't use non-trivial variables to hold the hid, interface, or string lists. The global
99105
// initialization where things like the global Keyboard may be called before the non-trivial
100106
// objects (i.e. no std::vector).
101107

102108
// Either a USB interface or HID device descriptor, kept in a linked list
103109
typedef struct Entry {
104-
const uint8_t *descriptor;
110+
void (*cb)(int itf, uint8_t *dst, int len, void *data); // unused for HID, only the report ID needs updating and we can do that inline
111+
const void *param; // CB param or HID descriptor
105112
unsigned int len : 12;
106113
unsigned int interfaces : 4;
107114
unsigned int order : 18;
@@ -111,7 +118,7 @@ class USBClass {
111118
} Entry;
112119

113120
// Add or remove Entry in a linked list, keeping things ordered by ordering
114-
uint8_t addEntry(Entry **head, int interfaces, const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask);
121+
uint8_t addEntry(Entry **head, int interfaces, void (*cb)(int itf, uint8_t *dst, int len, void *param), const void *param, size_t len, int ordering, uint32_t vidMask);
115122
void removeEntry(Entry **head, unsigned int localid);
116123

117124
// Find the index (HID report ID or USB interface) of a given localid
@@ -124,7 +131,6 @@ class USBClass {
124131
// Gets a pointer to the HID report structure, optionally returning the size in len
125132
uint8_t *getDescHIDReport(int *len);
126133

127-
private:
128134
Entry *_hids = nullptr;
129135
Entry *_interfaces = nullptr;
130136

libraries/FatFSUSB/src/FatFSUSB.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ bool FatFSUSBClass::begin() {
6363
_epIn = USB.registerEndpointIn();
6464
_epOut = USB.registerEndpointOut();
6565
static uint8_t msd_desc[] = { TUD_MSC_DESCRIPTOR(1 /* placeholder */, 0, _epOut, _epIn, USBD_MSC_EPSIZE) };
66-
_id = USB.registerInterface(2, msd_desc, sizeof(msd_desc), 2, 0);
66+
_id = USB.registerInterface(1, USBClass::simpleInterface, msd_desc, sizeof(msd_desc), 2, 0);
6767
USB.connect();
6868

6969
_started = true;

libraries/SingleFileDrive/src/SingleFileDrive.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ bool SingleFileDrive::begin(const char *localFile, const char *dosFile) {
6161
_epIn = USB.registerEndpointIn();
6262
_epOut = USB.registerEndpointOut();
6363
static uint8_t msd_desc[] = { TUD_MSC_DESCRIPTOR(1 /* placeholder */, 0, _epOut, _epIn, USBD_MSC_EPSIZE) };
64-
_id = USB.registerInterface(2, msd_desc, sizeof(msd_desc), 2, 0);
64+
_id = USB.registerInterface(1, USBClass::simpleInterface, msd_desc, sizeof(msd_desc), 2, 0);
6565
USB.connect();
6666
_localFile = strdup(localFile);
6767
_dosFile = strdup(dosFile);

0 commit comments

Comments
 (0)