1 /*
2 * Copyright (c) 2022 Nordic Semiconductor ASA
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
7 #include <zephyr/drivers/usb/udc.h>
8 #include <zephyr/usb/usbd.h>
9
10 #include "usbd_device.h"
11 #include "usbd_class.h"
12 #include "usbd_class_api.h"
13 #include "usbd_endpoint.h"
14 #include "usbd_ch9.h"
15
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(usbd_iface, CONFIG_USBD_LOG_LEVEL);
18
19 enum ep_op {
20 EP_OP_TEST, /* Test if interface alternate available */
21 EP_OP_UP, /* Enable endpoint and update endpoints bitmap */
22 EP_OP_DOWN, /* Disable endpoint and update endpoints bitmap */
23 };
24
handle_ep_op(struct usbd_contex * const uds_ctx,const enum ep_op op,struct usb_ep_descriptor * const ed,uint32_t * const ep_bm)25 static int handle_ep_op(struct usbd_contex *const uds_ctx,
26 const enum ep_op op,
27 struct usb_ep_descriptor *const ed,
28 uint32_t *const ep_bm)
29 {
30 const uint8_t ep = ed->bEndpointAddress;
31 int ret;
32
33 switch (op) {
34 case EP_OP_TEST:
35 ret = 0;
36 break;
37 case EP_OP_UP:
38 ret = usbd_ep_enable(uds_ctx->dev, ed, ep_bm);
39 break;
40 case EP_OP_DOWN:
41 ret = usbd_ep_disable(uds_ctx->dev, ep, ep_bm);
42 break;
43 }
44
45 if (ret) {
46 LOG_ERR("Failed to handle op %d, ep 0x%02x, bm 0x%08x, %d",
47 op, ep, *ep_bm, ret);
48 }
49
50 return ret;
51 }
52
usbd_interface_modify(struct usbd_contex * const uds_ctx,struct usbd_class_node * const node,const enum ep_op op,const uint8_t iface,const uint8_t alt)53 static int usbd_interface_modify(struct usbd_contex *const uds_ctx,
54 struct usbd_class_node *const node,
55 const enum ep_op op,
56 const uint8_t iface,
57 const uint8_t alt)
58 {
59 struct usb_desc_header *dh;
60 bool found_iface = false;
61 uint8_t *ptr;
62 int ret;
63
64 dh = node->data->desc;
65 ptr = (uint8_t *)dh;
66
67 while (dh->bLength != 0) {
68 struct usb_if_descriptor *ifd;
69 struct usb_ep_descriptor *ed;
70
71 if (dh->bDescriptorType == USB_DESC_INTERFACE) {
72 ifd = (struct usb_if_descriptor *)ptr;
73
74 if (found_iface) {
75 break;
76 }
77
78 if (ifd->bInterfaceNumber == iface &&
79 ifd->bAlternateSetting == alt) {
80 found_iface = true;
81 LOG_DBG("Found interface %u %p", iface, node);
82 if (ifd->bNumEndpoints == 0) {
83 LOG_INF("No endpoints, skip interface");
84 break;
85 }
86 }
87 }
88
89 if (dh->bDescriptorType == USB_DESC_ENDPOINT && found_iface) {
90 ed = (struct usb_ep_descriptor *)ptr;
91 ret = handle_ep_op(uds_ctx, op, ed, &node->data->ep_active);
92 if (ret) {
93 return ret;
94 }
95
96 LOG_INF("Modify interface %u ep 0x%02x by op %u ep_bm %x",
97 iface, ed->bEndpointAddress,
98 op, node->data->ep_active);
99 }
100
101 ptr += dh->bLength;
102 dh = (struct usb_desc_header *)ptr;
103 }
104
105 /* TODO: rollback ep_bm on error? */
106
107 return found_iface ? 0 : -ENODATA;
108 }
109
usbd_interface_shutdown(struct usbd_contex * const uds_ctx,struct usbd_config_node * const cfg_nd)110 int usbd_interface_shutdown(struct usbd_contex *const uds_ctx,
111 struct usbd_config_node *const cfg_nd)
112 {
113 struct usbd_class_node *c_nd;
114
115 SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
116 uint32_t *ep_bm = &c_nd->data->ep_active;
117
118 for (int idx = 1; idx < 16 && *ep_bm; idx++) {
119 uint8_t ep_in = USB_EP_DIR_IN | idx;
120 uint8_t ep_out = idx;
121 int ret;
122
123 if (usbd_ep_bm_is_set(ep_bm, ep_in)) {
124 ret = usbd_ep_disable(uds_ctx->dev, ep_in, ep_bm);
125 if (ret) {
126 return ret;
127 }
128 }
129
130 if (usbd_ep_bm_is_set(ep_bm, ep_out)) {
131 ret = usbd_ep_disable(uds_ctx->dev, ep_out, ep_bm);
132 if (ret) {
133 return ret;
134 }
135 }
136 }
137 }
138
139 return 0;
140 }
141
usbd_interface_default(struct usbd_contex * const uds_ctx,struct usbd_config_node * const cfg_nd)142 int usbd_interface_default(struct usbd_contex *const uds_ctx,
143 struct usbd_config_node *const cfg_nd)
144 {
145 struct usb_cfg_descriptor *desc = cfg_nd->desc;
146 const uint8_t new_cfg = desc->bConfigurationValue;
147
148 /* Set default alternate for all interfaces */
149 for (int i = 0; i < desc->bNumInterfaces; i++) {
150 struct usbd_class_node *class;
151 int ret;
152
153 class = usbd_class_get_by_config(uds_ctx, new_cfg, i);
154 if (class == NULL) {
155 return -ENODATA;
156 }
157
158 ret = usbd_interface_modify(uds_ctx, class, EP_OP_UP, i, 0);
159 if (ret) {
160 return ret;
161 }
162 }
163
164 return 0;
165 }
166
usbd_interface_set(struct usbd_contex * const uds_ctx,const uint8_t iface,const uint8_t alt)167 int usbd_interface_set(struct usbd_contex *const uds_ctx,
168 const uint8_t iface,
169 const uint8_t alt)
170 {
171 struct usbd_class_node *class;
172 uint8_t cur_alt;
173 int ret;
174
175 class = usbd_class_get_by_iface(uds_ctx, iface);
176 if (class == NULL) {
177 return -ENOENT;
178 }
179
180 ret = usbd_get_alt_value(uds_ctx, iface, &cur_alt);
181 if (ret) {
182 return ret;
183 }
184
185 LOG_INF("Set Interfaces %u, alternate %u -> %u", iface, cur_alt, alt);
186 if (alt == cur_alt) {
187 return 0;
188 }
189
190 /* Test if interface or interface alternate exist */
191 ret = usbd_interface_modify(uds_ctx, class, EP_OP_TEST, iface, alt);
192 if (ret) {
193 return -ENOENT;
194 }
195
196 /* Shutdown current interface alternate */
197 ret = usbd_interface_modify(uds_ctx, class, EP_OP_DOWN, iface, cur_alt);
198 if (ret) {
199 return ret;
200 }
201
202 /* Setup new interface alternate */
203 ret = usbd_interface_modify(uds_ctx, class, EP_OP_UP, iface, alt);
204 if (ret) {
205 /* TODO: rollback on error? */
206 return ret;
207 }
208
209 usbd_class_update(class, iface, alt);
210 usbd_set_alt_value(uds_ctx, iface, alt);
211
212 return 0;
213 }
214