| /* |
| * Copyright (c) 2022 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/sys/util.h> |
| #include <zephyr/drivers/usb/udc.h> |
| #include <zephyr/usb/usbd.h> |
| |
| #include "usbd_device.h" |
| #include "usbd_class.h" |
| #include "usbd_ch9.h" |
| #include "usbd_desc.h" |
| #include "usbd_endpoint.h" |
| |
| #include <zephyr/logging/log.h> |
| LOG_MODULE_REGISTER(usbd_ep, CONFIG_USBD_LOG_LEVEL); |
| |
| int usbd_ep_enable(const struct device *dev, |
| const struct usb_ep_descriptor *const ed, |
| uint32_t *const ep_bm) |
| { |
| int ret; |
| |
| ret = udc_ep_enable(dev, ed->bEndpointAddress, ed->bmAttributes, |
| ed->wMaxPacketSize, ed->bInterval); |
| if (ret == 0) { |
| usbd_ep_bm_set(ep_bm, ed->bEndpointAddress); |
| } |
| |
| return ret; |
| } |
| |
| int usbd_ep_disable(const struct device *dev, |
| const uint8_t ep, |
| uint32_t *const ep_bm) |
| { |
| int ret; |
| |
| ret = udc_ep_disable(dev, ep); |
| if (ret) { |
| return ret; |
| } |
| |
| usbd_ep_bm_clear(ep_bm, ep); |
| |
| ret = udc_ep_dequeue(dev, ep); |
| if (ret) { |
| return ret; |
| } |
| |
| k_yield(); |
| |
| return ret; |
| } |
| |
| static void usbd_ep_ctrl_set_zlp(struct usbd_context *const uds_ctx, |
| struct net_buf *const buf) |
| { |
| struct usb_setup_packet *setup = usbd_get_setup_pkt(uds_ctx); |
| size_t min_len = MIN(setup->wLength, buf->len); |
| |
| if (buf->len == 0) { |
| return; |
| } |
| |
| /* |
| * Set ZLP flag when host asks for a bigger length and the |
| * last chunk is wMaxPacketSize long, to indicate the last |
| * packet. |
| */ |
| if (setup->wLength > min_len && !(min_len % USB_CONTROL_EP_MPS)) { |
| /* |
| * Transfer length is less as requested by wLength and |
| * is multiple of wMaxPacketSize. |
| */ |
| LOG_DBG("add ZLP, wLength %u buf length %zu", |
| setup->wLength, min_len); |
| udc_ep_buf_set_zlp(buf); |
| |
| } |
| } |
| |
| /* |
| * All the functions below are part of public USB device support API. |
| */ |
| |
| int usbd_ep_ctrl_enqueue(struct usbd_context *const uds_ctx, |
| struct net_buf *const buf) |
| { |
| struct udc_buf_info *bi; |
| |
| bi = udc_get_buf_info(buf); |
| if (USB_EP_GET_IDX(bi->ep)) { |
| /* Not a control endpoint */ |
| return -EINVAL; |
| } |
| |
| if (USB_EP_DIR_IS_IN(bi->ep)) { |
| if (usbd_is_suspended(uds_ctx)) { |
| LOG_ERR("device is suspended"); |
| return -EPERM; |
| } |
| |
| usbd_ep_ctrl_set_zlp(uds_ctx, buf); |
| } |
| |
| return udc_ep_enqueue(uds_ctx->dev, buf); |
| } |
| |
| struct net_buf *usbd_ep_buf_alloc(const struct usbd_class_data *const c_data, |
| const uint8_t ep, const size_t size) |
| { |
| struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); |
| |
| return udc_ep_buf_alloc(uds_ctx->dev, ep, size); |
| } |
| |
| int usbd_ep_enqueue(const struct usbd_class_data *const c_data, |
| struct net_buf *const buf) |
| { |
| struct usbd_context *uds_ctx = usbd_class_get_ctx(c_data); |
| struct udc_buf_info *bi = udc_get_buf_info(buf); |
| |
| if (USB_EP_DIR_IS_IN(bi->ep)) { |
| if (usbd_is_suspended(uds_ctx)) { |
| return -EPERM; |
| } |
| } |
| |
| bi->owner = (void *)c_data; |
| |
| return udc_ep_enqueue(uds_ctx->dev, buf); |
| } |
| |
| int usbd_ep_buf_free(struct usbd_context *const uds_ctx, struct net_buf *buf) |
| { |
| return udc_ep_buf_free(uds_ctx->dev, buf); |
| } |
| |
| int usbd_ep_dequeue(struct usbd_context *const uds_ctx, const uint8_t ep) |
| { |
| return udc_ep_dequeue(uds_ctx->dev, ep); |
| } |
| |
| int usbd_ep_set_halt(struct usbd_context *const uds_ctx, const uint8_t ep) |
| { |
| struct usbd_ch9_data *ch9_data = &uds_ctx->ch9_data; |
| int ret; |
| |
| ret = udc_ep_set_halt(uds_ctx->dev, ep); |
| if (ret) { |
| LOG_WRN("Set halt 0x%02x failed", ep); |
| return ret; |
| } |
| |
| usbd_ep_bm_set(&ch9_data->ep_halt, ep); |
| |
| return ret; |
| } |
| |
| int usbd_ep_clear_halt(struct usbd_context *const uds_ctx, const uint8_t ep) |
| { |
| struct usbd_ch9_data *ch9_data = &uds_ctx->ch9_data; |
| int ret; |
| |
| ret = udc_ep_clear_halt(uds_ctx->dev, ep); |
| if (ret) { |
| LOG_WRN("Clear halt 0x%02x failed", ep); |
| return ret; |
| } |
| |
| usbd_ep_bm_clear(&ch9_data->ep_halt, ep); |
| |
| return ret; |
| } |
| |
| bool usbd_ep_is_halted(struct usbd_context *const uds_ctx, const uint8_t ep) |
| { |
| struct usbd_ch9_data *ch9_data = &uds_ctx->ch9_data; |
| |
| return usbd_ep_bm_is_set(&ch9_data->ep_halt, ep); |
| } |