zephyr/drivers/usb/udc/udc_common.h
Tomasz Moń 1cccf28d5e drivers: usb: udc: Reduce unnecessary ep config lookups
UDC API passes struct udc_ep_config to all functions. Some UDC functions
were using endpoint config structure while some were using device and
endpoint number. This API inconsistency led to completely unnecessary
endpoint structure lookups. Remove unnecessary lookups by using endpoint
config structure pointer where it makes sense.

Signed-off-by: Tomasz Moń <tomasz.mon@nordicsemi.no>
2025-03-31 14:32:17 +02:00

498 lines
15 KiB
C

/*
* Copyright (c) 2021-2022 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
/**
* @file
* @brief Private API for USB device controller (UDC) drivers
*/
#ifndef ZEPHYR_INCLUDE_UDC_COMMON_H
#define ZEPHYR_INCLUDE_UDC_COMMON_H
#include <zephyr/drivers/usb/udc.h>
#include <zephyr/sys/byteorder.h>
#define CTRL_PIPE_STAGE_SETUP 0
#define CTRL_PIPE_STAGE_DATA_OUT 1
#define CTRL_PIPE_STAGE_DATA_IN 2
#define CTRL_PIPE_STAGE_NO_DATA 3
#define CTRL_PIPE_STAGE_STATUS_OUT 4
#define CTRL_PIPE_STAGE_STATUS_IN 5
#define CTRL_PIPE_STAGE_ERROR 6
/**
* @brief Get driver's private data
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return pointer to driver's private data
*/
static inline void *udc_get_private(const struct device *dev)
{
struct udc_data *data = dev->data;
return data->priv;
}
/**
* @brief Helper function to set suspended status
*
* This function can be used by the driver to set suspended status
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] values True to set suspended status
*/
void udc_set_suspended(const struct device *dev, const bool value);
/**
* @brief Get pointer to endpoint configuration structure.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address
*
* @return pointer to endpoint configuration or NULL on error.
*/
struct udc_ep_config *udc_get_ep_cfg(const struct device *dev,
const uint8_t ep);
/**
* @brief Checks if the endpoint is busy
*
* @param[in] ep_cfg Pointer to endpoint configuration
*
* @return true if endpoint is busy
*/
bool udc_ep_is_busy(const struct udc_ep_config *const ep_cfg);
/**
* @brief Helper function to set endpoint busy state
*
* @param[in] ep_cfg Pointer to endpoint configuration
* @param[in] busy Busy state
*/
void udc_ep_set_busy(struct udc_ep_config *const ep_cfg,
const bool busy);
/**
* @brief Get UDC request from endpoint FIFO.
*
* This function removes request from endpoint FIFO.
* Use it when transfer is finished and request should
* be passed to the higher level.
*
* @param[in] ep_cfg Pointer to endpoint configuration
*
* @return pointer to UDC request or NULL on error.
*/
struct net_buf *udc_buf_get(struct udc_ep_config *const ep_cfg);
/**
* @brief Get all UDC request from endpoint FIFO.
*
* Get all UDC request from endpoint FIFO as single-linked list.
* This function removes all request from endpoint FIFO and
* is typically used to dequeue endpoint FIFO.
*
* @param[in] ep_cfg Pointer to endpoint configuration
*
* @return pointer to UDC request or NULL on error.
*/
struct net_buf *udc_buf_get_all(struct udc_ep_config *const ep_cfg);
/**
* @brief Peek request at the head of endpoint FIFO.
*
* Return request from the head of endpoint FIFO without removing.
* Use it when request buffer is required for a transfer.
*
* @param[in] ep_cfg Pointer to endpoint configuration
*
* @return pointer to request or NULL on error.
*/
struct net_buf *udc_buf_peek(struct udc_ep_config *const ep_cfg);
/**
* @brief Put request at the tail of endpoint FIFO.
*
* @param[in] ep_cfg Pointer to endpoint configuration
* @param[in] buf Pointer to UDC request buffer
*
* @return pointer to request or NULL on error.
*/
void udc_buf_put(struct udc_ep_config *const ep_cfg,
struct net_buf *const buf);
/**
* @brief Helper function to send UDC event to a higher level.
*
* The callback would typically sends UDC even to a message queue (k_msgq).
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] type Event type
* @param[in] status Event status
*
* @return 0 on success, all other values should be treated as error.
* @retval -EPERM controller is not initialized
*/
int udc_submit_event(const struct device *dev,
const enum udc_event_type type,
const int status);
/**
* @brief Helper function to send UDC endpoint event to a higher level.
*
* Type of this event is hardcoded to UDC_EVT_EP_REQUEST.
* The callback would typically sends UDC even to a message queue (k_msgq).
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] buf Pointer to UDC request buffer
* @param[in] err Request result
*
* @return 0 on success, all other values should be treated as error.
* @retval -EPERM controller is not initialized
*/
int udc_submit_ep_event(const struct device *dev,
struct net_buf *const buf,
const int err);
/**
* @brief Helper function to enable endpoint.
*
* This function can be used by the driver to enable control IN/OUT endpoint.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address (same as bEndpointAddress)
* @param[in] attributes Endpoint attributes (same as bmAttributes)
* @param[in] mps Maximum packet size (same as wMaxPacketSize)
* @param[in] interval Polling interval (same as bInterval)
*
* @return 0 on success, all other values should be treated as error.
* @retval -ENODEV endpoint is not assigned or no configuration found
* @retval -EALREADY endpoint is already enabled
*/
int udc_ep_enable_internal(const struct device *dev,
const uint8_t ep,
const uint8_t attributes,
const uint16_t mps,
const uint8_t interval);
/**
* @brief Helper function to disable endpoint.
*
* This function can be used by the driver to disable control IN/OUT endpoint.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] ep Endpoint address
*
* @return 0 on success, all other values should be treated as error.
* @retval -ENODEV endpoint is not assigned or no configuration found
* @retval -EALREADY endpoint is already enabled
*/
int udc_ep_disable_internal(const struct device *dev,
const uint8_t ep);
/**
* @brief Helper function to register endpoint configuration.
*
* This function initializes endpoint FIFO and
* appends endpoint configuration to drivers endpoint list.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] cfg Pointer to endpoint configuration structure
*
* @return 0 on success, all other values should be treated as error.
* @retval -EACCES controller is initialized or enabled
*/
int udc_register_ep(const struct device *dev,
struct udc_ep_config *const cfg);
/**
* @brief Set setup flag in requests metadata.
*
* A control transfer can be either setup or data OUT,
* use this function to mark request as setup packet.
*
* @param[in] buf Pointer to UDC request buffer
*/
void udc_ep_buf_set_setup(struct net_buf *const buf);
/**
* @brief Checks whether the driver must finish transfer with a ZLP
*
* @param[in] buf Pointer to UDC request buffer
*
* @return true if ZLP is requested
*/
bool udc_ep_buf_has_zlp(const struct net_buf *const buf);
/**
* @brief Clear ZLP flag
*
* @param[in] buf Pointer to UDC request buffer
*/
void udc_ep_buf_clear_zlp(const struct net_buf *const buf);
/**
* @brief Locking function for the drivers.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] timeout Timeout
*
* @return values provided by k_mutex_lock()
*/
static inline int udc_lock_internal(const struct device *dev,
k_timeout_t timeout)
{
struct udc_data *data = dev->data;
return k_mutex_lock(&data->mutex, timeout);
}
/**
* @brief Unlocking function for the drivers.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return values provided by k_mutex_lock()
*/
static inline int udc_unlock_internal(const struct device *dev)
{
struct udc_data *data = dev->data;
return k_mutex_unlock(&data->mutex);
}
/**
* @brief Allocate UDC control transfer buffer
*
* Allocate a new buffer from common control transfer buffer pool.
*
* @param[in] dev Pointer to device struct of the driver instance
* @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 *udc_ctrl_alloc(const struct device *dev,
const uint8_t ep,
const size_t size);
static inline uint16_t udc_data_stage_length(const struct net_buf *const buf)
{
struct usb_setup_packet *setup = (void *)buf->data;
return sys_le16_to_cpu(setup->wLength);
}
/**
* @brief Checks whether the current control transfer stage is Data Stage OUT
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return true if stage is Data Stage OUT
*/
bool udc_ctrl_stage_is_data_out(const struct device *dev);
/**
* @brief Checks whether the current control transfer stage is Data Stage IN
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return true if stage is Data Stage IN
*/
bool udc_ctrl_stage_is_data_in(const struct device *dev);
/**
* @brief Checks whether the current control transfer stage is Status IN
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return true if stage is Data Stage IN
*/
bool udc_ctrl_stage_is_status_in(const struct device *dev);
/**
* @brief Checks whether the current control transfer stage is Status OUT
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return true if stage is Data Stage OUT
*/
bool udc_ctrl_stage_is_status_out(const struct device *dev);
/**
* @brief Checks whether the current control transfer stage is Status no-data
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return true if stage is Status no-data
*/
bool udc_ctrl_stage_is_no_data(const struct device *dev);
/**
* @brief Submit Control Write (s-out-status) transfer
*
* Allocate buffer for data stage IN,
* submit both setup and data buffer to upper layer.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] dout Pointer to UDC buffer containing data transaction
*
* @return 0 on success, all other values should be treated as error.
*/
int udc_ctrl_submit_s_out_status(const struct device *dev,
struct net_buf *const dout);
/**
* @brief Prepare control data IN stage
*
* Allocate buffer for data stage IN,
* submit both setup and data buffer to upper layer.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return 0 on success, all other values should be treated as error.
*/
int udc_ctrl_submit_s_in_status(const struct device *dev);
/**
* @brief Prepare control (no-data) status stage
*
* Allocate buffer for status stage IN,
* submit both setup and status buffer to upper layer.
*
* @param[in] dev Pointer to device struct of the driver instance
*
* @return 0 on success, all other values should be treated as error.
*/
int udc_ctrl_submit_s_status(const struct device *dev);
/**
* @brief Submit status transaction
*
* Submit both status transaction to upper layer.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] dout Pointer to UDC buffer containing data transaction
*
* @return 0 on success, all other values should be treated as error.
*/
int udc_ctrl_submit_status(const struct device *dev,
struct net_buf *const buf);
/**
* @brief Update internal control stage status based on the net_buf metadata
*
* Use it in the driver to update the stage, typically there are
* three places where this function should be called:
* - when a setup packet is received
* - when a data stage is completed (all data stage transactions)
* - when a status stage transaction is finished
*
* The functions of type udc_ctrl_stage_is_*() can be called before or
* after this function, depending on the desired action.
* To keep protocol processing running the following should be taken
* into account:
*
* - Upper layer may not allocate buffers but remove or release buffers
* from the chain that are no longer needed. Only control IN transfers may
* be enqueued by the upper layer.
*
* - For "Control Write" (s-out-status), the driver should allocate the buffer,
* insert it as a fragment to setup buffer and perform the Data Stage
* transaction. Allocate and insert a fragment for the status (IN) stage to
* setup buffer, and then pass setup packet with the chain of s-out-status to
* upper layer. Upper layer should either halt control endpoint or
* enqueue status buffer for status stage. There should be second
* notification to upper layer when the status transaction is finished.
*
* ->driver_foo_setup_rcvd(dev)
* ->udc_ctrl_update_stage(dev, buf)
* ->udc_ctrl_alloc(dev, USB_CONTROL_EP_OUT, wLength)
* ->driver_foo_xfer_start(dev, USB_CONTROL_EP_OUT)
*
* ->driver_foo_dout_rcvd(dev)
* -...
* ->driver_foo_feed_next_dout(dev, ....)
* -...
* ->udc_ctrl_update_stage(dev, dout_buf)
* -...
* ->udc_ctrl_submit_s_out_status(dev, dout_buf);
*
* ->driver_foo_din_rcvd(dev)
* -...
* ->udc_ctrl_submit_status(dev, status_buf);
* -...
* ->udc_ctrl_update_stage(dev, status_buf)
*
* - For "Control Read" (s-in-status), depending on the controller,
* the driver should reserve the buffers for subsequent status stage and
* setup packet and prepare everything. The driver should allocate the buffer
* for IN transaction insert it as a fragment to setup buffer, and pass
* the chain of s-in to upper layer. Upper layer should either halt control
* endpoint or enqueue (in) buffer. There should be second
* notification to upper layer when the status transaction is finished.
*
* ->driver_foo_setup_rcvd(dev)
* ->udc_ctrl_update_stage(dev, buf)
* ->driver_foo_feed_next_dout(dev, ....)
* -...
* ->udc_ctrl_submit_s_in_status(dev);
*
* ->driver_foo_din_rcvd(dev)
* -...
* ->udc_ctrl_update_stage(dev, dout_buf)
* -...
*
* ->driver_foo_dout_rcvd(dev)
* -...
* ->udc_ctrl_submit_status(dev, status_buf);
* -...
* ->udc_ctrl_update_stage(dev, dout_buf)
*
* - For "No-data Control" (s-status), the driver should allocate the buffer
* for the status (IN) stage, insert it as a fragment to setup buffer,
* and then pass setup packet with the chain of s-status to
* upper layer. Upper layer should either halt control endpoint or
* enqueue status buffer for status stage. There should be second
* notification to upper layer when the status transaction is finished.
*
* ->driver_foo_setup_rcvd(dev)
* ->udc_ctrl_update_stage(dev, buf)
* ->driver_foo_feed_next_dout(dev, ....)
* -...
* ->udc_ctrl_submit_s_status(dev);
*
* ->driver_foo_din_rcvd(dev)
* -...
* ->udc_ctrl_submit_status(dev, status_buf);
* -...
* ->udc_ctrl_update_stage(dev, status_buf)
*
* Please refer to Chapter 8.5.3 Control Transfers USB 2.0 spec.
*
* @param[in] dev Pointer to device struct of the driver instance
* @param[in] buf Buffer containing setup packet
*
* @return 0 on success, all other values should be treated as error.
*/
void udc_ctrl_update_stage(const struct device *dev,
struct net_buf *const buf);
#if defined(CONFIG_UDC_WORKQUEUE)
extern struct k_work_q udc_work_q;
static inline struct k_work_q *udc_get_work_q(void)
{
return &udc_work_q;
}
#else
static inline struct k_work_q *udc_get_work_q(void)
{
return &k_sys_work_q;
}
#endif
#endif /* ZEPHYR_INCLUDE_UDC_COMMON_H */