| /** |
| * \file |
| * |
| * \brief SAM USB device HAL |
| * |
| * Copyright (c) 2015-2018 Microchip Technology Inc. and its subsidiaries. |
| * |
| * \asf_license_start |
| * |
| * \page License |
| * |
| * Subject to your compliance with these terms, you may use Microchip |
| * software and any derivatives exclusively with Microchip products. |
| * It is your responsibility to comply with third party license terms applicable |
| * to your use of third party software (including open source software) that |
| * may accompany Microchip software. |
| * |
| * THIS SOFTWARE IS SUPPLIED BY MICROCHIP "AS IS". NO WARRANTIES, |
| * WHETHER EXPRESS, IMPLIED OR STATUTORY, APPLY TO THIS SOFTWARE, |
| * INCLUDING ANY IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY, |
| * AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT WILL MICROCHIP BE |
| * LIABLE FOR ANY INDIRECT, SPECIAL, PUNITIVE, INCIDENTAL OR CONSEQUENTIAL |
| * LOSS, DAMAGE, COST OR EXPENSE OF ANY KIND WHATSOEVER RELATED TO THE |
| * SOFTWARE, HOWEVER CAUSED, EVEN IF MICROCHIP HAS BEEN ADVISED OF THE |
| * POSSIBILITY OR THE DAMAGES ARE FORESEEABLE. TO THE FULLEST EXTENT |
| * ALLOWED BY LAW, MICROCHIP'S TOTAL LIABILITY ON ALL CLAIMS IN ANY WAY |
| * RELATED TO THIS SOFTWARE WILL NOT EXCEED THE AMOUNT OF FEES, IF ANY, |
| * THAT YOU HAVE PAID DIRECTLY TO MICROCHIP FOR THIS SOFTWARE. |
| * |
| * \asf_license_stop |
| * |
| */ |
| |
| #include "hal_usb_device.h" |
| #include "hal_atomic.h" |
| |
| #include <string.h> |
| |
| #ifdef __cplusplus |
| extern "C" { |
| #endif |
| |
| /** USB device HAL driver version. */ |
| #define USB_D_VERSION 0x00000001u |
| |
| /** |
| * Endpoint callbacks for data transfer. |
| */ |
| struct usb_d_ep_callbacks { |
| /** Callback that is invoked when setup packet is received. */ |
| usb_d_ep_cb_setup_t req; |
| /** Callback invoked when buffer is done, but last packet is full size |
| * packet without ZLP. Return \c true if new transfer has been submitted. |
| */ |
| usb_d_ep_cb_more_t more; |
| /** Callback invoked when transfer is finished/halted/aborted or error |
| * occurs. |
| */ |
| usb_d_ep_cb_xfer_t xfer; |
| }; |
| |
| /** |
| * Endpoint transfer descriptor header. |
| */ |
| struct usb_ep_xfer_hdr { |
| /** Transfer type, reuse \ref usb_ep_type. */ |
| uint8_t type; |
| /** Endpoint address. */ |
| uint8_t ep; |
| /** Endpoint state. */ |
| uint8_t state; |
| /** Last status code. */ |
| uint8_t status; |
| }; |
| |
| /** |
| * Transfer descriptor. |
| */ |
| struct usb_ep_xfer { |
| /** General transfer descriptor. */ |
| struct usb_ep_xfer_hdr hdr; |
| /** Pointer to data buffer. */ |
| uint8_t *buf; |
| /** Transfer size. */ |
| uint32_t size; |
| /** Control request packet. */ |
| uint8_t req[8]; |
| }; |
| |
| /** |
| * USB device endpoint descriptor. |
| */ |
| struct usb_d_ep { |
| /** On-going transfer on the endpoint. */ |
| struct usb_ep_xfer xfer; |
| /** Endpoint callbacks. */ |
| struct usb_d_ep_callbacks callbacks; |
| }; |
| |
| /** |
| * USB device HAL driver descriptor. |
| */ |
| struct usb_d_descriptor { |
| /** USB device endpoints. */ |
| struct usb_d_ep ep[CONF_USB_D_NUM_EP_SP]; |
| }; |
| |
| /** The USB HAL driver descriptor instance. */ |
| static struct usb_d_descriptor usb_d_inst; |
| |
| /** \brief Find the endpoint. |
| * \param[in] ep Endpoint address. |
| * \return Index of endpoint descriptor. |
| * \retval >=0 The index. |
| * \retval <0 Not found (endpoint is not initialized). |
| */ |
| static int8_t _usb_d_find_ep(const uint8_t ep) |
| { |
| int8_t i; |
| for (i = 0; i < CONF_USB_D_NUM_EP_SP; i++) { |
| if (usb_d_inst.ep[i].xfer.hdr.ep == ep) { |
| return i; |
| } |
| if (usb_d_inst.ep[i].xfer.hdr.type == USB_EP_XTYPE_CTRL |
| && (ep & USB_EP_N_MASK) == usb_d_inst.ep[i].xfer.hdr.ep) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| /** |
| * \brief Start transactions |
| * \param[in] ep Endpoint address. |
| * \param[in] dir Endpoint transfer direction. |
| * \param[in] buf Pointer to transfer buffer. |
| * \param[in] size Transfer size. |
| * \param[in] zlp Auto append ZLP for IN, or wait ZLP for OUT. |
| */ |
| static inline int32_t _usb_d_trans(const uint8_t ep, const bool dir, const uint8_t *buf, const uint32_t size, |
| const uint8_t zlp) |
| { |
| struct usb_d_transfer trans |
| = {(uint8_t *)buf, size, dir ? (uint8_t)(ep | USB_EP_DIR) : (uint8_t)(ep & USB_EP_N_MASK), zlp}; |
| |
| return _usb_d_dev_ep_trans(&trans); |
| } |
| |
| /** |
| * \brief Dummy callback that returns false |
| * \param[in] unused0 Unused parameter. |
| * \param[in] unused1 Unused parameter. |
| * \param[in] unused2 Unused parameter. |
| * \return Always \c false. |
| */ |
| static bool usb_d_dummy_cb_false(uint32_t unused0, uint32_t unused1, uint32_t unused2) |
| { |
| (void)unused0; |
| (void)unused1; |
| (void)unused2; |
| return false; |
| } |
| |
| /** |
| * \brief Callback invoked when SETUP packet is ready |
| * \param[in] ep Endpoint number with transfer direction on bit 8. |
| */ |
| static void usb_d_cb_trans_setup(const uint8_t ep) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| uint8_t * req = ept->xfer.req; |
| |
| uint8_t n = _usb_d_dev_ep_read_req(ep, req); |
| if (n != 8) { |
| _usb_d_dev_ep_stall(ep, USB_EP_STALL_SET); |
| _usb_d_dev_ep_stall(ep | USB_EP_DIR, USB_EP_STALL_SET); |
| return; |
| } |
| |
| _usb_d_dev_ep_stall(ep, USB_EP_STALL_CLR); |
| _usb_d_dev_ep_stall(ep | USB_EP_DIR, USB_EP_STALL_CLR); |
| ept->xfer.hdr.state = USB_EP_S_IDLE; |
| if (!ept->callbacks.req(ep, req)) { |
| ept->xfer.hdr.state = USB_EP_S_HALTED; |
| _usb_d_dev_ep_stall(ep, USB_EP_STALL_SET); |
| _usb_d_dev_ep_stall(ep | USB_EP_DIR, USB_EP_STALL_SET); |
| } |
| } |
| |
| /** |
| * \brief Callback invoked when request more data |
| * \param[in] ep Endpoint number with transfer direction on bit 8. |
| * \param[in] transfered Number of bytes transfered. |
| */ |
| static bool usb_d_cb_trans_more(const uint8_t ep, const uint32_t transfered) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| if (ept->xfer.hdr.state == USB_EP_S_X_DATA) { |
| return ept->callbacks.more(ep, transfered); |
| } |
| return false; |
| } |
| |
| /** |
| * \brief Handles the case that control endpoint transactions are done |
| * \param[in,out] ept Pointer to endpoint information. |
| */ |
| static inline void usb_d_ctrl_trans_done(struct usb_d_ep *ept) |
| { |
| uint8_t state = ept->xfer.hdr.state; |
| bool req_dir = USB_GET_bmRequestType(ept->xfer.req) & USB_REQ_TYPE_IN; |
| |
| if (state == USB_EP_S_X_DATA) { |
| /* Data stage -> Status stage */ |
| bool err = ept->callbacks.xfer(ept->xfer.hdr.ep, USB_XFER_DATA, ept->xfer.req); |
| if (err) { |
| ept->xfer.hdr.state = USB_EP_S_HALTED; |
| ept->xfer.hdr.status = USB_XFER_HALT; |
| _usb_d_dev_ep_stall(req_dir ? ept->xfer.hdr.ep : (ept->xfer.hdr.ep | USB_EP_DIR), USB_EP_STALL_SET); |
| } else { |
| ept->xfer.hdr.state = USB_EP_S_X_STATUS; |
| _usb_d_trans(ept->xfer.hdr.ep, !req_dir, NULL, 0, 1); |
| } |
| } else { |
| /* Status stage done */ |
| ept->callbacks.xfer(ept->xfer.hdr.ep, USB_XFER_DONE, ept->xfer.req); |
| ept->xfer.hdr.state = USB_EP_S_X_SETUP; |
| } |
| } |
| |
| /** |
| * Callback when USB transactions are finished. |
| */ |
| static void _usb_d_cb_trans_done(const uint8_t ep, const int32_t code, const uint32_t transferred) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| |
| if (code == USB_TRANS_DONE) { |
| ept->xfer.hdr.status = USB_XFER_DONE; |
| if (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) { |
| usb_d_ctrl_trans_done(ept); |
| return; |
| } |
| ept->xfer.hdr.state = USB_EP_S_IDLE; |
| } else if (code == USB_TRANS_STALL) { |
| ept->xfer.hdr.status = USB_XFER_HALT; |
| if (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) { |
| ept->xfer.hdr.state = USB_EP_S_X_SETUP; |
| _usb_d_dev_ep_stall(ep, USB_EP_STALL_CLR); |
| } else { |
| ept->xfer.hdr.state = USB_EP_S_HALTED; |
| } |
| } else if (code == USB_TRANS_ABORT) { |
| ept->xfer.hdr.status = USB_XFER_ABORT; |
| if (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) { |
| ept->xfer.hdr.state = USB_EP_S_X_SETUP; |
| return; |
| } |
| ept->xfer.hdr.state = USB_EP_S_IDLE; |
| } else if (code == USB_TRANS_RESET) { |
| ept->xfer.hdr.state = USB_EP_S_DISABLED; |
| ept->xfer.hdr.status = USB_XFER_RESET; |
| } else { |
| ept->xfer.hdr.state = USB_EP_S_ERROR; |
| ept->xfer.hdr.status = USB_XFER_ERROR; |
| } |
| |
| ept->callbacks.xfer(ep, (enum usb_xfer_code)ept->xfer.hdr.status, (void *)transferred); |
| } |
| |
| int32_t usb_d_init(void) |
| { |
| int32_t rc = _usb_d_dev_init(); |
| uint8_t i; |
| if (rc < 0) { |
| return rc; |
| } |
| memset(usb_d_inst.ep, 0x00, sizeof(struct usb_d_ep) * CONF_USB_D_NUM_EP_SP); |
| for (i = 0; i < CONF_USB_D_NUM_EP_SP; i++) { |
| usb_d_inst.ep[i].xfer.hdr.ep = 0xFF; |
| usb_d_inst.ep[i].callbacks.req = (usb_d_ep_cb_setup_t)usb_d_dummy_cb_false; |
| usb_d_inst.ep[i].callbacks.more = (usb_d_ep_cb_more_t)usb_d_dummy_cb_false; |
| usb_d_inst.ep[i].callbacks.xfer = (usb_d_ep_cb_xfer_t)usb_d_dummy_cb_false; |
| } |
| /* Handles device driver endpoint callbacks to build transfer. */ |
| _usb_d_dev_register_ep_callback(USB_D_DEV_EP_CB_SETUP, (FUNC_PTR)usb_d_cb_trans_setup); |
| _usb_d_dev_register_ep_callback(USB_D_DEV_EP_CB_MORE, (FUNC_PTR)usb_d_cb_trans_more); |
| _usb_d_dev_register_ep_callback(USB_D_DEV_EP_CB_DONE, (FUNC_PTR)_usb_d_cb_trans_done); |
| return ERR_NONE; |
| } |
| |
| void usb_d_deinit(void) |
| { |
| _usb_d_dev_deinit(); |
| } |
| |
| void usb_d_register_callback(const enum usb_d_cb_type type, const FUNC_PTR func) |
| { |
| /* Directly uses device driver callback. */ |
| _usb_d_dev_register_callback(type, func); |
| } |
| |
| int32_t usb_d_enable(void) |
| { |
| return _usb_d_dev_enable(); |
| } |
| |
| void usb_d_disable(void) |
| { |
| _usb_d_dev_disable(); |
| } |
| |
| void usb_d_attach(void) |
| { |
| _usb_d_dev_attach(); |
| } |
| |
| void usb_d_detach(void) |
| { |
| _usb_d_dev_detach(); |
| } |
| |
| enum usb_speed usb_d_get_speed(void) |
| { |
| return _usb_d_dev_get_speed(); |
| } |
| |
| uint16_t usb_d_get_frame_num(void) |
| { |
| return _usb_d_dev_get_frame_n(); |
| } |
| |
| uint8_t usb_d_get_uframe_num(void) |
| { |
| return _usb_d_dev_get_uframe_n(); |
| } |
| |
| void usb_d_set_address(const uint8_t addr) |
| { |
| _usb_d_dev_set_address(addr); |
| } |
| |
| void usb_d_send_remotewakeup(void) |
| { |
| _usb_d_dev_send_remotewakeup(); |
| } |
| |
| int32_t usb_d_ep0_init(const uint8_t max_pkt_size) |
| { |
| return usb_d_ep_init(0, USB_EP_XTYPE_CTRL, max_pkt_size); |
| } |
| |
| int32_t usb_d_ep_init(const uint8_t ep, const uint8_t attr, const uint16_t max_pkt_size) |
| { |
| int32_t rc; |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| if (ep_index >= 0) { |
| return -USB_ERR_REDO; |
| } else { |
| ep_index = _usb_d_find_ep(0xFF); |
| if (ep_index < 0) { |
| return -USB_ERR_ALLOC_FAIL; |
| } |
| ept = &usb_d_inst.ep[ep_index]; |
| } |
| rc = _usb_d_dev_ep_init(ep, attr, max_pkt_size); |
| if (rc < 0) { |
| return rc; |
| } |
| ept->xfer.hdr.ep = ep; |
| ept->xfer.hdr.type = attr & USB_EP_XTYPE_MASK; |
| return ERR_NONE; |
| } |
| |
| void usb_d_ep_deinit(const uint8_t ep) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| if (ep_index < 0) { |
| return; |
| } |
| _usb_d_dev_ep_deinit(ep); |
| ept->xfer.hdr.ep = 0xFF; |
| } |
| |
| int32_t usb_d_ep_enable(const uint8_t ep) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| int32_t rc; |
| if (ep_index < 0) { |
| return -USB_ERR_PARAM; |
| } |
| ept->xfer.hdr.state = (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) ? USB_EP_S_X_SETUP : USB_EP_S_IDLE; |
| rc = _usb_d_dev_ep_enable(ep); |
| if (rc < 0) { |
| ept->xfer.hdr.state = USB_EP_S_DISABLED; |
| } |
| return rc; |
| } |
| |
| void usb_d_ep_disable(const uint8_t ep) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| if (ep_index < 0) { |
| return; |
| } |
| _usb_d_dev_ep_disable(ep); |
| ept->xfer.hdr.state = USB_EP_S_DISABLED; |
| } |
| |
| uint8_t *usb_d_ep_get_req(const uint8_t ep) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| if (ep_index < 0) { |
| return NULL; |
| } |
| return usb_d_inst.ep[ep_index].xfer.req; |
| } |
| |
| int32_t usb_d_ep_transfer(const struct usb_d_transfer *xfer) |
| { |
| int8_t ep_index = _usb_d_find_ep(xfer->ep); |
| struct usb_d_ep * ept = &usb_d_inst.ep[ep_index]; |
| bool dir = USB_EP_GET_DIR(xfer->ep), zlp = xfer->zlp; |
| uint32_t len = xfer->size; |
| int32_t rc; |
| volatile uint8_t state; |
| volatile hal_atomic_t flags; |
| |
| if (ep_index < 0) { |
| return -USB_ERR_PARAM; |
| } |
| |
| atomic_enter_critical(&flags); |
| state = ept->xfer.hdr.state; |
| if (state == USB_EP_S_IDLE) { |
| ept->xfer.hdr.state = USB_EP_S_X_DATA; |
| atomic_leave_critical(&flags); |
| } else { |
| atomic_leave_critical(&flags); |
| switch (state) { |
| case USB_EP_S_HALTED: |
| return USB_HALTED; |
| case USB_EP_S_ERROR: |
| return -USB_ERROR; |
| case USB_EP_S_DISABLED: |
| return -USB_ERR_FUNC; |
| default: /* USB_EP_S_X_xxxx */ |
| return USB_BUSY; |
| } |
| } |
| |
| if (ept->xfer.hdr.type == USB_EP_XTYPE_CTRL) { |
| uint16_t req_len = USB_GET_wLength(ept->xfer.req); |
| /* SETUP without data: ZLP IN as status. */ |
| if (req_len == 0) { |
| dir = true; |
| len = 0; |
| zlp = true; |
| ept->xfer.hdr.state = USB_EP_S_X_STATUS; |
| } else { |
| dir = (USB_GET_bmRequestType(ept->xfer.req) & USB_REQ_TYPE_IN); |
| /* Data length not exceed requested. */ |
| if (len > req_len) { |
| len = req_len; |
| } |
| if (dir) { |
| /* Setup -> In */ |
| zlp = (req_len > len); |
| } else { |
| zlp = false; |
| } |
| } |
| } |
| |
| rc = _usb_d_trans(xfer->ep, dir, xfer->buf, len, zlp); |
| return rc; |
| } |
| |
| void usb_d_ep_abort(const uint8_t ep) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| if (ep_index < 0) { |
| return; |
| } |
| _usb_d_dev_ep_abort(ep); |
| ept->xfer.hdr.state = USB_EP_S_IDLE; |
| ept->xfer.hdr.status = USB_XFER_ABORT; |
| } |
| |
| int32_t usb_d_ep_get_status(const uint8_t ep, struct usb_d_ep_status *stat) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep * ept = &usb_d_inst.ep[ep_index]; |
| struct usb_d_trans_status tmp; |
| uint8_t state = ept->xfer.hdr.state; |
| if (ep_index < 0) { |
| return -USB_ERR_PARAM; |
| } |
| if (stat) { |
| /* Check transaction status if transferring data. */ |
| _usb_d_dev_ep_get_status(ep, &tmp); |
| stat->ep = ep; |
| stat->state = state; |
| stat->code = ept->xfer.hdr.status; |
| stat->count = tmp.count; |
| stat->size = tmp.size; |
| } |
| switch (state) { |
| case USB_EP_S_IDLE: |
| return USB_OK; |
| case USB_EP_S_HALTED: |
| return USB_HALTED; |
| case USB_EP_S_ERROR: |
| return -USB_ERROR; |
| case USB_EP_S_DISABLED: |
| return -USB_ERR_FUNC; |
| default: |
| /* Busy */ |
| return USB_BUSY; |
| } |
| } |
| |
| static inline int32_t _usb_d_ep_halt_clr(const uint8_t ep) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| int32_t rc; |
| if (ep_index < 0) { |
| return -USB_ERR_PARAM; |
| } |
| if (_usb_d_dev_ep_stall(ep, USB_EP_STALL_GET)) { |
| rc = _usb_d_dev_ep_stall(ep, USB_EP_STALL_CLR); |
| if (rc < 0) { |
| return rc; |
| } |
| ept->xfer.hdr.state = USB_EP_S_IDLE; |
| ept->xfer.hdr.status = USB_XFER_UNHALT; |
| ept->callbacks.xfer(ep, USB_XFER_UNHALT, NULL); |
| } |
| return ERR_NONE; |
| } |
| |
| int32_t usb_d_ep_halt(const uint8_t ep, const enum usb_ep_halt_ctrl ctrl) |
| { |
| if (ctrl == USB_EP_HALT_CLR) { |
| return _usb_d_ep_halt_clr(ep); |
| } else if (ctrl == USB_EP_HALT_SET) { |
| return _usb_d_dev_ep_stall(ep, USB_EP_STALL_SET); |
| } else { |
| return _usb_d_dev_ep_stall(ep, USB_EP_STALL_GET); |
| } |
| } |
| |
| void usb_d_ep_register_callback(const uint8_t ep, const enum usb_d_ep_cb_type type, const FUNC_PTR func) |
| { |
| int8_t ep_index = _usb_d_find_ep(ep); |
| struct usb_d_ep *ept = &usb_d_inst.ep[ep_index]; |
| FUNC_PTR f = func ? (FUNC_PTR)func : (FUNC_PTR)usb_d_dummy_cb_false; |
| if (ep_index < 0) { |
| return; |
| } |
| switch (type) { |
| case USB_D_EP_CB_SETUP: |
| ept->callbacks.req = (usb_d_ep_cb_setup_t)f; |
| break; |
| case USB_D_EP_CB_MORE: |
| ept->callbacks.more = (usb_d_ep_cb_more_t)f; |
| break; |
| case USB_D_EP_CB_XFER: |
| ept->callbacks.xfer = (usb_d_ep_cb_xfer_t)f; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| uint32_t usb_d_get_version(void) |
| { |
| return USB_D_VERSION; |
| } |
| |
| #ifdef __cplusplus |
| } |
| #endif |