| /** |
| * \file |
| * |
| * \brief USB Device Stack CDC ACM Function Implementation. |
| * |
| * 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 "cdcdf_acm.h" |
| |
| #define CDCDF_ACM_VERSION 0x00000001u |
| #define CDCDF_ACM_COMM_EP_INDEX 0 |
| #define CDCDF_ACM_DATA_EP_INDEX 1 |
| |
| /** USB Device CDC ACM Fucntion Specific Data */ |
| struct cdcdf_acm_func_data { |
| /** CDC Device ACM Interface information */ |
| uint8_t func_iface[2]; |
| /** CDC Device ACM IN Endpoint */ |
| uint8_t func_ep_in[2]; |
| /** CDC Device ACM OUT Endpoint */ |
| uint8_t func_ep_out; |
| /** CDC Device ACM Enable Flag */ |
| bool enabled; |
| }; |
| |
| static struct usbdf_driver _cdcdf_acm; |
| static struct cdcdf_acm_func_data _cdcdf_acm_funcd; |
| static struct usb_cdc_line_coding usbd_cdc_line_coding; |
| |
| static cdcdf_acm_notify_state_t cdcdf_acm_notify_state = NULL; |
| static cdcdf_acm_set_line_coding_t cdcdf_acm_set_line_coding = NULL; |
| |
| /** |
| * \brief Enable CDC ACM Function |
| * \param[in] drv Pointer to USB device function driver |
| * \param[in] desc Pointer to USB interface descriptor |
| * \return Operation status. |
| */ |
| static int32_t cdcdf_acm_enable(struct usbdf_driver *drv, struct usbd_descriptors *desc) |
| { |
| struct cdcdf_acm_func_data *func_data = (struct cdcdf_acm_func_data *)(drv->func_data); |
| |
| usb_ep_desc_t ep_desc; |
| usb_iface_desc_t ifc_desc; |
| uint8_t * ifc, *ep; |
| uint8_t i; |
| |
| ifc = desc->sod; |
| for (i = 0; i < 2; i++) { |
| if (NULL == ifc) { |
| return ERR_NOT_FOUND; |
| } |
| |
| ifc_desc.bInterfaceNumber = ifc[2]; |
| ifc_desc.bInterfaceClass = ifc[5]; |
| |
| if ((CDC_CLASS_COMM == ifc_desc.bInterfaceClass) || (CDC_CLASS_DATA == ifc_desc.bInterfaceClass)) { |
| if (func_data->func_iface[i] == ifc_desc.bInterfaceNumber) { // Initialized |
| return ERR_ALREADY_INITIALIZED; |
| } else if (func_data->func_iface[i] != 0xFF) { // Occupied |
| return ERR_NO_RESOURCE; |
| } else { |
| func_data->func_iface[i] = ifc_desc.bInterfaceNumber; |
| } |
| } else { // Not supported by this function driver |
| return ERR_NOT_FOUND; |
| } |
| |
| // Install endpoints |
| ep = usb_find_desc(ifc, desc->eod, USB_DT_ENDPOINT); |
| while (NULL != ep) { |
| ep_desc.bEndpointAddress = ep[2]; |
| ep_desc.bmAttributes = ep[3]; |
| ep_desc.wMaxPacketSize = usb_get_u16(ep + 4); |
| if (usb_d_ep_init(ep_desc.bEndpointAddress, ep_desc.bmAttributes, ep_desc.wMaxPacketSize)) { |
| return ERR_NOT_INITIALIZED; |
| } |
| if (ep_desc.bEndpointAddress & USB_EP_DIR_IN) { |
| func_data->func_ep_in[i] = ep_desc.bEndpointAddress; |
| usb_d_ep_enable(func_data->func_ep_in[i]); |
| } else { |
| func_data->func_ep_out = ep_desc.bEndpointAddress; |
| usb_d_ep_enable(func_data->func_ep_out); |
| } |
| desc->sod = ep; |
| ep = usb_find_ep_desc(usb_desc_next(desc->sod), desc->eod); |
| } |
| ifc = usb_find_desc(usb_desc_next(desc->sod), desc->eod, USB_DT_INTERFACE); |
| } |
| // Installed |
| _cdcdf_acm_funcd.enabled = true; |
| return ERR_NONE; |
| } |
| |
| /** |
| * \brief Disable CDC ACM Function |
| * \param[in] drv Pointer to USB device function driver |
| * \param[in] desc Pointer to USB device descriptor |
| * \return Operation status. |
| */ |
| static int32_t cdcdf_acm_disable(struct usbdf_driver *drv, struct usbd_descriptors *desc) |
| { |
| struct cdcdf_acm_func_data *func_data = (struct cdcdf_acm_func_data *)(drv->func_data); |
| |
| usb_iface_desc_t ifc_desc; |
| uint8_t i; |
| |
| if (desc) { |
| ifc_desc.bInterfaceClass = desc->sod[5]; |
| // Check interface |
| if ((ifc_desc.bInterfaceClass != CDC_CLASS_COMM) && (ifc_desc.bInterfaceClass != CDC_CLASS_DATA)) { |
| return ERR_NOT_FOUND; |
| } |
| } |
| |
| for (i = 0; i < 2; i++) { |
| if (func_data->func_iface[i] == 0xFF) { |
| continue; |
| } else { |
| func_data->func_iface[i] = 0xFF; |
| if (func_data->func_ep_in[i] != 0xFF) { |
| usb_d_ep_deinit(func_data->func_ep_in[i]); |
| func_data->func_ep_in[i] = 0xFF; |
| } |
| } |
| } |
| |
| if (func_data->func_ep_out != 0xFF) { |
| usb_d_ep_deinit(func_data->func_ep_out); |
| func_data->func_ep_out = 0xFF; |
| } |
| |
| _cdcdf_acm_funcd.enabled = false; |
| return ERR_NONE; |
| } |
| |
| /** |
| * \brief CDC ACM Control Function |
| * \param[in] drv Pointer to USB device function driver |
| * \param[in] ctrl USB device general function control type |
| * \param[in] param Parameter pointer |
| * \return Operation status. |
| */ |
| static int32_t cdcdf_acm_ctrl(struct usbdf_driver *drv, enum usbdf_control ctrl, void *param) |
| { |
| switch (ctrl) { |
| case USBDF_ENABLE: |
| return cdcdf_acm_enable(drv, (struct usbd_descriptors *)param); |
| |
| case USBDF_DISABLE: |
| return cdcdf_acm_disable(drv, (struct usbd_descriptors *)param); |
| |
| case USBDF_GET_IFACE: |
| return ERR_UNSUPPORTED_OP; |
| |
| default: |
| return ERR_INVALID_ARG; |
| } |
| } |
| |
| /** |
| * \brief Process the CDC class set request |
| * \param[in] ep Endpoint address. |
| * \param[in] req Pointer to the request. |
| * \return Operation status. |
| */ |
| static int32_t cdcdf_acm_set_req(uint8_t ep, struct usb_req *req, enum usb_ctrl_stage stage) |
| { |
| struct usb_cdc_line_coding line_coding_tmp; |
| uint16_t len = req->wLength; |
| uint8_t * ctrl_buf = usbdc_get_ctrl_buffer(); |
| |
| switch (req->bRequest) { |
| case USB_REQ_CDC_SET_LINE_CODING: |
| if (sizeof(struct usb_cdc_line_coding) != len) { |
| return ERR_INVALID_DATA; |
| } |
| if (USB_SETUP_STAGE == stage) { |
| return usbdc_xfer(ep, ctrl_buf, len, false); |
| } else { |
| memcpy(&line_coding_tmp, ctrl_buf, sizeof(struct usb_cdc_line_coding)); |
| if ((NULL == cdcdf_acm_set_line_coding) || (true == cdcdf_acm_set_line_coding(&line_coding_tmp))) { |
| usbd_cdc_line_coding = line_coding_tmp; |
| } |
| return ERR_NONE; |
| } |
| case USB_REQ_CDC_SET_CONTROL_LINE_STATE: |
| usbdc_xfer(0, NULL, 0, 0); |
| if (NULL != cdcdf_acm_notify_state) { |
| cdcdf_acm_notify_state(req->wValue); |
| } |
| return ERR_NONE; |
| default: |
| return ERR_INVALID_ARG; |
| } |
| } |
| |
| /** |
| * \brief Process the CDC class get request |
| * \param[in] ep Endpoint address. |
| * \param[in] req Pointer to the request. |
| * \return Operation status. |
| */ |
| static int32_t cdcdf_acm_get_req(uint8_t ep, struct usb_req *req, enum usb_ctrl_stage stage) |
| { |
| uint16_t len = req->wLength; |
| |
| if (USB_DATA_STAGE == stage) { |
| return ERR_NONE; |
| } |
| |
| switch (req->bRequest) { |
| case USB_REQ_CDC_GET_LINE_CODING: |
| if (sizeof(struct usb_cdc_line_coding) != len) { |
| return ERR_INVALID_DATA; |
| } |
| return usbdc_xfer(ep, (uint8_t *)&usbd_cdc_line_coding, len, false); |
| default: |
| return ERR_INVALID_ARG; |
| } |
| } |
| |
| /** |
| * \brief Process the CDC class request |
| * \param[in] ep Endpoint address. |
| * \param[in] req Pointer to the request. |
| * \return Operation status. |
| */ |
| static int32_t cdcdf_acm_req(uint8_t ep, struct usb_req *req, enum usb_ctrl_stage stage) |
| { |
| if (0x01 != ((req->bmRequestType >> 5) & 0x03)) { // class request |
| return ERR_NOT_FOUND; |
| } |
| if ((req->wIndex == _cdcdf_acm_funcd.func_iface[0]) || (req->wIndex == _cdcdf_acm_funcd.func_iface[1])) { |
| if (req->bmRequestType & USB_EP_DIR_IN) { |
| return cdcdf_acm_get_req(ep, req, stage); |
| } else { |
| return cdcdf_acm_set_req(ep, req, stage); |
| } |
| } else { |
| return ERR_NOT_FOUND; |
| } |
| } |
| |
| /** USB Device CDC ACM Handler Struct */ |
| static struct usbdc_handler cdcdf_acm_req_h = {NULL, (FUNC_PTR)cdcdf_acm_req}; |
| |
| /** |
| * \brief Initialize the USB CDC ACM Function Driver |
| */ |
| int32_t cdcdf_acm_init(void) |
| { |
| if (usbdc_get_state() > USBD_S_POWER) { |
| return ERR_DENIED; |
| } |
| |
| _cdcdf_acm.ctrl = cdcdf_acm_ctrl; |
| _cdcdf_acm.func_data = &_cdcdf_acm_funcd; |
| |
| usbdc_register_function(&_cdcdf_acm); |
| usbdc_register_handler(USBDC_HDL_REQ, &cdcdf_acm_req_h); |
| return ERR_NONE; |
| } |
| |
| /** |
| * \brief Deinitialize the USB CDC ACM Function Driver |
| */ |
| void cdcdf_acm_deinit(void) |
| { |
| usb_d_ep_deinit(_cdcdf_acm_funcd.func_ep_in[CDCDF_ACM_COMM_EP_INDEX]); |
| usb_d_ep_deinit(_cdcdf_acm_funcd.func_ep_in[CDCDF_ACM_DATA_EP_INDEX]); |
| usb_d_ep_deinit(_cdcdf_acm_funcd.func_ep_out); |
| } |
| |
| /** |
| * \brief USB CDC ACM Function Read Data |
| */ |
| int32_t cdcdf_acm_read(uint8_t *buf, uint32_t size) |
| { |
| if (!cdcdf_acm_is_enabled()) { |
| return ERR_DENIED; |
| } |
| return usbdc_xfer(_cdcdf_acm_funcd.func_ep_out, buf, size, false); |
| } |
| |
| /** |
| * \brief USB CDC ACM Function Write Data |
| */ |
| int32_t cdcdf_acm_write(uint8_t *buf, uint32_t size) |
| { |
| if (!cdcdf_acm_is_enabled()) { |
| return ERR_DENIED; |
| } |
| return usbdc_xfer(_cdcdf_acm_funcd.func_ep_in[CDCDF_ACM_DATA_EP_INDEX], buf, size, true); |
| } |
| |
| /** |
| * \brief USB CDC ACM Stop the data transfer |
| */ |
| void cdcdf_acm_stop_xfer(void) |
| { |
| /* Stop transfer. */ |
| usb_d_ep_abort(_cdcdf_acm_funcd.func_ep_in[CDCDF_ACM_DATA_EP_INDEX]); |
| usb_d_ep_abort(_cdcdf_acm_funcd.func_ep_out); |
| } |
| |
| /** |
| * \brief USB CDC ACM Function Register Callback |
| */ |
| int32_t cdcdf_acm_register_callback(enum cdcdf_acm_cb_type cb_type, FUNC_PTR func) |
| { |
| switch (cb_type) { |
| case CDCDF_ACM_CB_READ: |
| usb_d_ep_register_callback(_cdcdf_acm_funcd.func_ep_out, USB_D_EP_CB_XFER, func); |
| break; |
| case CDCDF_ACM_CB_WRITE: |
| usb_d_ep_register_callback(_cdcdf_acm_funcd.func_ep_in[CDCDF_ACM_DATA_EP_INDEX], USB_D_EP_CB_XFER, func); |
| break; |
| case CDCDF_ACM_CB_LINE_CODING_C: |
| cdcdf_acm_set_line_coding = (cdcdf_acm_set_line_coding_t)func; |
| break; |
| case CDCDF_ACM_CB_STATE_C: |
| cdcdf_acm_notify_state = (cdcdf_acm_notify_state_t)func; |
| break; |
| default: |
| return ERR_INVALID_ARG; |
| } |
| return ERR_NONE; |
| } |
| |
| /** |
| * \brief Check whether CDC ACM Function is enabled |
| */ |
| bool cdcdf_acm_is_enabled(void) |
| { |
| return _cdcdf_acm_funcd.enabled; |
| } |
| |
| /** |
| * \brief Return the CDC ACM line coding structure start address |
| */ |
| const struct usb_cdc_line_coding *cdcdf_acm_get_line_coding(void) |
| { |
| return (const struct usb_cdc_line_coding *)&usbd_cdc_line_coding; |
| } |
| |
| /** |
| * \brief Return version |
| */ |
| uint32_t cdcdf_acm_get_version(void) |
| { |
| return CDCDF_ACM_VERSION; |
| } |