blob: f9f37c910ea886a4e05ced89871556196df578c1 [file] [log] [blame]
/*
* 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);
static sys_slist_t *usbd_configs(struct usbd_contex *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_contex *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_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_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_iter *iter;
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, iter, node) {
if (enable) {
usbd_class_enable(iter->c_nd);
} else {
usbd_class_disable(iter->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 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_contex *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_contex *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_contex *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_contex *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_contex *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;
}