| /* |
| * Copyright (c) 2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/drivers/usb/udc.h> |
| #include <zephyr/usb/usbd.h> |
| |
| #include "usbd_device.h" |
| #include "usbd_config.h" |
| #include "usbd_interface.h" |
| #include "usbd_ch9.h" |
| #include "usbd_class_api.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(usbd_cfg, CONFIG_USBD_LOG_LEVEL); |
| |
| struct usbd_config_node *usbd_config_get(struct usbd_contex *const uds_ctx, |
| const uint8_t cfg) |
| { |
| struct usbd_config_node *cfg_nd; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->configs, 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_contex *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_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); |
| } else { |
| usbd_class_disable(c_nd); |
| } |
| } |
| } |
| |
| /* Reset configuration to addressed state, shutdown all endpoints */ |
| static int usbd_config_reset(struct usbd_contex *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_contex *const uds_ctx, |
| const uint8_t cfg) |
| { |
| struct usbd_config_node *config; |
| |
| config = usbd_config_get(uds_ctx, cfg); |
| |
| return (config != NULL) ? true : false; |
| } |
| |
| int usbd_config_set(struct usbd_contex *const uds_ctx, |
| const uint8_t new_cfg) |
| { |
| struct usbd_config_node *cfg_nd; |
| 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, new_cfg); |
| if (cfg_nd == NULL) { |
| return -ENODATA; |
| } |
| |
| ret = usbd_interface_default(uds_ctx, 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_contex *const uds_ctx, |
| 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, 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_contex *const uds_ctx, |
| 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, 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_contex *const uds_ctx, |
| 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, 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_contex *const uds_ctx, |
| struct usbd_config_node *const cfg_nd) |
| { |
| struct usb_cfg_descriptor *desc = cfg_nd->desc; |
| 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 (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; |
| } |
| } |
| |
| if (sys_slist_find_and_remove(&uds_ctx->configs, &cfg_nd->node)) { |
| LOG_WRN("Configuration %u re-inserted", |
| usbd_config_get_value(cfg_nd)); |
| } else { |
| usbd_config_set_value(cfg_nd, usbd_get_num_configs(uds_ctx) + 1); |
| usbd_set_num_configs(uds_ctx, usbd_get_num_configs(uds_ctx) + 1); |
| } |
| |
| sys_slist_append(&uds_ctx->configs, &cfg_nd->node); |
| |
| usbd_device_unlock(uds_ctx); |
| |
| add_configuration_exit: |
| usbd_device_unlock(uds_ctx); |
| return ret; |
| } |