blob: f8c97c15baa77e64950a5ba547cb9fec0b503968 [file] [log] [blame]
/*
* Copyright (c) 2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief New experimental USB device stack APIs and structures
*
* This file contains the USB device stack APIs and structures.
*/
#ifndef ZEPHYR_INCLUDE_USBD_H_
#define ZEPHYR_INCLUDE_USBD_H_
#include <zephyr/device.h>
#include <zephyr/usb/usb_ch9.h>
#include <zephyr/net/buf.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/slist.h>
#include <zephyr/logging/log.h>
#ifdef __cplusplus
extern "C" {
#endif
/*
* The USB Unicode bString is encoded in UTF16LE, which means it takes up
* twice the amount of bytes than the same string encoded in ASCII7.
* Use this macro to determine the length of the bString array.
*
* bString length without null character:
* bString_length = (sizeof(initializer_string) - 1) * 2
* or:
* bString_length = sizeof(initializer_string) * 2 - 2
*/
#define USB_BSTRING_LENGTH(s) (sizeof(s) * 2 - 2)
/*
* The length of the string descriptor (bLength) is calculated from the
* size of the two octets bLength and bDescriptorType plus the
* length of the UTF16LE string:
*
* bLength = 2 + bString_length
* bLength = 2 + sizeof(initializer_string) * 2 - 2
* bLength = sizeof(initializer_string) * 2
* Use this macro to determine the bLength of the string descriptor.
*/
#define USB_STRING_DESCRIPTOR_LENGTH(s) (sizeof(s) * 2)
#define USBD_DESC_MANUFACTURER_IDX 1
#define USBD_DESC_PRODUCT_IDX 2
#define USBD_DESC_SERIAL_NUMBER_IDX 3
/**
* Descriptor node
*
* Descriptor node is used to manage descriptors that are not
* directly part of a structure string, and bos descriptors.
*/
struct usbd_desc_node {
/** slist node struct */
sys_snode_t node;
/** Optional descriptor index, required for string descriptors */
uint32_t idx;
/** Pointer to a descriptor */
void *desc;
};
/**
* Device configuration node
*
* Configuration node is used to manage device configurations,
* at least one configuration is required. It does not have an index,
* instead bConfigurationValue of the descriptor is used for
* identification.
*/
struct usbd_config_node {
/** slist node struct */
sys_snode_t node;
/** Pointer to configuration descriptor */
void *desc;
/** List of registered classes (functions) */
sys_slist_t class_list;
};
/* TODO: Kconfig option USBD_NUMOF_INTERFACES_MAX? */
#define USBD_NUMOF_INTERFACES_MAX 16U
/**
* USB device support middle layer runtime state
*
* Part of USB device states without suspended and powered
* states, as it is better to track them separately.
*/
enum usbd_ch9_state {
USBD_STATE_DEFAULT = 0,
USBD_STATE_ADDRESS,
USBD_STATE_CONFIGURED,
};
/**
* USB device support middle layer runtime data
*/
struct usbd_ch9_data {
/** Setup packet, up-to-date for the respective control request */
struct usb_setup_packet setup;
/** Control type, internaly used for stage verification */
int ctrl_type;
/** Protocol state of the USB device stack */
enum usbd_ch9_state state;
/** Halted endpoints bitmap */
uint32_t ep_halt;
/** USB device stack selected configuration */
uint8_t configuration;
/** Indicate new device address */
bool new_address;
/** Array to track interfaces alternate settings */
uint8_t alternate[USBD_NUMOF_INTERFACES_MAX];
};
/**
* USB device support status
*/
struct usbd_status {
/** USB device support is initialized */
unsigned int initialized : 1;
/** USB device support is enabled */
unsigned int enabled : 1;
/** USB device is suspended */
unsigned int suspended : 1;
/** USB remote wake-up feature is enabled */
unsigned int rwup : 1;
};
/**
* USB device support runtime context
*
* Main structure that organizes all descriptors, configuration,
* and interfaces. An UDC device must be assigned to this structure.
*/
struct usbd_contex {
/** Name of the USB device */
const char *name;
/** Access mutex */
struct k_mutex mutex;
/** Pointer to UDC device */
const struct device *dev;
/** Middle layer runtime data */
struct usbd_ch9_data ch9_data;
/** slist to manage descriptors like string, bos */
sys_slist_t descriptors;
/** slist to manage device configurations */
sys_slist_t configs;
/** Status of the USB device support */
struct usbd_status status;
/** Pointer to device descriptor */
void *desc;
};
/**
* @brief Vendor Requests Table
*/
struct usbd_cctx_vendor_req {
/** Array of vendor requests supportd by the class */
const uint8_t *reqs;
/** Length of the array */
uint8_t len;
};
/** USB Class instance registered flag */
#define USBD_CCTX_REGISTERED 0
struct usbd_class_node;
/**
* @brief USB device support class instance API
*/
struct usbd_class_api {
/** Configuration update handler */
void (*update)(struct usbd_class_node *const node,
uint8_t iface, uint8_t alternate);
/** USB control request handler to device */
int (*control_to_dev)(struct usbd_class_node *const node,
const struct usb_setup_packet *const setup,
const struct net_buf *const buf);
/** USB control request handler to host */
int (*control_to_host)(struct usbd_class_node *const node,
const struct usb_setup_packet *const setup,
struct net_buf *const buf);
/** Endpoint request completion event handler */
int (*request)(struct usbd_class_node *const node,
struct net_buf *buf, int err);
/** USB power management handler suspended */
void (*suspended)(struct usbd_class_node *const node);
/** USB power management handler resumed */
void (*resumed)(struct usbd_class_node *const node);
/** Class associated configuration is selected */
void (*enable)(struct usbd_class_node *const node);
/** Class associated configuration is disabled */
void (*disable)(struct usbd_class_node *const node);
/** Initialization of the class implementation */
int (*init)(struct usbd_class_node *const node);
/** Shutdown of the class implementation (TODO) */
int (*shutdown)(struct usbd_class_node *const node);
};
/**
* @brief USB device support class data
*/
struct usbd_class_data {
/** Pointer to USB device stack context structure */
struct usbd_contex *uds_ctx;
/** Terminated descriptor for class implementation */
void *desc;
/** Supported vendor request table, can be NULL */
const struct usbd_cctx_vendor_req *v_reqs;
/** Bitmap of all endpoints assigned to the instance.
* The IN endpoints are mapped in the upper halfword.
*/
uint32_t ep_assigned;
/** Bitmap of the enabled endpoints of the instance.
* The IN endpoints are mapped in the upper halfword.
*/
uint32_t ep_active;
/** Bitmap of the bInterfaceNumbers of the class instance */
uint32_t iface_bm;
/** Variable to store the state of the class instance */
atomic_t state;
/** Pointer to private data */
void *priv;
};
struct usbd_class_node {
/** Node information for the slist. */
sys_snode_t node;
/** Name of the USB device class instance */
const char *name;
/** Pointer to device support class API */
const struct usbd_class_api *api;
/** Pointer to USB device support class data */
struct usbd_class_data *data;
};
/**
* @brief New USB device stack core API
* @defgroup usbd_api USB device core API
* @ingroup usb
* @{
*/
#define USBD_DEVICE_DEFINE(device_name, uhc_dev, vid, pid) \
static struct usb_device_descriptor \
desc_##device_name = { \
.bLength = sizeof(struct usb_device_descriptor), \
.bDescriptorType = USB_DESC_DEVICE, \
.bcdUSB = sys_cpu_to_le16(USB_SRN_2_0), \
.bDeviceClass = USB_BCC_MISCELLANEOUS, \
.bDeviceSubClass = 2, \
.bDeviceProtocol = 1, \
.bMaxPacketSize0 = USB_CONTROL_EP_MPS, \
.idVendor = vid, \
.idProduct = pid, \
.bcdDevice = sys_cpu_to_le16(USB_BCD_DRN), \
.iManufacturer = 0, \
.iProduct = 0, \
.iSerialNumber = 0, \
.bNumConfigurations = 0, \
}; \
static STRUCT_SECTION_ITERABLE(usbd_contex, device_name) = { \
.name = STRINGIFY(device_name), \
.dev = uhc_dev, \
.desc = &desc_##device_name, \
}
#define USBD_CONFIGURATION_DEFINE(name, attrib, power) \
static struct usb_cfg_descriptor \
cfg_desc_##name = { \
.bLength = sizeof(struct usb_cfg_descriptor), \
.bDescriptorType = USB_DESC_CONFIGURATION, \
.wTotalLength = 0, \
.bNumInterfaces = 0, \
.bConfigurationValue = 1, \
.iConfiguration = 0, \
.bmAttributes = USB_SCD_RESERVED | (attrib), \
.bMaxPower = (power), \
}; \
BUILD_ASSERT((power) < 256, "Too much power"); \
static struct usbd_config_node name = { \
.desc = &cfg_desc_##name, \
}
#define USBD_DESC_LANG_DEFINE(name) \
static struct usb_string_descriptor \
string_desc_##name = { \
.bLength = sizeof(struct usb_string_descriptor), \
.bDescriptorType = USB_DESC_STRING, \
.bString = sys_cpu_to_le16(0x0409), \
}; \
static struct usbd_desc_node name = { \
.idx = 0, \
.desc = &string_desc_##name, \
}
#define USBD_DESC_STRING_DEFINE(d_name, d_string, d_idx) \
struct usb_string_descriptor_##d_name { \
uint8_t bLength; \
uint8_t bDescriptorType; \
uint8_t bString[USB_BSTRING_LENGTH(d_string)]; \
} __packed; \
static struct usb_string_descriptor_##d_name \
string_desc_##d_name = { \
.bLength = USB_STRING_DESCRIPTOR_LENGTH(d_string), \
.bDescriptorType = USB_DESC_STRING, \
.bString = d_string, \
}; \
BUILD_ASSERT(d_idx != 0, "Index 0 is not allowed"); \
static struct usbd_desc_node d_name = { \
.idx = d_idx, \
.desc = &string_desc_##d_name, \
}
#define USBD_DEFINE_CLASS(class_name, class_api, class_data) \
static STRUCT_SECTION_ITERABLE(usbd_class_node, class_name) = { \
.name = STRINGIFY(class_name), \
.api = class_api, \
.data = class_data, \
}
/** @brief Helper to declare request table of usbd_cctx_vendor_req
*
* @param _reqs Pointer to the vendor request field
* @param _len Number of supported vendor requests
*/
#define VENDOR_REQ_DEFINE(_reqs, _len) \
{ \
.reqs = (const uint8_t *)(_reqs), \
.len = (_len), \
}
/** @brief Helper to declare supported vendor requests
*
* @param _reqs Variable number of vendor requests
*/
#define USBD_VENDOR_REQ(_reqs...) \
VENDOR_REQ_DEFINE(((uint8_t []) { _reqs }), \
sizeof((uint8_t []) { _reqs }))
/**
* @brief Add common USB descriptor
*
* Add common descriptor like string or bos.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] dn Pointer to USB descriptor node
*
* @return 0 on success, other values on fail.
*/
int usbd_add_descriptor(struct usbd_contex *uds_ctx,
struct usbd_desc_node *dn);
/**
* @brief Add a USB device configuration
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] cd Pointer to USB configuration node
*
* @return 0 on success, other values on fail.
*/
int usbd_add_configuration(struct usbd_contex *uds_ctx,
struct usbd_config_node *cd);
/**
* @brief Register an USB class instance
*
* An USB class implementation can have one or more instances.
* To identify the instances we use device drivers API.
* Device names have a prefix derived from the name of the class,
* for example CDC_ACM for CDC ACM class instance,
* and can also be easily identified in the shell.
* Class instance can only be registered when the USB device stack
* is disabled.
* Registered instances are initialized at initialization
* of the USB device stack, and the interface descriptors
* of each instance are adapted to the whole context.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] name Class instance name
* @param[in] cfg Configuration value (similar to bConfigurationValue)
*
* @return 0 on success, other values on fail.
*/
int usbd_register_class(struct usbd_contex *uds_ctx,
const char *name,
uint8_t cfg);
/**
* @brief Unregister an USB class instance
*
* USB class instance will be removed and will not appear
* on the next start of the stack. Instance can only be unregistered
* when the USB device stack is disabled.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] name Class instance name
* @param[in] cfg Configuration value (similar to bConfigurationValue)
*
* @return 0 on success, other values on fail.
*/
int usbd_unregister_class(struct usbd_contex *uds_ctx,
const char *name,
uint8_t cfg);
/**
* @brief Initialize USB device
*
* Initialize USB device descriptors and configuration,
* initialize USB device controller.
* Class instances should be registered before they are involved.
* However, the stack should also initialize without registered instances,
* even if the host would complain about missing interfaces.
*
* @param[in] uds_ctx Pointer to USB device support context
*
* @return 0 on success, other values on fail.
*/
int usbd_init(struct usbd_contex *uds_ctx);
/**
* @brief Enable the USB device support and registered class instances
*
* This function enables the USB device support.
*
* @param[in] uds_ctx Pointer to USB device support context
*
* @return 0 on success, other values on fail.
*/
int usbd_enable(struct usbd_contex *uds_ctx);
/**
* @brief Disable the USB device support
*
* This function disables the USB device support.
*
* @param[in] uds_ctx Pointer to USB device support context
*
* @return 0 on success, other values on fail.
*/
int usbd_disable(struct usbd_contex *uds_ctx);
/**
* @brief Shutdown the USB device support
*
* This function completely disables the USB device support.
*
* @param[in] uds_ctx Pointer to USB device support context
*
* @return 0 on success, other values on fail.
*/
int usbd_shutdown(struct usbd_contex *const uds_ctx);
/**
* @brief Halt endpoint
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] ep Endpoint address
*
* @return 0 on success, or error from udc_ep_set_halt()
*/
int usbd_ep_set_halt(struct usbd_contex *uds_ctx, uint8_t ep);
/**
* @brief Clear endpoint halt
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] ep Endpoint address
*
* @return 0 on success, or error from udc_ep_clear_halt()
*/
int usbd_ep_clear_halt(struct usbd_contex *uds_ctx, uint8_t ep);
/**
* @brief Checks whether the endpoint is halted.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] ep Endpoint address
*
* @return true if endpoint is halted, false otherwise
*/
bool usbd_ep_is_halted(struct usbd_contex *uds_ctx, uint8_t ep);
/**
* @brief Allocate buffer for USB device control request
*
* Allocate a new buffer from controller's driver buffer pool.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] ep Endpoint address
* @param[in] size Size of the request buffer
*
* @return pointer to allocated request or NULL on error.
*/
struct net_buf *usbd_ep_ctrl_buf_alloc(struct usbd_contex *const uds_ctx,
const uint8_t ep, const size_t size);
/**
* @brief Allocate buffer for USB device request
*
* Allocate a new buffer from controller's driver buffer pool.
*
* @param[in] c_nd Pointer to USB device class node
* @param[in] ep Endpoint address
* @param[in] size Size of the request buffer
*
* @return pointer to allocated request or NULL on error.
*/
struct net_buf *usbd_ep_buf_alloc(const struct usbd_class_node *const c_nd,
const uint8_t ep, const size_t size);
/**
* @brief Queue USB device control request
*
* Add control request to the queue.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] buf Pointer to UDC request buffer
*
* @return 0 on success, all other values should be treated as error.
*/
int usbd_ep_ctrl_enqueue(struct usbd_contex *const uds_ctx,
struct net_buf *const buf);
/**
* @brief Queue USB device request
*
* Add request to the queue.
*
* @param[in] c_nd Pointer to USB device class node
* @param[in] buf Pointer to UDC request buffer
*
* @return 0 on success, or error from udc_ep_enqueue()
*/
int usbd_ep_enqueue(const struct usbd_class_node *const c_nd,
struct net_buf *const buf);
/**
* @brief Remove all USB device controller requests from endpoint queue
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] ep Endpoint address
*
* @return 0 on success, or error from udc_ep_dequeue()
*/
int usbd_ep_dequeue(struct usbd_contex *uds_ctx, const uint8_t ep);
/**
* @brief Free USB device request buffer
*
* Put the buffer back into the request buffer pool.
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] buf Pointer to UDC request buffer
*
* @return 0 on success, all other values should be treated as error.
*/
int usbd_ep_buf_free(struct usbd_contex *uds_ctx, struct net_buf *buf);
/**
* @brief Checks whether the USB device controller is suspended.
*
* @param[in] uds_ctx Pointer to USB device support context
*
* @return true if endpoint is halted, false otherwise
*/
bool usbd_is_suspended(struct usbd_contex *uds_ctx);
/**
* @brief Initiate the USB remote wakeup (TBD)
*
* @return 0 on success, other values on fail.
*/
int usbd_wakeup_request(struct usbd_contex *uds_ctx);
/**
* @brief Set USB device descriptor value bcdUSB
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] bcd bcdUSB value
*
* @return 0 on success, other values on fail.
*/
int usbd_device_set_bcd(struct usbd_contex *const uds_ctx,
const uint16_t bcd);
/**
* @brief Set USB device descriptor value idVendor
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] vid idVendor value
*
* @return 0 on success, other values on fail.
*/
int usbd_device_set_vid(struct usbd_contex *const uds_ctx,
const uint16_t vid);
/**
* @brief Set USB device descriptor value idProduct
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] pid idProduct value
*
* @return 0 on success, other values on fail.
*/
int usbd_device_set_pid(struct usbd_contex *const uds_ctx,
const uint16_t pid);
/**
* @brief Set USB device descriptor value bDeviceClass
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] value bDeviceClass value
*
* @return 0 on success, other values on fail.
*/
int usbd_device_set_class(struct usbd_contex *const uds_ctx,
const uint8_t value);
/**
* @brief Set USB device descriptor value bDeviceSubClass
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] value bDeviceSubClass value
*
* @return 0 on success, other values on fail.
*/
int usbd_device_set_subclass(struct usbd_contex *const uds_ctx,
const uint8_t value);
/**
* @brief Set USB device descriptor value bDeviceProtocol
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] value bDeviceProtocol value
*
* @return 0 on success, other values on fail.
*/
int usbd_device_set_proto(struct usbd_contex *const uds_ctx,
const uint8_t value);
/**
* @brief Setup USB device configuration attribute Remote Wakeup
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] cfg Configuration number
* @param[in] enable Sets attribute if true, clears it otherwise
*
* @return 0 on success, other values on fail.
*/
int usbd_config_attrib_rwup(struct usbd_contex *const uds_ctx,
const uint8_t cfg, const bool enable);
/**
* @brief Setup USB device configuration attribute Self-powered
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] cfg Configuration number
* @param[in] enable Sets attribute if true, clears it otherwise
*
* @return 0 on success, other values on fail.
*/
int usbd_config_attrib_self(struct usbd_contex *const uds_ctx,
const uint8_t cfg, const bool enable);
/**
* @brief Setup USB device configuration power consumption
*
* @param[in] uds_ctx Pointer to USB device support context
* @param[in] cfg Configuration number
* @param[in] power Maximum power consumption value (bMaxPower)
*
* @return 0 on success, other values on fail.
*/
int usbd_config_maxpower(struct usbd_contex *const uds_ctx,
const uint8_t cfg, const uint8_t power);
/**
* @}
*/
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_USBD_H_ */