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_context * 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_context *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 = -ENOTSUP;
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_context * const uds_ctx,struct usbd_class_node * const c_nd,const enum ep_op op,const uint8_t iface,const uint8_t alt)53 static int usbd_interface_modify(struct usbd_context *const uds_ctx,
54 struct usbd_class_node *const c_nd,
55 const enum ep_op op,
56 const uint8_t iface,
57 const uint8_t alt)
58 {
59 struct usb_desc_header **dhp;
60 bool found_iface = false;
61 int ret;
62
63 dhp = usbd_class_get_desc(c_nd->c_data, usbd_bus_speed(uds_ctx));
64 if (dhp == NULL) {
65 return -EINVAL;
66 }
67
68 while (*dhp != NULL && (*dhp)->bLength != 0) {
69 struct usb_if_descriptor *ifd;
70 struct usb_ep_descriptor *ed;
71
72 if ((*dhp)->bDescriptorType == USB_DESC_INTERFACE) {
73 ifd = (struct usb_if_descriptor *)(*dhp);
74
75 if (found_iface) {
76 break;
77 }
78
79 if (ifd->bInterfaceNumber == iface &&
80 ifd->bAlternateSetting == alt) {
81 found_iface = true;
82 LOG_DBG("Found interface %u %p", iface, c_nd);
83 if (ifd->bNumEndpoints == 0) {
84 LOG_INF("No endpoints, skip interface");
85 break;
86 }
87 }
88 }
89
90 if ((*dhp)->bDescriptorType == USB_DESC_ENDPOINT && found_iface) {
91 ed = (struct usb_ep_descriptor *)(*dhp);
92 ret = handle_ep_op(uds_ctx, op, ed, &c_nd->ep_active);
93 if (ret) {
94 return ret;
95 }
96
97 LOG_INF("Modify interface %u ep 0x%02x by op %u ep_bm %x",
98 iface, ed->bEndpointAddress,
99 op, c_nd->ep_active);
100 }
101
102 dhp++;
103 }
104
105 /* TODO: rollback ep_bm on error? */
106
107 return found_iface ? 0 : -ENODATA;
108 }
109
usbd_interface_shutdown(struct usbd_context * const uds_ctx,struct usbd_config_node * const cfg_nd)110 int usbd_interface_shutdown(struct usbd_context *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->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_context * const uds_ctx,const enum usbd_speed speed,struct usbd_config_node * const cfg_nd)142 int usbd_interface_default(struct usbd_context *const uds_ctx,
143 const enum usbd_speed speed,
144 struct usbd_config_node *const cfg_nd)
145 {
146 struct usb_cfg_descriptor *desc = cfg_nd->desc;
147 const uint8_t new_cfg = desc->bConfigurationValue;
148
149 /* Set default alternate for all interfaces */
150 for (int i = 0; i < desc->bNumInterfaces; i++) {
151 struct usbd_class_node *class;
152 int ret;
153
154 class = usbd_class_get_by_config(uds_ctx, speed, new_cfg, i);
155 if (class == NULL) {
156 return -ENODATA;
157 }
158
159 ret = usbd_interface_modify(uds_ctx, class, EP_OP_UP, i, 0);
160 if (ret) {
161 return ret;
162 }
163 }
164
165 return 0;
166 }
167
usbd_interface_set(struct usbd_context * const uds_ctx,const uint8_t iface,const uint8_t alt)168 int usbd_interface_set(struct usbd_context *const uds_ctx,
169 const uint8_t iface,
170 const uint8_t alt)
171 {
172 struct usbd_class_node *class;
173 uint8_t cur_alt;
174 int ret;
175
176 class = usbd_class_get_by_iface(uds_ctx, iface);
177 if (class == NULL) {
178 return -ENOENT;
179 }
180
181 ret = usbd_get_alt_value(uds_ctx, iface, &cur_alt);
182 if (ret) {
183 return ret;
184 }
185
186 LOG_INF("Set Interfaces %u, alternate %u -> %u", iface, cur_alt, alt);
187 if (alt == cur_alt) {
188 return 0;
189 }
190
191 /* Test if interface or interface alternate exist */
192 ret = usbd_interface_modify(uds_ctx, class, EP_OP_TEST, iface, alt);
193 if (ret) {
194 return -ENOENT;
195 }
196
197 /* Shutdown current interface alternate */
198 ret = usbd_interface_modify(uds_ctx, class, EP_OP_DOWN, iface, cur_alt);
199 if (ret) {
200 return ret;
201 }
202
203 /* Setup new interface alternate */
204 ret = usbd_interface_modify(uds_ctx, class, EP_OP_UP, iface, alt);
205 if (ret) {
206 /* TODO: rollback on error? */
207 return ret;
208 }
209
210 usbd_class_update(class->c_data, iface, alt);
211 usbd_set_alt_value(uds_ctx, iface, alt);
212
213 return 0;
214 }
215