Skip to content

Commit 1408ca3

Browse files
BREAKING: Support dynamic USB reconfiguration
Now that the USB descriptor and HID reports are handled by code, it is possible to change then on-the-fly from an application and customize the USB vendor/product/etc. It is also possible to add and remove HID and other devices, at runtime. Serial (USB) now properly handles `Serial.end()` and completely removes the USB CDC descriptor for the Serial port when called. That makes it possible to build sketches which only export HID (Keyboard, Joystick, Mouse) or MSC (FatFSUSB or SingleFileDrive) devices and no Serial port. All USB devices now register themselves on `xxx.begin()` and not at the start of the sketch. This could be breaking for sketches which (incorrectly) forget to call `Keyboard.begin()` before running, for example. In most cases this will be a non-issue because most scripts (all examples here!) so call begin() as appropriate. Updates the documentation with the new USB calls for end users. Advanced users can check out the shipped libraries for more complicated customization (adding/removing custom devices).
1 parent dba8f17 commit 1408ca3

File tree

24 files changed

+434
-47
lines changed

24 files changed

+434
-47
lines changed

cores/rp2040/Arduino.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <string.h>
2626
#include "stdlib_noniso.h" // Wacky deprecated AVR compatibility functions
2727
#include "RP2040Version.h"
28+
#include "RP2040USB.h"
2829
#include "api/ArduinoAPI.h"
2930
#include "api/itoa.h" // ARM toolchain doesn't provide itoa etc, provide them
3031
#include <pico.h>

cores/rp2040/RP2040USB.cpp

Lines changed: 152 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -79,25 +79,59 @@ static uint8_t usbd_desc_str_cnt = 0;
7979
static uint8_t usbd_desc_str_alloc = 0;
8080

8181
// HID report
82+
static unsigned int __hid_interface = (unsigned int)-1;
83+
static uint8_t __hid_endpoint = 0;
8284
static int __hid_report_len = 0;
8385
static uint8_t *__hid_report = nullptr;
8486

8587
// Global USB descriptor
8688
static uint8_t *usbd_desc_cfg = nullptr;
89+
static int usbd_desc_cfg_len = 0;
90+
8791
#ifdef ENABLE_PICOTOOL_USB
8892
static uint8_t _picotool_itf_num;
8993
#endif
9094
int usb_hid_poll_interval __attribute__((weak)) = 10;
9195

9296

97+
// Available bitmask for endpoints, can never be EP 0
98+
static uint32_t _endpointIn = 0xfffffffe;
99+
static uint32_t _endpointOut = 0xfffffffe;
100+
101+
// GCC doesn't seem to have builtin_ffs here
102+
static int ffs(uint32_t v) {
103+
for (auto i = 0; i < 32; i++) {
104+
if (v & (1<<i)) {
105+
return i;
106+
}
107+
}
108+
return 0;
109+
}
110+
93111
uint8_t usbRegisterEndpointIn() {
94-
static uint8_t epin = 0x81;
95-
return epin++;
112+
if (!_endpointIn) {
113+
return 0; // ERROR, out of EPs
114+
}
115+
int firstFree = ffs(_endpointIn);
116+
_endpointIn &= ~(1 << firstFree);
117+
return 0x80 + firstFree;
118+
}
119+
120+
void usbUnregisterEndpointIn(int ep) {
121+
_endpointIn |= 1 << ep;
96122
}
97123

98124
uint8_t usbRegisterEndpointOut() {
99-
static uint8_t epout = 0x01;
100-
return epout++;
125+
if (!_endpointOut) {
126+
return 0; // ERROR, out of EPs
127+
}
128+
int firstFree = ffs(_endpointOut);
129+
_endpointOut &= ~(1 << firstFree);
130+
return firstFree;
131+
}
132+
133+
void usbUnregisterEndpointOut(int ep) {
134+
_endpointOut |= (1 << (ep - 0x80));
101135
}
102136

103137
static uint8_t AddEntry(Entry **head, int interfaces, const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask) {
@@ -132,6 +166,27 @@ static uint8_t AddEntry(Entry **head, int interfaces, const uint8_t *descriptor,
132166
return n->localid;
133167
}
134168

169+
static void RemoveEntry(Entry **head, unsigned int localid) {
170+
Entry *prev = nullptr;
171+
Entry *cur = *head;
172+
while (cur && cur->localid != localid) {
173+
prev = cur;
174+
cur = cur->next;
175+
}
176+
if (!cur) {
177+
// Not found, just exit
178+
return;
179+
}
180+
if (cur == *head) {
181+
auto p = cur->next;
182+
free(*head);
183+
*head = p;
184+
} else {
185+
prev->next = cur->next;
186+
free(cur);
187+
}
188+
}
189+
135190
// Find the index (HID report ID or USB interface) of a given localid
136191
static unsigned int usbFindID(Entry *head, unsigned int localid) {
137192
unsigned int x = 0;
@@ -156,11 +211,18 @@ uint8_t usbRegisterHIDDevice(const uint8_t *descriptor, size_t len, int ordering
156211
return AddEntry(&_hids, 0, descriptor, len, ordering, vidMask);
157212
}
158213

214+
void usbUnregisterHIDDevice(unsigned int localid) {
215+
RemoveEntry(&_hids, localid);
216+
}
217+
159218
// Called by an object at global init time to add a new interface (non-HID, like CDC or Picotool)
160219
uint8_t usbRegisterInterface(int interfaces, const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask) {
161220
return AddEntry(&_interfaces, interfaces, descriptor, len, ordering, vidMask);
162221
}
163222

223+
void usbUnregisterInterface(unsigned int localid) {
224+
RemoveEntry(&_interfaces, localid);
225+
}
164226

165227
uint8_t usbRegisterString(const char *str) {
166228
if (usbd_desc_str_alloc <= usbd_desc_str_cnt) {
@@ -182,41 +244,62 @@ uint8_t usbRegisterString(const char *str) {
182244
return usbd_desc_str_cnt++;
183245
}
184246

247+
static uint16_t _forceVID = 0;
248+
static uint16_t _forcePID = 0;
249+
void usbSetVIDPID(uint16_t vid, uint16_t pid) {
250+
_forceVID = vid;
251+
_forcePID = pid;
252+
}
185253

254+
static uint8_t _forceManuf = 0;
255+
static uint8_t _forceProd = 0;
256+
static uint8_t _forceSerial = 0;
257+
void usbSetManufacturer(const char *str) {
258+
_forceManuf = usbRegisterString(str);
259+
}
260+
void usbSetProduct(const char *str) {
261+
_forceProd = usbRegisterString(str);
262+
}
263+
void usbSetSerialNumber(const char *str) {
264+
_forceSerial = usbRegisterString(str);
265+
}
186266

267+
static tusb_desc_device_t usbd_desc_device;
187268
const uint8_t *tud_descriptor_device_cb(void) {
188269
static char idString[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 3 + 1];
189270
if (!idString[0]) {
190271
pico_get_unique_board_id_string(idString, sizeof(idString));
191272
}
192273

193-
static tusb_desc_device_t usbd_desc_device = {
274+
usbd_desc_device = {
194275
.bLength = sizeof(tusb_desc_device_t),
195276
.bDescriptorType = TUSB_DESC_DEVICE,
196277
.bcdUSB = 0x0200,
197278
.bDeviceClass = 0,
198279
.bDeviceSubClass = 0,
199280
.bDeviceProtocol = 0,
200281
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
201-
.idVendor = USBD_VID,
202-
.idProduct = USBD_PID,
282+
.idVendor = _forceVID ? _forceVID : (uint16_t)USBD_VID,
283+
.idProduct = _forcePID ? _forcePID : (uint16_t)USBD_PID,
203284
.bcdDevice = 0x0100,
204-
.iManufacturer = usbRegisterString(USB_MANUFACTURER),
205-
.iProduct = usbRegisterString(USB_PRODUCT),
206-
.iSerialNumber = usbRegisterString(idString),
285+
.iManufacturer = _forceManuf ? _forceManuf : usbRegisterString(USB_MANUFACTURER),
286+
.iProduct = _forceProd ? _forceProd : usbRegisterString(USB_PRODUCT),
287+
.iSerialNumber = _forceSerial ? _forceSerial : usbRegisterString(idString),
207288
.bNumConfigurations = 1
208289
};
209290

210-
// Handle any inversions from the sub-devices
211-
Entry *h = _hids;
212-
while (h) {
213-
usbd_desc_device.idProduct ^= h->mask;
214-
h = h->next;
215-
}
216-
h = _interfaces;
217-
while (h) {
218-
usbd_desc_device.idProduct ^= h->mask;
219-
h = h->next;
291+
// Handle any inversions from the sub-devices, if we're not forcing things
292+
if (!_forcePID) {
293+
Entry *h = _hids;
294+
while (h) {
295+
usbd_desc_device.idProduct ^= h->mask;
296+
h = h->next;
297+
}
298+
h = _interfaces;
299+
while (h) {
300+
usbd_desc_device.idProduct ^= h->mask;
301+
h = h->next;
302+
}
220303
}
221304

222305
return (const uint8_t *)&usbd_desc_device;
@@ -282,7 +365,6 @@ const uint8_t *tud_descriptor_configuration_cb(uint8_t index) {
282365
// needed ever again
283366
void __SetupUSBDescriptor() {
284367
uint8_t interface_count = 0;
285-
int usbd_desc_len;
286368
if (usbd_desc_cfg) {
287369
return;
288370
}
@@ -291,32 +373,33 @@ void __SetupUSBDescriptor() {
291373
if (GetDescHIDReport(&hid_report_len)) {
292374
uint8_t hid_desc[TUD_HID_DESC_LEN] = {
293375
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
294-
TUD_HID_DESCRIPTOR(1 /* placeholder*/, 0, HID_ITF_PROTOCOL_NONE, hid_report_len, usbRegisterEndpointIn(), CFG_TUD_HID_EP_BUFSIZE, (uint8_t)usb_hid_poll_interval)
376+
TUD_HID_DESCRIPTOR(1 /* placeholder*/, 0, HID_ITF_PROTOCOL_NONE, hid_report_len, __hid_endpoint = usbRegisterEndpointIn(), CFG_TUD_HID_EP_BUFSIZE, (uint8_t)usb_hid_poll_interval)
295377
};
296-
usbRegisterInterface(1, hid_desc, sizeof(hid_desc), 10, 0);
378+
__hid_interface = usbRegisterInterface(1, hid_desc, sizeof(hid_desc), 10, 0);
297379
}
298380

299381
#ifdef ENABLE_PICOTOOL_USB
300382
uint8_t picotool_desc[] = { TUD_RPI_RESET_DESCRIPTOR(1, usbRegisterString("Reset")) };
301383
usbRegisterInterface(1, picotool_desc, sizeof(picotool_desc), 100, 0);
302384
#endif
303385

304-
usbd_desc_len = TUD_CONFIG_DESC_LEN; // Always have a config descriptor
386+
usbd_desc_cfg_len = TUD_CONFIG_DESC_LEN; // Always have a config descriptor
305387
Entry *h = _interfaces;
306388
while (h) {
307-
usbd_desc_len += h->len;
389+
usbd_desc_cfg_len += h->len;
308390
interface_count += h->interfaces;
309391
h = h->next;
310392
}
311393

312394
uint8_t tud_cfg_desc[TUD_CONFIG_DESC_LEN] = {
313395
// Config number, interface count, string index, total length, attribute, power in mA
314-
TUD_CONFIG_DESCRIPTOR(1, interface_count, usbRegisterString(""), usbd_desc_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA)
396+
TUD_CONFIG_DESCRIPTOR(1, interface_count, usbRegisterString(""), usbd_desc_cfg_len, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, USBD_MAX_POWER_MA)
315397
};
316398

317399
// Allocate the "real" HID report descriptor
318-
usbd_desc_cfg = (uint8_t *)malloc(usbd_desc_len);
400+
usbd_desc_cfg = (uint8_t *)malloc(usbd_desc_cfg_len);
319401
assert(usbd_desc_cfg);
402+
bzero(usbd_desc_cfg, usbd_desc_cfg_len);
320403

321404
// Now copy the descriptors
322405
h = _interfaces;
@@ -374,6 +457,48 @@ static int64_t timer_task(__unused alarm_id_t id, __unused void *user_data) {
374457
return USB_TASK_INTERVAL;
375458
}
376459

460+
void usbDisconnect() {
461+
#ifdef __FREERTOS
462+
auto m = __get_freertos_mutex_for_ptr(&__usb_mutex);
463+
xSemaphoreTake(m, portMAX_DELAY);
464+
tud_disconnect();
465+
vTaskDelay(500 / portTICK_PERIOD_MS);
466+
xSemaphoreGive(m);
467+
#else
468+
mutex_enter_blocking(&__usb_mutex);
469+
tud_disconnect();
470+
sleep_ms(500);
471+
mutex_exit(&__usb_mutex);
472+
#endif
473+
// Ensure when we reconnect we make the new descriptor
474+
free(usbd_desc_cfg);
475+
usbd_desc_cfg = nullptr;
476+
usbd_desc_cfg_len = 0;
477+
if (__hid_report) {
478+
usbUnregisterInterface(__hid_interface);
479+
usbUnregisterEndpointIn(__hid_endpoint);
480+
}
481+
free(__hid_report);
482+
__hid_report = nullptr;
483+
__hid_report_len = 0;
484+
}
485+
486+
void usbConnect() {
487+
__SetupDescHIDReport();
488+
__SetupUSBDescriptor();
489+
490+
#ifdef __FREERTOS
491+
auto m = __get_freertos_mutex_for_ptr(&__usb_mutex);
492+
xSemaphoreTake(m, portMAX_DELAY);
493+
tusb_connect();
494+
xSemaphoreGive(m);
495+
#else
496+
mutex_enter_blocking(&__usb_mutex);
497+
tud_connect();
498+
mutex_exit(&__usb_mutex);
499+
#endif
500+
}
501+
377502
void __USBStart() __attribute__((weak));
378503

379504
void __USBStart() {

cores/rp2040/RP2040USB.h

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,16 @@
2525
// Called by an object at global init time to register a HID device, returns a localID to be mapped using findHIDReportID
2626
// vidMask is the bits in the VID that should be XOR'd when this device is present.
2727
// 0 means don't invert anything, OTW select a single bitmask 1<<n.
28-
uint8_t usbRegisterHIDDevice(const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask = 0);
28+
uint8_t usbRegisterHIDDevice(const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask);
29+
30+
// Remove a HID device from the USB descriptor. Only call after usbDisconnect or results could be unpredictable!
31+
void usbUnregisterHIDDevice(unsigned int localid);
2932

3033
// Called by an object at global init time to add a new interface (non-HID, like CDC or Picotool)
31-
uint8_t usbRegisterInterface(int interfaces, const uint8_t *descriptor, size_t len, int ordering = INT_MAX, uint32_t vidMask = 0);
34+
uint8_t usbRegisterInterface(int interfaces, const uint8_t *descriptor, size_t len, int ordering, uint32_t vidMask);
35+
36+
// Remove a USB interface from the USB descriptor. Only call after usbDisconnect or results could be unpredictable!
37+
void usbUnregisterInterface(unsigned int localid);
3238

3339
// Get the USB HID actual report ID from the localid
3440
uint8_t usbFindHIDReportID(unsigned int localid);
@@ -42,6 +48,20 @@ uint8_t usbRegisterString(const char *str);
4248
// Get an unassigned in/cmd or out endpoint number
4349
uint8_t usbRegisterEndpointIn();
4450
uint8_t usbRegisterEndpointOut();
51+
void usbUnregisterEndpointIn(int ep);
52+
void usbUnregisterEndpointOut(int ep);
53+
54+
// Disconnects the USB connection to allow editing the HID/interface list
55+
void usbDisconnect();
56+
57+
// Reconnects the USB connection to pick up the new descriptor
58+
void usbConnect();
59+
60+
// Override the hardcoded USB VID:PID, product, manufacturer, and serials
61+
void usbSetVIDPID(uint16_t vid, uint16_t pid);
62+
void usbSetManufacturer(const char *str);
63+
void usbSetProduct(const char *str);
64+
void usbSetSerialNumber(const char *str);
4565

4666
// Big, global USB mutex, shared with all USB devices to make sure we don't
4767
// have multiple cores updating the TUSB state in parallel

cores/rp2040/SerialUSB.cpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,13 @@ extern mutex_t __usb_mutex;
4141
#define USBD_CDC_IN_OUT_MAX_SIZE (64)
4242

4343

44-
static const uint8_t cdc_desc[TUD_CDC_DESC_LEN] = {
45-
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
46-
TUD_CDC_DESCRIPTOR(0 /* placeholder*/, usbRegisterString("Pico Serial"), usbRegisterEndpointIn(), USBD_CDC_CMD_MAX_SIZE, usbRegisterEndpointOut(), usbRegisterEndpointIn(), USBD_CDC_IN_OUT_MAX_SIZE)
47-
};
44+
//static const uint8_t cdc_desc[TUD_CDC_DESC_LEN] = {
45+
// // Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
46+
// TUD_CDC_DESCRIPTOR(0 /* placeholder*/, usbRegisterString("Pico Serial"), usbRegisterEndpointIn(), USBD_CDC_CMD_MAX_SIZE, usbRegisterEndpointOut(), usbRegisterEndpointIn(), USBD_CDC_IN_OUT_MAX_SIZE)
47+
//};
4848

4949
SerialUSB::SerialUSB() {
50-
_id = usbRegisterInterface(2, cdc_desc, sizeof(cdc_desc), 1, 0);
50+
// _id = usbRegisterInterface(2, cdc_desc, sizeof(cdc_desc), 1, 0);
5151
}
5252

5353

@@ -58,11 +58,28 @@ void SerialUSB::begin(unsigned long baud) {
5858
return;
5959
}
6060

61+
usbDisconnect();
62+
static uint8_t cdc_desc[TUD_CDC_DESC_LEN] = {
63+
// Interface number, string index, protocol, report descriptor len, EP In & Out address, size & polling interval
64+
TUD_CDC_DESCRIPTOR(0 /* placeholder*/, usbRegisterString("Pico Serial"), _epIn = usbRegisterEndpointIn(), USBD_CDC_CMD_MAX_SIZE, _epOut = usbRegisterEndpointOut(), usbRegisterEndpointIn(), USBD_CDC_IN_OUT_MAX_SIZE)
65+
};
66+
67+
_id = usbRegisterInterface(2, cdc_desc, sizeof(cdc_desc), 1, 0);
68+
69+
usbConnect();
6170
_running = true;
6271
}
6372

6473
void SerialUSB::end() {
65-
// TODO
74+
if (_running) {
75+
usbDisconnect();
76+
usbUnregisterInterface(_id);
77+
usbUnregisterEndpointIn(_epIn);
78+
usbUnregisterEndpointOut(_epOut);
79+
_running = false;
80+
usbConnect();
81+
}
82+
6683
}
6784

6885
int SerialUSB::peek() {

cores/rp2040/SerialUSB.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ class SerialUSB : public arduino::HardwareSerial {
5757
bool _running = false;
5858
bool _ignoreFlowControl = false;
5959
uint8_t _id;
60+
uint8_t _epIn;
61+
uint8_t _epOut;
6062
};
6163

6264
extern SerialUSB Serial;

0 commit comments

Comments
 (0)