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