summaryrefslogtreecommitdiff
path: root/usb_common.c
blob: 65882b2de90d42049c14df1bc9d31d389cbce966 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
/* 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_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_init(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 */
}
		
/* 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];
}