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_config.h"
12 #include "usbd_interface.h"
13 #include "usbd_ch9.h"
14 #include "usbd_class_api.h"
15 
16 #include <zephyr/logging/log.h>
17 LOG_MODULE_REGISTER(usbd_cfg, CONFIG_USBD_LOG_LEVEL);
18 
usbd_config_get(struct usbd_contex * const uds_ctx,const uint8_t cfg)19 struct usbd_config_node *usbd_config_get(struct usbd_contex *const uds_ctx,
20 					 const uint8_t cfg)
21 {
22 	struct usbd_config_node *cfg_nd;
23 
24 	SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->configs, cfg_nd, node) {
25 		if (usbd_config_get_value(cfg_nd) == cfg) {
26 			return cfg_nd;
27 		}
28 	}
29 
30 	return NULL;
31 }
32 
33 struct usbd_config_node *
usbd_config_get_current(struct usbd_contex * const uds_ctx)34 usbd_config_get_current(struct usbd_contex *const uds_ctx)
35 {
36 	if (!usbd_state_is_configured(uds_ctx)) {
37 		LOG_INF("No configuration set (Address state?)");
38 		return NULL;
39 	}
40 
41 	return usbd_config_get(uds_ctx, usbd_get_config_value(uds_ctx));
42 }
43 
usbd_config_classes_enable(struct usbd_config_node * const cfg_nd,const bool enable)44 static void usbd_config_classes_enable(struct usbd_config_node *const cfg_nd,
45 				       const bool enable)
46 {
47 	struct usbd_class_node *c_nd;
48 
49 	SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
50 		if (enable) {
51 			usbd_class_enable(c_nd);
52 		} else {
53 			usbd_class_disable(c_nd);
54 		}
55 	}
56 }
57 
58 /* Reset configuration to addressed state, shutdown all endpoints */
usbd_config_reset(struct usbd_contex * const uds_ctx)59 static int usbd_config_reset(struct usbd_contex *const uds_ctx)
60 {
61 	struct usbd_config_node *cfg_nd;
62 	int ret = 0;
63 
64 	cfg_nd = usbd_config_get_current(uds_ctx);
65 	if (cfg_nd == NULL) {
66 		return -ENODATA;
67 	}
68 
69 	ret = usbd_interface_shutdown(uds_ctx, cfg_nd);
70 
71 	memset(&uds_ctx->ch9_data.alternate, 0,
72 	       USBD_NUMOF_INTERFACES_MAX);
73 
74 	usbd_set_config_value(uds_ctx, 0);
75 	usbd_config_classes_enable(cfg_nd, false);
76 
77 	return ret;
78 }
79 
usbd_config_exist(struct usbd_contex * const uds_ctx,const uint8_t cfg)80 bool usbd_config_exist(struct usbd_contex *const uds_ctx,
81 		       const uint8_t cfg)
82 {
83 	struct usbd_config_node *config;
84 
85 	config = usbd_config_get(uds_ctx, cfg);
86 
87 	return (config != NULL) ? true : false;
88 }
89 
usbd_config_set(struct usbd_contex * const uds_ctx,const uint8_t new_cfg)90 int usbd_config_set(struct usbd_contex *const uds_ctx,
91 		    const uint8_t new_cfg)
92 {
93 	struct usbd_config_node *cfg_nd;
94 	int ret;
95 
96 	if (usbd_get_config_value(uds_ctx) != 0) {
97 		ret = usbd_config_reset(uds_ctx);
98 		if (ret) {
99 			LOG_ERR("Failed to reset configuration");
100 			return ret;
101 		}
102 	}
103 
104 	if (new_cfg == 0) {
105 		usbd_set_config_value(uds_ctx, new_cfg);
106 		return 0;
107 	}
108 
109 	cfg_nd = usbd_config_get(uds_ctx, new_cfg);
110 	if (cfg_nd == NULL) {
111 		return -ENODATA;
112 	}
113 
114 	ret = usbd_interface_default(uds_ctx, cfg_nd);
115 	if (ret) {
116 		return ret;
117 	}
118 
119 	usbd_set_config_value(uds_ctx, new_cfg);
120 	usbd_config_classes_enable(cfg_nd, true);
121 
122 	return 0;
123 }
124 
125 /*
126  * All the functions below are part of public USB device support API.
127  */
128 
usbd_config_attrib_rwup(struct usbd_contex * const uds_ctx,const uint8_t cfg,const bool enable)129 int usbd_config_attrib_rwup(struct usbd_contex *const uds_ctx,
130 			    const uint8_t cfg, const bool enable)
131 {
132 	struct usbd_config_node *cfg_nd;
133 	struct usb_cfg_descriptor *desc;
134 	struct udc_device_caps caps;
135 	int ret = 0;
136 
137 	usbd_device_lock(uds_ctx);
138 
139 	if (usbd_is_enabled(uds_ctx)) {
140 		ret = -EALREADY;
141 		goto attrib_rwup_exit;
142 	}
143 
144 	caps = udc_caps(uds_ctx->dev);
145 	if (!caps.rwup) {
146 		LOG_ERR("Feature not supported by controller");
147 		ret = -ENOTSUP;
148 		goto attrib_rwup_exit;
149 	}
150 
151 	cfg_nd = usbd_config_get(uds_ctx, cfg);
152 	if (cfg_nd == NULL) {
153 		LOG_INF("Configuration %u not found", cfg);
154 		ret = -ENODATA;
155 		goto attrib_rwup_exit;
156 	}
157 
158 	desc = cfg_nd->desc;
159 	if (enable) {
160 		desc->bmAttributes |= USB_SCD_REMOTE_WAKEUP;
161 	} else {
162 		desc->bmAttributes &= ~USB_SCD_REMOTE_WAKEUP;
163 	}
164 
165 attrib_rwup_exit:
166 	usbd_device_unlock(uds_ctx);
167 	return ret;
168 }
169 
usbd_config_attrib_self(struct usbd_contex * const uds_ctx,const uint8_t cfg,const bool enable)170 int usbd_config_attrib_self(struct usbd_contex *const uds_ctx,
171 			    const uint8_t cfg, const bool enable)
172 {
173 	struct usbd_config_node *cfg_nd;
174 	struct usb_cfg_descriptor *desc;
175 	int ret = 0;
176 
177 	usbd_device_lock(uds_ctx);
178 
179 	if (usbd_is_enabled(uds_ctx)) {
180 		ret = -EALREADY;
181 		goto attrib_self_exit;
182 	}
183 
184 	cfg_nd = usbd_config_get(uds_ctx, cfg);
185 	if (cfg_nd == NULL) {
186 		LOG_INF("Configuration %u not found", cfg);
187 		ret = -ENODATA;
188 		goto attrib_self_exit;
189 	}
190 
191 	desc = cfg_nd->desc;
192 	if (enable) {
193 		desc->bmAttributes |= USB_SCD_SELF_POWERED;
194 	} else {
195 		desc->bmAttributes &= ~USB_SCD_SELF_POWERED;
196 	}
197 
198 attrib_self_exit:
199 	usbd_device_unlock(uds_ctx);
200 	return ret;
201 }
202 
usbd_config_maxpower(struct usbd_contex * const uds_ctx,const uint8_t cfg,const uint8_t power)203 int usbd_config_maxpower(struct usbd_contex *const uds_ctx,
204 			 const uint8_t cfg, const uint8_t power)
205 {
206 	struct usbd_config_node *cfg_nd;
207 	struct usb_cfg_descriptor *desc;
208 	int ret = 0;
209 
210 	usbd_device_lock(uds_ctx);
211 
212 	if (usbd_is_enabled(uds_ctx)) {
213 		ret = -EALREADY;
214 		goto maxpower_exit;
215 	}
216 
217 	cfg_nd = usbd_config_get(uds_ctx, cfg);
218 	if (cfg_nd == NULL) {
219 		LOG_INF("Configuration %u not found", cfg);
220 		ret = -ENODATA;
221 		goto maxpower_exit;
222 	}
223 
224 	desc = cfg_nd->desc;
225 	desc->bMaxPower = power;
226 
227 maxpower_exit:
228 	usbd_device_unlock(uds_ctx);
229 	return ret;
230 }
231 
usbd_add_configuration(struct usbd_contex * const uds_ctx,struct usbd_config_node * const cfg_nd)232 int usbd_add_configuration(struct usbd_contex *const uds_ctx,
233 			   struct usbd_config_node *const cfg_nd)
234 {
235 	struct usb_cfg_descriptor *desc = cfg_nd->desc;
236 	int ret = 0;
237 
238 	usbd_device_lock(uds_ctx);
239 
240 	if (usbd_is_initialized(uds_ctx)) {
241 		LOG_ERR("USB device support is initialized");
242 		ret = -EBUSY;
243 		goto add_configuration_exit;
244 	}
245 
246 	if (desc->bmAttributes & USB_SCD_REMOTE_WAKEUP) {
247 		struct udc_device_caps caps = udc_caps(uds_ctx->dev);
248 
249 		if (!caps.rwup) {
250 			LOG_ERR("Feature not supported by controller");
251 			ret = -ENOTSUP;
252 			goto add_configuration_exit;
253 		}
254 	}
255 
256 	if (sys_slist_find_and_remove(&uds_ctx->configs, &cfg_nd->node)) {
257 		LOG_WRN("Configuration %u re-inserted",
258 			usbd_config_get_value(cfg_nd));
259 	} else {
260 		usbd_config_set_value(cfg_nd, usbd_get_num_configs(uds_ctx) + 1);
261 		usbd_set_num_configs(uds_ctx, usbd_get_num_configs(uds_ctx) + 1);
262 	}
263 
264 	sys_slist_append(&uds_ctx->configs, &cfg_nd->node);
265 
266 	usbd_device_unlock(uds_ctx);
267 
268 add_configuration_exit:
269 	usbd_device_unlock(uds_ctx);
270 	return ret;
271 }
272