| /* |
| * The MIT License (MIT) |
| * |
| * Copyright (c) 2019 Ha Thach (tinyusb.org) |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| * |
| * This file is part of the TinyUSB stack. |
| */ |
| |
| #include "tusb_option.h" |
| |
| #if CFG_TUD_ENABLED |
| |
| #include "dcd.h" |
| #include "tusb.h" |
| #include "device/usbd_pvt.h" |
| |
| //--------------------------------------------------------------------+ |
| // Callback weak stubs (called if application does not provide) |
| //--------------------------------------------------------------------+ |
| TU_ATTR_WEAK void dcd_edpt0_status_complete(uint8_t rhport, const tusb_control_request_t* request) { |
| (void) rhport; |
| (void) request; |
| } |
| |
| //--------------------------------------------------------------------+ |
| // MACRO CONSTANT TYPEDEF |
| //--------------------------------------------------------------------+ |
| |
| enum { |
| EDPT_CTRL_OUT = 0x00, |
| EDPT_CTRL_IN = 0x80 |
| }; |
| |
| typedef struct { |
| tusb_control_request_t request; |
| uint8_t* buffer; |
| uint16_t data_len; |
| uint16_t total_xferred; |
| usbd_control_xfer_cb_t complete_cb; |
| } usbd_control_xfer_t; |
| |
| static usbd_control_xfer_t _ctrl_xfer; |
| |
| CFG_TUD_MEM_SECTION static struct { |
| TUD_EPBUF_DEF(buf, CFG_TUD_ENDPOINT0_BUFSIZE); |
| } _ctrl_epbuf; |
| |
| //--------------------------------------------------------------------+ |
| // Application API |
| //--------------------------------------------------------------------+ |
| |
| // Queue ZLP status transaction |
| static inline bool status_stage_xact(uint8_t rhport, const tusb_control_request_t* request) { |
| // Opposite to endpoint in Data Phase |
| const uint8_t ep_addr = request->bmRequestType_bit.direction ? EDPT_CTRL_OUT : EDPT_CTRL_IN; |
| return usbd_edpt_xfer(rhport, ep_addr, NULL, 0, false); |
| } |
| |
| // Status phase |
| bool tud_control_status(uint8_t rhport, const tusb_control_request_t* request) { |
| _ctrl_xfer.request = (*request); |
| _ctrl_xfer.buffer = NULL; |
| _ctrl_xfer.total_xferred = 0; |
| _ctrl_xfer.data_len = 0; |
| |
| return status_stage_xact(rhport, request); |
| } |
| |
| // Queue a transaction in Data Stage |
| // Each transaction has up to Endpoint0's max packet size. |
| // This function can also transfer an zero-length packet |
| static bool data_stage_xact(uint8_t rhport) { |
| const uint16_t xact_len = tu_min16(_ctrl_xfer.data_len - _ctrl_xfer.total_xferred, CFG_TUD_ENDPOINT0_BUFSIZE); |
| uint8_t ep_addr = EDPT_CTRL_OUT; |
| |
| if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_IN) { |
| ep_addr = EDPT_CTRL_IN; |
| if (0u != xact_len) { |
| TU_VERIFY(0 == tu_memcpy_s(_ctrl_epbuf.buf, CFG_TUD_ENDPOINT0_BUFSIZE, _ctrl_xfer.buffer, xact_len)); |
| } |
| } |
| |
| return usbd_edpt_xfer(rhport, ep_addr, xact_len ? _ctrl_epbuf.buf : NULL, xact_len, false); |
| } |
| |
| // Transmit data to/from the control endpoint. |
| // If the request's wLength is zero, a status packet is sent instead. |
| bool tud_control_xfer(uint8_t rhport, const tusb_control_request_t* request, void* buffer, uint16_t len) { |
| _ctrl_xfer.request = (*request); |
| _ctrl_xfer.buffer = (uint8_t*) buffer; |
| _ctrl_xfer.total_xferred = 0U; |
| _ctrl_xfer.data_len = tu_min16(len, request->wLength); |
| |
| if (request->wLength > 0U) { |
| if (_ctrl_xfer.data_len > 0U) { |
| TU_ASSERT(buffer); |
| } |
| TU_ASSERT(data_stage_xact(rhport)); |
| } else { |
| TU_ASSERT(status_stage_xact(rhport, request)); |
| } |
| |
| return true; |
| } |
| |
| //--------------------------------------------------------------------+ |
| // USBD API |
| //--------------------------------------------------------------------+ |
| void usbd_control_reset(void); |
| void usbd_control_set_request(const tusb_control_request_t* request); |
| void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp); |
| bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes); |
| |
| void usbd_control_reset(void) { |
| tu_varclr(&_ctrl_xfer); |
| } |
| |
| // Set complete callback |
| void usbd_control_set_complete_callback(usbd_control_xfer_cb_t fp) { |
| _ctrl_xfer.complete_cb = fp; |
| } |
| |
| // for dcd_set_address where DCD is responsible for status response |
| void usbd_control_set_request(const tusb_control_request_t* request) { |
| _ctrl_xfer.request = (*request); |
| _ctrl_xfer.buffer = NULL; |
| _ctrl_xfer.total_xferred = 0; |
| _ctrl_xfer.data_len = 0; |
| } |
| |
| // callback when a transaction complete on |
| // - DATA stage of control endpoint or |
| // - Status stage |
| bool usbd_control_xfer_cb(uint8_t rhport, uint8_t ep_addr, xfer_result_t result, uint32_t xferred_bytes) { |
| (void) result; |
| |
| // Endpoint Address is opposite to direction bit, this is Status Stage complete event |
| if (tu_edpt_dir(ep_addr) != _ctrl_xfer.request.bmRequestType_bit.direction) { |
| TU_ASSERT(0 == xferred_bytes); |
| |
| // invoke optional dcd hook if available |
| dcd_edpt0_status_complete(rhport, &_ctrl_xfer.request); |
| |
| if (NULL != _ctrl_xfer.complete_cb) { |
| // TODO refactor with usbd_driver_print_control_complete_name |
| _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_ACK, &_ctrl_xfer.request); |
| } |
| |
| return true; |
| } |
| |
| if (_ctrl_xfer.request.bmRequestType_bit.direction == TUSB_DIR_OUT) { |
| TU_VERIFY(_ctrl_xfer.buffer); |
| memcpy(_ctrl_xfer.buffer, _ctrl_epbuf.buf, xferred_bytes); |
| TU_LOG_MEM(CFG_TUD_LOG_LEVEL, _ctrl_xfer.buffer, xferred_bytes, 2); |
| } |
| |
| _ctrl_xfer.total_xferred += (uint16_t) xferred_bytes; |
| _ctrl_xfer.buffer += xferred_bytes; |
| |
| // Data Stage is complete when all request's length are transferred or |
| // a short packet is sent including zero-length packet. |
| if ((_ctrl_xfer.request.wLength == _ctrl_xfer.total_xferred) || |
| (xferred_bytes < CFG_TUD_ENDPOINT0_BUFSIZE)) { |
| // DATA stage is complete |
| bool is_ok = true; |
| |
| // invoke complete callback if set |
| // callback can still stall control in status phase e.g out data does not make sense |
| if (NULL != _ctrl_xfer.complete_cb) { |
| #if CFG_TUSB_DEBUG >= CFG_TUD_LOG_LEVEL |
| usbd_driver_print_control_complete_name(_ctrl_xfer.complete_cb); |
| #endif |
| |
| is_ok = _ctrl_xfer.complete_cb(rhport, CONTROL_STAGE_DATA, &_ctrl_xfer.request); |
| } |
| |
| if (is_ok) { |
| TU_ASSERT(status_stage_xact(rhport, &_ctrl_xfer.request)); |
| } else { |
| // Stall both IN and OUT control endpoint |
| dcd_edpt_stall(rhport, EDPT_CTRL_OUT); |
| dcd_edpt_stall(rhport, EDPT_CTRL_IN); |
| } |
| } else { |
| // More data to transfer |
| TU_ASSERT(data_stage_xact(rhport)); |
| } |
| |
| return true; |
| } |
| |
| #endif |