/* * Copyright (c) 2021-2022 Nordic Semiconductor ASA * * SPDX-License-Identifier: Apache-2.0 */ #include #include #include #include "usbd_device.h" #include "usbd_config.h" #include "usbd_class.h" #include "usbd_class_api.h" #include "usbd_endpoint.h" #include 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; }