|  | /* | 
|  | * Copyright (c) 2021-2022 Nordic Semiconductor ASA | 
|  | * | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | */ | 
|  |  | 
|  | #include <zephyr/init.h> | 
|  | #include <zephyr/kernel.h> | 
|  | #include <zephyr/net/buf.h> | 
|  | #include <zephyr/sys/byteorder.h> | 
|  | #include <zephyr/sys/__assert.h> | 
|  | #include <zephyr/usb/usb_ch9.h> | 
|  | #include "udc_common.h" | 
|  |  | 
|  | #include <zephyr/logging/log.h> | 
|  | #if defined(CONFIG_UDC_DRIVER_LOG_LEVEL) | 
|  | #define UDC_COMMON_LOG_LEVEL CONFIG_UDC_DRIVER_LOG_LEVEL | 
|  | #else | 
|  | #define UDC_COMMON_LOG_LEVEL LOG_LEVEL_NONE | 
|  | #endif | 
|  | LOG_MODULE_REGISTER(udc, CONFIG_UDC_DRIVER_LOG_LEVEL); | 
|  |  | 
|  | static inline void udc_buf_destroy(struct net_buf *buf); | 
|  |  | 
|  | NET_BUF_POOL_VAR_DEFINE(udc_ep_pool, | 
|  | CONFIG_UDC_BUF_COUNT, CONFIG_UDC_BUF_POOL_SIZE, | 
|  | sizeof(struct udc_buf_info), udc_buf_destroy); | 
|  |  | 
|  | #define USB_EP_LUT_IDX(ep) (USB_EP_DIR_IS_IN(ep) ? (ep & BIT_MASK(4)) + 16 : \ | 
|  | ep & BIT_MASK(4)) | 
|  |  | 
|  | void udc_set_suspended(const struct device *dev, const bool value) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  |  | 
|  | if (value == udc_is_suspended(dev)) { | 
|  | LOG_WRN("Spurious suspend/resume event"); | 
|  | } | 
|  |  | 
|  | atomic_set_bit_to(&data->status, UDC_STATUS_SUSPENDED, value); | 
|  | } | 
|  |  | 
|  | struct udc_ep_config *udc_get_ep_cfg(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  |  | 
|  | return data->ep_lut[USB_EP_LUT_IDX(ep)]; | 
|  | } | 
|  |  | 
|  | bool udc_ep_is_busy(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | struct udc_ep_config *ep_cfg; | 
|  |  | 
|  | ep_cfg = udc_get_ep_cfg(dev, ep); | 
|  | __ASSERT(ep_cfg != NULL, "ep 0x%02x is not available", ep); | 
|  |  | 
|  | return ep_cfg->stat.busy; | 
|  | } | 
|  |  | 
|  | void udc_ep_set_busy(const struct device *dev, const uint8_t ep, const bool busy) | 
|  | { | 
|  | struct udc_ep_config *ep_cfg; | 
|  |  | 
|  | ep_cfg = udc_get_ep_cfg(dev, ep); | 
|  | __ASSERT(ep_cfg != NULL, "ep 0x%02x is not available", ep); | 
|  | ep_cfg->stat.busy = busy; | 
|  | } | 
|  |  | 
|  | int udc_register_ep(const struct device *dev, struct udc_ep_config *const cfg) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  | uint8_t idx; | 
|  |  | 
|  | if (udc_is_initialized(dev)) { | 
|  | return -EACCES; | 
|  | } | 
|  |  | 
|  | idx = USB_EP_LUT_IDX(cfg->addr); | 
|  | __ASSERT_NO_MSG(idx < ARRAY_SIZE(data->ep_lut)); | 
|  |  | 
|  | data->ep_lut[idx] = cfg; | 
|  | k_fifo_init(&cfg->fifo); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | struct net_buf *udc_buf_get(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | struct udc_ep_config *ep_cfg; | 
|  |  | 
|  | ep_cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (ep_cfg == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return net_buf_get(&ep_cfg->fifo, K_NO_WAIT); | 
|  | } | 
|  |  | 
|  | struct net_buf *udc_buf_get_all(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | struct udc_ep_config *ep_cfg; | 
|  | struct net_buf *buf; | 
|  |  | 
|  | ep_cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (ep_cfg == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | buf = k_fifo_get(&ep_cfg->fifo, K_NO_WAIT); | 
|  | if (!buf) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | LOG_DBG("ep 0x%02x dequeue %p", ep, buf); | 
|  | for (struct net_buf *n = buf; !k_fifo_is_empty(&ep_cfg->fifo); n = n->frags) { | 
|  | n->frags = k_fifo_get(&ep_cfg->fifo, K_NO_WAIT); | 
|  | LOG_DBG("|-> %p ", n->frags); | 
|  | if (n->frags == NULL) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | struct net_buf *udc_buf_peek(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | struct udc_ep_config *ep_cfg; | 
|  |  | 
|  | ep_cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (ep_cfg == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | return k_fifo_peek_head(&ep_cfg->fifo); | 
|  | } | 
|  |  | 
|  | void udc_buf_put(struct udc_ep_config *const ep_cfg, | 
|  | struct net_buf *const buf) | 
|  | { | 
|  | net_buf_put(&ep_cfg->fifo, buf); | 
|  | } | 
|  |  | 
|  | void udc_ep_buf_set_setup(struct net_buf *const buf) | 
|  | { | 
|  | struct udc_buf_info *bi = udc_get_buf_info(buf); | 
|  |  | 
|  | bi->setup = 1; | 
|  | bi->data = 0; | 
|  | bi->status = 0; | 
|  | } | 
|  |  | 
|  | bool udc_ep_buf_has_zlp(const struct net_buf *const buf) | 
|  | { | 
|  | const struct udc_buf_info *bi = udc_get_buf_info(buf); | 
|  |  | 
|  | return bi->zlp; | 
|  | } | 
|  |  | 
|  | void udc_ep_buf_clear_zlp(const struct net_buf *const buf) | 
|  | { | 
|  | struct udc_buf_info *bi = udc_get_buf_info(buf); | 
|  |  | 
|  | bi->zlp = false; | 
|  | } | 
|  |  | 
|  | int udc_submit_event(const struct device *dev, | 
|  | const enum udc_event_type type, | 
|  | const int status) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  | struct udc_event drv_evt = { | 
|  | .type = type, | 
|  | .status = status, | 
|  | .dev = dev, | 
|  | }; | 
|  |  | 
|  | if (!udc_is_initialized(dev)) { | 
|  | return -EPERM; | 
|  | } | 
|  |  | 
|  | return data->event_cb(dev, &drv_evt); | 
|  | } | 
|  |  | 
|  | int udc_submit_ep_event(const struct device *dev, | 
|  | struct net_buf *const buf, | 
|  | const int err) | 
|  | { | 
|  | struct udc_buf_info *bi = udc_get_buf_info(buf); | 
|  | struct udc_data *data = dev->data; | 
|  | const struct udc_event drv_evt = { | 
|  | .type = UDC_EVT_EP_REQUEST, | 
|  | .buf = buf, | 
|  | .dev = dev, | 
|  | }; | 
|  |  | 
|  | if (!udc_is_initialized(dev)) { | 
|  | return -EPERM; | 
|  | } | 
|  |  | 
|  | bi->err = err; | 
|  |  | 
|  | return data->event_cb(dev, &drv_evt); | 
|  | } | 
|  |  | 
|  | static uint8_t ep_attrib_get_transfer(uint8_t attributes) | 
|  | { | 
|  | return attributes & USB_EP_TRANSFER_TYPE_MASK; | 
|  | } | 
|  |  | 
|  | static bool ep_check_config(const struct device *dev, | 
|  | const struct udc_ep_config *const cfg, | 
|  | const uint8_t ep, | 
|  | const uint8_t attributes, | 
|  | const uint16_t mps, | 
|  | const uint8_t interval) | 
|  | { | 
|  | bool dir_is_in = USB_EP_DIR_IS_IN(ep); | 
|  | bool dir_is_out = USB_EP_DIR_IS_OUT(ep); | 
|  |  | 
|  | LOG_DBG("cfg d:%c|%c t:%c|%c|%c|%c, mps %u", | 
|  | cfg->caps.in ? 'I' : '-', | 
|  | cfg->caps.out ? 'O' : '-', | 
|  | cfg->caps.iso ? 'S' : '-', | 
|  | cfg->caps.bulk ? 'B' : '-', | 
|  | cfg->caps.interrupt ? 'I' : '-', | 
|  | cfg->caps.control ? 'C' : '-', | 
|  | cfg->caps.mps); | 
|  |  | 
|  | if (dir_is_out && !cfg->caps.out) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (dir_is_in && !cfg->caps.in) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (mps > cfg->caps.mps) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | switch (ep_attrib_get_transfer(attributes)) { | 
|  | case USB_EP_TYPE_BULK: | 
|  | if (!cfg->caps.bulk) { | 
|  | return false; | 
|  | } | 
|  | break; | 
|  | case USB_EP_TYPE_INTERRUPT: | 
|  | if (!cfg->caps.interrupt) { | 
|  | return false; | 
|  | } | 
|  | break; | 
|  | case USB_EP_TYPE_ISO: | 
|  | if (!cfg->caps.iso) { | 
|  | return false; | 
|  | } | 
|  | break; | 
|  | case USB_EP_TYPE_CONTROL: | 
|  | if (!cfg->caps.control) { | 
|  | return false; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | return false; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static void ep_update_mps(const struct device *dev, | 
|  | const struct udc_ep_config *const cfg, | 
|  | const uint8_t attributes, | 
|  | uint16_t *const mps) | 
|  | { | 
|  | struct udc_device_caps caps = udc_caps(dev); | 
|  | const uint16_t spec_int_mps = caps.hs ? 1024 : 64; | 
|  | const uint16_t spec_bulk_mps = caps.hs ? 512 : 64; | 
|  |  | 
|  | /* | 
|  | * TODO: It does not take into account the actual speed of the | 
|  | * bus after the RESET. Should be fixed/improved when the driver | 
|  | * for high speed controller are ported. | 
|  | */ | 
|  | switch (ep_attrib_get_transfer(attributes)) { | 
|  | case USB_EP_TYPE_BULK: | 
|  | *mps = MIN(cfg->caps.mps, spec_bulk_mps); | 
|  | break; | 
|  | case USB_EP_TYPE_INTERRUPT: | 
|  | *mps = MIN(cfg->caps.mps, spec_int_mps); | 
|  | break; | 
|  | case USB_EP_TYPE_CONTROL: | 
|  | __fallthrough; | 
|  | case USB_EP_TYPE_ISO: | 
|  | __fallthrough; | 
|  | default: | 
|  | return; | 
|  | } | 
|  | } | 
|  |  | 
|  | int udc_ep_try_config(const struct device *dev, | 
|  | const uint8_t ep, | 
|  | const uint8_t attributes, | 
|  | uint16_t *const mps, | 
|  | const uint8_t interval) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_ep_config *cfg; | 
|  | bool ret; | 
|  |  | 
|  | cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (cfg == NULL) { | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | ret = ep_check_config(dev, cfg, ep, attributes, *mps, interval); | 
|  | if (ret == true && *mps == 0U) { | 
|  | ep_update_mps(dev, cfg, attributes, mps); | 
|  | } | 
|  |  | 
|  | api->unlock(dev); | 
|  |  | 
|  | return (ret == false) ? -ENOTSUP : 0; | 
|  | } | 
|  |  | 
|  | 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) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_ep_config *cfg; | 
|  | int ret; | 
|  |  | 
|  | cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (cfg == NULL) { | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (cfg->stat.enabled) { | 
|  | LOG_ERR("ep 0x%02x already enabled", cfg->addr); | 
|  | return -EALREADY; | 
|  | } | 
|  |  | 
|  | if (!ep_check_config(dev, cfg, ep, attributes, mps, interval)) { | 
|  | LOG_ERR("Endpoint 0x%02x validation failed", cfg->addr); | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | cfg->attributes = attributes; | 
|  | cfg->mps = mps; | 
|  | cfg->interval = interval; | 
|  |  | 
|  | cfg->stat.odd = 0; | 
|  | cfg->stat.halted = 0; | 
|  | cfg->stat.data1 = false; | 
|  | ret = api->ep_enable(dev, cfg); | 
|  | cfg->stat.enabled = ret ? false : true; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_ep_enable(const struct device *dev, | 
|  | const uint8_t ep, | 
|  | const uint8_t attributes, | 
|  | const uint16_t mps, | 
|  | const uint8_t interval) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | int ret; | 
|  |  | 
|  | if (ep == USB_CONTROL_EP_OUT || ep == USB_CONTROL_EP_IN) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_enabled(dev)) { | 
|  | ret = -EPERM; | 
|  | goto ep_enable_error; | 
|  | } | 
|  |  | 
|  | ret = udc_ep_enable_internal(dev, ep, attributes, mps, interval); | 
|  |  | 
|  | ep_enable_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_ep_disable_internal(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_ep_config *cfg; | 
|  | int ret; | 
|  |  | 
|  | cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (cfg == NULL) { | 
|  | return -ENODEV; | 
|  | } | 
|  |  | 
|  | if (!cfg->stat.enabled) { | 
|  | LOG_ERR("ep 0x%02x already disabled", cfg->addr); | 
|  | return -EALREADY; | 
|  | } | 
|  |  | 
|  | ret = api->ep_disable(dev, cfg); | 
|  | cfg->stat.enabled = ret ? cfg->stat.enabled : false; | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_ep_disable(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | int ret; | 
|  |  | 
|  | if (ep == USB_CONTROL_EP_OUT || ep == USB_CONTROL_EP_IN) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_initialized(dev)) { | 
|  | ret = -EPERM; | 
|  | goto ep_disable_error; | 
|  | } | 
|  |  | 
|  | ret = udc_ep_disable_internal(dev, ep); | 
|  |  | 
|  | ep_disable_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_ep_set_halt(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_ep_config *cfg; | 
|  | int ret; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_enabled(dev)) { | 
|  | ret = -EPERM; | 
|  | goto ep_set_halt_error; | 
|  | } | 
|  |  | 
|  | cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (cfg == NULL) { | 
|  | ret = -ENODEV; | 
|  | goto ep_set_halt_error; | 
|  | } | 
|  |  | 
|  | if (!cfg->stat.enabled) { | 
|  | ret = -ENODEV; | 
|  | goto ep_set_halt_error; | 
|  | } | 
|  |  | 
|  | if (ep_attrib_get_transfer(cfg->attributes) == USB_EP_TYPE_ISO) { | 
|  | ret = -ENOTSUP; | 
|  | goto ep_set_halt_error; | 
|  | } | 
|  |  | 
|  | ret = api->ep_set_halt(dev, cfg); | 
|  |  | 
|  | ep_set_halt_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_ep_clear_halt(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_ep_config *cfg; | 
|  | int ret; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_enabled(dev)) { | 
|  | ret = -EPERM; | 
|  | goto ep_clear_halt_error; | 
|  | } | 
|  |  | 
|  | cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (cfg == NULL) { | 
|  | ret = -ENODEV; | 
|  | goto ep_clear_halt_error; | 
|  | } | 
|  |  | 
|  | if (!cfg->stat.enabled) { | 
|  | ret = -ENODEV; | 
|  | goto ep_clear_halt_error; | 
|  | } | 
|  |  | 
|  | if (ep_attrib_get_transfer(cfg->attributes) == USB_EP_TYPE_ISO) { | 
|  | ret = -ENOTSUP; | 
|  | goto ep_clear_halt_error; | 
|  | } | 
|  |  | 
|  | ret = api->ep_clear_halt(dev, cfg); | 
|  | if (ret == 0) { | 
|  | cfg->stat.halted = false; | 
|  | } | 
|  |  | 
|  | ep_clear_halt_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static void udc_debug_ep_enqueue(const struct device *dev, | 
|  | struct udc_ep_config *const cfg) | 
|  | { | 
|  | struct udc_buf_info *bi; | 
|  | struct net_buf *buf; | 
|  | sys_slist_t list; | 
|  |  | 
|  | list.head = k_fifo_peek_head(&cfg->fifo); | 
|  | list.tail = k_fifo_peek_tail(&cfg->fifo); | 
|  | if (list.head == NULL) { | 
|  | LOG_DBG("ep 0x%02x queue is empty", cfg->addr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | LOG_DBG("[de]queue ep 0x%02x:", cfg->addr); | 
|  |  | 
|  | SYS_SLIST_FOR_EACH_CONTAINER(&list, buf, node) { | 
|  | bi = udc_get_buf_info(buf); | 
|  | LOG_DBG("|-> %p (%u) ->", buf, buf->size); | 
|  | } | 
|  | } | 
|  |  | 
|  | int udc_ep_enqueue(const struct device *dev, struct net_buf *const buf) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_ep_config *cfg; | 
|  | struct udc_buf_info *bi; | 
|  | int ret; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_enabled(dev)) { | 
|  | ret = -EPERM; | 
|  | goto ep_enqueue_error; | 
|  | } | 
|  |  | 
|  | bi = udc_get_buf_info(buf); | 
|  | if (bi->ep == USB_CONTROL_EP_OUT) { | 
|  | ret = -EPERM; | 
|  | goto ep_enqueue_error; | 
|  | } | 
|  |  | 
|  | cfg = udc_get_ep_cfg(dev, bi->ep); | 
|  | if (cfg == NULL) { | 
|  | ret = -ENODEV; | 
|  | goto ep_enqueue_error; | 
|  | } | 
|  |  | 
|  | LOG_DBG("Queue ep 0x%02x %p len %u", cfg->addr, buf, | 
|  | USB_EP_DIR_IS_IN(cfg->addr) ? buf->len : buf->size); | 
|  |  | 
|  | bi->setup = 0; | 
|  | ret = api->ep_enqueue(dev, cfg, buf); | 
|  |  | 
|  | ep_enqueue_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_ep_dequeue(const struct device *dev, const uint8_t ep) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_ep_config *cfg; | 
|  | int ret; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_initialized(dev)) { | 
|  | ret = -EPERM; | 
|  | goto ep_dequeue_error; | 
|  | } | 
|  |  | 
|  | cfg = udc_get_ep_cfg(dev, ep); | 
|  | if (cfg == NULL) { | 
|  | ret = -ENODEV; | 
|  | goto ep_dequeue_error; | 
|  | } | 
|  |  | 
|  | if (cfg->stat.enabled || cfg->stat.halted) { | 
|  | LOG_INF("ep 0x%02x is not halted|disabled", cfg->addr); | 
|  | } | 
|  |  | 
|  | if (UDC_COMMON_LOG_LEVEL == LOG_LEVEL_DBG) { | 
|  | udc_debug_ep_enqueue(dev, cfg); | 
|  | } | 
|  |  | 
|  | if (k_fifo_is_empty(&cfg->fifo)) { | 
|  | ret = 0; | 
|  | } else  { | 
|  | ret = api->ep_dequeue(dev, cfg); | 
|  | } | 
|  |  | 
|  | ep_dequeue_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | struct net_buf *udc_ep_buf_alloc(const struct device *dev, | 
|  | const uint8_t ep, | 
|  | const size_t size) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct net_buf *buf = NULL; | 
|  | struct udc_buf_info *bi; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | buf = net_buf_alloc_len(&udc_ep_pool, size, K_NO_WAIT); | 
|  | if (!buf) { | 
|  | LOG_ERR("Failed to allocate net_buf %zd", size); | 
|  | goto ep_alloc_error; | 
|  | } | 
|  |  | 
|  | bi = udc_get_buf_info(buf); | 
|  | memset(bi, 0, sizeof(struct udc_buf_info)); | 
|  | bi->ep = ep; | 
|  | LOG_DBG("Allocate net_buf, ep 0x%02x, size %zd", ep, size); | 
|  |  | 
|  | ep_alloc_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | struct net_buf *udc_ctrl_alloc(const struct device *dev, | 
|  | const uint8_t ep, | 
|  | const size_t size) | 
|  | { | 
|  | /* TODO: for now just pass to udc_buf_alloc() */ | 
|  | return udc_ep_buf_alloc(dev, ep, size); | 
|  | } | 
|  |  | 
|  | static inline void udc_buf_destroy(struct net_buf *buf) | 
|  | { | 
|  | /* Adjust level and use together with the log in udc_ep_buf_alloc() */ | 
|  | LOG_DBG("destroy %p", buf); | 
|  | net_buf_destroy(buf); | 
|  | } | 
|  |  | 
|  | int udc_ep_buf_free(const struct device *dev, struct net_buf *const buf) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | int ret = 0; | 
|  |  | 
|  | api->lock(dev); | 
|  | net_buf_unref(buf); | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | enum udc_bus_speed udc_device_speed(const struct device *dev) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | enum udc_bus_speed speed = UDC_BUS_UNKNOWN; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_enabled(dev)) { | 
|  | goto device_speed_error; | 
|  | } | 
|  |  | 
|  | if (api->device_speed) { | 
|  | speed = api->device_speed(dev); | 
|  | } else { | 
|  | /* TODO: Shall we track connected status in UDC? */ | 
|  | speed = UDC_BUS_SPEED_FS; | 
|  | } | 
|  |  | 
|  | device_speed_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return speed; | 
|  | } | 
|  |  | 
|  | int udc_enable(const struct device *dev) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_data *data = dev->data; | 
|  | int ret; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_initialized(dev)) { | 
|  | ret = -EPERM; | 
|  | goto udc_enable_error; | 
|  | } | 
|  |  | 
|  | if (udc_is_enabled(dev)) { | 
|  | ret = -EALREADY; | 
|  | goto udc_enable_error; | 
|  | } | 
|  |  | 
|  | data->stage = CTRL_PIPE_STAGE_SETUP; | 
|  |  | 
|  | ret = api->enable(dev); | 
|  | if (ret == 0) { | 
|  | atomic_set_bit(&data->status, UDC_STATUS_ENABLED); | 
|  | } | 
|  |  | 
|  | udc_enable_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_disable(const struct device *dev) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_data *data = dev->data; | 
|  | int ret; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (!udc_is_enabled(dev)) { | 
|  | ret = -EALREADY; | 
|  | goto udc_disable_error; | 
|  | } | 
|  |  | 
|  | ret = api->disable(dev); | 
|  | atomic_clear_bit(&data->status, UDC_STATUS_ENABLED); | 
|  |  | 
|  | udc_disable_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_init(const struct device *dev, udc_event_cb_t event_cb) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_data *data = dev->data; | 
|  | int ret; | 
|  |  | 
|  | if (event_cb == NULL) { | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (udc_is_initialized(dev)) { | 
|  | ret = -EALREADY; | 
|  | goto udc_init_error; | 
|  | } | 
|  |  | 
|  | data->event_cb = event_cb; | 
|  |  | 
|  | ret = api->init(dev); | 
|  | if (ret == 0) { | 
|  | atomic_set_bit(&data->status, UDC_STATUS_INITIALIZED); | 
|  | } | 
|  |  | 
|  | udc_init_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | int udc_shutdown(const struct device *dev) | 
|  | { | 
|  | const struct udc_api *api = dev->api; | 
|  | struct udc_data *data = dev->data; | 
|  | int ret; | 
|  |  | 
|  | api->lock(dev); | 
|  |  | 
|  | if (udc_is_enabled(dev)) { | 
|  | ret = -EBUSY; | 
|  | goto udc_shutdown_error; | 
|  | } | 
|  |  | 
|  | if (!udc_is_initialized(dev)) { | 
|  | ret = -EALREADY; | 
|  | goto udc_shutdown_error; | 
|  | } | 
|  |  | 
|  | ret = api->shutdown(dev); | 
|  | atomic_clear_bit(&data->status, UDC_STATUS_INITIALIZED); | 
|  |  | 
|  | udc_shutdown_error: | 
|  | api->unlock(dev); | 
|  |  | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static ALWAYS_INLINE | 
|  | struct net_buf *udc_ctrl_alloc_stage(const struct device *dev, | 
|  | struct net_buf *const parent, | 
|  | const uint8_t ep, | 
|  | const size_t size) | 
|  | { | 
|  | struct net_buf *buf; | 
|  |  | 
|  | buf = udc_ctrl_alloc(dev, ep, size); | 
|  | if (buf == NULL) { | 
|  | return NULL; | 
|  | } | 
|  |  | 
|  | if (parent) { | 
|  | net_buf_frag_add(parent, buf); | 
|  | } | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | static struct net_buf *udc_ctrl_alloc_data(const struct device *dev, | 
|  | struct net_buf *const setup, | 
|  | const uint8_t ep) | 
|  | { | 
|  | size_t size = udc_data_stage_length(setup); | 
|  | struct udc_buf_info *bi; | 
|  | struct net_buf *buf; | 
|  |  | 
|  | buf = udc_ctrl_alloc_stage(dev, setup, ep, size); | 
|  | if (buf) { | 
|  | bi = udc_get_buf_info(buf); | 
|  | bi->data = true; | 
|  | } | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | static struct net_buf *udc_ctrl_alloc_status(const struct device *dev, | 
|  | struct net_buf *const parent, | 
|  | const uint8_t ep) | 
|  | { | 
|  | size_t size = (ep == USB_CONTROL_EP_OUT) ? 64 : 0; | 
|  | struct udc_buf_info *bi; | 
|  | struct net_buf *buf; | 
|  |  | 
|  | buf = udc_ctrl_alloc_stage(dev, parent, ep, size); | 
|  | if (buf) { | 
|  | bi = udc_get_buf_info(buf); | 
|  | bi->status = true; | 
|  | } | 
|  |  | 
|  | return buf; | 
|  | } | 
|  |  | 
|  | int udc_ctrl_submit_s_out_status(const struct device *dev, | 
|  | struct net_buf *const dout) | 
|  | { | 
|  | struct udc_buf_info *bi = udc_get_buf_info(dout); | 
|  | struct udc_data *data = dev->data; | 
|  | struct net_buf *buf; | 
|  | int ret = 0; | 
|  |  | 
|  | bi->data = true; | 
|  | net_buf_frag_add(data->setup, dout); | 
|  |  | 
|  | buf = udc_ctrl_alloc_status(dev, dout, USB_CONTROL_EP_IN); | 
|  | if (buf == NULL) { | 
|  | ret = -ENOMEM; | 
|  | } | 
|  |  | 
|  | return udc_submit_ep_event(dev, data->setup, ret); | 
|  | } | 
|  |  | 
|  | int udc_ctrl_submit_s_in_status(const struct device *dev) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  | struct net_buf *buf; | 
|  | int ret = 0; | 
|  |  | 
|  | if (!udc_ctrl_stage_is_data_in(dev)) { | 
|  | return -ENOTSUP; | 
|  | } | 
|  |  | 
|  | /* Allocate buffer for data stage IN */ | 
|  | buf = udc_ctrl_alloc_data(dev, data->setup, USB_CONTROL_EP_IN); | 
|  | if (buf == NULL) { | 
|  | ret = -ENOMEM; | 
|  | } | 
|  |  | 
|  | return udc_submit_ep_event(dev, data->setup, ret); | 
|  | } | 
|  |  | 
|  | int udc_ctrl_submit_s_status(const struct device *dev) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  | struct net_buf *buf; | 
|  | int ret = 0; | 
|  |  | 
|  | /* Allocate buffer for possible status IN */ | 
|  | buf = udc_ctrl_alloc_status(dev, data->setup, USB_CONTROL_EP_IN); | 
|  | if (buf == NULL) { | 
|  | ret = -ENOMEM; | 
|  | } | 
|  |  | 
|  | return udc_submit_ep_event(dev, data->setup, ret); | 
|  | } | 
|  |  | 
|  | int udc_ctrl_submit_status(const struct device *dev, | 
|  | struct net_buf *const buf) | 
|  | { | 
|  | struct udc_buf_info *bi = udc_get_buf_info(buf); | 
|  |  | 
|  | bi->status = true; | 
|  |  | 
|  | return udc_submit_ep_event(dev, buf, 0); | 
|  | } | 
|  |  | 
|  | bool udc_ctrl_stage_is_data_out(const struct device *dev) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  |  | 
|  | return data->stage == CTRL_PIPE_STAGE_DATA_OUT ? true : false; | 
|  | } | 
|  |  | 
|  | bool udc_ctrl_stage_is_data_in(const struct device *dev) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  |  | 
|  | return data->stage == CTRL_PIPE_STAGE_DATA_IN ? true : false; | 
|  | } | 
|  |  | 
|  | bool udc_ctrl_stage_is_status_out(const struct device *dev) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  |  | 
|  | return data->stage == CTRL_PIPE_STAGE_STATUS_OUT ? true : false; | 
|  | } | 
|  |  | 
|  | bool udc_ctrl_stage_is_status_in(const struct device *dev) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  |  | 
|  | return data->stage == CTRL_PIPE_STAGE_STATUS_IN ? true : false; | 
|  | } | 
|  |  | 
|  | bool udc_ctrl_stage_is_no_data(const struct device *dev) | 
|  | { | 
|  | struct udc_data *data = dev->data; | 
|  |  | 
|  | return data->stage == CTRL_PIPE_STAGE_NO_DATA ? true : false; | 
|  | } | 
|  |  | 
|  | static bool udc_data_stage_to_host(const struct net_buf *const buf) | 
|  | { | 
|  | struct usb_setup_packet *setup = (void *)buf->data; | 
|  |  | 
|  | return USB_REQTYPE_GET_DIR(setup->bmRequestType); | 
|  | } | 
|  |  | 
|  | void udc_ctrl_update_stage(const struct device *dev, | 
|  | struct net_buf *const buf) | 
|  | { | 
|  | struct udc_buf_info *bi = udc_get_buf_info(buf); | 
|  | struct udc_device_caps caps = udc_caps(dev); | 
|  | uint8_t next_stage = CTRL_PIPE_STAGE_ERROR; | 
|  | struct udc_data *data = dev->data; | 
|  |  | 
|  | __ASSERT(USB_EP_GET_IDX(bi->ep) == 0, | 
|  | "0x%02x is not a control endpoint", bi->ep); | 
|  |  | 
|  | if (bi->setup && bi->ep == USB_CONTROL_EP_OUT) { | 
|  | uint16_t length  = udc_data_stage_length(buf); | 
|  |  | 
|  | data->setup = buf; | 
|  |  | 
|  | if (data->stage != CTRL_PIPE_STAGE_SETUP) { | 
|  | LOG_INF("Sequence %u not completed", data->stage); | 
|  | data->stage = CTRL_PIPE_STAGE_SETUP; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Setup Stage has been completed (setup packet received), | 
|  | * regardless of the previous stage, this is now being reset. | 
|  | * Next state depends on wLength and the direction bit (D7). | 
|  | */ | 
|  | if (length == 0) { | 
|  | /* | 
|  | * No Data Stage, next is Status Stage | 
|  | * complete sequence: s->status | 
|  | */ | 
|  | LOG_DBG("s->(status)"); | 
|  | next_stage = CTRL_PIPE_STAGE_NO_DATA; | 
|  | } else if (udc_data_stage_to_host(buf)) { | 
|  | /* | 
|  | * Next is Data Stage (to host / IN) | 
|  | * complete sequence: s->in->status | 
|  | */ | 
|  | LOG_DBG("s->(in)"); | 
|  | next_stage = CTRL_PIPE_STAGE_DATA_IN; | 
|  | } else { | 
|  | /* | 
|  | * Next is Data Stage (to device / OUT) | 
|  | * complete sequence: s->out->status | 
|  | */ | 
|  | LOG_DBG("s->(out)"); | 
|  | next_stage = CTRL_PIPE_STAGE_DATA_OUT; | 
|  | } | 
|  |  | 
|  | } else if (bi->ep == USB_CONTROL_EP_OUT) { | 
|  | if (data->stage == CTRL_PIPE_STAGE_DATA_OUT) { | 
|  | /* | 
|  | * Next sequence is Status Stage if request is okay, | 
|  | * (IN ZLP status to host) | 
|  | */ | 
|  | next_stage = CTRL_PIPE_STAGE_STATUS_IN; | 
|  | } else if (data->stage == CTRL_PIPE_STAGE_STATUS_OUT) { | 
|  | /* | 
|  | * End of a sequence: s->in->status, | 
|  | * We should check the length here because we always | 
|  | * submit a OUT request with the minimum length | 
|  | * of the control endpoint. | 
|  | */ | 
|  | if (buf->len == 0) { | 
|  | LOG_DBG("s-in-status"); | 
|  | next_stage = CTRL_PIPE_STAGE_SETUP; | 
|  | } else { | 
|  | LOG_WRN("ZLP expected"); | 
|  | next_stage = CTRL_PIPE_STAGE_ERROR; | 
|  | } | 
|  | } else { | 
|  | LOG_ERR("Cannot determine the next stage"); | 
|  | next_stage = CTRL_PIPE_STAGE_ERROR; | 
|  | } | 
|  |  | 
|  | } else { /* if (bi->ep == USB_CONTROL_EP_IN) */ | 
|  | if (data->stage == CTRL_PIPE_STAGE_STATUS_IN) { | 
|  | /* | 
|  | * End of a sequence: setup->out->in | 
|  | */ | 
|  | LOG_DBG("s-out-status"); | 
|  | next_stage = CTRL_PIPE_STAGE_SETUP; | 
|  | } else if (data->stage == CTRL_PIPE_STAGE_DATA_IN) { | 
|  | /* | 
|  | * Data IN stage completed, next sequence | 
|  | * is Status Stage (OUT ZLP status to device). | 
|  | * over-engineered controllers can send status | 
|  | * on their own, skip this state then. | 
|  | */ | 
|  | if (caps.out_ack) { | 
|  | LOG_DBG("s-in->[status]"); | 
|  | next_stage = CTRL_PIPE_STAGE_SETUP; | 
|  | } else { | 
|  | LOG_DBG("s-in->(status)"); | 
|  | next_stage = CTRL_PIPE_STAGE_STATUS_OUT; | 
|  | } | 
|  | } else if (data->stage == CTRL_PIPE_STAGE_NO_DATA) { | 
|  | /* | 
|  | * End of a sequence (setup->in) | 
|  | * Previous NO Data stage was completed and | 
|  | * we confirmed it with an IN ZLP. | 
|  | */ | 
|  | LOG_DBG("s-status"); | 
|  | next_stage = CTRL_PIPE_STAGE_SETUP; | 
|  | } else { | 
|  | LOG_ERR("Cannot determine the next stage"); | 
|  | next_stage = CTRL_PIPE_STAGE_ERROR; | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | if (next_stage == data->stage) { | 
|  | LOG_WRN("State not changed!"); | 
|  | } | 
|  |  | 
|  | data->stage = next_stage; | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_UDC_WORKQUEUE) | 
|  | K_KERNEL_STACK_DEFINE(udc_work_q_stack, CONFIG_UDC_WORKQUEUE_STACK_SIZE); | 
|  |  | 
|  | struct k_work_q udc_work_q; | 
|  |  | 
|  | static int udc_work_q_init(void) | 
|  | { | 
|  |  | 
|  | k_work_queue_start(&udc_work_q, | 
|  | udc_work_q_stack, | 
|  | K_KERNEL_STACK_SIZEOF(udc_work_q_stack), | 
|  | CONFIG_UDC_WORKQUEUE_PRIORITY, NULL); | 
|  | k_thread_name_set(&udc_work_q.thread, "udc_work_q"); | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | SYS_INIT(udc_work_q_init, POST_KERNEL, CONFIG_KERNEL_INIT_PRIORITY_DEFAULT); | 
|  | #endif |