osmo-asf4-dfu/usb/class/cdc/device/cdcdf_acm.c

380 lines
10 KiB
C

/**
* \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;
}