| /* |
| * Copyright (c) 2021-2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #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)]; |
| } |
| |
| 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, |
| const bool pending) |
| { |
| 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 = net_buf_get(&ep_cfg->fifo, K_NO_WAIT); |
| if (buf != NULL) { |
| ep_cfg->stat.pending = 0; |
| } else { |
| if (pending) { |
| ep_cfg->stat.pending = 1; |
| } |
| } |
| |
| return buf; |
| } |
| |
| 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, |
| const bool pending) |
| { |
| struct udc_ep_config *ep_cfg; |
| struct net_buf *buf = NULL; |
| |
| ep_cfg = udc_get_ep_cfg(dev, ep); |
| if (ep_cfg != NULL) { |
| buf = k_fifo_peek_head(&ep_cfg->fifo); |
| } |
| |
| if (buf == NULL && pending) { |
| ep_cfg->stat.pending = 1; |
| } |
| |
| if (buf != NULL) { |
| ep_cfg->stat.pending = 0; |
| } |
| |
| |
| return buf; |
| } |
| |
| 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 net_buf *const buf) |
| { |
| struct udc_data *data = dev->data; |
| struct udc_event drv_evt = { |
| .type = type, |
| .buf = buf, |
| .status = status, |
| .dev = dev, |
| }; |
| |
| if (!udc_is_initialized(dev)) { |
| return -EPERM; |
| } |
| |
| 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_iso_mps = caps.hs ? 1024 : 1023; |
| 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_ISO: |
| *mps = MIN(cfg->caps.mps, spec_iso_mps); |
| break; |
| case USB_EP_TYPE_CONTROL: |
| *mps = 64U; |
| break; |
| 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.pending = 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; |
| } |
| |
| int udc_ep_flush(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_flush_error; |
| } |
| |
| cfg = udc_get_ep_cfg(dev, ep); |
| if (cfg == NULL) { |
| ret = -ENODEV; |
| goto ep_flush_error; |
| } |
| |
| ret = api->ep_flush(dev, cfg); |
| |
| ep_flush_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_event(dev, UDC_EVT_EP_REQUEST, ret, data->setup); |
| } |
| |
| 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_event(dev, UDC_EVT_EP_REQUEST, ret, data->setup); |
| } |
| |
| 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_event(dev, UDC_EVT_EP_REQUEST, ret, data->setup); |
| } |
| |
| 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_event(dev, UDC_EVT_EP_REQUEST, 0, buf); |
| } |
| |
| 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(const struct device *dev) |
| { |
| ARG_UNUSED(dev); |
| |
| 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 |