1 /*
2 * Copyright (c) 2021-2022 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/sys/slist.h>
8 #include <zephyr/drivers/usb/udc.h>
9 #include <zephyr/usb/usbd.h>
10
11 #include "usbd_device.h"
12 #include "usbd_config.h"
13 #include "usbd_class.h"
14 #include "usbd_class_api.h"
15 #include "usbd_endpoint.h"
16
17 #include <zephyr/logging/log.h>
18 LOG_MODULE_REGISTER(usbd_init, CONFIG_USBD_LOG_LEVEL);
19
20 /* TODO: Allow to disable automatic assignment of endpoint features */
21
22 /* Assign endpoint address and update wMaxPacketSize */
assign_ep_addr(const struct device * dev,struct usb_ep_descriptor * const ed,uint32_t * const config_ep_bm,uint32_t * const class_ep_bm)23 static int assign_ep_addr(const struct device *dev,
24 struct usb_ep_descriptor *const ed,
25 uint32_t *const config_ep_bm,
26 uint32_t *const class_ep_bm)
27 {
28 int ret = -ENODEV;
29
30 for (unsigned int idx = 1; idx < 16U; idx++) {
31 uint16_t mps = ed->wMaxPacketSize;
32 uint8_t ep;
33
34 if (USB_EP_DIR_IS_IN(ed->bEndpointAddress)) {
35 ep = USB_EP_DIR_IN | idx;
36 } else {
37 ep = idx;
38 }
39
40 if (usbd_ep_bm_is_set(config_ep_bm, ep) ||
41 usbd_ep_bm_is_set(class_ep_bm, ep)) {
42 continue;
43 }
44
45
46 ret = udc_ep_try_config(dev, ep,
47 ed->bmAttributes, &mps,
48 ed->bInterval);
49
50 if (ret == 0) {
51 LOG_DBG("ep 0x%02x -> 0x%02x", ed->bEndpointAddress, ep);
52 ed->bEndpointAddress = ep;
53 ed->wMaxPacketSize = mps;
54 usbd_ep_bm_set(class_ep_bm, ed->bEndpointAddress);
55 usbd_ep_bm_set(config_ep_bm, ed->bEndpointAddress);
56
57 return 0;
58 }
59 }
60
61 return ret;
62 }
63
64 /* Unassign all endpoint of a class instance based on class_ep_bm */
unassign_eps(struct usbd_contex * const uds_ctx,uint32_t * const config_ep_bm,uint32_t * const class_ep_bm)65 static int unassign_eps(struct usbd_contex *const uds_ctx,
66 uint32_t *const config_ep_bm,
67 uint32_t *const class_ep_bm)
68 {
69 for (unsigned int idx = 1; idx < 16U && *class_ep_bm; idx++) {
70 uint8_t ep_in = USB_EP_DIR_IN | idx;
71 uint8_t ep_out = idx;
72
73 if (usbd_ep_bm_is_set(class_ep_bm, ep_in)) {
74 if (!usbd_ep_bm_is_set(config_ep_bm, ep_in)) {
75 LOG_ERR("Endpoing 0x%02x not assigned", ep_in);
76 return -EINVAL;
77 }
78
79 usbd_ep_bm_clear(config_ep_bm, ep_in);
80 usbd_ep_bm_clear(class_ep_bm, ep_in);
81 }
82
83 if (usbd_ep_bm_is_set(class_ep_bm, ep_out)) {
84 if (!usbd_ep_bm_is_set(config_ep_bm, ep_out)) {
85 LOG_ERR("Endpoing 0x%02x not assigned", ep_out);
86 return -EINVAL;
87 }
88
89 usbd_ep_bm_clear(config_ep_bm, ep_out);
90 usbd_ep_bm_clear(class_ep_bm, ep_out);
91 }
92 }
93
94 return 0;
95 }
96
97 /*
98 * Configure all interfaces and endpoints of a class instance
99 *
100 * The total number of interfaces is stored in the configuration descriptor's
101 * value bNumInterfaces. This value is reset at the beginning of configuration
102 * initialization and is increased according to the number of interfaces.
103 * The respective bInterfaceNumber must be assigned to all interfaces
104 * of a class instance.
105 *
106 * Like bInterfaceNumber the endpoint addresses must be assigned
107 * for all registered instances and respective endpoint descriptors.
108 * We use config_ep_bm variable as map for assigned endpoint for an
109 * USB device configuration.
110 */
init_configuration_inst(struct usbd_contex * const uds_ctx,struct usbd_class_data * const data,uint32_t * const config_ep_bm,uint8_t * const nif)111 static int init_configuration_inst(struct usbd_contex *const uds_ctx,
112 struct usbd_class_data *const data,
113 uint32_t *const config_ep_bm,
114 uint8_t *const nif)
115 {
116 struct usb_desc_header *dh = data->desc;
117 uint8_t *ptr = (uint8_t *)dh;
118 struct usb_if_descriptor *ifd = NULL;
119 struct usb_ep_descriptor *ed;
120 uint32_t class_ep_bm = 0;
121 uint8_t tmp_nif;
122 int ret;
123
124 tmp_nif = *nif;
125 data->iface_bm = 0U;
126 data->ep_active = 0U;
127
128 while (dh->bLength != 0) {
129
130 if (dh->bDescriptorType == USB_DESC_INTERFACE) {
131 ifd = (struct usb_if_descriptor *)ptr;
132
133 data->ep_active |= class_ep_bm;
134
135 if (ifd->bAlternateSetting == 0) {
136 ifd->bInterfaceNumber = tmp_nif;
137 data->iface_bm |= BIT(tmp_nif);
138 tmp_nif++;
139 } else {
140 ifd->bInterfaceNumber = tmp_nif - 1;
141 /*
142 * Unassign endpoints from last alternate,
143 * to work properly it requires that the
144 * characteristics of endpoints in alternate
145 * interfaces are ascending.
146 */
147 unassign_eps(uds_ctx, config_ep_bm, &class_ep_bm);
148 }
149
150 class_ep_bm = 0;
151 LOG_INF("interface %u alternate %u",
152 ifd->bInterfaceNumber, ifd->bAlternateSetting);
153 }
154
155 if (dh->bDescriptorType == USB_DESC_ENDPOINT) {
156 ed = (struct usb_ep_descriptor *)ptr;
157 ret = assign_ep_addr(uds_ctx->dev, ed,
158 config_ep_bm, &class_ep_bm);
159 if (ret) {
160 return ret;
161 }
162
163 LOG_INF("\tep 0x%02x interface ep-bm 0x%08x",
164 ed->bEndpointAddress, class_ep_bm);
165 }
166
167 ptr += dh->bLength;
168 dh = (struct usb_desc_header *)ptr;
169 }
170
171 if (tmp_nif <= *nif) {
172 return -EINVAL;
173 }
174
175 *nif = tmp_nif;
176 data->ep_active |= class_ep_bm;
177
178 LOG_INF("Instance iface-bm 0x%08x ep-bm 0x%08x",
179 data->iface_bm, data->ep_active);
180
181 return 0;
182 }
183
184 /*
185 * Initialize a device configuration
186 *
187 * Iterate on a list of all classes in a configuration
188 */
init_configuration(struct usbd_contex * const uds_ctx,struct usbd_config_node * const cfg_nd)189 static int init_configuration(struct usbd_contex *const uds_ctx,
190 struct usbd_config_node *const cfg_nd)
191 {
192 struct usb_cfg_descriptor *cfg_desc = cfg_nd->desc;
193 struct usbd_class_node *c_nd;
194 uint32_t config_ep_bm = 0;
195 size_t cfg_len = 0;
196 uint8_t nif = 0;
197 int ret;
198
199 SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
200
201 ret = init_configuration_inst(uds_ctx, c_nd->data,
202 &config_ep_bm, &nif);
203 if (ret != 0) {
204 LOG_ERR("Failed to assign endpoint addresses");
205 return ret;
206 }
207
208 ret = usbd_class_init(c_nd);
209 if (ret != 0) {
210 LOG_ERR("Failed to initialize class instance");
211 return ret;
212 }
213
214 LOG_INF("Init class node %p, descriptor length %zu",
215 c_nd, usbd_class_desc_len(c_nd));
216 cfg_len += usbd_class_desc_len(c_nd);
217 }
218
219 /* Update wTotalLength and bNumInterfaces of configuration descriptor */
220 sys_put_le16(sizeof(struct usb_cfg_descriptor) + cfg_len,
221 (uint8_t *)&cfg_desc->wTotalLength);
222 cfg_desc->bNumInterfaces = nif;
223
224 LOG_INF("bNumInterfaces %u wTotalLength %u",
225 cfg_desc->bNumInterfaces,
226 cfg_desc->wTotalLength);
227
228 /* Finally reset configuration's endpoint assignment */
229 SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
230 c_nd->data->ep_assigned = c_nd->data->ep_active;
231 ret = unassign_eps(uds_ctx, &config_ep_bm, &c_nd->data->ep_active);
232 if (ret != 0) {
233 return ret;
234 }
235 }
236
237 return 0;
238 }
239
usbd_init_update_mps0(struct usbd_contex * const uds_ctx)240 static void usbd_init_update_mps0(struct usbd_contex *const uds_ctx)
241 {
242 struct udc_device_caps caps = udc_caps(uds_ctx->dev);
243 struct usb_device_descriptor *desc = uds_ctx->desc;
244
245 switch (caps.mps0) {
246 case UDC_MPS0_8:
247 desc->bMaxPacketSize0 = 8;
248 break;
249 case UDC_MPS0_16:
250 desc->bMaxPacketSize0 = 16;
251 break;
252 case UDC_MPS0_32:
253 desc->bMaxPacketSize0 = 32;
254 break;
255 case UDC_MPS0_64:
256 desc->bMaxPacketSize0 = 64;
257 break;
258 }
259 }
260
usbd_init_configurations(struct usbd_contex * const uds_ctx)261 int usbd_init_configurations(struct usbd_contex *const uds_ctx)
262 {
263 struct usbd_config_node *cfg_nd;
264
265 usbd_init_update_mps0(uds_ctx);
266
267 SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->configs, cfg_nd, node) {
268 int ret;
269
270 ret = init_configuration(uds_ctx, cfg_nd);
271 if (ret) {
272 LOG_ERR("Failed to init configuration %u",
273 usbd_config_get_value(cfg_nd));
274 return ret;
275 }
276
277 LOG_INF("bNumConfigurations %u",
278 usbd_get_num_configs(uds_ctx));
279 }
280
281 return 0;
282 }
283