zephyr/subsys/usb/device_next/usbd_interface.c

215 lines
4.7 KiB
C
Raw Permalink Normal View History

/*
* 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)
{
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
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));
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
if (dhp == NULL) {
return -EINVAL;
}
while (*dhp != NULL && (*dhp)->bLength != 0) {
struct usb_if_descriptor *ifd;
struct usb_ep_descriptor *ed;
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
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;
}
}
}
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
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);
}
usb: device_next: make HS support compliant with the USB2.0 specification For specification-compliant high-speed support, we need to support device quilifiers and other-speed-configuration descriptor requests. We also need to store different configurations of the class/function descriptors, which typically only affect the endpoint descriptors. With this change, the stack expects class/function descriptors to be passed as an array of struct usb_desc_header pointers to e.g. interface, interface-specific, and endpoint descriptors, with the last element of the array pointing to a nil descriptor. And also passed for a specific speed, for now we support full and high speed configurations. During instantiation, the class/function implementation must choose the correct configuration in the full-speed and high-speed descriptor sets for values such as maximum packet size and bInterval values of interrupt and isochronous endpoints. During initialization, the stack reads the highest speed supported by the controller and uses it to get the appropriate descriptors set from the instance. If the controller supports only full speed, the stack configures the class/function descriptor for full speed only, if the controller supports high speed, the stack configures the descriptors for high speed only, and a class/function must update the full speed descriptor during the init callback processing. During device operation, the class/function implementation must check the actual speed of the device and use the correct configuration, such as the endpoint address or maximum packet size. Signed-off-by: Johann Fischer <johann.fischer@nordicsemi.no>
2024-01-02 17:10:06 +01:00
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;
}