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