diff options
Diffstat (limited to 'libusb/usb_common.c')
-rw-r--r-- | libusb/usb_common.c | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/libusb/usb_common.c b/libusb/usb_common.c new file mode 100644 index 0000000..f10ace8 --- /dev/null +++ b/libusb/usb_common.c @@ -0,0 +1,189 @@ +/* usb_common.c - Common framework for implementing multiple USB devices at once + * + * Copyright (C) 2024 Luke T. Shumaker <lukeshu@lukeshu.com> + * SPDX-Licence-Identifier: AGPL-3.0-or-later + */ + +#include <stdint.h> /* for uint{n}_t types */ +#include <string.h> /* memcpy(newlib) */ +#include <assert.h> /* for assert(newlib) */ +#include <stdlib.h> /* for malloc(pico_malloc), realloc(pico_malloc), reallocarray(pico_malloc) */ +#include "bsp/board_api.h" /* for board_init(), board_init_after_usb(), board_usb_get_serial(TinyUSB) */ +#include "tusb.h" /* for various tusb_*_t types */ + +#include "tusb_helpers.h" /* for LANGID_*, TU_UTF16() */ +#include "usb_common.h" + +/* Strings ********************************************************************/ + +/** + * Return a pointer to the USB "String Descriptor" (see USB 2.0 §9.6.7 + * "String"). + */ +uint16_t const *tud_descriptor_string_cb(uint8_t strid, uint16_t langid) { + static struct TU_ATTR_PACKED { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bString[(0xFF-2)/2]; + } desc; + TU_VERIFY_STATIC(sizeof desc == (0xFF/2)*2, "incorrect size"); + + static uint16_t const langids[] = { + LANGID_EN_US, + }; + + size_t bytelen = 0; + if (strid == 0) { + memcpy(desc.bString, langids, sizeof langids); + bytelen = sizeof langids; + } else { +#define CONST_STR(str) bytelen = ((sizeof (str))-1); memcpy(desc.bString, (str), ((sizeof (str))-1)); + switch (langid) { + case LANGID_EN_US: + switch (strid) { + case STRID_MANUF: CONST_STR(TU_UTF16("Umorpha Systems")); break; + case STRID_PRODUCT: CONST_STR(TU_UTF16("SBC-Harness Keyboard")); break; + case STRID_CFG: CONST_STR(TU_UTF16("Standard Configuration")); break; + case STRID_KBD_IFC: CONST_STR(TU_UTF16("Keyboard Interface")); break; + case STRID_SERIAL: +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Waddress-of-packed-member" + bytelen = 2 * board_usb_get_serial(desc.bString, TU_ARRAY_SIZE(desc.bString)); +#pragma GCC diagnostic pop + break; + default: + printf("GET STRING: unknown string id=%"PRIu8, strid); + return NULL; + } + break; + default: + printf("GET STRING: unknown LANGID=%"PRIx16, langid); + return NULL; + } + } + assert(bytelen <= sizeof desc.bString); + desc.bLength = bytelen + 2; + desc.bDescriptorType = TUSB_DESC_STRING; + return (uint16_t *)&desc; +} + +/* Globals ********************************************************************/ + +uint8_t cfgnum_std = 0; + +void usb_common_earlyinit(void) { + if (cfgnum_std) + return; + cfgnum_std = usb_add_config( + STRID_CFG, /* iConfiguration ; Index of string descriptor describing this configuration */ + TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, /* bmAttributes ; Bitmap of flags (self-powered and remote-wakeup are the only flags defined in USB 2.0) */ + 100); /* bMaxPower (in mA) ; Maximum power consumption of the device when in this configuration */ +} + +void usb_common_lateinit(void) { + board_init(); + tud_init(BOARD_TUD_RHPORT); + if (board_init_after_tusb) + board_init_after_tusb(); +} + +COROUTINE usb_common_cr(void *_arg) { + (void) _arg; + cr_begin(); + + for (;;) { + tud_task(); + cr_yield(); + } + + cr_end(); +} + +/* Main utilities *************************************************************/ + +static uint8_t configc = 0; +static uint8_t **configv = NULL; + +uint8_t usb_add_config(uint8_t iConfiguration, uint8_t bmAttributes, uint8_t bMaxPower_mA) { + uint8_t *desc; + + configv = reallocarray(configv, ++configc, sizeof configv[0]); + desc = configv[configc-1] = malloc(TUD_CONFIG_DESC_LEN); + + memcpy(desc, (uint8_t[]){ + /* USB configuration descriptor header (USB 2.0 §9.6.4 "Configuration") */ + TUD_CONFIG_DESCRIPTOR( + configc, /* bConfigurationValue */ + 0, /* bNumInterfaces (will be incremented by usb_add_interface() */ + iConfiguration, /* iConfiguration */ + TUD_CONFIG_DESC_LEN, /* wTotalLength (will be incremented by usb_add_interface() */ + bmAttributes, /* bmAttributes */ + bMaxPower_mA+1), /* bMaxPower (+1 because tusb just does n/2 instead of (n+1)/2) */ + }, TUD_CONFIG_DESC_LEN); + + return configc; +} + +uint8_t usb_add_interface(uint8_t cfg_num, uint16_t ifc_len, uint8_t *ifc_dat) { + assert(cfg_num > 0 && cfg_num <= configc); + assert(ifc_len >= 3); + assert(ifc_dat); + + uint8_t *desc = configv[cfg_num-1]; + // wTotalLength + uint16_t total_len = TU_U16(desc[3], desc[2]) + ifc_len; + desc[3] = TU_U16_HIGH(total_len); + desc[2] = TU_U16_LOW(total_len); + // bNumInterfaces + ifc_dat[3] = desc[4]++; + + desc = configv[cfg_num-1] = realloc(desc, total_len); + memcpy(&desc[total_len-ifc_len], ifc_dat, ifc_len); + + return ifc_dat[3]; +} + +/** + * Return a pointer to the USB "Device Descriptor" (see USB 2.0 §9.6.1 + * "Device") as a byte-array. + */ +uint8_t const *tud_descriptor_device_cb(void) { + /* Our device descriptor is static, so just declare it as a + * static const. */ + static tusb_desc_device_t desc = { + .bLength = sizeof(tusb_desc_device_t), + .bDescriptorType = TUSB_DESC_DEVICE, + .bcdUSB = 0x200, /* USB 2.0 */ + + /* Use the "base device class", which means to use information + * from interface descriptors instead of a global device + * descriptor. */ + .bDeviceClass = 0x00, + .bDeviceSubClass = 0x00, + .bDeviceProtocol = 0x00, + + .bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE, + + .idVendor = 0xCAFE, /* Vendor ID (assigned by the USB-IF) */ + .idProduct = 0x4001, /* Product ID (assigned by the manufacturer) */ + .bcdDevice = 0x0100, /* Device release number */ + + .iManufacturer = STRID_MANUF, /* Index of string descriptor describing manufacturer */ + .iProduct = STRID_PRODUCT, /* Index of string descriptor describing product */ + .iSerialNumber = STRID_SERIAL, /* Index of string descriptor describing the device's serial number */ + + .bNumConfigurations = 0, /* Number of possible configurations */ + }; + desc.bNumConfigurations = configc; + return (uint8_t const *) &desc; +} + +/** + * Return a pointer to the USB config "Configuration Descriptor" (see + * USB 2.0 §9.6.4 "Configuration") for the given index. + */ +uint8_t const *tud_descriptor_configuration_cb(uint8_t index) { + if (index >= configc) + return NULL; + return configv[index]; +} |