blob: a151e60b076415ef2f713cf94f92a0ca51eadab5 [file] [log] [blame]
/**
* Copyright (c) 2016 - 2018, Nordic Semiconductor ASA
*
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form, except as embedded into a Nordic
* Semiconductor ASA integrated circuit in a product or a software update for
* such product, must reproduce the above copyright notice, this list of
* conditions and the following disclaimer in the documentation and/or other
* materials provided with the distribution.
*
* 3. Neither the name of Nordic Semiconductor ASA nor the names of its
* contributors may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* 4. This software, with or without modification, must only be used with a
* Nordic Semiconductor ASA integrated circuit.
*
* 5. Any software provided in binary form under this license must not be reverse
* engineered, decompiled, modified and/or disassembled.
*
* THIS SOFTWARE IS PROVIDED BY NORDIC SEMICONDUCTOR ASA "AS IS" AND ANY EXPRESS
* OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY, NONINFRINGEMENT, AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL NORDIC SEMICONDUCTOR ASA OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
* GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
#include "sdk_common.h"
#if NRF_MODULE_ENABLED(APP_USBD_CDC_ACM)
#include "app_usbd_cdc_acm.h"
#include <inttypes.h>
/**
* @defgroup app_usbd_cdc_acm_internal CDC ACM internals
* @{
* @ingroup app_usbd_cdc
* @internal
*/
#define NRF_LOG_MODULE_NAME cdc_acm
#if APP_USBD_CDC_ACM_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL APP_USBD_CDC_ACM_CONFIG_LOG_LEVEL
#define NRF_LOG_INFO_COLOR APP_USBD_CDC_ACM_CONFIG_INFO_COLOR
#define NRF_LOG_DEBUG_COLOR APP_USBD_CDC_ACM_CONFIG_DEBUG_COLOR
#else //APP_USBD_CDC_ACM_CONFIG_LOG_ENABLED
#define NRF_LOG_LEVEL 0
#endif //APP_USBD_CDC_ACM_CONFIG_LOG_ENABLED
#include "nrf_log.h"
NRF_LOG_MODULE_REGISTER();
#define APP_USBD_CDC_ACM_COMM_IFACE_IDX 0 /**< CDC ACM class comm interface index. */
#define APP_USBD_CDC_ACM_DATA_IFACE_IDX 1 /**< CDC ACM class data interface index. */
#define APP_USBD_CDC_ACM_COMM_EPIN_IDX 0 /**< CDC ACM comm class endpoint IN index. */
#define APP_USBD_CDC_ACM_DATA_EPIN_IDX 0 /**< CDC ACM data class endpoint IN index. */
#define APP_USBD_CDC_ACM_DATA_EPOUT_IDX 1 /**< CDC ACM data class endpoint OUT index. */
/**
* @brief Auxiliary function to access cdc_acm class instance data.
*
* @param[in] p_inst Class instance data.
*
* @return CDC ACM class instance.
*/
static inline app_usbd_cdc_acm_t const * cdc_acm_get(app_usbd_class_inst_t const * p_inst)
{
ASSERT(p_inst != NULL);
return (app_usbd_cdc_acm_t const *)p_inst;
}
/**
* @brief Auxiliary function to access cdc_acm class context data.
*
* @param[in] p_cdc_acm CDC ACM class instance data.
*
* @return CDC ACM class instance context.
*/
static inline app_usbd_cdc_acm_ctx_t * cdc_acm_ctx_get(app_usbd_cdc_acm_t const * p_cdc_acm)
{
ASSERT(p_cdc_acm != NULL);
ASSERT(p_cdc_acm->specific.p_data != NULL);
return &p_cdc_acm->specific.p_data->ctx;
}
/**
* @brief User event handler.
*
* @param[in] p_inst Class instance.
* @param[in] event user Event type.
*/
static inline void user_event_handler(app_usbd_class_inst_t const * p_inst,
app_usbd_cdc_acm_user_event_t event)
{
app_usbd_cdc_acm_t const * p_cdc_acm = cdc_acm_get(p_inst);
if (p_cdc_acm->specific.inst.user_ev_handler != NULL)
{
p_cdc_acm->specific.inst.user_ev_handler(p_inst, event);
}
}
/**
* @brief Auxiliary function to access CDC ACM COMM IN endpoint address.
*
* @param[in] p_inst Class instance data.
*
* @return IN endpoint address.
*/
static inline nrf_drv_usbd_ep_t comm_ep_in_addr_get(app_usbd_class_inst_t const * p_inst)
{
app_usbd_class_iface_conf_t const * class_iface;
class_iface = app_usbd_class_iface_get(p_inst, APP_USBD_CDC_ACM_COMM_IFACE_IDX);
app_usbd_class_ep_conf_t const * ep_cfg;
ep_cfg = app_usbd_class_iface_ep_get(class_iface, APP_USBD_CDC_ACM_COMM_EPIN_IDX);
return app_usbd_class_ep_address_get(ep_cfg);
}
/**
* @brief Auxiliary function to access CDC ACM DATA IN endpoint address.
*
* @param[in] p_inst Class instance data.
*
* @return IN endpoint address.
*/
static inline nrf_drv_usbd_ep_t data_ep_in_addr_get(app_usbd_class_inst_t const * p_inst)
{
app_usbd_class_iface_conf_t const * class_iface;
class_iface = app_usbd_class_iface_get(p_inst, APP_USBD_CDC_ACM_DATA_IFACE_IDX);
app_usbd_class_ep_conf_t const * ep_cfg;
ep_cfg = app_usbd_class_iface_ep_get(class_iface, APP_USBD_CDC_ACM_DATA_EPIN_IDX);
return app_usbd_class_ep_address_get(ep_cfg);
}
/**
* @brief Auxiliary function to access CDC ACM DATA OUT endpoint address.
*
* @param[in] p_inst Class instance data.
*
* @return OUT endpoint address.
*/
static inline nrf_drv_usbd_ep_t data_ep_out_addr_get(app_usbd_class_inst_t const * p_inst)
{
app_usbd_class_iface_conf_t const * class_iface;
class_iface = app_usbd_class_iface_get(p_inst, APP_USBD_CDC_ACM_DATA_IFACE_IDX);
app_usbd_class_ep_conf_t const * ep_cfg;
ep_cfg = app_usbd_class_iface_ep_get(class_iface, APP_USBD_CDC_ACM_DATA_EPOUT_IDX);
return app_usbd_class_ep_address_get(ep_cfg);
}
/**
* @brief Internal SETUP standard IN request handler.
*
* @param[in] p_inst Generic class instance.
* @param[in] p_setup_ev Setup event.
*
* @return Standard error code.
*/
static ret_code_t setup_req_std_in(app_usbd_class_inst_t const * p_inst,
app_usbd_setup_evt_t const * p_setup_ev)
{
/* Only Get Descriptor standard IN request is supported by CDC class */
if ((app_usbd_setup_req_rec(p_setup_ev->setup.bmRequestType) == APP_USBD_SETUP_REQREC_INTERFACE)
&&
(p_setup_ev->setup.bmRequest == APP_USBD_SETUP_STDREQ_GET_DESCRIPTOR))
{
size_t dsc_len = 0;
size_t max_size;
uint8_t * p_trans_buff = app_usbd_core_setup_transfer_buff_get(&max_size);
/* Try to find descriptor in class internals*/
ret_code_t ret = app_usbd_class_descriptor_find(
p_inst,
p_setup_ev->setup.wValue.hb,
p_setup_ev->setup.wValue.lb,
p_trans_buff,
&dsc_len);
if (ret != NRF_ERROR_NOT_FOUND)
{
ASSERT(dsc_len < NRF_DRV_USBD_EPSIZE);
return app_usbd_core_setup_rsp(&(p_setup_ev->setup), p_trans_buff, dsc_len);
}
}
return NRF_ERROR_NOT_SUPPORTED;
}
/**
* @brief Internal SETUP standard OUT request handler.
*
* @param[in] p_inst Generic class instance.
* @param[in] p_setup_ev Setup event.
*
* @return Standard error code.
*/
static ret_code_t setup_req_std_out(app_usbd_class_inst_t const * p_inst,
app_usbd_setup_evt_t const * p_setup_ev)
{
switch (p_setup_ev->setup.bmRequest)
{
default:
break;
}
return NRF_ERROR_NOT_SUPPORTED;
}
/**
* @brief Internal SETUP class IN request handler.
*
* @param[in] p_inst Generic class instance.
* @param[in] p_setup_ev Setup event.
*
* @return Standard error code.
*/
static ret_code_t setup_req_class_in(app_usbd_class_inst_t const * p_inst,
app_usbd_setup_evt_t const * p_setup_ev)
{
app_usbd_cdc_acm_t const * p_cdc_acm = cdc_acm_get(p_inst);
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
switch (p_setup_ev->setup.bmRequest)
{
case APP_USBD_CDC_REQ_GET_LINE_CODING:
{
if (p_setup_ev->setup.wLength.w != sizeof(app_usbd_cdc_line_coding_t))
{
return NRF_ERROR_NOT_SUPPORTED;
}
return app_usbd_core_setup_rsp(&p_setup_ev->setup,
&p_cdc_acm_ctx->line_coding,
sizeof(app_usbd_cdc_line_coding_t));
}
default:
break;
}
return NRF_ERROR_NOT_SUPPORTED;
}
/**
* @brief Class specific OUT request data callback.
*
* @param status Endpoint status.
* @param p_context Context of transfer (set by @ref app_usbd_core_setup_data_handler_set).
*
* @return Standard error code.
*/
static ret_code_t cdc_acm_req_out_data_cb(nrf_drv_usbd_ep_status_t status, void * p_context)
{
if (status != NRF_USBD_EP_OK)
{
return NRF_ERROR_INTERNAL;
}
app_usbd_cdc_acm_t const * p_cdc_acm = p_context;
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
switch (p_cdc_acm_ctx->request.type)
{
case APP_USBD_CDC_REQ_SET_LINE_CODING:
{
memcpy(&p_cdc_acm_ctx->line_coding,
&p_cdc_acm_ctx->request.payload.line_coding,
sizeof(app_usbd_cdc_line_coding_t));
NRF_LOG_INFO("REQ_SET_LINE_CODING: baudrate: %"PRIu32", databits: %u, "
"format: %u, parity: %u",
uint32_decode(p_cdc_acm_ctx->line_coding.dwDTERate),
p_cdc_acm_ctx->line_coding.bDataBits,
p_cdc_acm_ctx->line_coding.bCharFormat,
p_cdc_acm_ctx->line_coding.bParityType);
break;
}
default:
return NRF_ERROR_NOT_SUPPORTED;
}
return NRF_SUCCESS;
}
/**
* @brief Class specific request data stage setup.
*
* @param[in] p_inst Generic class instance.
* @param[in] p_setup_ev Setup event.
*
* @return Standard error code.
*/
static ret_code_t cdc_acm_req_out_datastage(app_usbd_class_inst_t const * p_inst,
app_usbd_setup_evt_t const * p_setup_ev)
{
app_usbd_cdc_acm_t const * p_cdc_acm = cdc_acm_get(p_inst);
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
p_cdc_acm_ctx->request.type = p_setup_ev->setup.bmRequest;
p_cdc_acm_ctx->request.len = p_setup_ev->setup.wLength.w;
/*Request setup data*/
NRF_DRV_USBD_TRANSFER_OUT(transfer,
&p_cdc_acm_ctx->request.payload,
p_cdc_acm_ctx->request.len);
ret_code_t ret;
CRITICAL_REGION_ENTER();
ret = app_usbd_ep_transfer(NRF_DRV_USBD_EPOUT0, &transfer);
if (ret == NRF_SUCCESS)
{
const app_usbd_core_setup_data_handler_desc_t desc = {
.handler = cdc_acm_req_out_data_cb,
.p_context = (void*)p_cdc_acm
};
ret = app_usbd_core_setup_data_handler_set(NRF_DRV_USBD_EPOUT0, &desc);
}
CRITICAL_REGION_EXIT();
return ret;
}
/**
* @brief Reset port to default state.
*
* @param p_inst Generic class instance.
*/
static void cdc_acm_reset_port(app_usbd_class_inst_t const * p_inst)
{
app_usbd_cdc_acm_t const * p_cdc_acm = cdc_acm_get(p_inst);
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
p_cdc_acm_ctx->line_state = 0;
// Set rx transfers configuration to default state.
p_cdc_acm_ctx->rx_transfer[0].p_buf = NULL;
p_cdc_acm_ctx->rx_transfer[1].p_buf = NULL;
p_cdc_acm_ctx->bytes_left = 0;
p_cdc_acm_ctx->bytes_read = 0;
p_cdc_acm_ctx->last_read = 0;
p_cdc_acm_ctx->cur_read = 0;
p_cdc_acm_ctx->p_copy_pos = p_cdc_acm_ctx->internal_rx_buf;
}
/**
* @brief Internal SETUP class OUT request handler.
*
* @param[in] p_inst Generic class instance.
* @param[in] p_setup_ev Setup event.
*
* @return Standard error code.
*/
static ret_code_t setup_req_class_out(app_usbd_class_inst_t const * p_inst,
app_usbd_setup_evt_t const * p_setup_ev)
{
app_usbd_cdc_acm_t const * p_cdc_acm = cdc_acm_get(p_inst);
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
switch (p_setup_ev->setup.bmRequest)
{
case APP_USBD_CDC_REQ_SET_LINE_CODING:
{
if (p_setup_ev->setup.wLength.w != sizeof(app_usbd_cdc_line_coding_t))
{
return NRF_ERROR_NOT_SUPPORTED;
}
return cdc_acm_req_out_datastage(p_inst, p_setup_ev);
}
case APP_USBD_CDC_REQ_SET_CONTROL_LINE_STATE:
{
if (p_setup_ev->setup.wLength.w != 0)
{
return NRF_ERROR_NOT_SUPPORTED;
}
NRF_LOG_INFO("REQ_SET_CONTROL_LINE_STATE: 0x%x", p_setup_ev->setup.wValue.w);
bool old_dtr = (p_cdc_acm_ctx->line_state & APP_USBD_CDC_ACM_LINE_STATE_DTR) ?
true : false;
p_cdc_acm_ctx->line_state = p_setup_ev->setup.wValue.w;
bool new_dtr = (p_cdc_acm_ctx->line_state & APP_USBD_CDC_ACM_LINE_STATE_DTR) ?
true : false;
if (old_dtr == new_dtr)
{
return NRF_SUCCESS;
}
const app_usbd_cdc_acm_user_event_t ev = new_dtr ?
APP_USBD_CDC_ACM_USER_EVT_PORT_OPEN : APP_USBD_CDC_ACM_USER_EVT_PORT_CLOSE;
user_event_handler(p_inst, ev);
if (!new_dtr)
{
/*Abort DATA endpoints on port close */
nrf_drv_usbd_ep_t ep;
ep = data_ep_in_addr_get(p_inst);
nrf_drv_usbd_ep_abort(ep);
ep = data_ep_out_addr_get(p_inst);
nrf_drv_usbd_ep_abort(ep);
// Set rx transfers configuration to default state.
p_cdc_acm_ctx->rx_transfer[0].p_buf = NULL;
p_cdc_acm_ctx->rx_transfer[1].p_buf = NULL;
p_cdc_acm_ctx->bytes_left = 0;
p_cdc_acm_ctx->bytes_read = 0;
p_cdc_acm_ctx->last_read = 0;
p_cdc_acm_ctx->cur_read = 0;
p_cdc_acm_ctx->p_copy_pos = p_cdc_acm_ctx->internal_rx_buf;
}
return NRF_SUCCESS;
}
default:
break;
}
return NRF_ERROR_NOT_SUPPORTED;
}
/**
* @brief Control endpoint handler.
*
* @param[in] p_inst Generic class instance.
* @param[in] p_setup_ev Setup event.
*
* @return Standard error code.
*/
static ret_code_t setup_event_handler(app_usbd_class_inst_t const * p_inst,
app_usbd_setup_evt_t const * p_setup_ev)
{
ASSERT(p_inst != NULL);
ASSERT(p_setup_ev != NULL);
if (app_usbd_setup_req_dir(p_setup_ev->setup.bmRequestType) == APP_USBD_SETUP_REQDIR_IN)
{
switch (app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType))
{
case APP_USBD_SETUP_REQTYPE_STD:
return setup_req_std_in(p_inst, p_setup_ev);
case APP_USBD_SETUP_REQTYPE_CLASS:
return setup_req_class_in(p_inst, p_setup_ev);
default:
break;
}
}
else /*APP_USBD_SETUP_REQDIR_OUT*/
{
switch (app_usbd_setup_req_typ(p_setup_ev->setup.bmRequestType))
{
case APP_USBD_SETUP_REQTYPE_STD:
return setup_req_std_out(p_inst, p_setup_ev);
case APP_USBD_SETUP_REQTYPE_CLASS:
return setup_req_class_out(p_inst, p_setup_ev);
default:
break;
}
}
return NRF_ERROR_NOT_SUPPORTED;
}
/**
* @brief CDC ACM consumer.
*
* @note See @ref nrf_drv_usbd_consumer_t
*/
static bool cdc_acm_consumer(nrf_drv_usbd_ep_transfer_t * p_next,
void * p_context,
size_t ep_size,
size_t data_size)
{
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = (app_usbd_cdc_acm_ctx_t *) p_context;
p_next->size = data_size;
if (data_size <= p_cdc_acm_ctx->rx_transfer[0].read_left)
{
p_next->p_data.rx = p_cdc_acm_ctx->rx_transfer[0].p_buf;
p_cdc_acm_ctx->rx_transfer[0].p_buf += data_size;
p_cdc_acm_ctx->bytes_read += data_size;
p_cdc_acm_ctx->rx_transfer[0].read_left -= data_size;
NRF_LOG_DEBUG("Received %d bytes. Space left in user buffer: %d bytes.",
data_size,
p_cdc_acm_ctx->rx_transfer[0].read_left);
return (p_cdc_acm_ctx->rx_transfer[0].read_left) != 0;
}
else
{
p_next->p_data.rx = p_cdc_acm_ctx->internal_rx_buf;
p_cdc_acm_ctx->cur_read = data_size;
NRF_LOG_DEBUG("Received %d bytes. Stored in internal buffer.", data_size);
return false;
}
}
/**
* @brief CDC ACM single transfer consumer.
*
* This function finalizes transfer after any received block.
*
* @note See @ref nrf_drv_usbd_consumer_t
*/
static bool cdc_acm_single_shoot_consumer(nrf_drv_usbd_ep_transfer_t * p_next,
void * p_context,
size_t ep_size,
size_t data_size)
{
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = (app_usbd_cdc_acm_ctx_t *) p_context;
p_next->size = data_size;
if (data_size <= p_cdc_acm_ctx->rx_transfer[0].read_left)
{
p_next->p_data.rx = p_cdc_acm_ctx->rx_transfer[0].p_buf;
p_cdc_acm_ctx->bytes_read = data_size;
p_cdc_acm_ctx->rx_transfer[0].read_left = data_size;
NRF_LOG_DEBUG("Received %d bytes. Space left in user buffer: %d bytes.",
data_size,
p_cdc_acm_ctx->rx_transfer[0].read_left);
}
else
{
p_next->p_data.rx = p_cdc_acm_ctx->internal_rx_buf;
p_cdc_acm_ctx->cur_read = data_size;
NRF_LOG_DEBUG("Received %d bytes. Stored in internal buffer.", data_size);
}
return false;
}
/**
* @brief Manage switching between user buffers and copying data from internal buffer.
*
* @param p_inst Generic USB class instance.
*
* @return Standard error code.
*/
static ret_code_t cdc_acm_rx_block_finished(app_usbd_class_inst_t const * p_inst)
{
app_usbd_cdc_acm_t const * p_cdc_acm = cdc_acm_get(p_inst);
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
nrf_drv_usbd_ep_t ep = data_ep_out_addr_get(p_inst);
nrf_drv_usbd_handler_desc_t handler_desc = {
.handler.consumer = cdc_acm_consumer,
.p_context = p_cdc_acm_ctx
};
if (p_cdc_acm_ctx->rx_transfer[0].read_left == 0) // Buffer completely filled by consumer
{
p_cdc_acm_ctx->last_read = p_cdc_acm_ctx->bytes_read;
p_cdc_acm_ctx->bytes_read = 0;
p_cdc_acm_ctx->bytes_left = 0;
if (p_cdc_acm_ctx->rx_transfer[1].p_buf != NULL)
{
p_cdc_acm_ctx->rx_transfer[0] = p_cdc_acm_ctx->rx_transfer[1];
p_cdc_acm_ctx->rx_transfer[1].p_buf = NULL;
return app_usbd_ep_handled_transfer(ep, &handler_desc);
}
else
{
p_cdc_acm_ctx->rx_transfer[0].p_buf = NULL;
return NRF_SUCCESS;
}
}
size_t bytes_read = p_cdc_acm_ctx->cur_read;
size_t bytes_to_cpy = bytes_read;
if (bytes_read > p_cdc_acm_ctx->rx_transfer[0].read_left)
{
bytes_to_cpy = p_cdc_acm_ctx->rx_transfer[0].read_left;
}
memcpy(p_cdc_acm_ctx->rx_transfer[0].p_buf,
p_cdc_acm_ctx->internal_rx_buf,
bytes_to_cpy);
// First buffer is full
p_cdc_acm_ctx->last_read = p_cdc_acm_ctx->bytes_read + bytes_to_cpy;
p_cdc_acm_ctx->bytes_read = 0;
p_cdc_acm_ctx->bytes_left = bytes_read - bytes_to_cpy;
p_cdc_acm_ctx->p_copy_pos = p_cdc_acm_ctx->internal_rx_buf + bytes_to_cpy;
if (p_cdc_acm_ctx->rx_transfer[1].p_buf != NULL)
{
// If there is second transfer, copy it to first
p_cdc_acm_ctx->rx_transfer[0] = p_cdc_acm_ctx->rx_transfer[1];
p_cdc_acm_ctx->rx_transfer[1].p_buf = NULL;
while (p_cdc_acm_ctx->bytes_left > 0)
{
if (p_cdc_acm_ctx->bytes_left >= p_cdc_acm_ctx->rx_transfer[0].read_left)
{
// If there are enough bytes left in internal buffer to completely fill next transfer,
// we call user event handler to obtain next buffer and continue double buffering.
memcpy(p_cdc_acm_ctx->rx_transfer[0].p_buf,
p_cdc_acm_ctx->p_copy_pos,
p_cdc_acm_ctx->rx_transfer[0].read_left);
p_cdc_acm_ctx->bytes_left -= p_cdc_acm_ctx->rx_transfer[0].read_left;
p_cdc_acm_ctx->p_copy_pos += p_cdc_acm_ctx->rx_transfer[0].read_left;
p_cdc_acm_ctx->last_read = p_cdc_acm_ctx->rx_transfer[0].read_left;
user_event_handler(p_inst, APP_USBD_CDC_ACM_USER_EVT_RX_DONE);
if (p_cdc_acm_ctx->rx_transfer[1].p_buf != NULL)
{
p_cdc_acm_ctx->rx_transfer[0] = p_cdc_acm_ctx->rx_transfer[1];
p_cdc_acm_ctx->rx_transfer[1].p_buf = NULL;
}
else
{
// If user does not specify a second buffer, all data transfers are done
// and data left in internal buffer is lost.
p_cdc_acm_ctx->rx_transfer[0].p_buf = NULL;
break;
}
}
else
{
memcpy(p_cdc_acm_ctx->rx_transfer[0].p_buf,
p_cdc_acm_ctx->p_copy_pos,
p_cdc_acm_ctx->bytes_left);
p_cdc_acm_ctx->bytes_read = p_cdc_acm_ctx->bytes_left;
p_cdc_acm_ctx->rx_transfer[0].read_left -= p_cdc_acm_ctx->bytes_left;
p_cdc_acm_ctx->rx_transfer[0].p_buf += p_cdc_acm_ctx->bytes_left;
break;
}
}
}
else
{
p_cdc_acm_ctx->rx_transfer[0].p_buf = NULL;
}
if (p_cdc_acm_ctx->rx_transfer[0].p_buf != NULL)
{
return app_usbd_ep_handled_transfer(ep, &handler_desc);
}
return NRF_SUCCESS;
}
/**
* @brief Class specific endpoint transfer handler.
*
* @param[in] p_inst Generic class instance.
* @param[in] p_setup_ev Setup event.
*
* @return Standard error code.
*/
static ret_code_t cdc_acm_endpoint_ev(app_usbd_class_inst_t const * p_inst,
app_usbd_complex_evt_t const * p_event)
{
if (comm_ep_in_addr_get(p_inst) == p_event->drv_evt.data.eptransfer.ep)
{
NRF_LOG_INFO("EPIN_COMM: notify");
return NRF_SUCCESS;
}
ret_code_t ret;
if (NRF_USBD_EPIN_CHECK(p_event->drv_evt.data.eptransfer.ep))
{
switch (p_event->drv_evt.data.eptransfer.status)
{
case NRF_USBD_EP_OK:
NRF_LOG_INFO("EPIN_DATA: %02x done", p_event->drv_evt.data.eptransfer.ep);
user_event_handler(p_inst, APP_USBD_CDC_ACM_USER_EVT_TX_DONE);
return NRF_SUCCESS;
case NRF_USBD_EP_ABORTED:
return NRF_SUCCESS;
default:
return NRF_ERROR_INTERNAL;
}
}
if (NRF_USBD_EPOUT_CHECK(p_event->drv_evt.data.eptransfer.ep))
{
switch (p_event->drv_evt.data.eptransfer.status)
{
case NRF_USBD_EP_OK:
ret = cdc_acm_rx_block_finished(p_inst);
NRF_LOG_INFO("EPOUT_DATA: %02x done", p_event->drv_evt.data.eptransfer.ep);
user_event_handler(p_inst, APP_USBD_CDC_ACM_USER_EVT_RX_DONE);
return ret;
case NRF_USBD_EP_WAITING:
case NRF_USBD_EP_ABORTED:
return NRF_SUCCESS;
default:
return NRF_ERROR_INTERNAL;
}
}
return NRF_ERROR_NOT_SUPPORTED;
}
/**
* @brief @ref app_usbd_class_methods_t::event_handler
*/
static ret_code_t cdc_acm_event_handler(app_usbd_class_inst_t const * p_inst,
app_usbd_complex_evt_t const * p_event)
{
ASSERT(p_inst != NULL);
ASSERT(p_event != NULL);
ret_code_t ret = NRF_SUCCESS;
switch (p_event->app_evt.type)
{
case APP_USBD_EVT_DRV_SOF:
break;
case APP_USBD_EVT_DRV_RESET:
cdc_acm_reset_port(p_inst);
break;
case APP_USBD_EVT_DRV_SETUP:
ret = setup_event_handler(p_inst, (app_usbd_setup_evt_t const *)p_event);
break;
case APP_USBD_EVT_DRV_EPTRANSFER:
ret = cdc_acm_endpoint_ev(p_inst, p_event);
break;
case APP_USBD_EVT_DRV_SUSPEND:
break;
case APP_USBD_EVT_DRV_RESUME:
break;
case APP_USBD_EVT_INST_APPEND:
break;
case APP_USBD_EVT_INST_REMOVE:
break;
case APP_USBD_EVT_STARTED:
break;
case APP_USBD_EVT_STOPPED:
break;
case APP_USBD_EVT_POWER_REMOVED:
cdc_acm_reset_port(p_inst);
break;
default:
ret = NRF_ERROR_NOT_SUPPORTED;
break;
}
return ret;
}
static bool cdc_acm_feed_descriptors(app_usbd_class_descriptor_ctx_t * p_ctx,
app_usbd_class_inst_t const * p_inst,
uint8_t * p_buff,
size_t max_size)
{
static uint8_t ifaces = 0;
ifaces = app_usbd_class_iface_count_get(p_inst);
app_usbd_cdc_acm_t const * p_cdc_acm = cdc_acm_get(p_inst);
APP_USBD_CLASS_DESCRIPTOR_BEGIN(p_ctx, p_buff, max_size);
/* INTERFACE ASSOCIATION DESCRIPTOR */
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x08); // bLength
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_DESCRIPTOR_INTERFACE_ASSOCIATION); // bDescriptorType = Interface Association
APP_USBD_CLASS_DESCRIPTOR_WRITE(p_cdc_acm->specific.inst.comm_interface); // bFirstInterface
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x02); // bInterfaceCount
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_COMM_CLASS); // bFunctionClass
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_SUBCLASS_ACM); // bFunctionSubClass
APP_USBD_CLASS_DESCRIPTOR_WRITE(p_cdc_acm->specific.inst.protocol); // bFunctionProtocol
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x00); // iFunction
static uint8_t i = 0;
for (i = 0; i < ifaces; i++)
{
/* INTERFACE DESCRIPTOR */
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x09); // bLength
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_DESCRIPTOR_INTERFACE); // bDescriptorType = Interface
static app_usbd_class_iface_conf_t const * p_cur_iface = NULL;
p_cur_iface = app_usbd_class_iface_get(p_inst, i);
APP_USBD_CLASS_DESCRIPTOR_WRITE(app_usbd_class_iface_number_get(p_cur_iface)); // bInterfaceNumber
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x00); // bAlternateSetting
APP_USBD_CLASS_DESCRIPTOR_WRITE(app_usbd_class_iface_ep_count_get(p_cur_iface)); // bNumEndpoints
if (p_cdc_acm->specific.inst.comm_interface == app_usbd_class_iface_number_get(p_cur_iface))
{
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_COMM_CLASS); // bInterfaceClass = CDC COMM
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_SUBCLASS_ACM); // bInterfaceSubclass
APP_USBD_CLASS_DESCRIPTOR_WRITE(p_cdc_acm->specific.inst.protocol); // bInterfaceProtocol
}
else if (p_cdc_acm->specific.inst.data_interface ==
app_usbd_class_iface_number_get(p_cur_iface))
{
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_DATA_CLASS); // bInterfaceClass = CDC DATA
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x00); // bInterfaceSubclass
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x00); // bInterfaceProtocol
}
else
{
ASSERT(0);
}
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x00); // iInterface
if (p_cdc_acm->specific.inst.comm_interface == app_usbd_class_iface_number_get(p_cur_iface))
{
/* HEADER DESCRIPTOR */
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x05); // bLength
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_CS_INTERFACE); // bDescriptorType = Class Specific Interface
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_SCS_HEADER); // bDescriptorSubtype = Header Functional Descriptor
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x10); // bcdCDC LSB
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x01); // bcdCDC MSB
/* CALL MANAGEMENT DESCRIPTOR */
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x05); // bLength
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_CS_INTERFACE); // bDescriptorType = Class Specific Interface
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_SCS_CALL_MGMT); // bDescriptorSubtype = Call Management Functional Descriptor
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x03); // bmCapabilities
APP_USBD_CLASS_DESCRIPTOR_WRITE(p_cdc_acm->specific.inst.data_interface); // bDataInterface
/* ABSTRACT CONTROL MANAGEMENT DESCRIPTOR */
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x04); // bLength
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_CS_INTERFACE); // bDescriptorType = Class Specific Interface
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_SCS_ACM); // bDescriptorSubtype = Abstract Control Management Functional Descriptor
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x02); // bmCapabilities
/* UNION DESCRIPTOR */
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x05);
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_CS_INTERFACE); // bDescriptorType = Class Specific Interface
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_CDC_SCS_UNION); // bDescriptorSubtype = Union Functional Descriptor
APP_USBD_CLASS_DESCRIPTOR_WRITE(p_cdc_acm->specific.inst.comm_interface); // bControlInterface
APP_USBD_CLASS_DESCRIPTOR_WRITE(p_cdc_acm->specific.inst.data_interface); // bSubordinateInterface
}
else if (p_cdc_acm->specific.inst.data_interface ==
app_usbd_class_iface_number_get(p_cur_iface))
{
;
}
else
{
ASSERT(0);
}
/* ENDPOINT DESCRIPTORS */
static uint8_t endpoints = 0;
endpoints = app_usbd_class_iface_ep_count_get(p_cur_iface);
static uint8_t j = 0;
for (j = 0; j < endpoints; j++)
{
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x07); // bLength
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_DESCRIPTOR_ENDPOINT); // bDescriptorType = Endpoint
static app_usbd_class_ep_conf_t const * p_cur_ep = NULL;
p_cur_ep = app_usbd_class_iface_ep_get(p_cur_iface, j);
APP_USBD_CLASS_DESCRIPTOR_WRITE(app_usbd_class_ep_address_get(p_cur_ep)); // bEndpointAddress
if (p_cdc_acm->specific.inst.comm_interface ==
app_usbd_class_iface_number_get(p_cur_iface))
{
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_DESCRIPTOR_EP_ATTR_TYPE_INTERRUPT); // bmAttributes
}
else if (p_cdc_acm->specific.inst.data_interface ==
app_usbd_class_iface_number_get(p_cur_iface))
{
APP_USBD_CLASS_DESCRIPTOR_WRITE(APP_USBD_DESCRIPTOR_EP_ATTR_TYPE_BULK); // bmAttributes
}
else
{
ASSERT(0);
}
APP_USBD_CLASS_DESCRIPTOR_WRITE(LSB_16(NRF_DRV_USBD_EPSIZE)); // wMaxPacketSize LSB
APP_USBD_CLASS_DESCRIPTOR_WRITE(MSB_16(NRF_DRV_USBD_EPSIZE)); // wMaxPacketSize MSB
if (p_cdc_acm->specific.inst.comm_interface ==
app_usbd_class_iface_number_get(p_cur_iface))
{
APP_USBD_CLASS_DESCRIPTOR_WRITE(p_cdc_acm->specific.inst.p_ep_interval[0]); // bInterval
}
else if (p_cdc_acm->specific.inst.data_interface ==
app_usbd_class_iface_number_get(p_cur_iface))
{
APP_USBD_CLASS_DESCRIPTOR_WRITE(0x00); // bInterval
}
else
{
ASSERT(0);
}
}
}
APP_USBD_CLASS_DESCRIPTOR_END();
}
/**
* @brief Public cdc_acm class interface.
*
*/
const app_usbd_class_methods_t app_usbd_cdc_acm_class_methods = {
.event_handler = cdc_acm_event_handler,
.feed_descriptors = cdc_acm_feed_descriptors,
};
/** @} */
ret_code_t app_usbd_cdc_acm_write(app_usbd_cdc_acm_t const * p_cdc_acm,
const void * p_buf,
size_t length)
{
app_usbd_class_inst_t const * p_inst = app_usbd_cdc_acm_class_inst_get(p_cdc_acm);
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
bool dtr_state = (p_cdc_acm_ctx->line_state & APP_USBD_CDC_ACM_LINE_STATE_DTR) ?
true : false;
if (!dtr_state)
{
/*Port is not opened*/
return NRF_ERROR_INVALID_STATE;
}
nrf_drv_usbd_ep_t ep = data_ep_in_addr_get(p_inst);
if (APP_USBD_CDC_ACM_ZLP_ON_EPSIZE_WRITE && ((length % NRF_DRV_USBD_EPSIZE) == 0))
{
NRF_DRV_USBD_TRANSFER_IN_ZLP(transfer, p_buf, length);
return app_usbd_ep_transfer(ep, &transfer);
}
else
{
NRF_DRV_USBD_TRANSFER_IN(transfer, p_buf, length);
return app_usbd_ep_transfer(ep, &transfer);
}
}
size_t app_usbd_cdc_acm_rx_size(app_usbd_cdc_acm_t const * p_cdc_acm)
{
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
return p_cdc_acm_ctx->last_read;
}
size_t app_usbd_cdc_acm_bytes_stored(app_usbd_cdc_acm_t const * p_cdc_acm)
{
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
return p_cdc_acm_ctx->bytes_left;
}
ret_code_t app_usbd_cdc_acm_read(app_usbd_cdc_acm_t const * p_cdc_acm,
void * p_buf,
size_t length)
{
ASSERT(p_buf != NULL);
ret_code_t ret;
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
if (0U == (p_cdc_acm_ctx->line_state & APP_USBD_CDC_ACM_LINE_STATE_DTR))
{
/*Port is not opened*/
return NRF_ERROR_INVALID_STATE;
}
#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE == 0)
CRITICAL_REGION_ENTER();
#endif // (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE == 0)
if (p_cdc_acm_ctx->rx_transfer[0].p_buf == NULL)
{
if (p_cdc_acm_ctx->bytes_left >= length)
{
memcpy(p_buf, p_cdc_acm_ctx->p_copy_pos, length);
p_cdc_acm_ctx->bytes_left -= length;
p_cdc_acm_ctx->p_copy_pos += length;
p_cdc_acm_ctx->last_read = length;
ret = NRF_SUCCESS;
}
else
{
p_cdc_acm_ctx->rx_transfer[0].p_buf = p_buf;
p_cdc_acm_ctx->rx_transfer[0].read_left = length;
nrf_drv_usbd_ep_t ep = data_ep_out_addr_get(app_usbd_cdc_acm_class_inst_get(p_cdc_acm));
nrf_drv_usbd_handler_desc_t const handler_desc = {
.handler.consumer = cdc_acm_consumer,
.p_context = p_cdc_acm_ctx
};
if (p_cdc_acm_ctx->bytes_left > 0)
{
memcpy(p_cdc_acm_ctx->rx_transfer[0].p_buf,
p_cdc_acm_ctx->p_copy_pos,
p_cdc_acm_ctx->bytes_left);
p_cdc_acm_ctx->rx_transfer[0].read_left -= p_cdc_acm_ctx->bytes_left;
p_cdc_acm_ctx->rx_transfer[0].p_buf += p_cdc_acm_ctx->bytes_left;
p_cdc_acm_ctx->bytes_read = p_cdc_acm_ctx->bytes_left;
p_cdc_acm_ctx->bytes_left = 0;
}
ret = app_usbd_ep_handled_transfer(ep, &handler_desc);
if (ret == NRF_SUCCESS)
{
ret = NRF_ERROR_IO_PENDING;
}
}
}
else if (p_cdc_acm_ctx->rx_transfer[1].p_buf == NULL)
{
p_cdc_acm_ctx->rx_transfer[1].p_buf = p_buf;
p_cdc_acm_ctx->rx_transfer[1].read_left = length;
ret = NRF_ERROR_IO_PENDING;
}
else
{
ret = NRF_ERROR_BUSY;
}
#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE == 0)
CRITICAL_REGION_EXIT();
#endif // (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE == 0)
return ret;
}
ret_code_t app_usbd_cdc_acm_read_any(app_usbd_cdc_acm_t const * p_cdc_acm,
void * p_buf,
size_t length)
{
ASSERT(p_buf != NULL);
ret_code_t ret;
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
if (0U == (p_cdc_acm_ctx->line_state & APP_USBD_CDC_ACM_LINE_STATE_DTR))
{
/*Port is not opened*/
return NRF_ERROR_INVALID_STATE;
}
#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE == 0)
CRITICAL_REGION_ENTER();
#endif // (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE == 0)
if (p_cdc_acm_ctx->bytes_left > 0)
{
size_t to_copy = MIN(length, p_cdc_acm_ctx->bytes_left);
memcpy(p_buf, p_cdc_acm_ctx->p_copy_pos, to_copy);
p_cdc_acm_ctx->bytes_left -= to_copy;
p_cdc_acm_ctx->p_copy_pos += to_copy;
p_cdc_acm_ctx->last_read = to_copy;
ret = NRF_SUCCESS;
}
else
{
if (p_cdc_acm_ctx->rx_transfer[0].p_buf == NULL)
{
p_cdc_acm_ctx->rx_transfer[0].p_buf = p_buf;
p_cdc_acm_ctx->rx_transfer[0].read_left = length;
nrf_drv_usbd_ep_t ep = data_ep_out_addr_get(app_usbd_cdc_acm_class_inst_get(p_cdc_acm));
nrf_drv_usbd_handler_desc_t const handler_desc = {
.handler.consumer = cdc_acm_single_shoot_consumer,
.p_context = p_cdc_acm_ctx
};
ret = app_usbd_ep_handled_transfer(ep, &handler_desc);
if (ret == NRF_SUCCESS)
{
ret = NRF_ERROR_IO_PENDING;
}
}
else
{
ret = NRF_ERROR_BUSY;
}
}
#if (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE == 0)
CRITICAL_REGION_EXIT();
#endif // (APP_USBD_CONFIG_EVENT_QUEUE_ENABLE == 0)
return ret;
}
static ret_code_t cdc_acm_serial_state_notify(app_usbd_cdc_acm_t const * p_cdc_acm)
{
app_usbd_class_inst_t const * p_inst = app_usbd_cdc_acm_class_inst_get(p_cdc_acm);
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
nrf_drv_usbd_ep_t ep = comm_ep_in_addr_get(p_inst);
NRF_DRV_USBD_TRANSFER_OUT(transfer,
&p_cdc_acm_ctx->request.payload,
sizeof(app_usbd_cdc_acm_notify_t));
return app_usbd_ep_transfer(ep, &transfer);
}
ret_code_t app_usbd_cdc_acm_serial_state_notify(app_usbd_cdc_acm_t const * p_cdc_acm,
app_usbd_cdc_acm_serial_state_t serial_state,
bool value)
{
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
ret_code_t ret;
CRITICAL_REGION_ENTER();
ret = NRF_SUCCESS;
switch (serial_state)
{
case APP_USBD_CDC_ACM_SERIAL_STATE_DCD:
case APP_USBD_CDC_ACM_SERIAL_STATE_DSR:
case APP_USBD_CDC_ACM_SERIAL_STATE_BREAK:
case APP_USBD_CDC_ACM_SERIAL_STATE_RING:
case APP_USBD_CDC_ACM_SERIAL_STATE_FRAMING:
case APP_USBD_CDC_ACM_SERIAL_STATE_PARITY:
case APP_USBD_CDC_ACM_SERIAL_STATE_OVERRUN:
if (value)
{
p_cdc_acm_ctx->serial_state |= serial_state;
}
else
{
p_cdc_acm_ctx->serial_state &= ~serial_state;
}
break;
default:
ret = NRF_ERROR_NOT_SUPPORTED;
break;
}
if (ret == NRF_SUCCESS)
{
app_usbd_cdc_acm_notify_t * notify = &p_cdc_acm_ctx->request.payload.notify;
notify->cdc_notify.bmRequestType = app_usbd_setup_req_val(APP_USBD_SETUP_REQREC_INTERFACE,
APP_USBD_SETUP_REQTYPE_CLASS,
APP_USBD_SETUP_REQDIR_IN);
notify->cdc_notify.bmRequest = APP_USBD_CDC_NOTIF_SERIAL_STATE;
notify->cdc_notify.wValue = 0;
notify->cdc_notify.wIndex = 0;
notify->cdc_notify.wLength = sizeof(notify->serial_state);
notify->serial_state = p_cdc_acm_ctx->serial_state;
ret = cdc_acm_serial_state_notify(p_cdc_acm);
}
CRITICAL_REGION_EXIT();
return ret;
}
ret_code_t app_usbd_cdc_acm_line_state_get(app_usbd_cdc_acm_t const * p_cdc_acm,
app_usbd_cdc_acm_line_state_t line_state,
uint32_t * value)
{
app_usbd_cdc_acm_ctx_t * p_cdc_acm_ctx = cdc_acm_ctx_get(p_cdc_acm);
ret_code_t ret;
CRITICAL_REGION_ENTER();
ret = NRF_SUCCESS;
switch (line_state)
{
case APP_USBD_CDC_ACM_LINE_STATE_DTR:
case APP_USBD_CDC_ACM_LINE_STATE_RTS:
*value = (p_cdc_acm_ctx->line_state & line_state) != 0;
break;
default:
ret = NRF_ERROR_NOT_SUPPORTED;
break;
}
CRITICAL_REGION_EXIT();
return ret;
}
#endif //NRF_MODULE_ENABLED(APP_USBD_CDC_ACM)