200 lines
5.0 KiB
C
200 lines
5.0 KiB
C
/*
|
|
* Copyright (c) 2017 PHYTEC Messtechnik GmbH
|
|
* Copyright (c) 2017, 2018 Intel Corporation
|
|
* Copyright (c) 2022 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <string.h>
|
|
#include <zephyr/sys/byteorder.h>
|
|
#include <zephyr/usb/usbd.h>
|
|
|
|
#include "usbd_desc.h"
|
|
#include "usbd_device.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(usbd_desc, CONFIG_USBD_LOG_LEVEL);
|
|
|
|
static inline bool desc_type_equal(const struct usbd_desc_node *const a,
|
|
const struct usbd_desc_node *const b)
|
|
{
|
|
return a->bDescriptorType == b->bDescriptorType;
|
|
}
|
|
|
|
/*
|
|
* Add descriptor node to the descriptor list in ascending order by index
|
|
* and sorted by bDescriptorType. For the string descriptors, the function
|
|
* does not care about index zero for the language string descriptor,
|
|
* so if it is not added first, the device will be non-compliant.
|
|
*/
|
|
static int desc_add_and_update_idx(struct usbd_context *const uds_ctx,
|
|
struct usbd_desc_node *const new_nd)
|
|
{
|
|
struct usbd_desc_node *tmp_nd;
|
|
|
|
SYS_DLIST_FOR_EACH_CONTAINER(&uds_ctx->descriptors, tmp_nd, node) {
|
|
struct usbd_desc_node *next_nd;
|
|
|
|
if (!desc_type_equal(tmp_nd, new_nd)) {
|
|
continue;
|
|
}
|
|
|
|
next_nd = SYS_DLIST_PEEK_NEXT_CONTAINER(&uds_ctx->descriptors,
|
|
tmp_nd,
|
|
node);
|
|
|
|
if (next_nd == NULL) {
|
|
/* Last node of the same bDescriptorType or tail */
|
|
new_nd->str.idx = tmp_nd->str.idx + 1;
|
|
sys_dlist_append(&uds_ctx->descriptors, &new_nd->node);
|
|
LOG_DBG("Add %u behind %u", new_nd->str.idx, tmp_nd->str.idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (!desc_type_equal(next_nd, new_nd)) {
|
|
/* Last node of the same bDescriptorType */
|
|
new_nd->str.idx = tmp_nd->str.idx + 1;
|
|
sys_dlist_insert(&next_nd->node, &new_nd->node);
|
|
LOG_DBG("Add %u before %u", new_nd->str.idx, next_nd->str.idx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
if (tmp_nd->str.idx != (next_nd->str.idx - 1)) {
|
|
/* Add between nodes of the same bDescriptorType */
|
|
new_nd->str.idx = tmp_nd->str.idx + 1;
|
|
sys_dlist_insert(&next_nd->node, &new_nd->node);
|
|
LOG_DBG("Add %u between %u and %u",
|
|
tmp_nd->str.idx, next_nd->str.idx, new_nd->str.idx);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
/* If there are none of same bDescriptorType, node idx is set to 0. */
|
|
new_nd->str.idx = 0;
|
|
sys_dlist_append(&uds_ctx->descriptors, &new_nd->node);
|
|
LOG_DBG("Added first descriptor node (usage type %u)", new_nd->str.utype);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct usbd_desc_node *usbd_get_descriptor(struct usbd_context *const uds_ctx,
|
|
const uint8_t type, const uint8_t idx)
|
|
{
|
|
struct usbd_desc_node *desc_nd;
|
|
|
|
SYS_DLIST_FOR_EACH_CONTAINER(&uds_ctx->descriptors, desc_nd, node) {
|
|
if (desc_nd->bDescriptorType == type) {
|
|
if (desc_nd->bDescriptorType == USB_DESC_STRING) {
|
|
if (desc_nd->str.idx == idx) {
|
|
return desc_nd;
|
|
}
|
|
}
|
|
|
|
if (desc_nd->bDescriptorType == USB_DESC_BOS) {
|
|
return desc_nd;
|
|
}
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
int usbd_desc_remove_all(struct usbd_context *const uds_ctx)
|
|
{
|
|
struct usbd_desc_node *tmp;
|
|
sys_dnode_t *node;
|
|
|
|
while ((node = sys_dlist_get(&uds_ctx->descriptors))) {
|
|
tmp = CONTAINER_OF(node, struct usbd_desc_node, node);
|
|
LOG_DBG("Remove descriptor node %p type %u",
|
|
(void *)tmp, tmp->str.utype);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int usbd_add_descriptor(struct usbd_context *const uds_ctx,
|
|
struct usbd_desc_node *const desc_nd)
|
|
{
|
|
struct usb_device_descriptor *hs_desc, *fs_desc;
|
|
int ret = 0;
|
|
|
|
usbd_device_lock(uds_ctx);
|
|
|
|
hs_desc = uds_ctx->hs_desc;
|
|
fs_desc = uds_ctx->fs_desc;
|
|
if (!fs_desc || !hs_desc || usbd_is_initialized(uds_ctx)) {
|
|
ret = -EPERM;
|
|
goto add_descriptor_error;
|
|
}
|
|
|
|
/* Check if descriptor list is initialized */
|
|
if (!sys_dnode_is_linked(&uds_ctx->descriptors)) {
|
|
LOG_DBG("Initialize descriptors list");
|
|
sys_dlist_init(&uds_ctx->descriptors);
|
|
}
|
|
|
|
if (sys_dnode_is_linked(&desc_nd->node)) {
|
|
ret = -EALREADY;
|
|
goto add_descriptor_error;
|
|
}
|
|
|
|
if (desc_nd->bDescriptorType == USB_DESC_BOS) {
|
|
sys_dlist_append(&uds_ctx->descriptors, &desc_nd->node);
|
|
}
|
|
|
|
if (desc_nd->bDescriptorType == USB_DESC_STRING) {
|
|
ret = desc_add_and_update_idx(uds_ctx, desc_nd);
|
|
if (ret) {
|
|
ret = -EINVAL;
|
|
goto add_descriptor_error;
|
|
}
|
|
|
|
switch (desc_nd->str.utype) {
|
|
case USBD_DUT_STRING_LANG:
|
|
break;
|
|
case USBD_DUT_STRING_MANUFACTURER:
|
|
hs_desc->iManufacturer = desc_nd->str.idx;
|
|
fs_desc->iManufacturer = desc_nd->str.idx;
|
|
break;
|
|
case USBD_DUT_STRING_PRODUCT:
|
|
hs_desc->iProduct = desc_nd->str.idx;
|
|
fs_desc->iProduct = desc_nd->str.idx;
|
|
break;
|
|
case USBD_DUT_STRING_SERIAL_NUMBER:
|
|
hs_desc->iSerialNumber = desc_nd->str.idx;
|
|
fs_desc->iSerialNumber = desc_nd->str.idx;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
add_descriptor_error:
|
|
usbd_device_unlock(uds_ctx);
|
|
return ret;
|
|
}
|
|
|
|
uint8_t usbd_str_desc_get_idx(const struct usbd_desc_node *const desc_nd)
|
|
{
|
|
if (sys_dnode_is_linked(&desc_nd->node)) {
|
|
return desc_nd->str.idx;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void usbd_remove_descriptor(struct usbd_desc_node *const desc_nd)
|
|
{
|
|
if (sys_dnode_is_linked(&desc_nd->node)) {
|
|
sys_dlist_remove(&desc_nd->node);
|
|
|
|
if (desc_nd->bDescriptorType == USB_DESC_STRING) {
|
|
desc_nd->str.idx = 0U;
|
|
}
|
|
}
|
|
}
|