/* * Copyright (c) 2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include "usbd_device.h" #include "usbd_config.h" #include "usbd_interface.h" #include "usbd_ch9.h" #include "usbd_class_api.h" #include LOG_MODULE_REGISTER(usbd_cfg, CONFIG_USBD_LOG_LEVEL); static sys_slist_t *usbd_configs(struct usbd_context *uds_ctx, const enum usbd_speed speed) { switch (speed) { case USBD_SPEED_FS: return &uds_ctx->fs_configs; case USBD_SPEED_HS: return &uds_ctx->hs_configs; default: return NULL; } } struct usbd_config_node *usbd_config_get(struct usbd_context *const uds_ctx, const enum usbd_speed speed, const uint8_t cfg) { struct usbd_config_node *cfg_nd; SYS_SLIST_FOR_EACH_CONTAINER(usbd_configs(uds_ctx, speed), cfg_nd, node) { if (usbd_config_get_value(cfg_nd) == cfg) { return cfg_nd; } } return NULL; } struct usbd_config_node * usbd_config_get_current(struct usbd_context *const uds_ctx) { if (!usbd_state_is_configured(uds_ctx)) { LOG_INF("No configuration set (Address state?)"); return NULL; } return usbd_config_get(uds_ctx, usbd_bus_speed(uds_ctx), usbd_get_config_value(uds_ctx)); } static void usbd_config_classes_enable(struct usbd_config_node *const cfg_nd, const bool enable) { struct usbd_class_node *c_nd; SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) { if (enable) { usbd_class_enable(c_nd->c_data); } else { usbd_class_disable(c_nd->c_data); } } } /* Reset configuration to addressed state, shutdown all endpoints */ static int usbd_config_reset(struct usbd_context *const uds_ctx) { struct usbd_config_node *cfg_nd; int ret = 0; cfg_nd = usbd_config_get_current(uds_ctx); if (cfg_nd == NULL) { return -ENODATA; } ret = usbd_interface_shutdown(uds_ctx, cfg_nd); memset(&uds_ctx->ch9_data.alternate, 0, USBD_NUMOF_INTERFACES_MAX); usbd_set_config_value(uds_ctx, 0); usbd_config_classes_enable(cfg_nd, false); return ret; } bool usbd_config_exist(struct usbd_context *const uds_ctx, const enum usbd_speed speed, const uint8_t cfg) { struct usbd_config_node *config; config = usbd_config_get(uds_ctx, speed, cfg); return (config != NULL) ? true : false; } int usbd_config_set(struct usbd_context *const uds_ctx, const uint8_t new_cfg) { struct usbd_config_node *cfg_nd; const enum usbd_speed speed = usbd_bus_speed(uds_ctx); int ret; if (usbd_get_config_value(uds_ctx) != 0) { ret = usbd_config_reset(uds_ctx); if (ret) { LOG_ERR("Failed to reset configuration"); return ret; } } if (new_cfg == 0) { usbd_set_config_value(uds_ctx, new_cfg); return 0; } cfg_nd = usbd_config_get(uds_ctx, speed, new_cfg); if (cfg_nd == NULL) { return -ENODATA; } ret = usbd_interface_default(uds_ctx, speed, cfg_nd); if (ret) { return ret; } usbd_set_config_value(uds_ctx, new_cfg); usbd_config_classes_enable(cfg_nd, true); return 0; } /* * All the functions below are part of public USB device support API. */ int usbd_config_attrib_rwup(struct usbd_context *const uds_ctx, const enum usbd_speed speed, const uint8_t cfg, const bool enable) { struct usbd_config_node *cfg_nd; struct usb_cfg_descriptor *desc; struct udc_device_caps caps; int ret = 0; usbd_device_lock(uds_ctx); if (usbd_is_enabled(uds_ctx)) { ret = -EALREADY; goto attrib_rwup_exit; } caps = udc_caps(uds_ctx->dev); if (!caps.rwup) { LOG_ERR("Feature not supported by controller"); ret = -ENOTSUP; goto attrib_rwup_exit; } cfg_nd = usbd_config_get(uds_ctx, speed, cfg); if (cfg_nd == NULL) { LOG_INF("Configuration %u not found", cfg); ret = -ENODATA; goto attrib_rwup_exit; } desc = cfg_nd->desc; if (enable) { desc->bmAttributes |= USB_SCD_REMOTE_WAKEUP; } else { desc->bmAttributes &= ~USB_SCD_REMOTE_WAKEUP; } attrib_rwup_exit: usbd_device_unlock(uds_ctx); return ret; } int usbd_config_attrib_self(struct usbd_context *const uds_ctx, const enum usbd_speed speed, const uint8_t cfg, const bool enable) { struct usbd_config_node *cfg_nd; struct usb_cfg_descriptor *desc; int ret = 0; usbd_device_lock(uds_ctx); if (usbd_is_enabled(uds_ctx)) { ret = -EALREADY; goto attrib_self_exit; } cfg_nd = usbd_config_get(uds_ctx, speed, cfg); if (cfg_nd == NULL) { LOG_INF("Configuration %u not found", cfg); ret = -ENODATA; goto attrib_self_exit; } desc = cfg_nd->desc; if (enable) { desc->bmAttributes |= USB_SCD_SELF_POWERED; } else { desc->bmAttributes &= ~USB_SCD_SELF_POWERED; } attrib_self_exit: usbd_device_unlock(uds_ctx); return ret; } int usbd_config_maxpower(struct usbd_context *const uds_ctx, const enum usbd_speed speed, const uint8_t cfg, const uint8_t power) { struct usbd_config_node *cfg_nd; struct usb_cfg_descriptor *desc; int ret = 0; usbd_device_lock(uds_ctx); if (usbd_is_enabled(uds_ctx)) { ret = -EALREADY; goto maxpower_exit; } cfg_nd = usbd_config_get(uds_ctx, speed, cfg); if (cfg_nd == NULL) { LOG_INF("Configuration %u not found", cfg); ret = -ENODATA; goto maxpower_exit; } desc = cfg_nd->desc; desc->bMaxPower = power; maxpower_exit: usbd_device_unlock(uds_ctx); return ret; } int usbd_add_configuration(struct usbd_context *const uds_ctx, const enum usbd_speed speed, struct usbd_config_node *const cfg_nd) { struct usb_cfg_descriptor *desc = cfg_nd->desc; sys_slist_t *configs; sys_snode_t *node; int ret = 0; usbd_device_lock(uds_ctx); if (usbd_is_initialized(uds_ctx)) { LOG_ERR("USB device support is initialized"); ret = -EBUSY; goto add_configuration_exit; } if (speed == USBD_SPEED_HS && usbd_caps_speed(uds_ctx) == USBD_SPEED_FS) { LOG_ERR("Controller doesn't support HS"); ret = -ENOTSUP; goto add_configuration_exit; } if (desc->bmAttributes & USB_SCD_REMOTE_WAKEUP) { struct udc_device_caps caps = udc_caps(uds_ctx->dev); if (!caps.rwup) { LOG_ERR("Feature not supported by controller"); ret = -ENOTSUP; goto add_configuration_exit; } } configs = usbd_configs(uds_ctx, speed); switch (speed) { case USBD_SPEED_HS: SYS_SLIST_FOR_EACH_NODE(&uds_ctx->fs_configs, node) { if (node == &cfg_nd->node) { LOG_ERR("HS config already on FS list"); ret = -EINVAL; goto add_configuration_exit; } } break; case USBD_SPEED_FS: SYS_SLIST_FOR_EACH_NODE(&uds_ctx->hs_configs, node) { if (node == &cfg_nd->node) { LOG_ERR("FS config already on HS list"); ret = -EINVAL; goto add_configuration_exit; } } break; default: LOG_ERR("Unsupported configuration speed"); ret = -ENOTSUP; goto add_configuration_exit; } if (sys_slist_find_and_remove(configs, &cfg_nd->node)) { LOG_WRN("Configuration %u re-inserted", usbd_config_get_value(cfg_nd)); } else { uint8_t num = usbd_get_num_configs(uds_ctx, speed) + 1; usbd_config_set_value(cfg_nd, num); usbd_set_num_configs(uds_ctx, speed, num); } sys_slist_append(configs, &cfg_nd->node); usbd_device_unlock(uds_ctx); add_configuration_exit: usbd_device_unlock(uds_ctx); return ret; }