| /* |
| * 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_class.h" |
| #include "usbd_class_api.h" |
| #include "usbd_endpoint.h" |
| #include "usbd_ch9.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(usbd_iface, CONFIG_USBD_LOG_LEVEL); |
| |
| enum ep_op { |
| EP_OP_TEST, /* Test if interface alternate available */ |
| EP_OP_UP, /* Enable endpoint and update endpoints bitmap */ |
| EP_OP_DOWN, /* Disable endpoint and update endpoints bitmap */ |
| }; |
| |
| static int handle_ep_op(struct usbd_context *const uds_ctx, |
| const enum ep_op op, |
| struct usb_ep_descriptor *const ed, |
| uint32_t *const ep_bm) |
| { |
| const uint8_t ep = ed->bEndpointAddress; |
| int ret = -ENOTSUP; |
| |
| switch (op) { |
| case EP_OP_TEST: |
| ret = 0; |
| break; |
| case EP_OP_UP: |
| ret = usbd_ep_enable(uds_ctx->dev, ed, ep_bm); |
| break; |
| case EP_OP_DOWN: |
| ret = usbd_ep_disable(uds_ctx->dev, ep, ep_bm); |
| break; |
| } |
| |
| if (ret) { |
| LOG_ERR("Failed to handle op %d, ep 0x%02x, bm 0x%08x, %d", |
| op, ep, *ep_bm, ret); |
| } |
| |
| return ret; |
| } |
| |
| static int usbd_interface_modify(struct usbd_context *const uds_ctx, |
| struct usbd_class_node *const c_nd, |
| const enum ep_op op, |
| const uint8_t iface, |
| const uint8_t alt) |
| { |
| struct usb_desc_header **dhp; |
| bool found_iface = false; |
| int ret; |
| |
| dhp = usbd_class_get_desc(c_nd->c_data, usbd_bus_speed(uds_ctx)); |
| if (dhp == NULL) { |
| return -EINVAL; |
| } |
| |
| while (*dhp != NULL && (*dhp)->bLength != 0) { |
| struct usb_if_descriptor *ifd; |
| struct usb_ep_descriptor *ed; |
| |
| if ((*dhp)->bDescriptorType == USB_DESC_INTERFACE) { |
| ifd = (struct usb_if_descriptor *)(*dhp); |
| |
| if (found_iface) { |
| break; |
| } |
| |
| if (ifd->bInterfaceNumber == iface && |
| ifd->bAlternateSetting == alt) { |
| found_iface = true; |
| LOG_DBG("Found interface %u %p", iface, c_nd); |
| if (ifd->bNumEndpoints == 0) { |
| LOG_INF("No endpoints, skip interface"); |
| break; |
| } |
| } |
| } |
| |
| if ((*dhp)->bDescriptorType == USB_DESC_ENDPOINT && found_iface) { |
| ed = (struct usb_ep_descriptor *)(*dhp); |
| ret = handle_ep_op(uds_ctx, op, ed, &c_nd->ep_active); |
| if (ret) { |
| return ret; |
| } |
| |
| LOG_INF("Modify interface %u ep 0x%02x by op %u ep_bm %x", |
| iface, ed->bEndpointAddress, |
| op, c_nd->ep_active); |
| } |
| |
| dhp++; |
| } |
| |
| /* TODO: rollback ep_bm on error? */ |
| |
| return found_iface ? 0 : -ENODATA; |
| } |
| |
| int usbd_interface_shutdown(struct usbd_context *const uds_ctx, |
| struct usbd_config_node *const cfg_nd) |
| { |
| struct usbd_class_node *c_nd; |
| |
| SYS_SLIST_FOR_EACH_CONTAINER(&cfg_nd->class_list, c_nd, node) { |
| uint32_t *ep_bm = &c_nd->ep_active; |
| |
| for (int idx = 1; idx < 16 && *ep_bm; idx++) { |
| uint8_t ep_in = USB_EP_DIR_IN | idx; |
| uint8_t ep_out = idx; |
| int ret; |
| |
| if (usbd_ep_bm_is_set(ep_bm, ep_in)) { |
| ret = usbd_ep_disable(uds_ctx->dev, ep_in, ep_bm); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| if (usbd_ep_bm_is_set(ep_bm, ep_out)) { |
| ret = usbd_ep_disable(uds_ctx->dev, ep_out, ep_bm); |
| if (ret) { |
| return ret; |
| } |
| } |
| } |
| } |
| |
| return 0; |
| } |
| |
| int usbd_interface_default(struct usbd_context *const uds_ctx, |
| const enum usbd_speed speed, |
| struct usbd_config_node *const cfg_nd) |
| { |
| struct usb_cfg_descriptor *desc = cfg_nd->desc; |
| const uint8_t new_cfg = desc->bConfigurationValue; |
| |
| /* Set default alternate for all interfaces */ |
| for (int i = 0; i < desc->bNumInterfaces; i++) { |
| struct usbd_class_node *class; |
| int ret; |
| |
| class = usbd_class_get_by_config(uds_ctx, speed, new_cfg, i); |
| if (class == NULL) { |
| return -ENODATA; |
| } |
| |
| ret = usbd_interface_modify(uds_ctx, class, EP_OP_UP, i, 0); |
| if (ret) { |
| return ret; |
| } |
| } |
| |
| return 0; |
| } |
| |
| int usbd_interface_set(struct usbd_context *const uds_ctx, |
| const uint8_t iface, |
| const uint8_t alt) |
| { |
| struct usbd_class_node *class; |
| uint8_t cur_alt; |
| int ret; |
| |
| class = usbd_class_get_by_iface(uds_ctx, iface); |
| if (class == NULL) { |
| return -ENOENT; |
| } |
| |
| ret = usbd_get_alt_value(uds_ctx, iface, &cur_alt); |
| if (ret) { |
| return ret; |
| } |
| |
| LOG_INF("Set Interfaces %u, alternate %u -> %u", iface, cur_alt, alt); |
| if (alt == cur_alt) { |
| return 0; |
| } |
| |
| /* Test if interface or interface alternate exist */ |
| ret = usbd_interface_modify(uds_ctx, class, EP_OP_TEST, iface, alt); |
| if (ret) { |
| return -ENOENT; |
| } |
| |
| /* Shutdown current interface alternate */ |
| ret = usbd_interface_modify(uds_ctx, class, EP_OP_DOWN, iface, cur_alt); |
| if (ret) { |
| return ret; |
| } |
| |
| /* Setup new interface alternate */ |
| ret = usbd_interface_modify(uds_ctx, class, EP_OP_UP, iface, alt); |
| if (ret) { |
| /* TODO: rollback on error? */ |
| return ret; |
| } |
| |
| usbd_class_update(class->c_data, iface, alt); |
| usbd_set_alt_value(uds_ctx, iface, alt); |
| |
| return 0; |
| } |