blob: 8b1df8a7ac19c587ac2e20c7544f68e22be40b3d [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/usb/usbd.h>
#include <zephyr/toolchain/common.h>
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/sys/slist.h>
#include "usbd_device.h"
#include "usbd_class_api.h"
#include "usbd_config.h"
#include "usbd_endpoint.h"
#include "usbd_ch9.h"
#include <zephyr/logging/log.h>
#if IS_ENABLED(CONFIG_USBD_LOG_LEVEL)
#define USBD_CLASS_LOG_LEVEL CONFIG_USBD_LOG_LEVEL
#else
#define USBD_CLASS_LOG_LEVEL LOG_LEVEL_NONE
#endif
LOG_MODULE_REGISTER(usbd_class, CONFIG_USBD_LOG_LEVEL);
size_t usbd_class_desc_len(struct usbd_class_node *const c_nd)
{
struct usbd_class_data *data = c_nd->data;
struct usb_desc_header *dh;
uint8_t *ptr;
size_t len = 0;
if (data->desc != NULL) {
dh = data->desc;
ptr = (uint8_t *)dh;
while (dh->bLength != 0) {
len += dh->bLength;
ptr += dh->bLength;
dh = (struct usb_desc_header *)ptr;
}
}
return len;
}
struct usbd_class_node *
usbd_class_get_by_config(struct usbd_contex *const uds_ctx,
const uint8_t cnum,
const uint8_t inum)
{
struct usbd_class_node *c_nd;
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get(uds_ctx, cnum);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->data->iface_bm & BIT(inum)) {
return c_nd;
}
}
return NULL;
}
struct usbd_class_node *
usbd_class_get_by_iface(struct usbd_contex *const uds_ctx,
const uint8_t inum)
{
struct usbd_class_node *c_nd;
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get_current(uds_ctx);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->data->iface_bm & BIT(inum)) {
return c_nd;
}
}
return NULL;
}
static bool xfer_owner_exist(struct usbd_contex *const uds_ctx,
struct usbd_config_node *const cfg_nd,
struct net_buf *const buf)
{
struct udc_buf_info *bi = udc_get_buf_info(buf);
struct usbd_class_node *c_nd;
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (bi->owner == c_nd) {
uint32_t ep_active = c_nd->data->ep_active;
uint32_t ep_assigned = c_nd->data->ep_assigned;
if (!usbd_ep_bm_is_set(&ep_active, bi->ep)) {
LOG_DBG("ep 0x%02x is not active", bi->ep);
}
if (!usbd_ep_bm_is_set(&ep_assigned, bi->ep)) {
LOG_DBG("ep 0x%02x is not assigned", bi->ep);
}
return true;
}
}
return false;
}
int usbd_class_handle_xfer(struct usbd_contex *const uds_ctx,
struct net_buf *const buf,
const int err)
{
struct udc_buf_info *bi = udc_get_buf_info(buf);
if (unlikely(USBD_CLASS_LOG_LEVEL == LOG_LEVEL_DBG)) {
struct usbd_config_node *cfg_nd;
if (usbd_state_is_configured(uds_ctx)) {
cfg_nd = usbd_config_get_current(uds_ctx);
if (xfer_owner_exist(uds_ctx, cfg_nd, buf)) {
return usbd_class_request(bi->owner, buf, err);
}
}
SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->configs, cfg_nd, node) {
if (xfer_owner_exist(uds_ctx, cfg_nd, buf)) {
return usbd_class_request(bi->owner, buf, err);
}
}
return -ENODATA;
}
return usbd_class_request(bi->owner, buf, err);
}
struct usbd_class_node *
usbd_class_get_by_ep(struct usbd_contex *const uds_ctx,
const uint8_t ep)
{
struct usbd_class_node *c_nd;
struct usbd_config_node *cfg_nd;
uint8_t ep_idx = USB_EP_GET_IDX(ep);
uint8_t cfg;
uint32_t ep_bm;
if (USB_EP_DIR_IS_IN(ep)) {
ep_bm = BIT(ep_idx + 16);
} else {
ep_bm = BIT(ep_idx);
}
if (!usbd_state_is_configured(uds_ctx)) {
LOG_ERR("No configuration set (Address state)");
return NULL;
}
cfg = usbd_get_config_value(uds_ctx);
cfg_nd = usbd_config_get(uds_ctx, cfg);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->data->ep_assigned & ep_bm) {
return c_nd;
}
}
return NULL;
}
struct usbd_class_node *
usbd_class_get_by_req(struct usbd_contex *const uds_ctx,
const uint8_t request)
{
struct usbd_config_node *cfg_nd;
struct usbd_class_node *c_nd;
cfg_nd = usbd_config_get_current(uds_ctx);
if (cfg_nd == NULL) {
return NULL;
}
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
if (c_nd->data->v_reqs == NULL) {
continue;
}
for (int i = 0; i < c_nd->data->v_reqs->len; i++) {
/*
* First instance always wins.
* There is no other way to determine the recipient.
*/
if (c_nd->data->v_reqs->reqs[i] == request) {
return c_nd;
}
}
}
return NULL;
}
static struct usbd_class_node *usbd_class_node_get(const char *name)
{
STRUCT_SECTION_FOREACH(usbd_class_node, c_nd) {
if (strcmp(name, c_nd->name) == 0) {
return c_nd;
}
}
LOG_ERR("USB device class %s not found", name);
return NULL;
}
static int usbd_class_append(struct usbd_contex *const uds_ctx,
struct usbd_class_node *const c_nd,
const uint8_t cfg)
{
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get(uds_ctx, cfg);
if (cfg_nd == NULL) {
return -ENODATA;
}
sys_slist_append(&cfg_nd->class_list, &c_nd->node);
return 0;
}
static int usbd_class_remove(struct usbd_contex *const uds_ctx,
struct usbd_class_node *const c_nd,
const uint8_t cfg)
{
struct usbd_config_node *cfg_nd;
cfg_nd = usbd_config_get(uds_ctx, cfg);
if (cfg_nd == NULL) {
return -ENODATA;
}
if (!sys_slist_find_and_remove(&cfg_nd->class_list, &c_nd->node)) {
return -ENODATA;
}
return 0;
}
/*
* All the functions below are part of public USB device support API.
*/
int usbd_register_class(struct usbd_contex *const uds_ctx,
const char *name,
const uint8_t cfg)
{
struct usbd_class_node *c_nd;
struct usbd_class_data *data;
int ret;
c_nd = usbd_class_node_get(name);
if (c_nd == NULL) {
return -ENODEV;
}
usbd_device_lock(uds_ctx);
if (usbd_is_initialized(uds_ctx)) {
LOG_ERR("USB device support is initialized");
ret = -EBUSY;
goto register_class_error;
}
data = c_nd->data;
if (data->desc == NULL) {
ret = -EINVAL;
goto register_class_error;
}
/* TODO: does it still need to be atomic ? */
if (atomic_test_bit(&data->state, USBD_CCTX_REGISTERED)) {
LOG_WRN("Class instance allready registered");
ret = -EBUSY;
goto register_class_error;
}
ret = usbd_class_append(uds_ctx, c_nd, cfg);
if (ret == 0) {
/* Initialize pointer back to the device struct */
atomic_set_bit(&data->state, USBD_CCTX_REGISTERED);
data->uds_ctx = uds_ctx;
}
register_class_error:
usbd_device_unlock(uds_ctx);
return ret;
}
int usbd_unregister_class(struct usbd_contex *const uds_ctx,
const char *name,
const uint8_t cfg)
{
struct usbd_class_node *c_nd;
struct usbd_class_data *data;
int ret;
c_nd = usbd_class_node_get(name);
if (c_nd == NULL) {
return -ENODEV;
}
usbd_device_lock(uds_ctx);
if (usbd_is_initialized(uds_ctx)) {
LOG_ERR("USB device support is initialized");
ret = -EBUSY;
goto unregister_class_error;
}
data = c_nd->data;
/* TODO: does it still need to be atomic ? */
if (!atomic_test_bit(&data->state, USBD_CCTX_REGISTERED)) {
LOG_WRN("Class instance not registered");
ret = -EBUSY;
goto unregister_class_error;
}
ret = usbd_class_remove(uds_ctx, c_nd, cfg);
if (ret == 0) {
atomic_clear_bit(&data->state, USBD_CCTX_REGISTERED);
data->uds_ctx = NULL;
}
unregister_class_error:
usbd_device_unlock(uds_ctx);
return ret;
}