283 lines
6.8 KiB
C
283 lines
6.8 KiB
C
/*
|
|
* Copyright (c) 2021-2022 Nordic Semiconductor ASA
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
#include <zephyr/sys/slist.h>
|
|
#include <zephyr/drivers/usb/udc.h>
|
|
#include <zephyr/usb/usbd.h>
|
|
|
|
#include "usbd_device.h"
|
|
#include "usbd_config.h"
|
|
#include "usbd_class.h"
|
|
#include "usbd_class_api.h"
|
|
#include "usbd_endpoint.h"
|
|
|
|
#include <zephyr/logging/log.h>
|
|
LOG_MODULE_REGISTER(usbd_init, CONFIG_USBD_LOG_LEVEL);
|
|
|
|
/* TODO: Allow to disable automatic assignment of endpoint features */
|
|
|
|
/* Assign endpoint address and update wMaxPacketSize */
|
|
static int assign_ep_addr(const struct device *dev,
|
|
struct usb_ep_descriptor *const ed,
|
|
uint32_t *const config_ep_bm,
|
|
uint32_t *const class_ep_bm)
|
|
{
|
|
int ret = -ENODEV;
|
|
|
|
for (unsigned int idx = 1; idx < 16U; idx++) {
|
|
uint16_t mps = ed->wMaxPacketSize;
|
|
uint8_t ep;
|
|
|
|
if (USB_EP_DIR_IS_IN(ed->bEndpointAddress)) {
|
|
ep = USB_EP_DIR_IN | idx;
|
|
} else {
|
|
ep = idx;
|
|
}
|
|
|
|
if (usbd_ep_bm_is_set(config_ep_bm, ep) ||
|
|
usbd_ep_bm_is_set(class_ep_bm, ep)) {
|
|
continue;
|
|
}
|
|
|
|
|
|
ret = udc_ep_try_config(dev, ep,
|
|
ed->bmAttributes, &mps,
|
|
ed->bInterval);
|
|
|
|
if (ret == 0) {
|
|
LOG_DBG("ep 0x%02x -> 0x%02x", ed->bEndpointAddress, ep);
|
|
ed->bEndpointAddress = ep;
|
|
ed->wMaxPacketSize = mps;
|
|
usbd_ep_bm_set(class_ep_bm, ed->bEndpointAddress);
|
|
usbd_ep_bm_set(config_ep_bm, ed->bEndpointAddress);
|
|
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Unassign all endpoint of a class instance based on class_ep_bm */
|
|
static int unassign_eps(struct usbd_contex *const uds_ctx,
|
|
uint32_t *const config_ep_bm,
|
|
uint32_t *const class_ep_bm)
|
|
{
|
|
for (unsigned int idx = 1; idx < 16U && *class_ep_bm; idx++) {
|
|
uint8_t ep_in = USB_EP_DIR_IN | idx;
|
|
uint8_t ep_out = idx;
|
|
|
|
if (usbd_ep_bm_is_set(class_ep_bm, ep_in)) {
|
|
if (!usbd_ep_bm_is_set(config_ep_bm, ep_in)) {
|
|
LOG_ERR("Endpoing 0x%02x not assigned", ep_in);
|
|
return -EINVAL;
|
|
}
|
|
|
|
usbd_ep_bm_clear(config_ep_bm, ep_in);
|
|
usbd_ep_bm_clear(class_ep_bm, ep_in);
|
|
}
|
|
|
|
if (usbd_ep_bm_is_set(class_ep_bm, ep_out)) {
|
|
if (!usbd_ep_bm_is_set(config_ep_bm, ep_out)) {
|
|
LOG_ERR("Endpoing 0x%02x not assigned", ep_out);
|
|
return -EINVAL;
|
|
}
|
|
|
|
usbd_ep_bm_clear(config_ep_bm, ep_out);
|
|
usbd_ep_bm_clear(class_ep_bm, ep_out);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Configure all interfaces and endpoints of a class instance
|
|
*
|
|
* The total number of interfaces is stored in the configuration descriptor's
|
|
* value bNumInterfaces. This value is reset at the beginning of configuration
|
|
* initialization and is increased according to the number of interfaces.
|
|
* The respective bInterfaceNumber must be assigned to all interfaces
|
|
* of a class instance.
|
|
*
|
|
* Like bInterfaceNumber the endpoint addresses must be assigned
|
|
* for all registered instances and respective endpoint descriptors.
|
|
* We use config_ep_bm variable as map for assigned endpoint for an
|
|
* USB device configuration.
|
|
*/
|
|
static int init_configuration_inst(struct usbd_contex *const uds_ctx,
|
|
struct usbd_class_data *const data,
|
|
uint32_t *const config_ep_bm,
|
|
uint8_t *const nif)
|
|
{
|
|
struct usb_desc_header *dh = data->desc;
|
|
uint8_t *ptr = (uint8_t *)dh;
|
|
struct usb_if_descriptor *ifd = NULL;
|
|
struct usb_ep_descriptor *ed;
|
|
uint32_t class_ep_bm = 0;
|
|
uint8_t tmp_nif;
|
|
int ret;
|
|
|
|
tmp_nif = *nif;
|
|
data->iface_bm = 0U;
|
|
data->ep_active = 0U;
|
|
|
|
while (dh->bLength != 0) {
|
|
|
|
if (dh->bDescriptorType == USB_DESC_INTERFACE) {
|
|
ifd = (struct usb_if_descriptor *)ptr;
|
|
|
|
data->ep_active |= class_ep_bm;
|
|
|
|
if (ifd->bAlternateSetting == 0) {
|
|
ifd->bInterfaceNumber = tmp_nif;
|
|
data->iface_bm |= BIT(tmp_nif);
|
|
tmp_nif++;
|
|
} else {
|
|
ifd->bInterfaceNumber = tmp_nif - 1;
|
|
/*
|
|
* Unassign endpoints from last alternate,
|
|
* to work properly it requires that the
|
|
* characteristics of endpoints in alternate
|
|
* interfaces are ascending.
|
|
*/
|
|
unassign_eps(uds_ctx, config_ep_bm, &class_ep_bm);
|
|
}
|
|
|
|
class_ep_bm = 0;
|
|
LOG_INF("interface %u alternate %u",
|
|
ifd->bInterfaceNumber, ifd->bAlternateSetting);
|
|
}
|
|
|
|
if (dh->bDescriptorType == USB_DESC_ENDPOINT) {
|
|
ed = (struct usb_ep_descriptor *)ptr;
|
|
ret = assign_ep_addr(uds_ctx->dev, ed,
|
|
config_ep_bm, &class_ep_bm);
|
|
if (ret) {
|
|
return ret;
|
|
}
|
|
|
|
LOG_INF("\tep 0x%02x interface ep-bm 0x%08x",
|
|
ed->bEndpointAddress, class_ep_bm);
|
|
}
|
|
|
|
ptr += dh->bLength;
|
|
dh = (struct usb_desc_header *)ptr;
|
|
}
|
|
|
|
if (tmp_nif <= *nif) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
*nif = tmp_nif;
|
|
data->ep_active |= class_ep_bm;
|
|
|
|
LOG_INF("Instance iface-bm 0x%08x ep-bm 0x%08x",
|
|
data->iface_bm, data->ep_active);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Initialize a device configuration
|
|
*
|
|
* Iterate on a list of all classes in a configuration
|
|
*/
|
|
static int init_configuration(struct usbd_contex *const uds_ctx,
|
|
struct usbd_config_node *const cfg_nd)
|
|
{
|
|
struct usb_cfg_descriptor *cfg_desc = cfg_nd->desc;
|
|
struct usbd_class_node *c_nd;
|
|
uint32_t config_ep_bm = 0;
|
|
size_t cfg_len = 0;
|
|
uint8_t nif = 0;
|
|
int ret;
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
|
|
|
|
ret = init_configuration_inst(uds_ctx, c_nd->data,
|
|
&config_ep_bm, &nif);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to assign endpoint addresses");
|
|
return ret;
|
|
}
|
|
|
|
ret = usbd_class_init(c_nd);
|
|
if (ret != 0) {
|
|
LOG_ERR("Failed to initialize class instance");
|
|
return ret;
|
|
}
|
|
|
|
LOG_INF("Init class node %p, descriptor length %zu",
|
|
c_nd, usbd_class_desc_len(c_nd));
|
|
cfg_len += usbd_class_desc_len(c_nd);
|
|
}
|
|
|
|
/* Update wTotalLength and bNumInterfaces of configuration descriptor */
|
|
sys_put_le16(sizeof(struct usb_cfg_descriptor) + cfg_len,
|
|
(uint8_t *)&cfg_desc->wTotalLength);
|
|
cfg_desc->bNumInterfaces = nif;
|
|
|
|
LOG_INF("bNumInterfaces %u wTotalLength %u",
|
|
cfg_desc->bNumInterfaces,
|
|
cfg_desc->wTotalLength);
|
|
|
|
/* Finally reset configuration's endpoint assignment */
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) {
|
|
c_nd->data->ep_assigned = c_nd->data->ep_active;
|
|
ret = unassign_eps(uds_ctx, &config_ep_bm, &c_nd->data->ep_active);
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void usbd_init_update_mps0(struct usbd_contex *const uds_ctx)
|
|
{
|
|
struct udc_device_caps caps = udc_caps(uds_ctx->dev);
|
|
struct usb_device_descriptor *desc = uds_ctx->desc;
|
|
|
|
switch (caps.mps0) {
|
|
case UDC_MPS0_8:
|
|
desc->bMaxPacketSize0 = 8;
|
|
break;
|
|
case UDC_MPS0_16:
|
|
desc->bMaxPacketSize0 = 16;
|
|
break;
|
|
case UDC_MPS0_32:
|
|
desc->bMaxPacketSize0 = 32;
|
|
break;
|
|
case UDC_MPS0_64:
|
|
desc->bMaxPacketSize0 = 64;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int usbd_init_configurations(struct usbd_contex *const uds_ctx)
|
|
{
|
|
struct usbd_config_node *cfg_nd;
|
|
|
|
usbd_init_update_mps0(uds_ctx);
|
|
|
|
SYS_SLIST_FOR_EACH_CONTAINER(&uds_ctx->configs, cfg_nd, node) {
|
|
int ret;
|
|
|
|
ret = init_configuration(uds_ctx, cfg_nd);
|
|
if (ret) {
|
|
LOG_ERR("Failed to init configuration %u",
|
|
usbd_config_get_value(cfg_nd));
|
|
return ret;
|
|
}
|
|
|
|
LOG_INF("bNumConfigurations %u",
|
|
usbd_get_num_configs(uds_ctx));
|
|
}
|
|
|
|
return 0;
|
|
}
|